diff --git a/README.md b/README.md index acc6584..21b9750 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/app/src/admin/channel.ts b/app/src/admin/channel.ts index 3c4d0bf..3683165 100644 --- a/app/src/admin/channel.ts +++ b/app/src/admin/channel.ts @@ -154,7 +154,7 @@ export const ChannelInfos: Record = { format: "", 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", diff --git a/app/src/assets/admin/channel.less b/app/src/assets/admin/channel.less index 2e72986..a1cf96c 100644 --- a/app/src/assets/admin/channel.less +++ b/app/src/assets/admin/channel.less @@ -27,6 +27,16 @@ } } +.channel-editor { + position: relative; + + .channel-loader { + position: absolute; + top: 0; + right: 0.25rem; + } +} + .channel-wrapper { display: flex; flex-direction: column; diff --git a/app/src/components/admin/assemblies/ChannelEditor.tsx b/app/src/components/admin/assemblies/ChannelEditor.tsx index 9c67a63..dd923df 100644 --- a/app/src/components/admin/assemblies/ChannelEditor.tsx +++ b/app/src/components/admin/assemblies/ChannelEditor.tsx @@ -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 && (
+ {loading && ( + + )}
diff --git a/app/src/components/app/AppProvider.tsx b/app/src/components/app/AppProvider.tsx index d38123b..474f4c4 100644 --- a/app/src/components/app/AppProvider.tsx +++ b/app/src/components/app/AppProvider.tsx @@ -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 ( diff --git a/app/src/events/market.ts b/app/src/events/market.ts new file mode 100644 index 0000000..9b2ecaa --- /dev/null +++ b/app/src/events/market.ts @@ -0,0 +1,5 @@ +import { EventCommitter } from "@/events/struct.ts"; + +export const marketEvent = new EventCommitter({ + name: "market", +}); diff --git a/app/src/resources/i18n/cn.json b/app/src/resources/i18n/cn.json index f562b95..d6b8e65 100644 --- a/app/src/resources/i18n/cn.json +++ b/app/src/resources/i18n/cn.json @@ -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": "更新失败", diff --git a/app/src/resources/i18n/en.json b/app/src/resources/i18n/en.json index 1dadb62..e4ff906 100644 --- a/app/src/resources/i18n/en.json +++ b/app/src/resources/i18n/en.json @@ -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", diff --git a/app/src/resources/i18n/ja.json b/app/src/resources/i18n/ja.json index ecd9a51..d52bf5f 100644 --- a/app/src/resources/i18n/ja.json +++ b/app/src/resources/i18n/ja.json @@ -465,7 +465,9 @@ "model-image": "モデル写真", "custom-image": "カスタム画像", "custom-image-placeholder": "画像リンクを入力してください", - "update": "更新" + "update": "更新", + "new-model": "新しいモデル", + "migrate": "提出" }, "model-chart-tip": "トークンの使用状況", "subscription": "サブスクリプション管理", diff --git a/app/src/resources/i18n/ru.json b/app/src/resources/i18n/ru.json index d38b424..4bce37f 100644 --- a/app/src/resources/i18n/ru.json +++ b/app/src/resources/i18n/ru.json @@ -465,7 +465,9 @@ "model-image": "Изображение модели", "custom-image": "Пользовательское изображение", "custom-image-placeholder": "Введите ссылку на изображение", - "update": "Обновить" + "update": "Обновить", + "new-model": "Новая модель", + "migrate": "передавать" }, "model-chart-tip": "Использование токенов", "subscription": "Управление подписками", diff --git a/app/src/routes/admin/Logger.tsx b/app/src/routes/admin/Logger.tsx index 6f7ca9b..5701ea6 100644 --- a/app/src/routes/admin/Logger.tsx +++ b/app/src/routes/admin/Logger.tsx @@ -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() { />
diff --git a/app/src/routes/admin/Market.tsx b/app/src/routes/admin/Market.tsx index 606a706..b52ece7 100644 --- a/app/src/routes/admin/Market.tsx +++ b/app/src/routes/admin/Market.tsx @@ -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(null); - const sync = useRef(false); + const [form, dispatch] = useReducer(reducer, supportModels); + const [loading, setLoading] = useState(false); const update = async (): Promise => { 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() {
- {t("admin.market.title")} + + {t("admin.market.title")} + {loading && } + @@ -592,6 +551,21 @@ function Market() { )} +
+
+ + +
diff --git a/app/src/routes/admin/System.tsx b/app/src/routes/admin/System.tsx index db250c2..c4d1896 100644 --- a/app/src/routes/admin/System.tsx +++ b/app/src/routes/admin/System.tsx @@ -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 = { data: T; @@ -422,6 +423,8 @@ function System() { initialSystemState, ); + const [loading, setLoading] = useState(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() {
- {t("admin.settings")} + + {t("admin.settings")} + {loading && } +