From 3963c6eebf5911343a2fe053149f7e4d247c8a5e Mon Sep 17 00:00:00 2001 From: HouYunFei <1844025705@qq.com> Date: Wed, 15 Jul 2026 10:13:37 +0800 Subject: [PATCH] feat(model): enhance model configuration and script handling for audio and video capabilities --- .../components/layout/model-select-modal.tsx | 153 ++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 web/src/components/layout/model-select-modal.tsx diff --git a/web/src/components/layout/model-select-modal.tsx b/web/src/components/layout/model-select-modal.tsx new file mode 100644 index 0000000..80df491 --- /dev/null +++ b/web/src/components/layout/model-select-modal.tsx @@ -0,0 +1,153 @@ +import { App, Button, Checkbox, Input, Modal, Tabs } from "antd"; +import { RefreshCw, Search } from "lucide-react"; +import { useEffect, useMemo, useState } from "react"; + +import { fetchChannelModels } from "@/services/api/image"; +import type { ModelChannel } from "@/stores/use-config-store"; + +// 选择渠道模型弹窗:拉取上游模型列表或手动增加,勾选后才会进入渠道模型列表。 +export function ModelSelectModal({ open, channel, selectedNames, onConfirm, onClose }: { open: boolean; channel: ModelChannel | null; selectedNames: string[]; onConfirm: (names: string[]) => void; onClose: () => void }) { + const { message } = App.useApp(); + const [existing, setExisting] = useState([]); + const [fetched, setFetched] = useState([]); + const [selected, setSelected] = useState>(new Set()); + const [activeTab, setActiveTab] = useState("new"); + const [search, setSearch] = useState(""); + const [manual, setManual] = useState(""); + const [loading, setLoading] = useState(false); + + useEffect(() => { + if (!open) return; + setExisting(selectedNames); + setFetched([]); + setSelected(new Set(selectedNames)); + setActiveTab(selectedNames.length ? "existing" : "new"); + setSearch(""); + setManual(""); + }, [open, selectedNames]); + + const currentList = activeTab === "new" ? fetched : existing; + const visibleList = useMemo(() => { + const keyword = search.trim().toLowerCase(); + return keyword ? currentList.filter((name) => name.toLowerCase().includes(keyword)) : currentList; + }, [currentList, search]); + const visibleSelectedCount = visibleList.filter((name) => selected.has(name)).length; + + const toggle = (name: string, checked: boolean) => + setSelected((current) => { + const next = new Set(current); + if (checked) next.add(name); + else next.delete(name); + return next; + }); + + const selectVisible = (checked: boolean) => + setSelected((current) => { + const next = new Set(current); + visibleList.forEach((name) => (checked ? next.add(name) : next.delete(name))); + return next; + }); + + const addManual = () => { + const name = manual.trim(); + if (!name) return; + if (!fetched.includes(name) && !existing.includes(name)) setFetched((current) => [name, ...current]); + setSelected((current) => new Set(current).add(name)); + setManual(""); + setActiveTab("new"); + }; + + const fetchModels = async () => { + if (!channel) return; + if (!channel.baseUrl.trim() || !channel.apiKey.trim()) { + message.error("请先填写接口地址和 API Key"); + return; + } + setLoading(true); + try { + const models = await fetchChannelModels(channel); + setFetched(models); + setActiveTab("new"); + message.success(`已拉取 ${models.length} 个模型`); + } catch (error) { + message.error(error instanceof Error ? error.message : "拉取模型失败"); + } finally { + setLoading(false); + } + }; + + const confirm = () => { + const ordered = [...existing, ...fetched].filter((name, index, list) => list.indexOf(name) === index).filter((name) => selected.has(name)); + onConfirm(ordered); + onClose(); + }; + + return ( + + 选择渠道模型 已选择 {selected.size} / {new Set([...existing, ...fetched]).size} + + } + styles={{ body: { maxHeight: "62vh", overflowY: "auto" } }} + footer={[ + , + , + ]} + > +
+ setSearch(event.target.value)} placeholder="搜索模型" prefix={} allowClear /> + setManual(event.target.value)} onPressEnter={addManual} placeholder="输入模型名称" /> + + +
+
如果上游不提供 OpenAI /models 模型列表接口,请在这里手动增加模型名称。
+ + + +
+ 当前列表已选择 {visibleSelectedCount} / {visibleList.length} +
+ + +
+
+ + {visibleList.length ? ( +
+ {visibleList.map((name) => ( + toggle(name, event.target.checked)}> + + {name} + + + ))} +
+ ) : ( +
{activeTab === "new" ? "点击「拉取模型列表」获取上游模型,或手动增加模型名称。" : "暂无已选择的模型。"}
+ )} +
+ ); +}