@ran-sh/dsh-crew 1.5.1 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/lib/client.js +2 -905
- package/official-web-bridge/lib/client.js +1 -71
- package/package.json +1 -1
- package/src/client/collapsible-sections.mjs +5 -7
- package/src/client/index.tsx +3 -357
- package/src/client/quick-entry.tsx +1 -1
- package/src/client/quick-panel.tsx +3 -38
- package/src/client/surface-detection.mjs +1 -1
- package/src/hub/index.mjs +1 -76
- package/src/i18n.mjs +1 -1
- package/src/install/install-legacy.mjs +717 -739
- package/src/policy-legacy.mjs +1 -51
- package/src/provider-inventory.mjs +1 -3
- package/src/runtime-controls.mjs +1 -7
- package/src/runtime-identity.mjs +1 -1
- package/src/server.mjs +1 -2
- package/src/image-output.mjs +0 -71
- package/src/multimodal.mjs +0 -686
- package/src/vision-route.mjs +0 -138
|
@@ -62,12 +62,7 @@ function PanelHeader({ title, eyebrow, description, href, linkText, children })
|
|
|
62
62
|
//#region src/client/quick-panel.tsx
|
|
63
63
|
const API = "/_dsh/dsh-crew";
|
|
64
64
|
const FULL = "http://127.0.0.1:3210/";
|
|
65
|
-
const RESTART_KEYS = /* @__PURE__ */ new Set([
|
|
66
|
-
"vision_enabled",
|
|
67
|
-
"imagegen_enabled",
|
|
68
|
-
"vision_provider",
|
|
69
|
-
"imagegen_provider"
|
|
70
|
-
]);
|
|
65
|
+
const RESTART_KEYS = /* @__PURE__ */ new Set([]);
|
|
71
66
|
const T = {
|
|
72
67
|
zh: {
|
|
73
68
|
title: "DSH Crew 快捷控制",
|
|
@@ -88,9 +83,6 @@ const T = {
|
|
|
88
83
|
flash: "Worker / Flash",
|
|
89
84
|
pro: "Reviewer / Pro",
|
|
90
85
|
addModel: "+ 添加模型",
|
|
91
|
-
multimodal: "多模态",
|
|
92
|
-
vision: "视觉",
|
|
93
|
-
imagegen: "生图",
|
|
94
86
|
provider: "Provider",
|
|
95
87
|
applyRestart: "应用并重启 Crew",
|
|
96
88
|
savedNeedsRestart: "配置已保存 · 需要重启 Crew 才会生效",
|
|
@@ -118,9 +110,6 @@ const T = {
|
|
|
118
110
|
flash: "Worker / Flash",
|
|
119
111
|
pro: "Reviewer / Pro",
|
|
120
112
|
addModel: "+ Add model",
|
|
121
|
-
multimodal: "Multimodal",
|
|
122
|
-
vision: "Vision",
|
|
123
|
-
imagegen: "Imagegen",
|
|
124
113
|
provider: "Provider",
|
|
125
114
|
applyRestart: "Apply & restart Crew",
|
|
126
115
|
savedNeedsRestart: "Saved · restart Crew to take effect",
|
|
@@ -229,13 +218,6 @@ function QuickPanel({ ctx }) {
|
|
|
229
218
|
}));
|
|
230
219
|
patch({ [key]: value });
|
|
231
220
|
};
|
|
232
|
-
const setProvider = (key, value) => {
|
|
233
|
-
setConfig((c) => ({
|
|
234
|
-
...c ?? {},
|
|
235
|
-
[key]: value
|
|
236
|
-
}));
|
|
237
|
-
patch({ [key]: value });
|
|
238
|
-
};
|
|
239
221
|
const moveModel = (listKey, index, dir) => {
|
|
240
222
|
const list = [...config?.[listKey] ?? []];
|
|
241
223
|
const to = index + dir;
|
|
@@ -473,58 +455,6 @@ function QuickPanel({ ctx }) {
|
|
|
473
455
|
}),
|
|
474
456
|
modelList("flash_model_priority", t.flash),
|
|
475
457
|
modelList("pro_model_priority", t.pro),
|
|
476
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
|
|
477
|
-
className: "crew-quick-group",
|
|
478
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("summary", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t.multimodal }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
479
|
-
className: "crew-quick-count",
|
|
480
|
-
children: [
|
|
481
|
-
t.vision,
|
|
482
|
-
" / ",
|
|
483
|
-
t.imagegen
|
|
484
|
-
]
|
|
485
|
-
})] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
486
|
-
className: "crew-quick-content",
|
|
487
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
488
|
-
className: "crew-quick-media",
|
|
489
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [
|
|
490
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
491
|
-
type: "checkbox",
|
|
492
|
-
disabled: busy,
|
|
493
|
-
checked: config.vision_enabled === true,
|
|
494
|
-
onChange: (e) => toggle("vision_enabled", e.target.checked)
|
|
495
|
-
}),
|
|
496
|
-
" ",
|
|
497
|
-
t.vision
|
|
498
|
-
] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
499
|
-
className: "crew-quick-input",
|
|
500
|
-
"aria-label": `${t.vision} Provider`,
|
|
501
|
-
placeholder: t.provider,
|
|
502
|
-
disabled: busy,
|
|
503
|
-
defaultValue: config.vision_provider ?? "",
|
|
504
|
-
onBlur: (e) => setProvider("vision_provider", e.target.value)
|
|
505
|
-
}, `vision-${config.vision_provider ?? ""}`)]
|
|
506
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
507
|
-
className: "crew-quick-media",
|
|
508
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [
|
|
509
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
510
|
-
type: "checkbox",
|
|
511
|
-
disabled: busy,
|
|
512
|
-
checked: config.imagegen_enabled === true,
|
|
513
|
-
onChange: (e) => toggle("imagegen_enabled", e.target.checked)
|
|
514
|
-
}),
|
|
515
|
-
" ",
|
|
516
|
-
t.imagegen
|
|
517
|
-
] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
518
|
-
className: "crew-quick-input",
|
|
519
|
-
"aria-label": `${t.imagegen} Provider`,
|
|
520
|
-
placeholder: t.provider,
|
|
521
|
-
disabled: busy,
|
|
522
|
-
defaultValue: config.imagegen_provider ?? "",
|
|
523
|
-
onBlur: (e) => setProvider("imagegen_provider", e.target.value)
|
|
524
|
-
})]
|
|
525
|
-
})]
|
|
526
|
-
})]
|
|
527
|
-
}),
|
|
528
458
|
restartPending && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
529
459
|
className: "crew-quick-row",
|
|
530
460
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
package/package.json
CHANGED
|
@@ -4,13 +4,11 @@ export const SETTINGS_SECTION_IDS = Object.freeze([
|
|
|
4
4
|
'integrations',
|
|
5
5
|
'workflow',
|
|
6
6
|
'flash',
|
|
7
|
-
'pro',
|
|
8
|
-
'dispatch',
|
|
9
|
-
'adaptive',
|
|
10
|
-
'runtime',
|
|
11
|
-
'
|
|
12
|
-
'harnessProviders',
|
|
13
|
-
'providers',
|
|
7
|
+
'pro',
|
|
8
|
+
'dispatch',
|
|
9
|
+
'adaptive',
|
|
10
|
+
'runtime',
|
|
11
|
+
'harnessProviders',
|
|
14
12
|
'jobs',
|
|
15
13
|
]);
|
|
16
14
|
|
package/src/client/index.tsx
CHANGED
|
@@ -63,7 +63,7 @@ const COPY = {
|
|
|
63
63
|
expandAll: '全部展开', collapseAll: '全部折叠',
|
|
64
64
|
modelCount: (count: number) => `${count} 个模型`, providerCount: (count: number) => `${count} 个 Provider`,
|
|
65
65
|
jobCount: (count: number) => `${count} 个任务`, runningCount: (count: number) => `${count} 个运行中`,
|
|
66
|
-
sectionNames: { integrations: 'Codex / Claude / ZCode 集成状态', workflow: 'Crew 工作流设置', flash: 'Worker / Flash', pro: 'Reviewer / Pro', dispatch: '模型优先级与派发', schedule: '波峰波谷调度', adaptive: '自适应路由', runtime: '运行 / 生效边界',
|
|
66
|
+
sectionNames: { integrations: 'Codex / Claude / ZCode 集成状态', workflow: 'Crew 工作流设置', flash: 'Worker / Flash', pro: 'Reviewer / Pro', dispatch: '模型优先级与派发', schedule: '波峰波谷调度', adaptive: '自适应路由', runtime: '运行 / 生效边界', harnessProviders: 'Harness Providers', jobs: '任务状态' },
|
|
67
67
|
harnessHint: 'DeepSeek 官方 Harness 的 dsh-crew profile:Provider、Harness Models 与运行时配置',
|
|
68
68
|
hostReadiness: '宿主集成就绪度', hostReadinessHint: '只使用结构化安装与运行时证据;缺少证据不会显示 READY。',
|
|
69
69
|
readinessLabels: { codex_mcp: 'Codex MCP', ds_worker: 'ds-worker', ds_reviewer: 'ds-reviewer', claude_plugin: 'Claude plugin', zcode_mcp: 'ZCode MCP', crew_harness: 'Crew plugin profile', official_bridge: 'Official bridge' },
|
|
@@ -100,19 +100,11 @@ const COPY = {
|
|
|
100
100
|
proReviewsFlash: 'Pro 复审 Flash 改动',
|
|
101
101
|
proReviewsFlashHint: 'Flash 成功后自动追加一次 Pro 复审(仅 blocking 派发)',
|
|
102
102
|
proReviewsLocked: 'Review Pipeline 模式固定开启',
|
|
103
|
-
enableCrewVision: '启用 Crew 视觉',
|
|
104
|
-
enableCrewVisionHint: '关闭后不注册 Crew 的 describe_image 与视觉 route(可与你的 dsh-vision 等共存)。',
|
|
105
|
-
enableImagegen: '启用生图',
|
|
106
|
-
enableImagegenHint: '关闭后不注册 Crew 的 generate_image。',
|
|
107
|
-
capRestartHint: '该开关改变工具注册,需重启 DSH 生效',
|
|
108
|
-
capDisabledNote: 'Crew 工具/route 已禁用(重启 DSH 后生效)',
|
|
109
103
|
tier: '默认档位', effort: '默认推理', mode: '执行模式', timeout: '默认超时(秒)', hubUrl: 'Hub 地址',
|
|
110
104
|
tierPolicy: '档位策略', escalate: '失败升档',
|
|
111
105
|
tierPolicyDesc: { auto: 'auto · orchestrator 自选', 'flash-only': '只用 flash', 'pro-only': '只用 pro' },
|
|
112
106
|
escalateHint: 'flash 失败自动用 pro 重试一次',
|
|
113
107
|
presetFlash: 'flash 模式', presetPro: 'pro 模式',
|
|
114
|
-
multimodal: '多模态',
|
|
115
|
-
visionProvider: '视觉 provider', visionModel: '视觉模型', imagegenProvider: '生图 provider',
|
|
116
108
|
harnessProviders: 'Harness Providers', harnessProviderHint: 'DeepSeek 官方 Harness 的 3210 dsh-crew profile 中的真实 Provider 注册与生命周期;不是视觉 / 生图适配器。',
|
|
117
109
|
providerState: (state: string) => `状态:${state}`, providerModels: (count: number) => `${count} 个模型`, providerJobs: (count: number) => `${count} 个运行任务`,
|
|
118
110
|
providerOfficialBuiltin: '· 官方内置(不可删除)', providerSourceUnresolved: '· 来源未解析(安全锁定)',
|
|
@@ -123,30 +115,11 @@ const COPY = {
|
|
|
123
115
|
providerNoInventory: '暂时无法读取 3210 Provider inventory。',
|
|
124
116
|
credentialReferences: 'Credential 引用', credentialReferenceHint: '仅显示引用名、归属和孤儿状态;不会读取或删除密钥。',
|
|
125
117
|
credentialOrphan: '孤儿 · 可单独申请清理', credentialInUse: (count: number) => `使用中 · ${count} 个 Provider`, credentialPurgePlan: '申请清理', credentialPurgeConfirm: '这是不可恢复的 Crew-owned 凭据清理,确认继续?', credentialPurging: '清理中…', credentialPurgeUnavailable: '凭据清理不可用', credentialPurgeUnverified: '已执行清理,但验证失败;引用仍保留用于恢复检查',
|
|
126
|
-
customProviders: '多模态适配器', addProvider: '+ 添加多模态适配器',
|
|
127
|
-
noCustomProviders: '暂无。添加后会出现在上方的视觉 / 生图 provider 选择里。',
|
|
128
|
-
providerName: '名称', modelsField: '模型列表(逗号分隔)',
|
|
129
|
-
providerType: '接入方式', typeApi: 'API · 兼容 OpenAI 接口', typeCli: 'CLI · 本地命令',
|
|
130
|
-
baseUrl: 'Base URL', apiKey: 'API Key', imagegenModel: '生图模型(留空=不提供生图)',
|
|
131
|
-
visionCmd: '视觉命令(可选)', imagegenCmd: '生图命令(可选)',
|
|
132
|
-
apiFormHint: '走 OpenAI 兼容接口:视觉调 {base}/chat/completions(图片以 base64 内联),生图调 {base}/images/generations。Key 保存在本机 ~/.config/dsh-crew/config.json,不会外发到第三方。',
|
|
133
|
-
cliFormHint: '命令经 bash 执行,占位符会被安全引用后代入。视觉: {image} {question} {model},stdout 即答案;生图: {prompt} {output} {size},命令须把图片写到 {output}。两条命令至少填一条。',
|
|
134
|
-
saveProvider: '保存', cancel: '取消', edit: '编辑', del: '删除',
|
|
135
|
-
testProvider: '连通测试', testing: '测试中…',
|
|
136
|
-
testTip: '按当前填写的内容实测:API 会检查可达性与鉴权并真发一次视觉请求;CLI 会检查可执行文件并真跑一次视觉命令。生图只校验配置,不实际出图。',
|
|
137
|
-
testPass: '连通正常', testFail: '连通失败',
|
|
138
118
|
staleHub: '本 DSH 实例还在跑旧版插件,缺少该接口 —— 重启 DSH 后再试',
|
|
139
119
|
emptyResponse: (path: string, status: number) => `${path} → HTTP ${status},响应为空`,
|
|
140
120
|
badJson: (path: string, body: string) => `${path} → 非 JSON 响应: ${body}`,
|
|
141
|
-
refreshModels: '重新从 CLI 获取模型列表',
|
|
142
121
|
presetDefault: (id?: string) => `default · 跟随 DSH${id ? ` (${id})` : ''}`,
|
|
143
122
|
progressTip: (t: string, turn: number, step: number, calls: number | string) => `耗时 ${t} · 第${turn}轮第${step}步 · ${calls} 次工具调用`,
|
|
144
|
-
confirmDeleteProvider: (n: string) => `删除自定义 provider「${n}」?`,
|
|
145
|
-
needName: '⚠ 名称必填', needCmd: '⚠ 视觉/生图命令至少填一条',
|
|
146
|
-
needBaseUrl: '⚠ Base URL 必填', needModels: '⚠ 模型列表至少填一个',
|
|
147
|
-
keySet: '已配置', keyPlaceholder: 'sk-…(留空则不发送 Authorization)',
|
|
148
|
-
addModelTip: '添加模型到列表', removeModelTip: '从列表移除当前模型', modelPlaceholder: '模型 id,回车确认',
|
|
149
|
-
capVision: '视觉', capImagegen: '生图',
|
|
150
123
|
groupDispatch: '派发', groupRuntime: '运行', groupMultimodal: '多模态',
|
|
151
124
|
groupWorkflow: '工作流 / 执行(v0.2)',
|
|
152
125
|
roleWorker: 'Worker 角色(执行)', roleReviewer: 'Reviewer 角色(独立审查)',
|
|
@@ -163,8 +136,6 @@ const COPY = {
|
|
|
163
136
|
legacyCompatibility: '↓ 以下为兼容配置(legacy,Flash/Pro 仍可编辑)',
|
|
164
137
|
cardDispatch: { t: '派发策略', d: 'orchestrator 未指定时的档位与推理强度,以及档位约束、失败升档与两档各自挂载的 Agent 预设。' },
|
|
165
138
|
cardRuntime: { t: '执行与连接', d: 'worker 会话跑在本实例内还是独立进程,单个任务的超时上限,以及 CC / Codex 探测本实例的地址。' },
|
|
166
|
-
cardMM: { t: '视觉与生图', d: '给纯文本的 DSH 模型借来眼睛和画笔:describe_image、会话贴图转写与 generate_image 都用这里的设置。' },
|
|
167
|
-
cardCustomProv: { t: '自定义 Provider', d: '接入自己的 API 或本地命令;保存后会出现在上面的视觉 / 生图 provider 选择里。' },
|
|
168
139
|
modeDesc: { auto: 'auto · 优先 3210', hub: 'hub · 必须 3210' },
|
|
169
140
|
save: '保存', saved: '已保存', jobs: 'Worker 任务', empty: '当前没有 Worker / Reviewer 任务。',
|
|
170
141
|
adaptiveTitle: '自适应模型路由(实验)', adaptiveHint: '默认关闭;只重排系统自动候选,手动模型优先级始终保持原序。健康信号仅来自本进程已观察到的成功、失败、超时与粗粒度延迟,重启后清空。',
|
|
@@ -211,7 +182,7 @@ const COPY = {
|
|
|
211
182
|
expandAll: 'Expand all', collapseAll: 'Collapse all',
|
|
212
183
|
modelCount: (count: number) => `${count} models`, providerCount: (count: number) => `${count} providers`,
|
|
213
184
|
jobCount: (count: number) => `${count} jobs`, runningCount: (count: number) => `${count} running`,
|
|
214
|
-
sectionNames: { integrations: 'Codex / Claude / ZCode integration status', workflow: 'Crew workflow settings', flash: 'Worker / Flash', pro: 'Reviewer / Pro', dispatch: 'Model priority & dispatch', schedule: 'Peak / off-peak scheduling', adaptive: 'Adaptive routing', runtime: 'Runtime / activation boundaries',
|
|
185
|
+
sectionNames: { integrations: 'Codex / Claude / ZCode integration status', workflow: 'Crew workflow settings', flash: 'Worker / Flash', pro: 'Reviewer / Pro', dispatch: 'Model priority & dispatch', schedule: 'Peak / off-peak scheduling', adaptive: 'Adaptive routing', runtime: 'Runtime / activation boundaries', harnessProviders: 'Harness Providers', jobs: 'Task status' },
|
|
215
186
|
harnessHint: 'The official DeepSeek Harness dsh-crew profile: providers, Harness Models, and runtime configuration',
|
|
216
187
|
hostReadiness: 'Host integration readiness', hostReadinessHint: 'Uses structured installer and runtime evidence only; missing evidence is never READY.',
|
|
217
188
|
readinessLabels: { codex_mcp: 'Codex MCP', ds_worker: 'ds-worker', ds_reviewer: 'ds-reviewer', claude_plugin: 'Claude plugin', zcode_mcp: 'ZCode MCP', crew_harness: 'Crew plugin profile', official_bridge: 'Official bridge' },
|
|
@@ -248,20 +219,12 @@ const COPY = {
|
|
|
248
219
|
proReviewsFlash: 'Pro reviews Flash changes',
|
|
249
220
|
proReviewsFlashHint: 'Automatically follow a successful Flash run with one Pro review (blocking dispatch only)',
|
|
250
221
|
proReviewsLocked: 'always on in Review Pipeline mode',
|
|
251
|
-
enableCrewVision: 'Enable Crew Vision',
|
|
252
|
-
enableCrewVisionHint: 'When off, the Crew describe_image tool and vision route are not registered (coexist with your own dsh-vision or other plugins).',
|
|
253
|
-
enableImagegen: 'Enable Image Generation',
|
|
254
|
-
enableImagegenHint: 'When off, the Crew generate_image tool is not registered.',
|
|
255
|
-
capRestartHint: 'This switch changes tool registration and takes effect after a DSH restart',
|
|
256
|
-
capDisabledNote: 'Crew tool/route disabled (effective after DSH restart)',
|
|
257
222
|
tier: 'Default tier', effort: 'Default effort', mode: 'Mode', timeout: 'Timeout (s)', hubUrl: 'Hub URL',
|
|
258
223
|
tierPolicy: 'Tier policy', escalate: 'Escalate on failure',
|
|
259
224
|
tierPolicyDesc: { auto: 'auto · orchestrator picks', 'flash-only': 'flash only', 'pro-only': 'pro only' },
|
|
260
225
|
escalateHint: 'retry a failed flash run once on pro',
|
|
261
226
|
presetFlash: 'flash preset', presetPro: 'pro preset',
|
|
262
|
-
|
|
263
|
-
visionProvider: 'Vision provider', visionModel: 'Vision model', imagegenProvider: 'Image-gen provider',
|
|
264
|
-
harnessProviders: 'Harness Providers', harnessProviderHint: 'Live Provider registration and lifecycle in the official DeepSeek Harness dsh-crew profile on 3210; separate from vision / image-gen adapters.',
|
|
227
|
+
harnessProviders: 'Harness Providers', harnessProviderHint: 'Live Provider registration and lifecycle in the official DeepSeek Harness dsh-crew profile on 3210.',
|
|
265
228
|
providerState: (state: string) => `State: ${state}`, providerModels: (count: number) => `${count} models`, providerJobs: (count: number) => `${count} running jobs`,
|
|
266
229
|
providerOfficialBuiltin: '· official built-in (immutable)', providerSourceUnresolved: '· source unresolved (locked)',
|
|
267
230
|
providerLayerMigration: 'Legacy profile Providers detected', providerLayerMigrationHint: 'These Providers still exist in the Crew profile/base layer, so the native 3210 page cannot show Delete. Promote them to Harness user settings with an explicit plan; keys are never copied.', providerLayerMigrationPlan: 'Migration plan', providerLayerMigrate: 'Migrate to user layer', providerLayerMigrating: 'Migrating…', providerLayerMigrationConfirm: 'Move this Provider into Harness user settings? A rollback snapshot is saved first; credential values are never copied.', providerLayerRollback: 'Rollback migration',
|
|
@@ -271,30 +234,11 @@ const COPY = {
|
|
|
271
234
|
providerNoInventory: '3210 Provider inventory is temporarily unavailable.',
|
|
272
235
|
credentialReferences: 'Credential references', credentialReferenceHint: 'Names, ownership, and orphan status only; values are never read or deleted.',
|
|
273
236
|
credentialOrphan: 'orphan · separate purge approval required', credentialInUse: (count: number) => `in use · ${count} provider(s)`, credentialPurgePlan: 'Purge…', credentialPurgeConfirm: 'This irreversibly removes a Crew-owned credential. Continue?', credentialPurging: 'Purging…', credentialPurgeUnavailable: 'Credential purge unavailable', credentialPurgeUnverified: 'Purge ran but verification failed; the reference remains visible for recovery',
|
|
274
|
-
customProviders: 'Multimodal adapters', addProvider: '+ Add multimodal adapter',
|
|
275
|
-
noCustomProviders: 'None yet. Added providers appear in the vision / image-gen selects above.',
|
|
276
|
-
providerName: 'Name', modelsField: 'Models (comma-separated)',
|
|
277
|
-
providerType: 'Connection', typeApi: 'API · OpenAI-compatible', typeCli: 'CLI · local command',
|
|
278
|
-
baseUrl: 'Base URL', apiKey: 'API key', imagegenModel: 'Image-gen model (empty = no image generation)',
|
|
279
|
-
visionCmd: 'Vision command (optional)', imagegenCmd: 'Image-gen command (optional)',
|
|
280
|
-
apiFormHint: 'OpenAI-compatible: vision calls {base}/chat/completions (image inlined as base64), image-gen calls {base}/images/generations. The key is stored locally in ~/.config/dsh-crew/config.json and never sent to third parties.',
|
|
281
|
-
cliFormHint: 'Commands run via bash with shell-quoted placeholders. Vision: {image} {question} {model}, stdout is the answer; image-gen: {prompt} {output} {size}, the command must write the image to {output}. Fill in at least one command.',
|
|
282
|
-
saveProvider: 'Save', cancel: 'Cancel', edit: 'Edit', del: 'Delete',
|
|
283
|
-
testProvider: 'Test', testing: 'Testing…',
|
|
284
|
-
testTip: 'Probes what is currently filled in: API checks reachability and auth then makes one real vision call; CLI checks the executable then runs the vision command once. Image generation is only validated, never actually run.',
|
|
285
|
-
testPass: 'Connection OK', testFail: 'Connection failed',
|
|
286
237
|
staleHub: 'This DSH instance is still running an older build of the plugin and lacks this route — restart DSH and retry',
|
|
287
238
|
emptyResponse: (path: string, status: number) => `${path} → HTTP ${status}, empty response`,
|
|
288
239
|
badJson: (path: string, body: string) => `${path} → non-JSON response: ${body}`,
|
|
289
|
-
refreshModels: 'Re-fetch the model list from the CLI',
|
|
290
240
|
presetDefault: (id?: string) => `default · follow DSH${id ? ` (${id})` : ''}`,
|
|
291
241
|
progressTip: (t: string, turn: number, step: number, calls: number | string) => `${t} elapsed · turn ${turn}, step ${step} · ${calls} tool calls`,
|
|
292
|
-
confirmDeleteProvider: (n: string) => `Delete custom provider "${n}"?`,
|
|
293
|
-
needName: '⚠ Name is required', needCmd: '⚠ Fill in at least one command',
|
|
294
|
-
needBaseUrl: '⚠ Base URL is required', needModels: '⚠ Add at least one model',
|
|
295
|
-
keySet: 'set', keyPlaceholder: 'sk-… (leave empty to send no Authorization header)',
|
|
296
|
-
addModelTip: 'Add a model to the list', removeModelTip: 'Remove the current model from the list', modelPlaceholder: 'model id, Enter to confirm',
|
|
297
|
-
capVision: 'vision', capImagegen: 'image-gen',
|
|
298
242
|
groupDispatch: 'Dispatch', groupRuntime: 'Runtime', groupMultimodal: 'Multimodal',
|
|
299
243
|
groupWorkflow: 'Workflow / Execution (v0.2)',
|
|
300
244
|
roleWorker: 'Worker role (execution)', roleReviewer: 'Reviewer role (independent review)',
|
|
@@ -311,8 +255,6 @@ const COPY = {
|
|
|
311
255
|
legacyCompatibility: '↓ Legacy compatibility controls below (Flash/Pro still editable)',
|
|
312
256
|
cardDispatch: { t: 'Dispatch policy', d: 'Tier and effort used when the orchestrator names none, plus tier clamping, failure escalation and the Agent preset mounted per tier.' },
|
|
313
257
|
cardRuntime: { t: 'Execution & connection', d: 'Whether worker sessions run inside this instance or a separate process, the per-job timeout, and the address CC / Codex probes.' },
|
|
314
|
-
cardMM: { t: 'Vision & image generation', d: "Lends the harness's text-only models eyes and a brush: describe_image, pasted-image transcription and generate_image all use these settings." },
|
|
315
|
-
cardCustomProv: { t: 'Custom providers', d: 'Bring your own API or local command; saved providers appear in the vision / image-gen selects above.' },
|
|
316
258
|
modeDesc: { auto: 'auto · prefer 3210', hub: 'hub · require 3210' },
|
|
317
259
|
save: 'Save', saved: 'Saved', jobs: 'Worker jobs', empty: 'No Worker / Reviewer jobs yet.',
|
|
318
260
|
adaptiveTitle: 'Adaptive model routing (experimental)', adaptiveHint: 'Off by default. Only system-derived candidates may be reordered; explicit model priorities always keep their order. Health uses only process-local success, failure, timeout, and coarse latency observations and resets on restart.',
|
|
@@ -549,11 +491,6 @@ function WorkersPanel({ ctx }: { ctx: any }) {
|
|
|
549
491
|
const [presetOptions, setPresetOptions] = useState<Array<{ value: string; label?: string }>>([{ value: 'default' }]);
|
|
550
492
|
const [notice, setNotice] = useState('');
|
|
551
493
|
const [busy, setBusy] = useState(false);
|
|
552
|
-
const [provForm, setProvForm] = useState<{
|
|
553
|
-
originalId?: string; name: string; type: 'api' | 'cli'; models: string;
|
|
554
|
-
base_url: string; api_key: string; imagegen_model: string;
|
|
555
|
-
vision_command: string; imagegen_command: string;
|
|
556
|
-
} | null>(null);
|
|
557
494
|
const [modelAdd, setModelAdd] = useState<string | null>(null);
|
|
558
495
|
const [modelCatalog, setModelCatalog] = useState<any>(null);
|
|
559
496
|
const [modelCatalogError, setModelCatalogError] = useState('');
|
|
@@ -567,7 +504,6 @@ function WorkersPanel({ ctx }: { ctx: any }) {
|
|
|
567
504
|
const [providerLifecycleBusy, setProviderLifecycleBusy] = useState<string | null>(null);
|
|
568
505
|
const [modelPickerTier, setModelPickerTier] = useState<'flash' | 'pro' | null>(null);
|
|
569
506
|
const [modelQuery, setModelQuery] = useState('');
|
|
570
|
-
const [testResult, setTestResult] = useState<{ key: string; ok?: boolean; steps?: any[]; error?: string; busy: boolean } | null>(null);
|
|
571
507
|
const [expandedSections, setExpandedSections] = useState<Record<string, boolean>>(() => readSectionState(sectionStorage()));
|
|
572
508
|
const [surface, setSurface] = useState<string>('detecting');
|
|
573
509
|
const [readinessEnvelope, setReadinessEnvelope] = useState<{ runtime?: any; snapshot?: any; expiresAt?: number | null }>({});
|
|
@@ -599,13 +535,6 @@ function WorkersPanel({ ctx }: { ctx: any }) {
|
|
|
599
535
|
useEffect(() => {
|
|
600
536
|
if (providerInventoryError) setExpandedSections((current) => openSections(current, ['harnessProviders']));
|
|
601
537
|
}, [providerInventoryError]);
|
|
602
|
-
|
|
603
|
-
useEffect(() => {
|
|
604
|
-
if (testResult && !testResult.busy && testResult.ok === false) {
|
|
605
|
-
setExpandedSections((current) => openSections(current, ['providers']));
|
|
606
|
-
}
|
|
607
|
-
}, [testResult]);
|
|
608
|
-
|
|
609
538
|
useEffect(() => {
|
|
610
539
|
if (jobs.some((job) => job.status === 'running')) {
|
|
611
540
|
setExpandedSections((current) => openSections(current, ['jobs']));
|
|
@@ -838,119 +767,6 @@ function WorkersPanel({ ctx }: { ctx: any }) {
|
|
|
838
767
|
};
|
|
839
768
|
|
|
840
769
|
// --- custom providers & user-added models -------------------------------
|
|
841
|
-
const customProviders: any[] = config?.custom_providers ?? [];
|
|
842
|
-
const extraModels: Record<string, string[]> = config?.extra_models ?? {};
|
|
843
|
-
const RESERVED_IDS = ['claude-code', 'codex', 'grok', 'agy', 'off', 'custom', 'default'];
|
|
844
|
-
const provType = (p: any): 'api' | 'cli' => p.type ?? ((p.vision_command || p.imagegen_command) ? 'cli' : 'api');
|
|
845
|
-
const canSee = (p: any) => (provType(p) === 'cli' ? !!p.vision_command : !!p.base_url && (p.models?.length ?? 0) > 0);
|
|
846
|
-
const canDraw = (p: any) => (provType(p) === 'cli' ? !!p.imagegen_command : !!p.base_url && !!String(p.imagegen_model ?? '').trim());
|
|
847
|
-
const visionProviderOptions = [
|
|
848
|
-
{ value: 'claude-code' }, { value: 'codex' }, { value: 'grok' }, { value: 'agy' },
|
|
849
|
-
...customProviders.filter(canSee).map((p) => ({ value: p.id, label: p.name || p.id })),
|
|
850
|
-
{ value: 'off' },
|
|
851
|
-
];
|
|
852
|
-
const imagegenProviderOptions = [
|
|
853
|
-
{ value: 'codex' }, { value: 'agy', label: 'agy (nano banana)' }, { value: 'grok', label: 'grok (imagine)' },
|
|
854
|
-
...customProviders.filter(canDraw).map((p) => ({ value: p.id, label: p.name || p.id })),
|
|
855
|
-
{ value: 'off' },
|
|
856
|
-
];
|
|
857
|
-
const visionModelOptions = (() => {
|
|
858
|
-
if (!config) return [];
|
|
859
|
-
const p = config.vision_provider;
|
|
860
|
-
const custom = customProviders.find((c) => c.id === p);
|
|
861
|
-
const base: Array<{ value: string; label?: string }> = custom
|
|
862
|
-
? [{ value: 'default' }, ...(custom.models ?? []).map((m: string) => ({ value: m }))]
|
|
863
|
-
: dynModels[p] ?? (VISION_MODELS[p] ?? ['default']).map((m) => ({ value: m }));
|
|
864
|
-
const extras = (extraModels[p] ?? []).filter((m) => !base.some((o) => o.value === m)).map((m) => ({ value: m }));
|
|
865
|
-
return [...base, ...extras];
|
|
866
|
-
})();
|
|
867
|
-
const commitModelAdd = () => {
|
|
868
|
-
const m = (modelAdd ?? '').trim();
|
|
869
|
-
setModelAdd(null);
|
|
870
|
-
if (m === '' || !config) return;
|
|
871
|
-
const p = config.vision_provider;
|
|
872
|
-
const cur = extraModels[p] ?? [];
|
|
873
|
-
const nextExtra = { ...extraModels, [p]: cur.includes(m) ? cur : [...cur, m] };
|
|
874
|
-
setConfig((c: any) => ({ ...c, extra_models: nextExtra, vision_model: m }));
|
|
875
|
-
void applyPatch({ extra_models: nextExtra, vision_model: m });
|
|
876
|
-
};
|
|
877
|
-
const removeCurrentModel = () => {
|
|
878
|
-
if (!config) return;
|
|
879
|
-
const p = config.vision_provider;
|
|
880
|
-
const m = config.vision_model;
|
|
881
|
-
const nextExtra = { ...extraModels, [p]: (extraModels[p] ?? []).filter((x) => x !== m) };
|
|
882
|
-
const fallback = visionModelOptions.find((o) => o.value !== m)?.value ?? 'default';
|
|
883
|
-
setConfig((c: any) => ({ ...c, extra_models: nextExtra, vision_model: fallback }));
|
|
884
|
-
void applyPatch({ extra_models: nextExtra, vision_model: fallback });
|
|
885
|
-
};
|
|
886
|
-
const saveProviderForm = () => {
|
|
887
|
-
if (!provForm || !config) return;
|
|
888
|
-
const name = provForm.name.trim();
|
|
889
|
-
if (name === '') { setNotice(copy.needName); return; }
|
|
890
|
-
const models = provForm.models.split(/[,,\n]+/).map((s) => s.trim()).filter(Boolean);
|
|
891
|
-
const vision_command = provForm.vision_command.trim();
|
|
892
|
-
const imagegen_command = provForm.imagegen_command.trim();
|
|
893
|
-
if (provForm.type === 'cli') {
|
|
894
|
-
if (vision_command === '' && imagegen_command === '') { setNotice(copy.needCmd); return; }
|
|
895
|
-
} else {
|
|
896
|
-
if (provForm.base_url.trim() === '') { setNotice(copy.needBaseUrl); return; }
|
|
897
|
-
if (models.length === 0) { setNotice(copy.needModels); return; }
|
|
898
|
-
}
|
|
899
|
-
let id = provForm.originalId;
|
|
900
|
-
if (!id) {
|
|
901
|
-
const base = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'provider';
|
|
902
|
-
id = base;
|
|
903
|
-
let n = 2;
|
|
904
|
-
while (RESERVED_IDS.includes(id) || customProviders.some((p) => p.id === id)) id = `${base}-${n++}`;
|
|
905
|
-
}
|
|
906
|
-
const entry = provForm.type === 'api'
|
|
907
|
-
? {
|
|
908
|
-
id, name, type: 'api', models,
|
|
909
|
-
base_url: provForm.base_url.trim(),
|
|
910
|
-
api_key: provForm.api_key,
|
|
911
|
-
imagegen_model: provForm.imagegen_model.trim(),
|
|
912
|
-
}
|
|
913
|
-
: { id, name, type: 'cli', models, vision_command, imagegen_command };
|
|
914
|
-
const next = provForm.originalId
|
|
915
|
-
? customProviders.map((p) => (p.id === provForm.originalId ? entry : p))
|
|
916
|
-
: [...customProviders, entry];
|
|
917
|
-
field('custom_providers', next);
|
|
918
|
-
setProvForm(null);
|
|
919
|
-
};
|
|
920
|
-
const runProviderTest = (key: string, entry: any) => {
|
|
921
|
-
setTestResult({ key, busy: true });
|
|
922
|
-
void post('/provider-test', entry)
|
|
923
|
-
.then((r) => setTestResult(r.ok
|
|
924
|
-
? { key, busy: false, ok: r.result.ok, steps: r.result.steps }
|
|
925
|
-
: { key, busy: false, ok: false, error: r.error }))
|
|
926
|
-
.catch((e) => setTestResult({ key, busy: false, ok: false, error: String(e?.message ?? e) }));
|
|
927
|
-
};
|
|
928
|
-
/** Shared renderer for a test outcome under the button that triggered it. */
|
|
929
|
-
const testReport = (key: string) => {
|
|
930
|
-
if (!testResult || testResult.key !== key) return null;
|
|
931
|
-
if (testResult.busy) return <div style={{ fontSize: 11.5, opacity: 0.6 }}>{copy.testing}</div>;
|
|
932
|
-
return (
|
|
933
|
-
<div style={{ fontSize: 11.5, display: 'flex', flexDirection: 'column', gap: 2 }}>
|
|
934
|
-
<span style={{ color: testResult.ok ? '#3fb950' : '#f85149' }}>
|
|
935
|
-
{testResult.ok ? `✓ ${copy.testPass}` : `✗ ${copy.testFail}`}
|
|
936
|
-
</span>
|
|
937
|
-
{testResult.error && <span style={{ opacity: 0.7, wordBreak: 'break-all' as const }}>{testResult.error}</span>}
|
|
938
|
-
{(testResult.steps ?? []).map((st: any, i: number) => (
|
|
939
|
-
<span key={i} style={{ opacity: 0.7, wordBreak: 'break-all' as const }}>
|
|
940
|
-
<span style={{ color: st.ok ? '#3fb950' : '#f85149' }}>{st.ok ? '✓' : '✗'}</span> {st.name}: {st.detail}
|
|
941
|
-
</span>
|
|
942
|
-
))}
|
|
943
|
-
</div>
|
|
944
|
-
);
|
|
945
|
-
};
|
|
946
|
-
const deleteProvider = (id: string) => {
|
|
947
|
-
if (!config) return;
|
|
948
|
-
const patch: any = { custom_providers: customProviders.filter((p) => p.id !== id) };
|
|
949
|
-
if (config.vision_provider === id) { patch.vision_provider = 'claude-code'; patch.vision_model = 'haiku'; }
|
|
950
|
-
if (config.imagegen_provider === id) patch.imagegen_provider = 'codex';
|
|
951
|
-
setConfig((c: any) => ({ ...c, ...patch }));
|
|
952
|
-
void applyPatch(patch);
|
|
953
|
-
};
|
|
954
770
|
|
|
955
771
|
const deleteHarnessProvider = async (record: any) => {
|
|
956
772
|
if (!record || record.delete_capability !== 'supported' || record.desired_state === 'absent' || Number(record.references?.active_jobs ?? 0) > 0) {
|
|
@@ -1638,77 +1454,6 @@ function WorkersPanel({ ctx }: { ctx: any }) {
|
|
|
1638
1454
|
<ActivationSummary activation={config.config_activation} locale={locale} />
|
|
1639
1455
|
</CollapsibleSection>
|
|
1640
1456
|
|
|
1641
|
-
<CollapsibleSection sectionId="multimodal" title={copy.sectionNames.multimodal}
|
|
1642
|
-
summary={sectionSummary(config.vision_enabled ? `${copy.capVision}: ${config.vision_provider}` : `${copy.capVision}: off`, config.imagegen_enabled ? `${copy.capImagegen}: ${config.imagegen_provider}` : `${copy.capImagegen}: off`)}
|
|
1643
|
-
expanded={!!expandedSections.multimodal} onToggle={() => toggleSection('multimodal')}>
|
|
1644
|
-
{block(copy.cardMM, (<>
|
|
1645
|
-
<label style={{ ...S.field, flexDirection: 'row' as const, alignItems: 'center', gap: 6, gridColumn: '1 / -1' }} title={copy.capRestartHint}>
|
|
1646
|
-
<input type="checkbox" checked={!!config.vision_enabled} onChange={(e) => field('vision_enabled', e.target.checked)} />
|
|
1647
|
-
<span style={{ fontSize: 12.5 }}>{copy.enableCrewVision}</span>
|
|
1648
|
-
{config.vision_enabled === false && <span style={{ fontSize: 11, opacity: 0.55 }}>{copy.capDisabledNote}</span>}
|
|
1649
|
-
</label>
|
|
1650
|
-
<label style={S.field}><span style={S.fieldLabel}>{copy.visionProvider}</span>
|
|
1651
|
-
<CustomSelect value={config.vision_provider}
|
|
1652
|
-
onChange={(p) => {
|
|
1653
|
-
const custom = customProviders.find((c) => c.id === p);
|
|
1654
|
-
const m = custom ? (custom.models?.[0] ?? 'default') : (VISION_MODELS[p] ?? ['default'])[0];
|
|
1655
|
-
setModelAdd(null);
|
|
1656
|
-
setConfig((c: any) => ({ ...c, vision_provider: p, vision_model: m }));
|
|
1657
|
-
void applyPatch({ vision_provider: p, vision_model: m });
|
|
1658
|
-
if ((p === 'grok' || p === 'agy') && !dynModels[p]) {
|
|
1659
|
-
void get(`/vision-models?provider=${p}`).then((r) => {
|
|
1660
|
-
if (r.ok) setDynModels((d) => ({ ...d, [p]: r.models }));
|
|
1661
|
-
}).catch(() => {});
|
|
1662
|
-
}
|
|
1663
|
-
}}
|
|
1664
|
-
options={visionProviderOptions} /></label>
|
|
1665
|
-
<label style={S.field}><span style={S.fieldLabel}>{copy.visionModel}</span>
|
|
1666
|
-
<div style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
|
|
1667
|
-
{modelAdd === null ? (
|
|
1668
|
-
<CustomSelect value={config.vision_model} onChange={(v) => field('vision_model', v)}
|
|
1669
|
-
options={visionModelOptions} />
|
|
1670
|
-
) : (
|
|
1671
|
-
<input autoFocus style={{ ...S.input, width: '100%', boxSizing: 'border-box' as const, ...S.mono }}
|
|
1672
|
-
value={modelAdd} placeholder={copy.modelPlaceholder}
|
|
1673
|
-
onChange={(e) => setModelAdd(e.target.value)}
|
|
1674
|
-
onBlur={commitModelAdd}
|
|
1675
|
-
onKeyDown={(e: any) => {
|
|
1676
|
-
if (e.key === 'Enter') e.currentTarget.blur();
|
|
1677
|
-
if (e.key === 'Escape') setModelAdd(null);
|
|
1678
|
-
}} />
|
|
1679
|
-
)}
|
|
1680
|
-
{modelAdd === null && (config.vision_provider === 'grok' || config.vision_provider === 'agy') && (
|
|
1681
|
-
<button type="button" title={copy.refreshModels} disabled={busy}
|
|
1682
|
-
style={{ ...S.btn, padding: '3px 8px', flexShrink: 0 }}
|
|
1683
|
-
onClick={() => {
|
|
1684
|
-
const p = config.vision_provider;
|
|
1685
|
-
setBusy(true);
|
|
1686
|
-
void get(`/vision-models?provider=${p}&refresh=1`)
|
|
1687
|
-
.then((r) => { if (r.ok) setDynModels((d) => ({ ...d, [p]: r.models })); })
|
|
1688
|
-
.catch(() => {})
|
|
1689
|
-
.finally(() => setBusy(false));
|
|
1690
|
-
}}>↻</button>
|
|
1691
|
-
)}
|
|
1692
|
-
{modelAdd === null && config.vision_provider !== 'off' && (
|
|
1693
|
-
<button type="button" title={copy.addModelTip} style={{ ...S.btn, padding: '3px 8px', flexShrink: 0 }}
|
|
1694
|
-
onClick={() => setModelAdd('')}>+</button>
|
|
1695
|
-
)}
|
|
1696
|
-
{modelAdd === null && (extraModels[config.vision_provider] ?? []).includes(config.vision_model) && (
|
|
1697
|
-
<button type="button" title={copy.removeModelTip} style={{ ...S.btn, padding: '3px 8px', flexShrink: 0 }}
|
|
1698
|
-
onClick={removeCurrentModel}>−</button>
|
|
1699
|
-
)}
|
|
1700
|
-
</div></label>
|
|
1701
|
-
<label style={{ ...S.field, flexDirection: 'row' as const, alignItems: 'center', gap: 6, gridColumn: '1 / -1' }} title={copy.capRestartHint}>
|
|
1702
|
-
<input type="checkbox" checked={!!config.imagegen_enabled} onChange={(e) => field('imagegen_enabled', e.target.checked)} />
|
|
1703
|
-
<span style={{ fontSize: 12.5 }}>{copy.enableImagegen}</span>
|
|
1704
|
-
{config.imagegen_enabled === false && <span style={{ fontSize: 11, opacity: 0.55 }}>{copy.capDisabledNote}</span>}
|
|
1705
|
-
</label>
|
|
1706
|
-
<label style={S.field}><span style={S.fieldLabel}>{copy.imagegenProvider}</span>
|
|
1707
|
-
<CustomSelect value={config.imagegen_provider} onChange={(v) => field('imagegen_provider', v)}
|
|
1708
|
-
options={imagegenProviderOptions} /></label>
|
|
1709
|
-
</>))}
|
|
1710
|
-
</CollapsibleSection>
|
|
1711
|
-
|
|
1712
1457
|
<CollapsibleSection sectionId="harnessProviders" title={copy.sectionNames.harnessProviders}
|
|
1713
1458
|
summary={sectionSummary(copy.providerCount(providerInventory?.records?.length ?? 0), providerInventoryError ? copy.providerLifecycleError : '')}
|
|
1714
1459
|
expanded={!!expandedSections.harnessProviders} onToggle={() => toggleSection('harnessProviders')}>
|
|
@@ -1818,105 +1563,6 @@ function WorkersPanel({ ctx }: { ctx: any }) {
|
|
|
1818
1563
|
)}
|
|
1819
1564
|
</CollapsibleSection>
|
|
1820
1565
|
|
|
1821
|
-
<CollapsibleSection sectionId="providers" title={copy.sectionNames.providers}
|
|
1822
|
-
summary={sectionSummary(copy.providerCount(customProviders.length), testResult?.ok === false && copy.testFail)}
|
|
1823
|
-
expanded={!!expandedSections.providers} onToggle={() => toggleSection('providers')}>
|
|
1824
|
-
{block(copy.cardCustomProv, (<>
|
|
1825
|
-
{customProviders.length === 0 && provForm === null && (
|
|
1826
|
-
<div style={{ fontSize: 12, opacity: 0.5 }}>{copy.noCustomProviders}</div>
|
|
1827
|
-
)}
|
|
1828
|
-
{customProviders.map((p) => (
|
|
1829
|
-
<div key={p.id} style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 12.5 }}>
|
|
1830
|
-
<span style={{ fontWeight: 500 }}>{p.name || p.id}</span>
|
|
1831
|
-
<span style={{ ...S.chip(false), opacity: 0.7 }}>{provType(p) === 'api' ? 'API' : 'CLI'}</span>
|
|
1832
|
-
{canSee(p) && <span style={S.chip(true)}>{copy.capVision}</span>}
|
|
1833
|
-
{canDraw(p) && <span style={S.chip(true)}>{copy.capImagegen}</span>}
|
|
1834
|
-
{provType(p) === 'api' && !!p.api_key && <span style={{ fontSize: 11.5, opacity: 0.5 }}>key {copy.keySet}</span>}
|
|
1835
|
-
<span style={{ flex: 1 }} />
|
|
1836
|
-
<button style={{ ...S.btn, padding: '2px 8px' }} title={copy.testTip}
|
|
1837
|
-
disabled={!!testResult?.busy}
|
|
1838
|
-
onClick={() => runProviderTest(`row:${p.id}`, p)}>
|
|
1839
|
-
{testResult?.key === `row:${p.id}` && testResult.busy ? copy.testing : copy.testProvider}</button>
|
|
1840
|
-
<button style={{ ...S.btn, padding: '2px 8px' }}
|
|
1841
|
-
onClick={() => setProvForm({
|
|
1842
|
-
originalId: p.id, name: p.name ?? p.id, type: provType(p),
|
|
1843
|
-
models: (p.models ?? []).join(', '),
|
|
1844
|
-
base_url: p.base_url ?? '', api_key: p.api_key ?? '', imagegen_model: p.imagegen_model ?? '',
|
|
1845
|
-
vision_command: p.vision_command ?? '', imagegen_command: p.imagegen_command ?? '',
|
|
1846
|
-
})}>{copy.edit}</button>
|
|
1847
|
-
<button style={{ ...S.btn, padding: '2px 8px', opacity: 0.75 }}
|
|
1848
|
-
onClick={() => { if (confirm(copy.confirmDeleteProvider(p.name || p.id))) deleteProvider(p.id); }}>{copy.del}</button>
|
|
1849
|
-
</div>
|
|
1850
|
-
))}
|
|
1851
|
-
{customProviders.some((p) => testResult?.key === `row:${p.id}`) && testReport(testResult!.key)}
|
|
1852
|
-
{provForm === null ? (
|
|
1853
|
-
<div><button style={S.btn} onClick={() => setProvForm({
|
|
1854
|
-
name: '', type: 'api', models: '', base_url: '', api_key: '', imagegen_model: '',
|
|
1855
|
-
vision_command: '', imagegen_command: '',
|
|
1856
|
-
})}>{copy.addProvider}</button></div>
|
|
1857
|
-
) : (
|
|
1858
|
-
<div style={{ borderTop: '1px solid rgba(128,128,128,0.16)', paddingTop: 10, display: 'flex', flexDirection: 'column', gap: 10 }}>
|
|
1859
|
-
<div style={S.blockGrid}>
|
|
1860
|
-
<label style={S.field}><span style={S.fieldLabel}>{copy.providerName}</span>
|
|
1861
|
-
<input autoFocus style={{ ...S.input, width: '100%', boxSizing: 'border-box' as const }}
|
|
1862
|
-
value={provForm.name} onChange={(e) => setProvForm({ ...provForm, name: e.target.value })} /></label>
|
|
1863
|
-
<label style={S.field}><span style={S.fieldLabel}>{copy.providerType}</span>
|
|
1864
|
-
<CustomSelect value={provForm.type} onChange={(v) => setProvForm({ ...provForm, type: v as 'api' | 'cli' })}
|
|
1865
|
-
options={[{ value: 'api', label: copy.typeApi }, { value: 'cli', label: copy.typeCli }]} /></label>
|
|
1866
|
-
</div>
|
|
1867
|
-
{provForm.type === 'api' ? (<>
|
|
1868
|
-
<div style={S.blockGrid}>
|
|
1869
|
-
<label style={S.field}><span style={S.fieldLabel}>{copy.baseUrl}</span>
|
|
1870
|
-
<input style={{ ...S.input, width: '100%', boxSizing: 'border-box' as const, ...S.mono }}
|
|
1871
|
-
value={provForm.base_url} placeholder="https://api.example.com/v1"
|
|
1872
|
-
onChange={(e) => setProvForm({ ...provForm, base_url: e.target.value })} /></label>
|
|
1873
|
-
<label style={S.field}><span style={S.fieldLabel}>{copy.apiKey}</span>
|
|
1874
|
-
<input type="password" autoComplete="off" style={{ ...S.input, width: '100%', boxSizing: 'border-box' as const, ...S.mono }}
|
|
1875
|
-
value={provForm.api_key} placeholder={copy.keyPlaceholder}
|
|
1876
|
-
onChange={(e) => setProvForm({ ...provForm, api_key: e.target.value })} /></label>
|
|
1877
|
-
<label style={S.field}><span style={S.fieldLabel}>{copy.modelsField}</span>
|
|
1878
|
-
<input style={{ ...S.input, width: '100%', boxSizing: 'border-box' as const, ...S.mono }}
|
|
1879
|
-
value={provForm.models} placeholder="gpt-4o-mini, qwen-vl-max"
|
|
1880
|
-
onChange={(e) => setProvForm({ ...provForm, models: e.target.value })} /></label>
|
|
1881
|
-
<label style={S.field}><span style={S.fieldLabel}>{copy.imagegenModel}</span>
|
|
1882
|
-
<input style={{ ...S.input, width: '100%', boxSizing: 'border-box' as const, ...S.mono }}
|
|
1883
|
-
value={provForm.imagegen_model} placeholder="dall-e-3"
|
|
1884
|
-
onChange={(e) => setProvForm({ ...provForm, imagegen_model: e.target.value })} /></label>
|
|
1885
|
-
</div>
|
|
1886
|
-
<div style={{ fontSize: 11.5, opacity: 0.55 }}>{copy.apiFormHint}</div>
|
|
1887
|
-
</>) : (<>
|
|
1888
|
-
<label style={S.field}><span style={S.fieldLabel}>{copy.visionCmd}</span>
|
|
1889
|
-
<input style={{ ...S.input, width: '100%', boxSizing: 'border-box' as const, ...S.mono }}
|
|
1890
|
-
value={provForm.vision_command} placeholder="mycli see {image} --ask {question} --model {model}"
|
|
1891
|
-
onChange={(e) => setProvForm({ ...provForm, vision_command: e.target.value })} /></label>
|
|
1892
|
-
<label style={S.field}><span style={S.fieldLabel}>{copy.imagegenCmd}</span>
|
|
1893
|
-
<input style={{ ...S.input, width: '100%', boxSizing: 'border-box' as const, ...S.mono }}
|
|
1894
|
-
value={provForm.imagegen_command} placeholder="mycli gen {prompt} -o {output}"
|
|
1895
|
-
onChange={(e) => setProvForm({ ...provForm, imagegen_command: e.target.value })} /></label>
|
|
1896
|
-
<label style={S.field}><span style={S.fieldLabel}>{copy.modelsField}</span>
|
|
1897
|
-
<input style={{ ...S.input, width: '100%', boxSizing: 'border-box' as const, ...S.mono }}
|
|
1898
|
-
value={provForm.models} placeholder="model-a, model-b"
|
|
1899
|
-
onChange={(e) => setProvForm({ ...provForm, models: e.target.value })} /></label>
|
|
1900
|
-
<div style={{ fontSize: 11.5, opacity: 0.55 }}>{copy.cliFormHint}</div>
|
|
1901
|
-
</>)}
|
|
1902
|
-
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
|
|
1903
|
-
<button style={S.btn} onClick={saveProviderForm}>{copy.saveProvider}</button>
|
|
1904
|
-
<button style={S.btn} title={copy.testTip} disabled={!!testResult?.busy}
|
|
1905
|
-
onClick={() => runProviderTest('form', {
|
|
1906
|
-
id: provForm.originalId ?? 'draft', name: provForm.name || 'draft', type: provForm.type,
|
|
1907
|
-
models: provForm.models.split(/[,,\n]+/).map((x) => x.trim()).filter(Boolean),
|
|
1908
|
-
base_url: provForm.base_url.trim(), api_key: provForm.api_key,
|
|
1909
|
-
imagegen_model: provForm.imagegen_model.trim(),
|
|
1910
|
-
vision_command: provForm.vision_command.trim(), imagegen_command: provForm.imagegen_command.trim(),
|
|
1911
|
-
})}>
|
|
1912
|
-
{testResult?.key === 'form' && testResult.busy ? copy.testing : copy.testProvider}</button>
|
|
1913
|
-
<button style={{ ...S.btn, opacity: 0.75 }} onClick={() => setProvForm(null)}>{copy.cancel}</button>
|
|
1914
|
-
</div>
|
|
1915
|
-
{testReport('form')}
|
|
1916
|
-
</div>
|
|
1917
|
-
)}
|
|
1918
|
-
</>), false)}
|
|
1919
|
-
</CollapsibleSection>
|
|
1920
1566
|
</>)}
|
|
1921
1567
|
<div style={{ fontSize: 11.5, opacity: 0.55, marginTop: 2 }}>{copy.globalHint}</div>
|
|
1922
1568
|
|
|
@@ -3,7 +3,7 @@ import { applyQuick } from './quick-panel';
|
|
|
3
3
|
export const inject = ['slots', 'locale'];
|
|
4
4
|
|
|
5
5
|
// Quick-controls entry for the official 3080 surface: master switch,
|
|
6
|
-
// flash/pro model priority
|
|
6
|
+
// flash/pro model priority only. Never ships the
|
|
7
7
|
// full control-plane code.
|
|
8
8
|
export function apply(ctx: any): void {
|
|
9
9
|
applyQuick(ctx);
|