chore: update admin actions

This commit is contained in:
Zhang Minghan 2024-01-19 11:58:31 +08:00
parent c0568bbbf1
commit 8dfb1185b8
13 changed files with 82 additions and 66 deletions

View File

@ -64,7 +64,7 @@
## 🔨 模型 | Models
- [x] OpenAI ChatGPT (GPT-3.5, GPT-4, Instruct, DALL-E 2, DALL-E 3, Text-Davincci, ...)
- [x] OpenAI ChatGPT (GPT-3.5, GPT-4, Instruct, DALL-E 2, DALL-E 3, ...)
- [x] Azure OpenAI
- [x] Anthropic Claude (claude-2, claude-instant)
- [x] Slack Claude (deprecated)
@ -79,9 +79,7 @@
- [x] Baichuan AI
- [x] Douyin Skylark (lite, plus, pro, chat)
- [x] 360 GPT
- [x] LLaMa 2 (70b, 13b, 7b)
- [x] Code LLaMa (34b, 13b, 7b)
- [ ] RWKV
- [x] LocalAI (RWKV, LLaMa 2, Baichuan 7b, Mixtral, ...) _*requires local deployment_
## 📦 部署 | Deploy

View File

@ -154,7 +154,7 @@ export const ChannelInfos: Record<string, ChannelInfo> = {
format: "<secret>",
models: ["bing-creative", "bing-balanced", "bing-precise"],
description:
"> Bing 服务需要自行搭建,详情请参考 [chatnio-bing-service](https://github.com/Deeptrain-Community/chatnio-bing-service) (如为 bing2api 可直接使用 OpenAI 格式映射)",
"> Bing 服务需要自行搭建,详情请参考 [chatnio-bing-service](https://github.com/Deeptrain-Community/chatnio-bing-service) \n > bing2api (如 [bingo](https://github.com/weaigc/bingo)) 可直接使用 OpenAI 格式",
},
palm: {
endpoint: "https://generativelanguage.googleapis.com",

View File

@ -27,6 +27,16 @@
}
}
.channel-editor {
position: relative;
.channel-loader {
position: absolute;
top: 0;
right: 0.25rem;
}
}
.channel-wrapper {
display: flex;
flex-direction: column;

View File

@ -22,7 +22,7 @@ import { Button } from "@/components/ui/button.tsx";
import { useTranslation } from "react-i18next";
import { useMemo, useReducer, useState } from "react";
import Required from "@/components/Require.tsx";
import { Plus, Search, X } from "lucide-react";
import { Loader2, Plus, Search, X } from "lucide-react";
import {
DropdownMenu,
DropdownMenuContent,
@ -209,6 +209,8 @@ function ChannelEditor({ display, id, setEnabled }: ChannelEditorProps) {
}, [edit.models]);
const enabled = useMemo(() => validator(edit), [edit]);
const [loading, setLoading] = useState(false);
const unusedGroups = useMemo(() => {
if (!edit.group) return channelGroups;
return channelGroups.filter(
@ -237,7 +239,9 @@ function ChannelEditor({ display, id, setEnabled }: ChannelEditorProps) {
useEffectAsync(async () => {
if (id === -1) dispatch({ type: "clear" });
else {
setLoading(true);
const resp = await getChannel(id);
setLoading(false);
toastState(toast, t, resp as CommonResponse);
if (resp.data) dispatch({ type: "set", value: resp.data });
}
@ -246,6 +250,9 @@ function ChannelEditor({ display, id, setEnabled }: ChannelEditorProps) {
return (
display && (
<div className={`channel-editor`}>
{loading && (
<Loader2 className={`channel-loader h-4 w-4 animate-spin`} />
)}
<div className={`channel-wrapper w-full h-max`}>
<div className={`channel-row`}>
<div className={`channel-content`}>

View File

@ -18,11 +18,14 @@ import { initChatModels } from "@/store/chat.ts";
import { Model } from "@/api/types.ts";
import { ChargeProps, nonBilling } from "@/admin/charge.ts";
import { dispatchSubscriptionData } from "@/store/globals.ts";
import { marketEvent } from "@/events/market.ts";
function AppProvider() {
const dispatch = useDispatch();
useEffectAsync(async () => {
marketEvent.emit(false);
const market = await getApiMarket();
const charge = await getApiCharge();
@ -48,6 +51,8 @@ function AppProvider() {
});
dispatchSubscriptionData(dispatch, await getApiPlans());
marketEvent.emit(true);
}, [allModels]);
return (

5
app/src/events/market.ts Normal file
View File

@ -0,0 +1,5 @@
import { EventCommitter } from "@/events/struct.ts";
export const marketEvent = new EventCommitter<boolean>({
name: "market",
});

View File

@ -403,6 +403,7 @@
"market": {
"title": "模型市场",
"model-name": "模型名称",
"new-model": "新建模型",
"model-name-placeholder": "请输入模型昵称 GPT-4",
"model-id": "模型 ID",
"model-id-placeholder": "请输入模型 ID gpt-4-0613",
@ -417,6 +418,7 @@
"custom-image": "自定义图片",
"custom-image-placeholder": "请输入图片链接",
"update": "更新",
"migrate": "提交",
"update-success": "更新成功",
"update-success-prompt": "模型市场设置已成功提交更新至服务器。",
"update-failed": "更新失败",

View File

@ -465,7 +465,9 @@
"model-image": "Model Picture",
"custom-image": "Custom Image",
"custom-image-placeholder": "Please enter an image link",
"update": "Update"
"update": "Update",
"new-model": "Create a new model",
"migrate": "Submit"
},
"model-chart-tip": "Token usage",
"subscription": "Subscription Management",

View File

@ -465,7 +465,9 @@
"model-image": "モデル写真",
"custom-image": "カスタム画像",
"custom-image-placeholder": "画像リンクを入力してください",
"update": "更新"
"update": "更新",
"new-model": "新しいモデル",
"migrate": "提出"
},
"model-chart-tip": "トークンの使用状況",
"subscription": "サブスクリプション管理",

View File

@ -465,7 +465,9 @@
"model-image": "Изображение модели",
"custom-image": "Пользовательское изображение",
"custom-image-placeholder": "Введите ссылку на изображение",
"update": "Обновить"
"update": "Обновить",
"new-model": "Новая модель",
"migrate": "передавать"
},
"model-chart-tip": "Использование токенов",
"subscription": "Управление подписками",

View File

@ -22,6 +22,7 @@ import Paragraph from "@/components/Paragraph.tsx";
import { Label } from "@/components/ui/label.tsx";
import { NumberInput } from "@/components/ui/number-input.tsx";
import { Button } from "@/components/ui/button.tsx";
import { cn } from "@/components/ui/lib/utils.ts";
type LoggerItemProps = Logger & {
onUpdate: () => void;
@ -104,7 +105,7 @@ function LoggerConsole() {
/>
<div className={`grow`} />
<Button onClick={sync} variant={`outline`} size={`icon`}>
<RotateCcw className={`w-4 h-4`} />
<RotateCcw className={cn("w-4 h-4", loading && "animate-spin")} />
</Button>
</div>
<div className={`logger-console`}>

View File

@ -5,19 +5,12 @@ import {
CardTitle,
} from "@/components/ui/card.tsx";
import { useTranslation } from "react-i18next";
import {
Dispatch,
useEffect,
useMemo,
useReducer,
useRef,
useState,
} from "react";
import { Dispatch, useMemo, useReducer, useState } from "react";
import { Model as RawModel } from "@/api/types.ts";
import { supportModels } from "@/conf";
import { DragDropContext, Draggable, Droppable } from "react-beautiful-dnd";
import { Input } from "@/components/ui/input.tsx";
import { GripVertical, HelpCircle, Plus, Trash2 } from "lucide-react";
import { GripVertical, HelpCircle, Loader2, Plus, Trash2 } from "lucide-react";
import { generateRandomChar, isUrl } from "@/utils/base.ts";
import Require from "@/components/Require.tsx";
import { Textarea } from "@/components/ui/textarea.tsx";
@ -32,6 +25,7 @@ import { updateMarket } from "@/admin/api/market.ts";
import { Combobox } from "@/components/ui/combo-box.tsx";
import { channelModels } from "@/admin/channel.ts";
import { cn } from "@/components/ui/lib/utils.ts";
import { marketEvent } from "@/events/market.ts";
type Model = RawModel & {
seed?: string;
@ -41,21 +35,6 @@ type MarketForm = Model[];
const generateSeed = () => generateRandomChar(8);
const initialState: MarketForm = [
{
id: "",
name: "",
free: false,
auth: false,
description: "",
high_context: false,
default: false,
tag: [],
avatar: modelImages[0],
seed: generateSeed(),
},
];
function reducer(state: MarketForm, action: any): MarketForm {
switch (action.type) {
case "set":
@ -324,9 +303,8 @@ function MarketImage({ image, idx, dispatch }: MarketImageProps) {
function Market() {
const { t } = useTranslation();
const [form, dispatch] = useReducer(reducer, initialState);
const timer = useRef<number | null>(null);
const sync = useRef<boolean>(false);
const [form, dispatch] = useReducer(reducer, supportModels);
const [loading, setLoading] = useState<boolean>(false);
const update = async (): Promise<void> => {
const preflight = form.filter(
@ -348,32 +326,10 @@ function Market() {
});
};
useEffect(() => {
if (supportModels.length > 0 && !sync.current) {
dispatch({ type: "set", payload: [...supportModels] });
sync.current = true;
}
}, [supportModels]);
useEffect(() => {
if (timer.current) {
clearTimeout(timer.current);
}
timer.current = Number(
setTimeout(async () => {
if (sync.current) {
sync.current = false;
return;
}
console.debug(
`[market] model market migrated, sync to server (models: ${form.length})`,
);
await update();
}, 2000),
);
}, [form]);
marketEvent.addEventListener((state: boolean) => {
setLoading(!state);
!state && dispatch({ type: "set", payload: [...supportModels] });
});
const checked = (index: number) => {
return useMemo((): boolean => {
@ -387,7 +343,10 @@ function Market() {
<div className={`market`}>
<Card className={`admin-card market-card`}>
<CardHeader className={`flex flex-row items-center select-none`}>
<CardTitle>{t("admin.market.title")}</CardTitle>
<CardTitle>
{t("admin.market.title")}
{loading && <Loader2 className={`h-4 w-4 ml-2 animate-spin`} />}
</CardTitle>
<Button
loading={true}
className={`ml-auto mt-0 whitespace-nowrap`}
@ -395,7 +354,7 @@ function Market() {
style={{ marginTop: 0 }}
onClick={update}
>
{t("admin.market.update")}
{t("admin.market.migrate")}
</Button>
</CardHeader>
<CardContent>
@ -592,6 +551,21 @@ function Market() {
)}
</Droppable>
</DragDropContext>
<div className={`market-footer flex flex-row items-center mt-4`}>
<div className={`grow`} />
<Button
size={`sm`}
variant={`outline`}
className={`mr-2`}
onClick={() => dispatch({ type: "new" })}
>
<Plus className={`h-4 w-4 mr-2`} />
{t("admin.market.new-model")}
</Button>
<Button size={`sm`} onClick={update} loading={true}>
{t("admin.market.migrate")}
</Button>
</div>
</CardContent>
</Card>
</div>

View File

@ -40,6 +40,7 @@ import {
} from "@/components/ui/dialog.tsx";
import { DialogTitle } from "@radix-ui/react-dialog";
import Require from "@/components/Require.tsx";
import { Loader2 } from "lucide-react";
type CompProps<T> = {
data: T;
@ -422,6 +423,8 @@ function System() {
initialSystemState,
);
const [loading, setLoading] = useState<boolean>(false);
const doSaving = async (doToast?: boolean) => {
const res = await setConfig(data);
@ -429,7 +432,9 @@ function System() {
};
useEffectAsync(async () => {
setLoading(true);
const res = await getConfig();
setLoading(false);
toastState(toast, t, res);
if (res.status) {
setData({ type: "set", value: res.data });
@ -440,7 +445,10 @@ function System() {
<div className={`system`}>
<Card className={`admin-card system-card`}>
<CardHeader className={`select-none`}>
<CardTitle>{t("admin.settings")}</CardTitle>
<CardTitle>
{t("admin.settings")}
{loading && <Loader2 className={`h-4 w-4 ml-2 inline-block`} />}
</CardTitle>
</CardHeader>
<CardContent className={`flex flex-col gap-1`}>
<General data={data.general} dispatch={setData} onChange={doSaving} />