feat(compute): 通道编辑表单 mosdel字段加「从 API 获取模型」按钮

- 支持从供应商 API 拉取模型列表,选择后自动填入 models(需先填 Base URL+Key)
This commit is contained in:
Pine
2026-08-25 12:54:16 +08:00
parent 24f8ad1eda
commit 81f1cb496a
+23 -1
View File
@@ -193,8 +193,22 @@ function ChannelFormDialog({ open, row, onClose, onDone }: {
}) {
const [f, setF] = React.useState<Row>(blank);
const [busy, setBusy] = React.useState(false);
const [fetching, setFetching] = React.useState(false);
const [err, setErr] = React.useState<string | null>(null);
const fetchModels = async () => {
if (!f.base_url || !f.key) { setErr("请先填 Base URL 与 Key 再获取模型"); return; }
setFetching(true); setErr(null);
const out = await computeAction<any>("POST", "channel/fetch-models", { base_url: f.base_url, key: f.key });
setFetching(false);
const list = out.data?.models;
if (out.ok && Array.isArray(list) && list.length) {
set("models", list.join(", "));
} else {
setErr(out.data?.message || out.message || "获取模型失败");
}
};
React.useEffect(() => {
if (open) { setF(row && row.id ? { ...blank, ...row, model_mapping: row.model_mapping ?? "" } : blank); setErr(null); }
}, [open, row]);
@@ -232,7 +246,15 @@ function ChannelFormDialog({ open, row, onClose, onDone }: {
</Field>
<Field label="Key" full><Input value={f.key} onChange={(e) => set("key", e.target.value)} placeholder="API Key / 可多Key" type="password" /></Field>
<Field label="Base URL" full><Input value={f.base_url || ""} onChange={(e) => set("base_url", e.target.value)} placeholder="https://api.xxx.com" /></Field>
<Field label="模型(逗号分隔)" full><Input value={f.models} onChange={(e) => set("models", e.target.value)} placeholder="gpt-4o,claude-sonnet-4-6" /></Field>
<Field label="模型(逗号分隔)" full>
<div className="flex items-start gap-2">
<Input value={f.models} onChange={(e) => set("models", e.target.value)} placeholder="gpt-4o,claude-sonnet-4-6" />
<Button type="button" variant="outline" size="sm" className="whitespace-nowrap"
onClick={fetchModels} disabled={fetching || busy}>
{fetching && <Loader2 className="animate-spin" />} API
</Button>
</div>
</Field>
<Field label="分组"><Input value={f.group} onChange={(e) => set("group", e.target.value)} placeholder="default" /></Field>
<Field label="模型映射(socat 映射)"><Input value={f.model_mapping || ""} onChange={(e) => set("model_mapping", e.target.value)} placeholder="model-a=>model-b, ..." /></Field>
<Field label="权重"><Input type="number" value={f.weight} onChange={(e) => set("weight", e.target.value)} /></Field>