mirror of
https://github.com/coaidev/coai.git
synced 2025-05-21 14:00:13 +09:00
chore: update admin actions
This commit is contained in:
parent
c0568bbbf1
commit
8dfb1185b8
@ -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
|
||||
|
@ -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",
|
||||
|
@ -27,6 +27,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
.channel-editor {
|
||||
position: relative;
|
||||
|
||||
.channel-loader {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.channel-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -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`}>
|
||||
|
@ -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
5
app/src/events/market.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { EventCommitter } from "@/events/struct.ts";
|
||||
|
||||
export const marketEvent = new EventCommitter<boolean>({
|
||||
name: "market",
|
||||
});
|
@ -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": "更新失败",
|
||||
|
@ -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",
|
||||
|
@ -465,7 +465,9 @@
|
||||
"model-image": "モデル写真",
|
||||
"custom-image": "カスタム画像",
|
||||
"custom-image-placeholder": "画像リンクを入力してください",
|
||||
"update": "更新"
|
||||
"update": "更新",
|
||||
"new-model": "新しいモデル",
|
||||
"migrate": "提出"
|
||||
},
|
||||
"model-chart-tip": "トークンの使用状況",
|
||||
"subscription": "サブスクリプション管理",
|
||||
|
@ -465,7 +465,9 @@
|
||||
"model-image": "Изображение модели",
|
||||
"custom-image": "Пользовательское изображение",
|
||||
"custom-image-placeholder": "Введите ссылку на изображение",
|
||||
"update": "Обновить"
|
||||
"update": "Обновить",
|
||||
"new-model": "Новая модель",
|
||||
"migrate": "передавать"
|
||||
},
|
||||
"model-chart-tip": "Использование токенов",
|
||||
"subscription": "Управление подписками",
|
||||
|
@ -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`}>
|
||||
|
@ -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>
|
||||
|
@ -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} />
|
||||
|
Loading…
Reference in New Issue
Block a user