feat(models): 「模型设置」接官方厂家目录 + 按官方页结构重做界面(档案 87 补做)
用户实测反馈:新增条目看不懂、厂家选择太少且国内一家都没有。根因是**第一版只做了手填表单**,没接官方 pi-ai 自带目录(实测 38 家,国内 11 家:蚂蚁 / 通义千问 / 小米 / 月之暗面 / 智谱 / MiniMax)。 - 新文件 src/web/model-catalog.ts:只读官方 data/*.json(10 分钟缓存,不导入 pi-ai 运行时);中文名走显示用静态表,选取范围以目录为准 - 新增 GET /api/me/model-providers(38 家 + cn 分组 + modelCount + 模型名;排除 deepseek —— 平台已有内置入口) - POST /api/me/keys 新增 provider 参数:**目录厂家只需 API 密钥**(端点/协议/模型全由官方目录兜底 —— 官方 config.d.ts 明示 route 命中即继承默认) - refForEntry 判据由「baseUrl 为空」改为「**没有 route**」:否则目录厂家的 key 会被写进 DEEPSEEK_API_KEY - SettingsEntry 的 baseURL/api/models 改为可选:目录厂家只写 apiKeyEnv(写得越少越不容易漂) - 前端按官方页结构重写:提供方行(厂家/说明/状态/操作)+ 新增**先选厂家**(中国大陆 / 国际分组 / 自定义)+ 保留并强化 admin 的「平台共享模型」区块 - 真环境验收:目录端点 38 家 / 国内 11 家;加 moonshotai-cn 只给 key → settings.yaml 仅 apiKeyEnv;删后整块消失
This commit is contained in:
1 parent
918f1d3a51
commit
022b1f770c
7 files changed
+489
-121
No files matched your search
@@ -404,19 +404,26 @@ window.__ModuleLoader__.load({
|
||||
"ms.ph.url": "Endpoint(留空 = 内置 DeepSeek)",
|
||||
"ms.ph.route": "厂家标识(可选,英文小写)",
|
||||
"ms.ph.models": "模型 id,一行一个",
|
||||
"ms.formHint": "Endpoint 留空即使用平台内置 DeepSeek;一旦填写,就必须给至少一个模型 id。厂家标识会写进实例配置,同一个标识不能重复。",
|
||||
"ms.pickProvider": "厂家:",
|
||||
"ms.optBuiltin": "内置 DeepSeek(平台共享)",
|
||||
"ms.optCustom": "自定义厂家(OpenAI 兼容网关)…",
|
||||
"ms.grpCn": "中国大陆",
|
||||
"ms.grpIntl": "国际",
|
||||
"ms.catalogHint": "端点与 {n} 个模型由官方目录提供,你只需填 API 密钥。",
|
||||
"ms.showModels": "看看自带哪些模型",
|
||||
"ms.hideModels": "收起模型清单",
|
||||
"ms.builtinPickHint": "内置 DeepSeek:平台内置通道;密钥留空则回落到平台共享密钥。",
|
||||
"ms.needUrl": "请填 Endpoint",
|
||||
"ms.save": "保存",
|
||||
"ms.list": "我的模型条目",
|
||||
"ms.col.name": "名称",
|
||||
"ms.col.route": "厂家标识",
|
||||
"ms.col.url": "Endpoint",
|
||||
"ms.col.api": "协议",
|
||||
"ms.col.models": "模型数",
|
||||
"ms.list": "我已添加的厂家",
|
||||
"ms.col.provider": "厂家",
|
||||
"ms.col.desc": "说明",
|
||||
"ms.col.state": "状态",
|
||||
"ms.col.act": "操作",
|
||||
"ms.empty": "还没有条目 —— 在上面添加一个即可",
|
||||
"ms.builtin": "内置 DeepSeek",
|
||||
"ms.builtinHint": "官方内置,无需 endpoint",
|
||||
"ms.empty": "还没有厂家 —— 在下面选一个添加即可",
|
||||
"ms.kindBuiltin": "内置 DeepSeek",
|
||||
"ms.builtinHint": "平台内置通道,无需 endpoint",
|
||||
"ms.catalogRow": "官方目录厂家(端点 / 协议 / 模型由目录提供)",
|
||||
"ms.on": "已启用",
|
||||
"ms.off": "已停用",
|
||||
"ms.enable": "启用",
|
||||
@@ -428,7 +435,8 @@ window.__ModuleLoader__.load({
|
||||
"ms.e.route": "厂家标识不合法(小写字母/数字/短横线,字母开头)",
|
||||
"ms.e.api": "不支持该协议",
|
||||
"ms.e.models": "请至少填一个模型 id",
|
||||
"ms.e.taken": "该厂家标识已被占用,换一个"
|
||||
"ms.e.taken": "该厂家标识已被占用,换一个",
|
||||
"ms.e.provider": "官方目录里没有这个厂家"
|
||||
};
|
||||
/** English dictionary, key-set complete against zh. */
|
||||
var en = {
|
||||
@@ -682,19 +690,26 @@ window.__ModuleLoader__.load({
|
||||
"ms.ph.url": "Endpoint (empty = built-in DeepSeek)",
|
||||
"ms.ph.route": "Provider id (optional, lowercase)",
|
||||
"ms.ph.models": "One model id per line",
|
||||
"ms.formHint": "Leave Endpoint empty to use the platform's built-in DeepSeek; if you fill it in you must give at least one model id. The provider id is written into the instance config and must be unique.",
|
||||
"ms.pickProvider": "Provider:",
|
||||
"ms.optBuiltin": "Built-in DeepSeek (platform-shared)",
|
||||
"ms.optCustom": "Custom provider (OpenAI-compatible gateway)…",
|
||||
"ms.grpCn": "Mainland China",
|
||||
"ms.grpIntl": "International",
|
||||
"ms.catalogHint": "Endpoint and {n} models come from the official catalog — you only need the API key.",
|
||||
"ms.showModels": "Show bundled models",
|
||||
"ms.hideModels": "Hide models",
|
||||
"ms.builtinPickHint": "Built-in DeepSeek: the platform channel; leave the key empty to fall back to the platform-shared one.",
|
||||
"ms.needUrl": "Endpoint is required",
|
||||
"ms.save": "Save",
|
||||
"ms.list": "My model entries",
|
||||
"ms.col.name": "Name",
|
||||
"ms.col.route": "Provider id",
|
||||
"ms.col.url": "Endpoint",
|
||||
"ms.col.api": "Protocol",
|
||||
"ms.col.models": "Models",
|
||||
"ms.list": "My providers",
|
||||
"ms.col.provider": "Provider",
|
||||
"ms.col.desc": "Details",
|
||||
"ms.col.state": "State",
|
||||
"ms.col.act": "Actions",
|
||||
"ms.empty": "No entries yet — add one above",
|
||||
"ms.builtin": "Built-in DeepSeek",
|
||||
"ms.builtinHint": "Built in; no endpoint needed",
|
||||
"ms.empty": "No providers yet — pick one below to add",
|
||||
"ms.kindBuiltin": "Built-in DeepSeek",
|
||||
"ms.builtinHint": "Platform channel; no endpoint needed",
|
||||
"ms.catalogRow": "Catalog provider (endpoint/protocol/models from the catalog)",
|
||||
"ms.on": "Enabled",
|
||||
"ms.off": "Disabled",
|
||||
"ms.enable": "Enable",
|
||||
@@ -706,7 +721,8 @@ window.__ModuleLoader__.load({
|
||||
"ms.e.route": "Invalid provider id (lowercase letters, digits, dashes; must start with a letter)",
|
||||
"ms.e.api": "Unsupported protocol",
|
||||
"ms.e.models": "Add at least one model id",
|
||||
"ms.e.taken": "That provider id is taken — pick another"
|
||||
"ms.e.taken": "That provider id is taken — pick another",
|
||||
"ms.e.provider": "That provider is not in the official catalog"
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1558,15 +1574,31 @@ window.__ModuleLoader__.load({
|
||||
var MS_ERR = {
|
||||
invalid_name: "ms.e.name", invalid_api_key: "ms.e.key", invalid_base_url: "ms.e.url",
|
||||
invalid_route: "ms.e.route", invalid_api: "ms.e.api", models_required: "ms.e.models",
|
||||
route_taken: "ms.e.taken"
|
||||
route_taken: "ms.e.taken", unknown_provider: "ms.e.provider"
|
||||
};
|
||||
|
||||
/**
|
||||
* 「模型设置」——**按官方 `dsh-client-ui-settings-models` 的结构做**(用户 2026-09-14 要求:
|
||||
* 「页面就按照 dsh 模型设置的页面,增加 admin 设置的模型管理」)。官方那页的骨架是:
|
||||
* · **提供方行**:每个已配置的厂家一行;卡片主字段是**单独一个「API 密钥」输入框**
|
||||
* (页面从不问环境变量名 —— 派生 `<ROUTE>_API_KEY`,pi-ai 记为 `apiKeyEnv`);
|
||||
* · 收起的「自定义设置」才承载 `baseURL` / 协议 / 模型目录;
|
||||
* · **新增** = 一张卡片,含**目录厂家选择框**(裸挂载的 `llm-pi-ai` 就能提供完整
|
||||
* 已安装 catalog)+ 「添加自定义提供方」(要 Provider ID / 端点 / 协议 / ≥1 模型)。
|
||||
* 本页与之同构,另加**平台共享模型**(= admin 配的那批,用户可开关,口径②)。
|
||||
*
|
||||
* 关键差别:官方页把密钥写进 profile 的凭据引用;本平台是**平台侧落地**(spawn 时写
|
||||
* 实例的 `.credentials.yaml` / `settings.yaml`),所以保存后**要重启实例才生效**。
|
||||
*/
|
||||
function ModelSettingsSection() {
|
||||
var useState = React.useState;
|
||||
var useEffect = React.useEffect;
|
||||
var dataState = useState(null);
|
||||
var data = dataState[0];
|
||||
var setData = dataState[1];
|
||||
var provState = useState([]);
|
||||
var providers = provState[0];
|
||||
var setProviders = provState[1];
|
||||
var loadingState = useState(true);
|
||||
var loading = loadingState[0];
|
||||
var setLoading = loadingState[1];
|
||||
@@ -1576,31 +1608,32 @@ window.__ModuleLoader__.load({
|
||||
var msgState = useState("");
|
||||
var msg = msgState[0];
|
||||
var setMsg = msgState[1];
|
||||
var fPick = useState("");
|
||||
var fName = useState("");
|
||||
var fKey = useState("");
|
||||
var fUrl = useState("");
|
||||
var fRoute = useState("");
|
||||
var fUrl = useState("");
|
||||
var fApi = useState("");
|
||||
var fModels = useState("");
|
||||
var fShow = useState(false);
|
||||
|
||||
function load() {
|
||||
setLoading(true);
|
||||
paReq("/api/me/keys")
|
||||
.then(function (r) { return r.ok ? r.json() : null; })
|
||||
.then(function (d) {
|
||||
if (d) {
|
||||
setData(d);
|
||||
// 协议默认取官方表的第一项(`dsh-llm-pi-ai` 的 PROTOCOLS 顺序即默认优先级)。
|
||||
if (!fApi[0]) fApi[1]((d.protocols || [])[0] || "");
|
||||
}
|
||||
Promise.all([
|
||||
paReq("/api/me/keys").then(function (r) { return r.ok ? r.json() : null; }),
|
||||
paReq("/api/me/model-providers").then(function (r) { return r.ok ? r.json() : null; })
|
||||
])
|
||||
.then(function (res) {
|
||||
if (res[0]) setData(res[0]);
|
||||
if (res[1] && res[1].providers) setProviders(res[1].providers);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(function () { setLoading(false); setMsg(t("ms.loadFailed")); });
|
||||
}
|
||||
useEffect(function () { load(); }, []);
|
||||
|
||||
/** 统一收尾:跑一个写操作 → 刷新列表 / 或把后端错误码翻成文案。 */
|
||||
function act(promise) {
|
||||
/** 统一收尾:跑一个写操作 → 成功则刷新列表 / 失败则把后端错误码翻成文案。 */
|
||||
function act(promise, onOk) {
|
||||
if (busy) return;
|
||||
setBusy(true);
|
||||
setMsg("");
|
||||
@@ -1609,32 +1642,59 @@ window.__ModuleLoader__.load({
|
||||
.then(function (res) {
|
||||
setBusy(false);
|
||||
if (!res.ok) {
|
||||
var code = res.d && res.d.error;
|
||||
setMsg(t(MS_ERR[code] || "ms.saveFailed"));
|
||||
setMsg(t(MS_ERR[res.d && res.d.error] || "ms.saveFailed"));
|
||||
return;
|
||||
}
|
||||
if (res.d && res.d.saved) setMsg(t("ms.saved"));
|
||||
setMsg(t("ms.saved"));
|
||||
if (onOk) onOk();
|
||||
load();
|
||||
})
|
||||
.catch(function () { setBusy(false); setMsg(t("ms.saveFailed")); });
|
||||
}
|
||||
|
||||
var pick = fPick[0];
|
||||
var isCatalog = pick !== "" && pick !== "custom";
|
||||
var picked = null;
|
||||
for (var pi = 0; pi < providers.length; pi++) {
|
||||
if (providers[pi].id === pick) picked = providers[pi];
|
||||
}
|
||||
|
||||
/** 行标题:内置 → 固定文案;目录厂家 → 官方目录里的中文名;自定义 → route。 */
|
||||
function labelOf(k) {
|
||||
if (!k.route) return t("ms.kindBuiltin");
|
||||
for (var i = 0; i < providers.length; i++) {
|
||||
if (providers[i].id === k.route) return providers[i].label;
|
||||
}
|
||||
return k.route;
|
||||
}
|
||||
/** 行副标题:内置 → 说明;自定义 → endpoint · 协议;目录厂家 → 「由目录提供」。 */
|
||||
function descOf(k) {
|
||||
if (!k.route) return t("ms.builtinHint");
|
||||
if (k.baseUrl) return k.baseUrl + " · " + (k.api || "openai-completions");
|
||||
return t("ms.catalogRow");
|
||||
}
|
||||
|
||||
function submit() {
|
||||
var name = fName[0].trim();
|
||||
var key = fKey[0].trim();
|
||||
if (isCatalog && name === "" && picked !== null) name = picked.label;
|
||||
if (name === "" || key === "") { setMsg(t("ms.needNameKey")); return; }
|
||||
var url = fUrl[0].trim();
|
||||
var body = { name: name, apiKey: key };
|
||||
if (url !== "") {
|
||||
if (isCatalog) {
|
||||
// 目录厂家:只给 apiKey —— 端点 / 协议 / 模型清单全部由官方目录兜底。
|
||||
body.provider = pick;
|
||||
} else if (pick === "custom") {
|
||||
if (fUrl[0].trim() === "") { setMsg(t("ms.needUrl")); return; }
|
||||
var ids = fModels[0].split("\n").map(function (s) { return s.trim(); }).filter(function (s) { return s !== ""; });
|
||||
if (ids.length === 0) { setMsg(t("ms.needModels")); return; }
|
||||
body.baseUrl = url;
|
||||
body.baseUrl = fUrl[0].trim();
|
||||
body.models = ids;
|
||||
if (fRoute[0].trim() !== "") body.route = fRoute[0].trim();
|
||||
if (fApi[0]) body.api = fApi[0];
|
||||
}
|
||||
act(papostJson("/api/me/keys", body));
|
||||
fName[1](""); fKey[1](""); fUrl[1](""); fRoute[1](""); fModels[1]("");
|
||||
act(papostJson("/api/me/keys", body), function () {
|
||||
fName[1](""); fKey[1](""); fUrl[1](""); fRoute[1](""); fModels[1](""); setShow(false);
|
||||
});
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
@@ -1644,18 +1704,19 @@ window.__ModuleLoader__.load({
|
||||
var keys = (data && data.keys) || [];
|
||||
var shared = (data && data.shared) || {};
|
||||
var sharedOn = !!(data && data.sharedModelEnabled);
|
||||
var protocols = (data && data.protocols) || (fApi[0] ? [fApi[0]] : []);
|
||||
var protocols = (data && data.protocols) || [];
|
||||
var cnList = providers.filter(function (p) { return p.cn; });
|
||||
var intlList = providers.filter(function (p) { return !p.cn; });
|
||||
function provOpts(list) {
|
||||
return list.map(function (p) {
|
||||
return jsxRuntime.jsx("option", { value: p.id, children: p.label + "(" + p.modelCount + " 个模型)" }, p.id);
|
||||
});
|
||||
}
|
||||
|
||||
var rows = keys.map(function (k) {
|
||||
var custom = !!(k.baseUrl && k.baseUrl !== "");
|
||||
var n = 0;
|
||||
try { var arr = JSON.parse(k.models || "[]"); n = Array.isArray(arr) ? arr.length : 0; } catch (e) { n = 0; }
|
||||
return jsxRuntime.jsxs("tr", { children: [
|
||||
jsxRuntime.jsx("td", { children: k.name }),
|
||||
jsxRuntime.jsx("td", { className: "pa-dim", children: custom ? (k.route || "—") : t("ms.builtin") }),
|
||||
jsxRuntime.jsx("td", { className: "pa-dim", children: custom ? k.baseUrl : t("ms.builtinHint") }),
|
||||
jsxRuntime.jsx("td", { className: "pa-dim", children: custom ? (k.api || "openai-completions") : "—" }),
|
||||
jsxRuntime.jsx("td", { className: "pa-num", children: custom ? String(n) : "—" }),
|
||||
jsxRuntime.jsx("td", { children: labelOf(k) }),
|
||||
jsxRuntime.jsx("td", { className: "pa-dim", children: descOf(k) }),
|
||||
jsxRuntime.jsx("td", { children: jsxRuntime.jsx("span", { className: "pa-badge " + (k.enabled ? "active" : "disabled"), children: k.enabled ? t("ms.on") : t("ms.off") }) }),
|
||||
jsxRuntime.jsxs("td", { children: [
|
||||
jsxRuntime.jsx("button", { key: "t", className: "pa-sm", disabled: busy, onClick: function () { act(papostJson("/api/me/keys/" + k.id + "/toggle", { enabled: !k.enabled })); }, children: k.enabled ? t("ms.disable") : t("ms.enable") }),
|
||||
@@ -1668,7 +1729,7 @@ window.__ModuleLoader__.load({
|
||||
jsxRuntime.jsx("h1", { className: "pa-hd", children: t("ms.title") }),
|
||||
jsxRuntime.jsx("p", { className: "pa-sub", children: t("ms.sub") }),
|
||||
|
||||
// ── 平台共享模型(口径②:列入 + 可开关;开关只影响自己,不动 admin 的配置)──
|
||||
// ── admin 配的「平台共享模型」(口径②:也列在这里、用户可开关)──
|
||||
jsxRuntime.jsxs("div", { className: "pa-box", style: { marginBottom: 16 }, children: [
|
||||
jsxRuntime.jsxs("div", { className: "pa-card-h", children: [
|
||||
jsxRuntime.jsx("span", { children: t("ms.shared") }),
|
||||
@@ -1676,51 +1737,62 @@ window.__ModuleLoader__.load({
|
||||
] }),
|
||||
jsxRuntime.jsx("p", { className: "pa-hint", children: t("ms.sharedHint") }),
|
||||
jsxRuntime.jsxs("div", { className: "pa-row", style: { margin: 0 }, children: [
|
||||
jsxRuntime.jsx("span", { className: "pa-badge " + (shared.available ? "active" : "disabled"), children: shared.available ? (shared.name || t("ms.available")) : t("ms.unavailable") }),
|
||||
jsxRuntime.jsx("span", { className: "pa-badge " + (shared.available ? "active" : "disabled"), children: shared.available ? (shared.name || t("ms.available")) + (shared.count > 1 ? " ×" + shared.count : "") : t("ms.unavailable") }),
|
||||
jsxRuntime.jsx("button", { className: "pa-btn" + (sharedOn ? "" : " pa-primary"), disabled: busy || !shared.available, onClick: function () { act(papostJson("/api/me/models/shared", { enabled: !sharedOn })); }, children: sharedOn ? t("ms.sharedOn") : t("ms.sharedOff") })
|
||||
] })
|
||||
] }),
|
||||
|
||||
// ── 新增条目(Endpoint 留空 = 内置 DeepSeek;填了就必须给模型清单)──
|
||||
jsxRuntime.jsxs("div", { className: "pa-box", style: { marginBottom: 16 }, children: [
|
||||
jsxRuntime.jsx("div", { className: "pa-card-h", children: jsxRuntime.jsx("span", { children: t("ms.add") }) }),
|
||||
jsxRuntime.jsxs("div", { className: "pa-row", children: [
|
||||
jsxRuntime.jsx("input", { className: "pa-input", placeholder: t("ms.ph.name"), value: fName[0], onChange: function (e) { fName[1](e.target.value); } }),
|
||||
jsxRuntime.jsx("input", { className: "pa-input", type: "password", autoComplete: "off", placeholder: t("ms.ph.key"), value: fKey[0], onChange: function (e) { fKey[1](e.target.value); } })
|
||||
] }),
|
||||
jsxRuntime.jsxs("div", { className: "pa-row", children: [
|
||||
jsxRuntime.jsx("input", { className: "pa-input", placeholder: t("ms.ph.url"), value: fUrl[0], onChange: function (e) { fUrl[1](e.target.value); } }),
|
||||
jsxRuntime.jsx("input", { className: "pa-input", placeholder: t("ms.ph.route"), value: fRoute[0], onChange: function (e) { fRoute[1](e.target.value); } })
|
||||
] }),
|
||||
jsxRuntime.jsxs("div", { className: "pa-row", children: [
|
||||
jsxRuntime.jsx("select", { className: "pa-input", value: fApi[0], onChange: function (e) { fApi[1](e.target.value); }, children: protocols.map(function (p) { return jsxRuntime.jsx("option", { value: p, children: p }, p); }) })
|
||||
] }),
|
||||
jsxRuntime.jsx("textarea", { className: "pa-input", rows: 3, style: { width: "100%", boxSizing: "border-box" }, placeholder: t("ms.ph.models"), value: fModels[0], onChange: function (e) { fModels[1](e.target.value); } }),
|
||||
jsxRuntime.jsx("p", { className: "pa-hint", children: t("ms.formHint") }),
|
||||
jsxRuntime.jsxs("div", { className: "pa-row", style: { marginBottom: 0 }, children: [
|
||||
jsxRuntime.jsx("button", { className: "pa-btn pa-primary", disabled: busy, onClick: submit, children: t("ms.save") }),
|
||||
msg ? jsxRuntime.jsx("span", { className: "pa-hint", style: { margin: 0 }, children: msg }) : null
|
||||
] })
|
||||
] }),
|
||||
|
||||
// ── 我的条目(口径①:每条独立开关,可同时启用)──
|
||||
// ── 我已添加的厂家(官方页的「提供方行」)──
|
||||
jsxRuntime.jsx("div", { className: "pa-sec", children: t("ms.list") }),
|
||||
jsxRuntime.jsx("div", { className: "pa-wrap", children: jsxRuntime.jsxs("table", { className: "pa-tbl", children: [
|
||||
jsxRuntime.jsx("div", { className: "pa-wrap", style: { marginBottom: 16 }, children: jsxRuntime.jsxs("table", { className: "pa-tbl", children: [
|
||||
jsxRuntime.jsx("thead", { children: jsxRuntime.jsxs("tr", { children: [
|
||||
jsxRuntime.jsx("th", { children: t("ms.col.name") }),
|
||||
jsxRuntime.jsx("th", { children: t("ms.col.route") }),
|
||||
jsxRuntime.jsx("th", { children: t("ms.col.url") }),
|
||||
jsxRuntime.jsx("th", { children: t("ms.col.api") }),
|
||||
jsxRuntime.jsx("th", { children: t("ms.col.models") }),
|
||||
jsxRuntime.jsx("th", { children: t("ms.col.provider") }),
|
||||
jsxRuntime.jsx("th", { children: t("ms.col.desc") }),
|
||||
jsxRuntime.jsx("th", { children: t("ms.col.state") }),
|
||||
jsxRuntime.jsx("th", { children: t("ms.col.act") })
|
||||
] }) }),
|
||||
jsxRuntime.jsx("tbody", {
|
||||
children: rows.length > 0
|
||||
? rows
|
||||
: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { className: "pa-empty-cell", colSpan: 7, children: t("ms.empty") }) })
|
||||
: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { className: "pa-empty-cell", colSpan: 4, children: t("ms.empty") }) })
|
||||
})
|
||||
] }) })
|
||||
] }) }),
|
||||
|
||||
// ── 新增厂家:**先选厂家**,目录厂家只需填 API 密钥(官方「新增」卡片同构)──
|
||||
jsxRuntime.jsxs("div", { className: "pa-box", children: [
|
||||
jsxRuntime.jsx("div", { className: "pa-card-h", children: jsxRuntime.jsx("span", { children: t("ms.add") }) }),
|
||||
jsxRuntime.jsxs("div", { className: "pa-row", children: [
|
||||
jsxRuntime.jsx("span", { className: "pa-hint", style: { margin: 0 }, children: t("ms.pickProvider") }),
|
||||
jsxRuntime.jsx("select", { className: "pa-input", value: pick, onChange: function (e) { fPick[1](e.target.value); setShow(false); }, children: [
|
||||
jsxRuntime.jsx("option", { value: "", children: t("ms.optBuiltin") }, "_b"),
|
||||
cnList.length > 0 ? jsxRuntime.jsx("optgroup", { label: t("ms.grpCn"), children: provOpts(cnList) }, "_cn") : null,
|
||||
intlList.length > 0 ? jsxRuntime.jsx("optgroup", { label: t("ms.grpIntl"), children: provOpts(intlList) }, "_intl") : null,
|
||||
jsxRuntime.jsx("option", { value: "custom", children: t("ms.optCustom") }, "_c")
|
||||
] })
|
||||
] }),
|
||||
jsxRuntime.jsxs("div", { className: "pa-row", children: [
|
||||
jsxRuntime.jsx("input", { className: "pa-input", placeholder: isCatalog && picked !== null ? picked.label : t("ms.ph.name"), value: fName[0], onChange: function (e) { fName[1](e.target.value); } }),
|
||||
jsxRuntime.jsx("input", { className: "pa-input", type: "password", autoComplete: "off", placeholder: t("ms.ph.key"), value: fKey[0], onChange: function (e) { fKey[1](e.target.value); } })
|
||||
] }),
|
||||
isCatalog && picked !== null ? jsxRuntime.jsxs("div", { className: "pa-row", children: [
|
||||
jsxRuntime.jsx("span", { className: "pa-hint", style: { margin: 0 }, children: t("ms.catalogHint").replace("{n}", String(picked.modelCount)) }),
|
||||
jsxRuntime.jsx("button", { className: "pa-sm", onClick: function () { setShow(!fShow[0]); }, children: fShow[0] ? t("ms.hideModels") : t("ms.showModels") })
|
||||
] }) : null,
|
||||
isCatalog && picked !== null && fShow[0] ? jsxRuntime.jsx("p", { className: "pa-hint", children: (picked.models || []).join("、") }) : null,
|
||||
pick === "custom" ? jsxRuntime.jsxs("div", { children: [
|
||||
jsxRuntime.jsxs("div", { className: "pa-row", children: [
|
||||
jsxRuntime.jsx("input", { className: "pa-input", placeholder: t("ms.ph.url"), value: fUrl[0], onChange: function (e) { fUrl[1](e.target.value); } }),
|
||||
jsxRuntime.jsx("input", { className: "pa-input", placeholder: t("ms.ph.route"), value: fRoute[0], onChange: function (e) { fRoute[1](e.target.value); } })
|
||||
] }),
|
||||
jsxRuntime.jsx("div", { className: "pa-row", children: jsxRuntime.jsx("select", { className: "pa-input", value: fApi[0], onChange: function (e) { fApi[1](e.target.value); }, children: protocols.map(function (p) { return jsxRuntime.jsx("option", { value: p, children: p }, p); }) }) }),
|
||||
jsxRuntime.jsx("textarea", { className: "pa-input", rows: 3, style: { width: "100%", boxSizing: "border-box" }, placeholder: t("ms.ph.models"), value: fModels[0], onChange: function (e) { fModels[1](e.target.value); } })
|
||||
] }) : null,
|
||||
pick === "" ? jsxRuntime.jsx("p", { className: "pa-hint", children: t("ms.builtinPickHint") }) : null,
|
||||
jsxRuntime.jsxs("div", { className: "pa-row", style: { marginBottom: 0 }, children: [
|
||||
jsxRuntime.jsx("button", { className: "pa-btn pa-primary", disabled: busy, onClick: submit, children: t("ms.save") }),
|
||||
msg ? jsxRuntime.jsx("span", { className: "pa-hint", style: { margin: 0 }, children: msg }) : null
|
||||
] })
|
||||
] })
|
||||
] });
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user