@ran-sh/dsh-crew 0.3.7 → 0.3.8
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/README.md +36 -67
- package/README.zh.md +37 -68
- package/lib/client.js +3446 -3446
- package/official-web-bridge/cordis.patch.yml +4 -0
- package/official-web-bridge/entry.mjs +1 -0
- package/official-web-bridge/lib/client.js +3446 -0
- package/official-web-bridge/package.json +26 -0
- package/package.json +5 -3
- package/scripts/build-client.mjs +5 -1
- package/scripts/verify-official-bridge-e2e.mjs +159 -0
- package/src/dsh-cli-runtime.mjs +219 -208
- package/src/install/npx-lifecycle.mjs +67 -3
- package/src/install/official-web.mjs +132 -0
- package/src/official-web-bridge.mjs +196 -0
- package/src/runtime-identity.mjs +1 -1
|
@@ -0,0 +1,3446 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({ id: "@ran-sh/dsh-crew-web-bridge", factory: (require) => {
|
|
2
|
+
var module = { exports: {} }; var exports = module.exports;
|
|
3
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
4
|
+
//#region \0rolldown/runtime.js
|
|
5
|
+
var __create = Object.create;
|
|
6
|
+
var __defProp = Object.defineProperty;
|
|
7
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
8
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
9
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
10
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
13
|
+
key = keys[i];
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
15
|
+
get: ((k) => from[k]).bind(null, key),
|
|
16
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
22
|
+
value: mod,
|
|
23
|
+
enumerable: true
|
|
24
|
+
}) : target, mod));
|
|
25
|
+
//#endregion
|
|
26
|
+
let react = require("react");
|
|
27
|
+
react = __toESM(react, 1);
|
|
28
|
+
let react_dom = require("react-dom");
|
|
29
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
30
|
+
//#region src/client/collapsible-sections.mjs
|
|
31
|
+
const SETTINGS_SECTION_STORAGE_KEY = "dsh-crew.settings-sections.v1";
|
|
32
|
+
const SETTINGS_SECTION_IDS = Object.freeze([
|
|
33
|
+
"integrations",
|
|
34
|
+
"workflow",
|
|
35
|
+
"flash",
|
|
36
|
+
"pro",
|
|
37
|
+
"dispatch",
|
|
38
|
+
"runtime",
|
|
39
|
+
"multimodal",
|
|
40
|
+
"providers",
|
|
41
|
+
"jobs"
|
|
42
|
+
]);
|
|
43
|
+
function createDefaultSectionState() {
|
|
44
|
+
return Object.fromEntries(SETTINGS_SECTION_IDS.map((id) => [id, id === "workflow"]));
|
|
45
|
+
}
|
|
46
|
+
function readSectionState(storage, key = SETTINGS_SECTION_STORAGE_KEY) {
|
|
47
|
+
const defaults = createDefaultSectionState();
|
|
48
|
+
if (!storage?.getItem) return defaults;
|
|
49
|
+
try {
|
|
50
|
+
const parsed = JSON.parse(storage.getItem(key) ?? "null");
|
|
51
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return defaults;
|
|
52
|
+
for (const id of SETTINGS_SECTION_IDS) if (typeof parsed[id] === "boolean") defaults[id] = parsed[id];
|
|
53
|
+
} catch {
|
|
54
|
+
return defaults;
|
|
55
|
+
}
|
|
56
|
+
return defaults;
|
|
57
|
+
}
|
|
58
|
+
function writeSectionState(storage, state, key = SETTINGS_SECTION_STORAGE_KEY) {
|
|
59
|
+
if (!storage?.setItem) return;
|
|
60
|
+
try {
|
|
61
|
+
storage.setItem(key, JSON.stringify(Object.fromEntries(SETTINGS_SECTION_IDS.map((id) => [id, state[id] === true]))));
|
|
62
|
+
} catch {}
|
|
63
|
+
}
|
|
64
|
+
function setEverySection(expanded) {
|
|
65
|
+
return Object.fromEntries(SETTINGS_SECTION_IDS.map((id) => [id, expanded === true]));
|
|
66
|
+
}
|
|
67
|
+
function openSections(state, ids) {
|
|
68
|
+
const next = { ...state };
|
|
69
|
+
for (const id of ids) if (SETTINGS_SECTION_IDS.includes(id)) next[id] = true;
|
|
70
|
+
return next;
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/client/index.tsx
|
|
74
|
+
const inject$1 = ["slots", "locale"];
|
|
75
|
+
const API$1 = "/_dsh/dsh-crew";
|
|
76
|
+
const COPY = {
|
|
77
|
+
zh: {
|
|
78
|
+
label: "DSH Crew",
|
|
79
|
+
title: "DSH Crew",
|
|
80
|
+
intro: "把 Claude Code / Codex 的子任务派给本实例的 DSH agent,在宿主里显示为原生子代理、进度可见。配置派发默认值、执行方式,以及接入视觉与生图能力,一键装进宿主。",
|
|
81
|
+
integrations: "集成",
|
|
82
|
+
installed: "已安装",
|
|
83
|
+
notInstalled: "未安装",
|
|
84
|
+
hud: "HUD 段",
|
|
85
|
+
install: "安装",
|
|
86
|
+
update: "更新",
|
|
87
|
+
restore: "还原",
|
|
88
|
+
confirmRestore: (name) => `确定从 ${name} 移除 dsh-crew 集成?(settings 会先备份)`,
|
|
89
|
+
globalConfig: "全局配置",
|
|
90
|
+
expandAll: "全部展开",
|
|
91
|
+
collapseAll: "全部折叠",
|
|
92
|
+
modelCount: (count) => `${count} 个模型`,
|
|
93
|
+
providerCount: (count) => `${count} 个 Provider`,
|
|
94
|
+
jobCount: (count) => `${count} 个任务`,
|
|
95
|
+
runningCount: (count) => `${count} 个运行中`,
|
|
96
|
+
sectionNames: {
|
|
97
|
+
integrations: "宿主集成",
|
|
98
|
+
workflow: "工作流",
|
|
99
|
+
flash: "Flash",
|
|
100
|
+
pro: "Pro",
|
|
101
|
+
dispatch: "派发策略",
|
|
102
|
+
runtime: "运行连接",
|
|
103
|
+
multimodal: "视觉与生图",
|
|
104
|
+
providers: "自定义 Provider",
|
|
105
|
+
jobs: "Worker 任务"
|
|
106
|
+
},
|
|
107
|
+
globalHint: "修改即时保存到 ~/.config/dsh-crew/config.json;CC / Codex 的新会话自动读取为默认值(会话内可用 /dsh-crew:config 临时覆盖)。",
|
|
108
|
+
orchestration: "Agent 编排",
|
|
109
|
+
enableSubagents: "启用子 Agent",
|
|
110
|
+
enableSubagentsHint: "总开关。关闭后 dsh_run_worker / dsh_spawn_worker 在工具层拒绝派发(配置保留,重新开启即恢复)。",
|
|
111
|
+
subagentsKept: "配置已保留",
|
|
112
|
+
mainAgentMode: "主 Agent 模式",
|
|
113
|
+
mainAgentModeDesc: {
|
|
114
|
+
"direct-allowed": "直接允许 · 可自己做,也可委派",
|
|
115
|
+
"coordinator-first": "协调优先 · 先规划、拆分、委派,再检查验证",
|
|
116
|
+
"dispatcher-only": "仅派发 · 尽量只做规划、派发、审查与整合"
|
|
117
|
+
},
|
|
118
|
+
mainAgentModeHint: "这是宿主路由指引,不是对宿主自身工具的硬限制。",
|
|
119
|
+
collaborationMode: "协作模式",
|
|
120
|
+
collaborationModeDesc: {
|
|
121
|
+
"flash-only": "Flash Only · 只用 Flash",
|
|
122
|
+
"pro-only": "Pro Only · 只用 Pro",
|
|
123
|
+
balanced: "Balanced · 双 Auto",
|
|
124
|
+
"review-pipeline": "Review Pipeline · Flash 实现 + Pro 复审",
|
|
125
|
+
custom: "Custom · 完全手动"
|
|
126
|
+
},
|
|
127
|
+
tierCardFlash: "Flash",
|
|
128
|
+
tierCardPro: "Pro",
|
|
129
|
+
tierState: "状态",
|
|
130
|
+
tierStateDesc: {
|
|
131
|
+
disabled: "禁用",
|
|
132
|
+
manual: "手动(仅用户点名时可用)",
|
|
133
|
+
auto: "自动(orchestrator 可自动委派)"
|
|
134
|
+
},
|
|
135
|
+
stateManagedByPreset: "由协作模式管理",
|
|
136
|
+
statePresetHint: "切到 Custom 可单独配置各档状态。",
|
|
137
|
+
workerProvider: "Worker Provider",
|
|
138
|
+
workerProviderDesc: {
|
|
139
|
+
"follow-dsh": "跟随 Harness · 使用各档独立的模型优先级",
|
|
140
|
+
"deepseek-official": "DeepSeek Official · 使用旧版固定模型路由"
|
|
141
|
+
},
|
|
142
|
+
workerProviderHint: "仅作用于 Hub 模式;Standalone 始终用 deepseek-official + DEEPSEEK_API_KEY。",
|
|
143
|
+
workerModels: "Worker 模型",
|
|
144
|
+
refreshHarnessModels: "刷新 Harness 模型",
|
|
145
|
+
refreshingModels: "刷新中…",
|
|
146
|
+
modelPriority: "模型优先级",
|
|
147
|
+
addPriorityModel: "+ 添加优先模型",
|
|
148
|
+
modelSearch: "搜索 Provider 或 Model…",
|
|
149
|
+
harnessDefault: "Harness 默认模型",
|
|
150
|
+
providerUnavailable: "Provider 不可用",
|
|
151
|
+
notAdvertised: "当前未列出",
|
|
152
|
+
noPriority: "未配置优先项",
|
|
153
|
+
modelPoolSummary: (p, m) => `Providers: ${p} · Models: ${m}`,
|
|
154
|
+
partialCatalog: "部分 Provider 读取失败",
|
|
155
|
+
catalogFailed: "无法读取 Harness 模型目录",
|
|
156
|
+
removePriority: "移除",
|
|
157
|
+
moveUp: "上移",
|
|
158
|
+
moveDown: "下移",
|
|
159
|
+
needsModelSelection: "多个 Provider 提供默认偏好模型,且 Harness Default 无法消歧;当前将回退到 Harness Default,请手动选择。",
|
|
160
|
+
responsibilities: "职责",
|
|
161
|
+
responsibilitiesHint: "路由指引,仅用于描述分工;只剩一个 Auto 档时它承担全部可委派工作。",
|
|
162
|
+
roleLabels: {
|
|
163
|
+
implementation: "代码实现",
|
|
164
|
+
simple_fix: "简单修复",
|
|
165
|
+
tests: "测试",
|
|
166
|
+
search_inspection: "搜索 / 检查",
|
|
167
|
+
architecture: "架构",
|
|
168
|
+
complex_debugging: "复杂调试",
|
|
169
|
+
refactor: "重构",
|
|
170
|
+
code_review: "代码审查"
|
|
171
|
+
},
|
|
172
|
+
routing: "路由",
|
|
173
|
+
proReviewsFlash: "Pro 复审 Flash 改动",
|
|
174
|
+
proReviewsFlashHint: "Flash 成功后自动追加一次 Pro 复审(仅 blocking 派发)",
|
|
175
|
+
proReviewsLocked: "Review Pipeline 模式固定开启",
|
|
176
|
+
enableCrewVision: "启用 Crew 视觉",
|
|
177
|
+
enableCrewVisionHint: "关闭后不注册 Crew 的 describe_image 与视觉 route(可与你的 dsh-vision 等共存)。",
|
|
178
|
+
enableImagegen: "启用生图",
|
|
179
|
+
enableImagegenHint: "关闭后不注册 Crew 的 generate_image。",
|
|
180
|
+
capRestartHint: "该开关改变工具注册,需重启 DSH 生效",
|
|
181
|
+
capDisabledNote: "Crew 工具/route 已禁用(重启 DSH 后生效)",
|
|
182
|
+
tier: "默认档位",
|
|
183
|
+
effort: "默认推理",
|
|
184
|
+
mode: "执行模式",
|
|
185
|
+
timeout: "默认超时(秒)",
|
|
186
|
+
hubUrl: "Hub 地址",
|
|
187
|
+
tierPolicy: "档位策略",
|
|
188
|
+
escalate: "失败升档",
|
|
189
|
+
tierPolicyDesc: {
|
|
190
|
+
auto: "auto · orchestrator 自选",
|
|
191
|
+
"flash-only": "只用 flash",
|
|
192
|
+
"pro-only": "只用 pro"
|
|
193
|
+
},
|
|
194
|
+
escalateHint: "flash 失败自动用 pro 重试一次",
|
|
195
|
+
presetFlash: "flash 模式",
|
|
196
|
+
presetPro: "pro 模式",
|
|
197
|
+
multimodal: "多模态",
|
|
198
|
+
visionProvider: "视觉 provider",
|
|
199
|
+
visionModel: "视觉模型",
|
|
200
|
+
imagegenProvider: "生图 provider",
|
|
201
|
+
customProviders: "自定义 Provider",
|
|
202
|
+
addProvider: "+ 添加 Provider",
|
|
203
|
+
noCustomProviders: "暂无。添加后会出现在上方的视觉 / 生图 provider 选择里。",
|
|
204
|
+
providerName: "名称",
|
|
205
|
+
modelsField: "模型列表(逗号分隔)",
|
|
206
|
+
providerType: "接入方式",
|
|
207
|
+
typeApi: "API · 兼容 OpenAI 接口",
|
|
208
|
+
typeCli: "CLI · 本地命令",
|
|
209
|
+
baseUrl: "Base URL",
|
|
210
|
+
apiKey: "API Key",
|
|
211
|
+
imagegenModel: "生图模型(留空=不提供生图)",
|
|
212
|
+
visionCmd: "视觉命令(可选)",
|
|
213
|
+
imagegenCmd: "生图命令(可选)",
|
|
214
|
+
apiFormHint: "走 OpenAI 兼容接口:视觉调 {base}/chat/completions(图片以 base64 内联),生图调 {base}/images/generations。Key 保存在本机 ~/.config/dsh-crew/config.json,不会外发到第三方。",
|
|
215
|
+
cliFormHint: "命令经 bash 执行,占位符会被安全引用后代入。视觉: {image} {question} {model},stdout 即答案;生图: {prompt} {output} {size},命令须把图片写到 {output}。两条命令至少填一条。",
|
|
216
|
+
saveProvider: "保存",
|
|
217
|
+
cancel: "取消",
|
|
218
|
+
edit: "编辑",
|
|
219
|
+
del: "删除",
|
|
220
|
+
testProvider: "连通测试",
|
|
221
|
+
testing: "测试中…",
|
|
222
|
+
testTip: "按当前填写的内容实测:API 会检查可达性与鉴权并真发一次视觉请求;CLI 会检查可执行文件并真跑一次视觉命令。生图只校验配置,不实际出图。",
|
|
223
|
+
testPass: "连通正常",
|
|
224
|
+
testFail: "连通失败",
|
|
225
|
+
staleHub: "本 DSH 实例还在跑旧版插件,缺少该接口 —— 重启 DSH 后再试",
|
|
226
|
+
emptyResponse: (path, status) => `${path} → HTTP ${status},响应为空`,
|
|
227
|
+
badJson: (path, body) => `${path} → 非 JSON 响应: ${body}`,
|
|
228
|
+
refreshModels: "重新从 CLI 获取模型列表",
|
|
229
|
+
presetDefault: (id) => `default · 跟随 DSH${id ? ` (${id})` : ""}`,
|
|
230
|
+
progressTip: (t, turn, step, calls) => `耗时 ${t} · 第${turn}轮第${step}步 · ${calls} 次工具调用`,
|
|
231
|
+
confirmDeleteProvider: (n) => `删除自定义 provider「${n}」?`,
|
|
232
|
+
needName: "⚠ 名称必填",
|
|
233
|
+
needCmd: "⚠ 视觉/生图命令至少填一条",
|
|
234
|
+
needBaseUrl: "⚠ Base URL 必填",
|
|
235
|
+
needModels: "⚠ 模型列表至少填一个",
|
|
236
|
+
keySet: "已配置",
|
|
237
|
+
keyPlaceholder: "sk-…(留空则不发送 Authorization)",
|
|
238
|
+
addModelTip: "添加模型到列表",
|
|
239
|
+
removeModelTip: "从列表移除当前模型",
|
|
240
|
+
modelPlaceholder: "模型 id,回车确认",
|
|
241
|
+
capVision: "视觉",
|
|
242
|
+
capImagegen: "生图",
|
|
243
|
+
groupDispatch: "派发",
|
|
244
|
+
groupRuntime: "运行",
|
|
245
|
+
groupMultimodal: "多模态",
|
|
246
|
+
groupWorkflow: "工作流 / 执行(v0.2)",
|
|
247
|
+
roleWorker: "Worker 角色(执行)",
|
|
248
|
+
roleReviewer: "Reviewer 角色(独立审查)",
|
|
249
|
+
workerStateLabel: "Worker 状态",
|
|
250
|
+
reviewStateLabel: "Reviewer 状态",
|
|
251
|
+
roleStateHint: "auto:orchestrator 可自动派发;manual:仅显式点名;disabled:禁用。未显式设置时由协作模式推导。",
|
|
252
|
+
autoReview: "自动复审",
|
|
253
|
+
autoReviewHint: "worker 成功后自动运行一次 reviewer(独立模型策略,只读)",
|
|
254
|
+
isolation: "工作区隔离",
|
|
255
|
+
isolationHint: "worktree 为默认;非 Git 仓库时需显式 shared,否则任务以 NOT_GIT_REPOSITORY 失败。",
|
|
256
|
+
isolationDesc: {
|
|
257
|
+
worktree: "worktree · 每个 coding worker 独立临时 git worktree(主工作区不动)",
|
|
258
|
+
shared: "shared · 旧版就地执行"
|
|
259
|
+
},
|
|
260
|
+
maxParallel: "最大并行",
|
|
261
|
+
maxParallelHint: "同时运行的工作流上限(1–16);超出进入队列。",
|
|
262
|
+
legacyCompatibility: "↓ 以下为兼容配置(legacy,Flash/Pro 仍可编辑)",
|
|
263
|
+
cardDispatch: {
|
|
264
|
+
t: "派发策略",
|
|
265
|
+
d: "orchestrator 未指定时的档位与推理强度,以及档位约束、失败升档与两档各自挂载的 Agent 预设。"
|
|
266
|
+
},
|
|
267
|
+
cardRuntime: {
|
|
268
|
+
t: "执行与连接",
|
|
269
|
+
d: "worker 会话跑在本实例内还是独立进程,单个任务的超时上限,以及 CC / Codex 探测本实例的地址。"
|
|
270
|
+
},
|
|
271
|
+
cardMM: {
|
|
272
|
+
t: "视觉与生图",
|
|
273
|
+
d: "给纯文本的 DSH 模型借来眼睛和画笔:describe_image、会话贴图转写与 generate_image 都用这里的设置。"
|
|
274
|
+
},
|
|
275
|
+
cardCustomProv: {
|
|
276
|
+
t: "自定义 Provider",
|
|
277
|
+
d: "接入自己的 API 或本地命令;保存后会出现在上面的视觉 / 生图 provider 选择里。"
|
|
278
|
+
},
|
|
279
|
+
modeDesc: {
|
|
280
|
+
auto: "auto · 优先 hub",
|
|
281
|
+
hub: "hub · 必须 hub",
|
|
282
|
+
standalone: "standalone · 独立进程"
|
|
283
|
+
},
|
|
284
|
+
save: "保存",
|
|
285
|
+
saved: "已保存",
|
|
286
|
+
jobs: "Worker 任务",
|
|
287
|
+
empty: "当前没有 worker 任务。",
|
|
288
|
+
col: {
|
|
289
|
+
id: "任务",
|
|
290
|
+
source: "来源",
|
|
291
|
+
tier: "档位",
|
|
292
|
+
status: "状态",
|
|
293
|
+
progress: "进度",
|
|
294
|
+
tokens: "tokens ⇅",
|
|
295
|
+
task: "内容"
|
|
296
|
+
},
|
|
297
|
+
working: "处理中…",
|
|
298
|
+
tips: {
|
|
299
|
+
claude: {
|
|
300
|
+
install: "一键装进 Claude Code:注册本地 marketplace、claude plugin install、写入工具权限白名单、接线 claude-hud worker 段;修改 settings.json 前自动备份",
|
|
301
|
+
update: "重跑安装流程:刷新注册与权限、迁移旧格式配置、更新 HUD 接线;幂等,可放心点",
|
|
302
|
+
restore: "从 Claude Code 移除集成:清除 marketplace 注册、插件启用项与权限规则(settings 先备份);不删除插件源码与已跑任务"
|
|
303
|
+
},
|
|
304
|
+
codex: {
|
|
305
|
+
install: "把 ds-flash / ds-pro 角色和 /dsh-config、/dsh-status 命令复制到 ~/.codex/(MCP 路径按本机自动渲染,含 approve 审批与超时配置)",
|
|
306
|
+
update: "重新渲染并覆盖角色文件与命令(插件路径或配置变更后用);原文件先备份",
|
|
307
|
+
restore: "删除 ~/.codex/ 下的 ds-flash / ds-pro 角色与 dsh 命令文件(角色文件先备份)"
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
},
|
|
311
|
+
en: {
|
|
312
|
+
label: "DSH Crew",
|
|
313
|
+
title: "DSH Crew",
|
|
314
|
+
intro: "Dispatch Claude Code / Codex subtasks to DSH agents within this instance, displayed as native subagents in the host with live progress. Configure dispatch defaults, execution mode, and vision/image generation (via subscription CLI or your own OpenAI API), then one-click install into the host.",
|
|
315
|
+
integrations: "Integrations",
|
|
316
|
+
installed: "installed",
|
|
317
|
+
notInstalled: "not installed",
|
|
318
|
+
hud: "HUD segment",
|
|
319
|
+
install: "Install",
|
|
320
|
+
update: "Update",
|
|
321
|
+
restore: "Restore",
|
|
322
|
+
confirmRestore: (name) => `Remove the dsh-crew integration from ${name}? (settings are backed up first)`,
|
|
323
|
+
globalConfig: "Global configuration",
|
|
324
|
+
expandAll: "Expand all",
|
|
325
|
+
collapseAll: "Collapse all",
|
|
326
|
+
modelCount: (count) => `${count} models`,
|
|
327
|
+
providerCount: (count) => `${count} providers`,
|
|
328
|
+
jobCount: (count) => `${count} jobs`,
|
|
329
|
+
runningCount: (count) => `${count} running`,
|
|
330
|
+
sectionNames: {
|
|
331
|
+
integrations: "Host integrations",
|
|
332
|
+
workflow: "Workflow",
|
|
333
|
+
flash: "Flash",
|
|
334
|
+
pro: "Pro",
|
|
335
|
+
dispatch: "Dispatch policy",
|
|
336
|
+
runtime: "Runtime connection",
|
|
337
|
+
multimodal: "Vision & image generation",
|
|
338
|
+
providers: "Custom providers",
|
|
339
|
+
jobs: "Worker jobs"
|
|
340
|
+
},
|
|
341
|
+
globalHint: "Changes save instantly to ~/.config/dsh-crew/config.json; new CC / Codex sessions pick them up as defaults (override per session with /dsh-crew:config).",
|
|
342
|
+
orchestration: "Agent orchestration",
|
|
343
|
+
enableSubagents: "Enable Subagents",
|
|
344
|
+
enableSubagentsHint: "Master switch. When off, dsh_run_worker / dsh_spawn_worker refuse dispatch at the tool layer (the configuration is kept; re-enable to restore).",
|
|
345
|
+
subagentsKept: "Configuration kept",
|
|
346
|
+
mainAgentMode: "Main Agent Mode",
|
|
347
|
+
mainAgentModeDesc: {
|
|
348
|
+
"direct-allowed": "Direct Allowed · implement directly or delegate",
|
|
349
|
+
"coordinator-first": "Coordinator First · plan, decompose, delegate, verify",
|
|
350
|
+
"dispatcher-only": "Dispatcher Only · plan, dispatch, review, integrate"
|
|
351
|
+
},
|
|
352
|
+
mainAgentModeHint: "Host routing guidance — not a hard restriction on the host's own tools.",
|
|
353
|
+
collaborationMode: "Collaboration Mode",
|
|
354
|
+
collaborationModeDesc: {
|
|
355
|
+
"flash-only": "Flash Only",
|
|
356
|
+
"pro-only": "Pro Only",
|
|
357
|
+
balanced: "Balanced",
|
|
358
|
+
"review-pipeline": "Review Pipeline · implement + review",
|
|
359
|
+
custom: "Custom"
|
|
360
|
+
},
|
|
361
|
+
tierCardFlash: "Flash",
|
|
362
|
+
tierCardPro: "Pro",
|
|
363
|
+
tierState: "State",
|
|
364
|
+
tierStateDesc: {
|
|
365
|
+
disabled: "Disabled",
|
|
366
|
+
manual: "Manual (only when the user names this tier)",
|
|
367
|
+
auto: "Auto (the orchestrator may delegate to it)"
|
|
368
|
+
},
|
|
369
|
+
stateManagedByPreset: "managed by the collaboration mode",
|
|
370
|
+
statePresetHint: "Switch to Custom to configure each tier state separately.",
|
|
371
|
+
workerProvider: "Worker Provider",
|
|
372
|
+
workerProviderDesc: {
|
|
373
|
+
"follow-dsh": "Follow Harness · use each tier's ordered model priority",
|
|
374
|
+
"deepseek-official": "DeepSeek Official · use legacy fixed model routing"
|
|
375
|
+
},
|
|
376
|
+
workerProviderHint: "Applies to Hub workers only; Standalone always uses deepseek-official + DEEPSEEK_API_KEY.",
|
|
377
|
+
workerModels: "Worker Models",
|
|
378
|
+
refreshHarnessModels: "Refresh Harness Models",
|
|
379
|
+
refreshingModels: "Refreshing…",
|
|
380
|
+
modelPriority: "Model Priority",
|
|
381
|
+
addPriorityModel: "+ Add Priority Model",
|
|
382
|
+
modelSearch: "Search Provider or Model…",
|
|
383
|
+
harnessDefault: "Harness Default",
|
|
384
|
+
providerUnavailable: "Provider unavailable",
|
|
385
|
+
notAdvertised: "Currently not advertised",
|
|
386
|
+
noPriority: "No priority models configured",
|
|
387
|
+
modelPoolSummary: (p, m) => `Providers: ${p} · Models: ${m}`,
|
|
388
|
+
partialCatalog: "Some providers failed to load",
|
|
389
|
+
catalogFailed: "Unable to read Harness model catalog",
|
|
390
|
+
removePriority: "Remove",
|
|
391
|
+
moveUp: "Move up",
|
|
392
|
+
moveDown: "Move down",
|
|
393
|
+
needsModelSelection: "Multiple providers advertise the preferred model and Harness Default cannot disambiguate them. Falling back to Harness Default; choose a priority model explicitly.",
|
|
394
|
+
responsibilities: "Responsibilities",
|
|
395
|
+
responsibilitiesHint: "Routing guidance describing the split of work; with a single Auto tier left, that tier carries all delegatable coding work.",
|
|
396
|
+
roleLabels: {
|
|
397
|
+
implementation: "Code implementation",
|
|
398
|
+
simple_fix: "Simple fixes",
|
|
399
|
+
tests: "Tests",
|
|
400
|
+
search_inspection: "Search / inspection",
|
|
401
|
+
architecture: "Architecture",
|
|
402
|
+
complex_debugging: "Complex debugging",
|
|
403
|
+
refactor: "Refactoring",
|
|
404
|
+
code_review: "Code review"
|
|
405
|
+
},
|
|
406
|
+
routing: "Routing",
|
|
407
|
+
proReviewsFlash: "Pro reviews Flash changes",
|
|
408
|
+
proReviewsFlashHint: "Automatically follow a successful Flash run with one Pro review (blocking dispatch only)",
|
|
409
|
+
proReviewsLocked: "always on in Review Pipeline mode",
|
|
410
|
+
enableCrewVision: "Enable Crew Vision",
|
|
411
|
+
enableCrewVisionHint: "When off, the Crew describe_image tool and vision route are not registered (coexist with your own dsh-vision or other plugins).",
|
|
412
|
+
enableImagegen: "Enable Image Generation",
|
|
413
|
+
enableImagegenHint: "When off, the Crew generate_image tool is not registered.",
|
|
414
|
+
capRestartHint: "This switch changes tool registration and takes effect after a DSH restart",
|
|
415
|
+
capDisabledNote: "Crew tool/route disabled (effective after DSH restart)",
|
|
416
|
+
tier: "Default tier",
|
|
417
|
+
effort: "Default effort",
|
|
418
|
+
mode: "Mode",
|
|
419
|
+
timeout: "Timeout (s)",
|
|
420
|
+
hubUrl: "Hub URL",
|
|
421
|
+
tierPolicy: "Tier policy",
|
|
422
|
+
escalate: "Escalate on failure",
|
|
423
|
+
tierPolicyDesc: {
|
|
424
|
+
auto: "auto · orchestrator picks",
|
|
425
|
+
"flash-only": "flash only",
|
|
426
|
+
"pro-only": "pro only"
|
|
427
|
+
},
|
|
428
|
+
escalateHint: "retry a failed flash run once on pro",
|
|
429
|
+
presetFlash: "flash preset",
|
|
430
|
+
presetPro: "pro preset",
|
|
431
|
+
multimodal: "Multimodal",
|
|
432
|
+
visionProvider: "Vision provider",
|
|
433
|
+
visionModel: "Vision model",
|
|
434
|
+
imagegenProvider: "Image-gen provider",
|
|
435
|
+
customProviders: "Custom providers",
|
|
436
|
+
addProvider: "+ Add provider",
|
|
437
|
+
noCustomProviders: "None yet. Added providers appear in the vision / image-gen selects above.",
|
|
438
|
+
providerName: "Name",
|
|
439
|
+
modelsField: "Models (comma-separated)",
|
|
440
|
+
providerType: "Connection",
|
|
441
|
+
typeApi: "API · OpenAI-compatible",
|
|
442
|
+
typeCli: "CLI · local command",
|
|
443
|
+
baseUrl: "Base URL",
|
|
444
|
+
apiKey: "API key",
|
|
445
|
+
imagegenModel: "Image-gen model (empty = no image generation)",
|
|
446
|
+
visionCmd: "Vision command (optional)",
|
|
447
|
+
imagegenCmd: "Image-gen command (optional)",
|
|
448
|
+
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.",
|
|
449
|
+
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.",
|
|
450
|
+
saveProvider: "Save",
|
|
451
|
+
cancel: "Cancel",
|
|
452
|
+
edit: "Edit",
|
|
453
|
+
del: "Delete",
|
|
454
|
+
testProvider: "Test",
|
|
455
|
+
testing: "Testing…",
|
|
456
|
+
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.",
|
|
457
|
+
testPass: "Connection OK",
|
|
458
|
+
testFail: "Connection failed",
|
|
459
|
+
staleHub: "This DSH instance is still running an older build of the plugin and lacks this route — restart DSH and retry",
|
|
460
|
+
emptyResponse: (path, status) => `${path} → HTTP ${status}, empty response`,
|
|
461
|
+
badJson: (path, body) => `${path} → non-JSON response: ${body}`,
|
|
462
|
+
refreshModels: "Re-fetch the model list from the CLI",
|
|
463
|
+
presetDefault: (id) => `default · follow DSH${id ? ` (${id})` : ""}`,
|
|
464
|
+
progressTip: (t, turn, step, calls) => `${t} elapsed · turn ${turn}, step ${step} · ${calls} tool calls`,
|
|
465
|
+
confirmDeleteProvider: (n) => `Delete custom provider "${n}"?`,
|
|
466
|
+
needName: "⚠ Name is required",
|
|
467
|
+
needCmd: "⚠ Fill in at least one command",
|
|
468
|
+
needBaseUrl: "⚠ Base URL is required",
|
|
469
|
+
needModels: "⚠ Add at least one model",
|
|
470
|
+
keySet: "set",
|
|
471
|
+
keyPlaceholder: "sk-… (leave empty to send no Authorization header)",
|
|
472
|
+
addModelTip: "Add a model to the list",
|
|
473
|
+
removeModelTip: "Remove the current model from the list",
|
|
474
|
+
modelPlaceholder: "model id, Enter to confirm",
|
|
475
|
+
capVision: "vision",
|
|
476
|
+
capImagegen: "image-gen",
|
|
477
|
+
groupDispatch: "Dispatch",
|
|
478
|
+
groupRuntime: "Runtime",
|
|
479
|
+
groupMultimodal: "Multimodal",
|
|
480
|
+
groupWorkflow: "Workflow / Execution (v0.2)",
|
|
481
|
+
roleWorker: "Worker role (execution)",
|
|
482
|
+
roleReviewer: "Reviewer role (independent review)",
|
|
483
|
+
workerStateLabel: "Worker state",
|
|
484
|
+
reviewStateLabel: "Reviewer state",
|
|
485
|
+
roleStateHint: "auto = the orchestrator may dispatch automatically; manual = only when explicitly named; disabled = off. When unset, derived from the collaboration mode.",
|
|
486
|
+
autoReview: "Automatic review",
|
|
487
|
+
autoReviewHint: "Run one reviewer pass (independent model policy, read-only) after a successful worker run",
|
|
488
|
+
isolation: "Workspace isolation",
|
|
489
|
+
isolationHint: "worktree is the default; non-git workspaces require explicit shared, otherwise jobs fail with NOT_GIT_REPOSITORY.",
|
|
490
|
+
isolationDesc: {
|
|
491
|
+
worktree: "worktree · each coding worker runs in its own temporary git worktree (primary untouched)",
|
|
492
|
+
shared: "shared · legacy in-place execution"
|
|
493
|
+
},
|
|
494
|
+
maxParallel: "Max parallel",
|
|
495
|
+
maxParallelHint: "Upper bound of concurrently running workflows (1–16); extra ones queue.",
|
|
496
|
+
legacyCompatibility: "↓ Legacy compatibility controls below (Flash/Pro still editable)",
|
|
497
|
+
cardDispatch: {
|
|
498
|
+
t: "Dispatch policy",
|
|
499
|
+
d: "Tier and effort used when the orchestrator names none, plus tier clamping, failure escalation and the Agent preset mounted per tier."
|
|
500
|
+
},
|
|
501
|
+
cardRuntime: {
|
|
502
|
+
t: "Execution & connection",
|
|
503
|
+
d: "Whether worker sessions run inside this instance or a separate process, the per-job timeout, and the address CC / Codex probes."
|
|
504
|
+
},
|
|
505
|
+
cardMM: {
|
|
506
|
+
t: "Vision & image generation",
|
|
507
|
+
d: "Lends the harness's text-only models eyes and a brush: describe_image, pasted-image transcription and generate_image all use these settings."
|
|
508
|
+
},
|
|
509
|
+
cardCustomProv: {
|
|
510
|
+
t: "Custom providers",
|
|
511
|
+
d: "Bring your own API or local command; saved providers appear in the vision / image-gen selects above."
|
|
512
|
+
},
|
|
513
|
+
modeDesc: {
|
|
514
|
+
auto: "auto · prefer hub",
|
|
515
|
+
hub: "hub · require hub",
|
|
516
|
+
standalone: "standalone"
|
|
517
|
+
},
|
|
518
|
+
save: "Save",
|
|
519
|
+
saved: "Saved",
|
|
520
|
+
jobs: "Worker jobs",
|
|
521
|
+
empty: "No worker jobs yet.",
|
|
522
|
+
col: {
|
|
523
|
+
id: "job",
|
|
524
|
+
source: "source",
|
|
525
|
+
tier: "tier",
|
|
526
|
+
status: "status",
|
|
527
|
+
progress: "progress",
|
|
528
|
+
tokens: "tokens ⇅",
|
|
529
|
+
task: "task"
|
|
530
|
+
},
|
|
531
|
+
working: "Working…",
|
|
532
|
+
tips: {
|
|
533
|
+
claude: {
|
|
534
|
+
install: "One-click install into Claude Code: register local marketplace, claude plugin install, permission allowlist, claude-hud segment wiring; settings.json backed up first",
|
|
535
|
+
update: "Re-run the installer: refresh registration & permissions, migrate legacy config, update HUD wiring; idempotent",
|
|
536
|
+
restore: "Remove the integration from Claude Code: marketplace registration, plugin enablement and permission rules (settings backed up); plugin source and past jobs untouched"
|
|
537
|
+
},
|
|
538
|
+
codex: {
|
|
539
|
+
install: "Copy ds-flash / ds-pro roles and /dsh-config, /dsh-status prompts into ~/.codex/ (MCP paths rendered for this machine, approve mode + timeout included)",
|
|
540
|
+
update: "Re-render and overwrite role files and prompts (after path/config changes); originals backed up",
|
|
541
|
+
restore: "Delete ds-flash / ds-pro roles and dsh prompts from ~/.codex/ (roles backed up first)"
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
const S = {
|
|
547
|
+
section: {
|
|
548
|
+
margin: "8px 0 -2px",
|
|
549
|
+
fontSize: 13,
|
|
550
|
+
fontWeight: 600,
|
|
551
|
+
opacity: .9
|
|
552
|
+
},
|
|
553
|
+
group: {
|
|
554
|
+
margin: "4px 0 -3px",
|
|
555
|
+
fontSize: 12,
|
|
556
|
+
opacity: .55
|
|
557
|
+
},
|
|
558
|
+
block: {
|
|
559
|
+
border: "1px solid rgba(128,128,128,0.22)",
|
|
560
|
+
borderRadius: 10,
|
|
561
|
+
padding: "11px 14px 13px",
|
|
562
|
+
display: "flex",
|
|
563
|
+
flexDirection: "column",
|
|
564
|
+
gap: 10
|
|
565
|
+
},
|
|
566
|
+
blockGrid: {
|
|
567
|
+
display: "grid",
|
|
568
|
+
gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
|
|
569
|
+
gap: "12px 14px",
|
|
570
|
+
alignItems: "end"
|
|
571
|
+
},
|
|
572
|
+
settingTitle: {
|
|
573
|
+
fontWeight: 600,
|
|
574
|
+
fontSize: 13
|
|
575
|
+
},
|
|
576
|
+
settingDesc: {
|
|
577
|
+
fontSize: 11.5,
|
|
578
|
+
opacity: .6,
|
|
579
|
+
marginTop: 2,
|
|
580
|
+
lineHeight: 1.5
|
|
581
|
+
},
|
|
582
|
+
card: {
|
|
583
|
+
border: "1px solid rgba(128,128,128,0.22)",
|
|
584
|
+
borderRadius: 8,
|
|
585
|
+
padding: "10px 14px",
|
|
586
|
+
display: "flex",
|
|
587
|
+
alignItems: "center",
|
|
588
|
+
gap: 10,
|
|
589
|
+
flexWrap: "wrap"
|
|
590
|
+
},
|
|
591
|
+
grid: {
|
|
592
|
+
border: "1px solid rgba(128,128,128,0.22)",
|
|
593
|
+
borderRadius: 8,
|
|
594
|
+
padding: "12px 14px",
|
|
595
|
+
display: "grid",
|
|
596
|
+
gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
|
|
597
|
+
gap: "12px 14px",
|
|
598
|
+
alignItems: "end"
|
|
599
|
+
},
|
|
600
|
+
btn: {
|
|
601
|
+
padding: "4px 12px",
|
|
602
|
+
borderRadius: 6,
|
|
603
|
+
border: "1px solid rgba(128,128,128,0.35)",
|
|
604
|
+
background: "transparent",
|
|
605
|
+
color: "inherit",
|
|
606
|
+
cursor: "pointer",
|
|
607
|
+
fontSize: 12.5
|
|
608
|
+
},
|
|
609
|
+
sectionShell: {
|
|
610
|
+
border: "1px solid rgba(128,128,128,0.24)",
|
|
611
|
+
borderRadius: 11,
|
|
612
|
+
overflow: "hidden",
|
|
613
|
+
background: "rgba(128,128,128,0.025)"
|
|
614
|
+
},
|
|
615
|
+
sectionButton: {
|
|
616
|
+
width: "100%",
|
|
617
|
+
padding: "10px 13px",
|
|
618
|
+
border: 0,
|
|
619
|
+
background: "transparent",
|
|
620
|
+
color: "inherit",
|
|
621
|
+
cursor: "pointer",
|
|
622
|
+
display: "flex",
|
|
623
|
+
alignItems: "center",
|
|
624
|
+
gap: 9,
|
|
625
|
+
textAlign: "left",
|
|
626
|
+
font: "inherit"
|
|
627
|
+
},
|
|
628
|
+
sectionBody: {
|
|
629
|
+
padding: "2px 11px 12px",
|
|
630
|
+
display: "flex",
|
|
631
|
+
flexDirection: "column",
|
|
632
|
+
gap: 8
|
|
633
|
+
},
|
|
634
|
+
chip: (on) => ({
|
|
635
|
+
fontSize: 11.5,
|
|
636
|
+
padding: "1px 8px",
|
|
637
|
+
borderRadius: 99,
|
|
638
|
+
border: "1px solid",
|
|
639
|
+
borderColor: on ? "rgba(63,185,80,0.5)" : "rgba(128,128,128,0.35)",
|
|
640
|
+
color: on ? "#3fb950" : "inherit",
|
|
641
|
+
opacity: on ? 1 : .55
|
|
642
|
+
}),
|
|
643
|
+
field: {
|
|
644
|
+
display: "flex",
|
|
645
|
+
flexDirection: "column",
|
|
646
|
+
gap: 3
|
|
647
|
+
},
|
|
648
|
+
fieldLabel: {
|
|
649
|
+
fontSize: 11,
|
|
650
|
+
opacity: .6
|
|
651
|
+
},
|
|
652
|
+
input: {
|
|
653
|
+
padding: "3px 8px",
|
|
654
|
+
borderRadius: 5,
|
|
655
|
+
border: "1px solid rgba(128,128,128,0.35)",
|
|
656
|
+
background: "transparent",
|
|
657
|
+
color: "inherit",
|
|
658
|
+
fontSize: 12.5
|
|
659
|
+
},
|
|
660
|
+
cell: {
|
|
661
|
+
padding: "5px 10px 5px 0",
|
|
662
|
+
borderBottom: "1px solid rgba(128,128,128,0.16)",
|
|
663
|
+
textAlign: "left",
|
|
664
|
+
verticalAlign: "top"
|
|
665
|
+
},
|
|
666
|
+
tight: {
|
|
667
|
+
padding: "5px 8px 5px 0",
|
|
668
|
+
borderBottom: "1px solid rgba(128,128,128,0.16)",
|
|
669
|
+
textAlign: "left",
|
|
670
|
+
verticalAlign: "top",
|
|
671
|
+
whiteSpace: "nowrap",
|
|
672
|
+
overflow: "hidden",
|
|
673
|
+
textOverflow: "ellipsis"
|
|
674
|
+
},
|
|
675
|
+
mono: {
|
|
676
|
+
fontFamily: "ui-monospace, Menlo, monospace",
|
|
677
|
+
fontSize: 12,
|
|
678
|
+
fontVariantNumeric: "tabular-nums"
|
|
679
|
+
}
|
|
680
|
+
};
|
|
681
|
+
const VISION_MODELS = {
|
|
682
|
+
"claude-code": [
|
|
683
|
+
"haiku",
|
|
684
|
+
"sonnet",
|
|
685
|
+
"opus"
|
|
686
|
+
],
|
|
687
|
+
codex: [
|
|
688
|
+
"default",
|
|
689
|
+
"gpt-5.4-mini",
|
|
690
|
+
"gpt-5.6"
|
|
691
|
+
],
|
|
692
|
+
grok: ["default"],
|
|
693
|
+
agy: ["default"]
|
|
694
|
+
};
|
|
695
|
+
function CustomSelect({ value, options, onChange, minWidth }) {
|
|
696
|
+
const [open, setOpen] = (0, react.useState)(null);
|
|
697
|
+
(0, react.useEffect)(() => {
|
|
698
|
+
if (!open) return;
|
|
699
|
+
const close = () => setOpen(null);
|
|
700
|
+
const onKey = (e) => {
|
|
701
|
+
if (e.key === "Escape") close();
|
|
702
|
+
};
|
|
703
|
+
const t = setTimeout(() => document.addEventListener("mousedown", close), 0);
|
|
704
|
+
document.addEventListener("keydown", onKey);
|
|
705
|
+
return () => {
|
|
706
|
+
clearTimeout(t);
|
|
707
|
+
document.removeEventListener("mousedown", close);
|
|
708
|
+
document.removeEventListener("keydown", onKey);
|
|
709
|
+
};
|
|
710
|
+
}, [open]);
|
|
711
|
+
const current = options.find((o) => o.value === value);
|
|
712
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
713
|
+
type: "button",
|
|
714
|
+
style: {
|
|
715
|
+
...S.input,
|
|
716
|
+
display: "flex",
|
|
717
|
+
alignItems: "center",
|
|
718
|
+
gap: 6,
|
|
719
|
+
cursor: "pointer",
|
|
720
|
+
minWidth: minWidth ?? 92,
|
|
721
|
+
width: "100%",
|
|
722
|
+
boxSizing: "border-box",
|
|
723
|
+
justifyContent: "space-between"
|
|
724
|
+
},
|
|
725
|
+
onClick: (e) => {
|
|
726
|
+
const r = e.currentTarget.getBoundingClientRect();
|
|
727
|
+
const up = window.innerHeight - r.bottom < 240;
|
|
728
|
+
setOpen({
|
|
729
|
+
left: r.left,
|
|
730
|
+
top: r.bottom + 4,
|
|
731
|
+
bottom: window.innerHeight - r.top + 4,
|
|
732
|
+
width: Math.max(r.width, 130),
|
|
733
|
+
up
|
|
734
|
+
});
|
|
735
|
+
},
|
|
736
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
737
|
+
style: {
|
|
738
|
+
overflow: "hidden",
|
|
739
|
+
textOverflow: "ellipsis",
|
|
740
|
+
whiteSpace: "nowrap"
|
|
741
|
+
},
|
|
742
|
+
children: current?.label ?? value
|
|
743
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
744
|
+
style: {
|
|
745
|
+
opacity: .5,
|
|
746
|
+
fontSize: 10
|
|
747
|
+
},
|
|
748
|
+
children: "▾"
|
|
749
|
+
})]
|
|
750
|
+
}), open && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
751
|
+
style: {
|
|
752
|
+
position: "fixed",
|
|
753
|
+
left: open.left,
|
|
754
|
+
...open.up ? { bottom: open.bottom } : { top: open.top },
|
|
755
|
+
minWidth: open.width,
|
|
756
|
+
zIndex: 9999,
|
|
757
|
+
background: "Canvas",
|
|
758
|
+
color: "CanvasText",
|
|
759
|
+
border: "1px solid rgba(128,128,128,0.3)",
|
|
760
|
+
borderRadius: 8,
|
|
761
|
+
boxShadow: "0 8px 28px rgba(0,0,0,0.22)",
|
|
762
|
+
padding: 4,
|
|
763
|
+
maxHeight: 240,
|
|
764
|
+
overflowY: "auto"
|
|
765
|
+
},
|
|
766
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
767
|
+
children: options.map((o) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
768
|
+
onClick: () => {
|
|
769
|
+
onChange(o.value);
|
|
770
|
+
setOpen(null);
|
|
771
|
+
},
|
|
772
|
+
style: {
|
|
773
|
+
padding: "5px 10px",
|
|
774
|
+
borderRadius: 5,
|
|
775
|
+
cursor: "pointer",
|
|
776
|
+
fontSize: 12.5,
|
|
777
|
+
whiteSpace: "nowrap",
|
|
778
|
+
background: o.value === value ? "rgba(128,128,128,0.16)" : "transparent",
|
|
779
|
+
display: "flex",
|
|
780
|
+
justifyContent: "space-between",
|
|
781
|
+
gap: 12,
|
|
782
|
+
alignItems: "center"
|
|
783
|
+
},
|
|
784
|
+
onMouseEnter: (e) => {
|
|
785
|
+
e.currentTarget.style.background = "rgba(128,128,128,0.24)";
|
|
786
|
+
},
|
|
787
|
+
onMouseLeave: (e) => {
|
|
788
|
+
e.currentTarget.style.background = o.value === value ? "rgba(128,128,128,0.16)" : "transparent";
|
|
789
|
+
},
|
|
790
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: o.label ?? o.value }), o.value === value && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
791
|
+
style: {
|
|
792
|
+
opacity: .6,
|
|
793
|
+
fontSize: 11
|
|
794
|
+
},
|
|
795
|
+
children: "✓"
|
|
796
|
+
})]
|
|
797
|
+
}, o.value))
|
|
798
|
+
}), document.body)] });
|
|
799
|
+
}
|
|
800
|
+
function sectionSummary(...parts) {
|
|
801
|
+
return parts.filter((part) => part !== false && part !== null && part !== void 0 && part !== "").join(" · ");
|
|
802
|
+
}
|
|
803
|
+
function sectionStorage() {
|
|
804
|
+
if (typeof window === "undefined") return null;
|
|
805
|
+
try {
|
|
806
|
+
return window.localStorage;
|
|
807
|
+
} catch {
|
|
808
|
+
return null;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
function CollapsibleSection({ sectionId, title, summary, expanded, onToggle, children }) {
|
|
812
|
+
const bodyId = `dsh-crew-section-${sectionId}`;
|
|
813
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
814
|
+
style: S.sectionShell,
|
|
815
|
+
"data-section-id": sectionId,
|
|
816
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
817
|
+
type: "button",
|
|
818
|
+
style: S.sectionButton,
|
|
819
|
+
"aria-expanded": expanded,
|
|
820
|
+
"aria-controls": bodyId,
|
|
821
|
+
onClick: onToggle,
|
|
822
|
+
children: [
|
|
823
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
824
|
+
"aria-hidden": "true",
|
|
825
|
+
style: {
|
|
826
|
+
width: 14,
|
|
827
|
+
opacity: .62,
|
|
828
|
+
transform: expanded ? "rotate(90deg)" : "rotate(0deg)",
|
|
829
|
+
transition: "transform 140ms ease"
|
|
830
|
+
},
|
|
831
|
+
children: "›"
|
|
832
|
+
}),
|
|
833
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
834
|
+
style: {
|
|
835
|
+
fontWeight: 650,
|
|
836
|
+
fontSize: 13,
|
|
837
|
+
minWidth: 92
|
|
838
|
+
},
|
|
839
|
+
children: title
|
|
840
|
+
}),
|
|
841
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
842
|
+
title: summary,
|
|
843
|
+
style: {
|
|
844
|
+
flex: 1,
|
|
845
|
+
minWidth: 0,
|
|
846
|
+
fontSize: 11.5,
|
|
847
|
+
opacity: .58,
|
|
848
|
+
overflow: "hidden",
|
|
849
|
+
textOverflow: "ellipsis",
|
|
850
|
+
whiteSpace: "nowrap"
|
|
851
|
+
},
|
|
852
|
+
children: summary
|
|
853
|
+
})
|
|
854
|
+
]
|
|
855
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
856
|
+
id: bodyId,
|
|
857
|
+
hidden: !expanded,
|
|
858
|
+
style: expanded ? S.sectionBody : void 0,
|
|
859
|
+
children
|
|
860
|
+
})]
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
function sourceLabel(source) {
|
|
864
|
+
if (source === "claude-code") return "CC";
|
|
865
|
+
if (source === "codex") return "Codex";
|
|
866
|
+
if (source === "api" || source === void 0) return "API";
|
|
867
|
+
return source;
|
|
868
|
+
}
|
|
869
|
+
function elapsed(startedAt, endedAt) {
|
|
870
|
+
const s = Math.max(0, Math.round(((endedAt ? +new Date(endedAt) : Date.now()) - +new Date(startedAt)) / 1e3));
|
|
871
|
+
return s >= 60 ? `${Math.floor(s / 60)}m${s % 60}s` : `${s}s`;
|
|
872
|
+
}
|
|
873
|
+
function ktok(n) {
|
|
874
|
+
return n >= 1e3 ? `${Math.round(n / 100) / 10}k` : `${n}`;
|
|
875
|
+
}
|
|
876
|
+
function WorkersPanel({ ctx }) {
|
|
877
|
+
const locale = (0, react.useSyncExternalStore)((notify) => ctx.on("locale/change", notify), () => ctx.locale.getLocale().active, () => ctx.locale.getLocale().active);
|
|
878
|
+
const copy = COPY[locale === "zh" ? "zh" : "en"];
|
|
879
|
+
const [jobs, setJobs] = (0, react.useState)([]);
|
|
880
|
+
const [hoverTask, setHoverTask] = (0, react.useState)(null);
|
|
881
|
+
const [status, setStatus] = (0, react.useState)(null);
|
|
882
|
+
const [config, setConfig] = (0, react.useState)(null);
|
|
883
|
+
const [dynModels, setDynModels] = (0, react.useState)({});
|
|
884
|
+
const [presetOptions, setPresetOptions] = (0, react.useState)([{ value: "default" }]);
|
|
885
|
+
const [notice, setNotice] = (0, react.useState)("");
|
|
886
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
887
|
+
const [provForm, setProvForm] = (0, react.useState)(null);
|
|
888
|
+
const [modelAdd, setModelAdd] = (0, react.useState)(null);
|
|
889
|
+
const [modelCatalog, setModelCatalog] = (0, react.useState)(null);
|
|
890
|
+
const [modelCatalogError, setModelCatalogError] = (0, react.useState)("");
|
|
891
|
+
const [modelCatalogBusy, setModelCatalogBusy] = (0, react.useState)(false);
|
|
892
|
+
const [modelPickerTier, setModelPickerTier] = (0, react.useState)(null);
|
|
893
|
+
const [modelQuery, setModelQuery] = (0, react.useState)("");
|
|
894
|
+
const [testResult, setTestResult] = (0, react.useState)(null);
|
|
895
|
+
const [expandedSections, setExpandedSections] = (0, react.useState)(() => readSectionState(sectionStorage()));
|
|
896
|
+
const toggleSection = (sectionId) => {
|
|
897
|
+
setExpandedSections((current) => ({
|
|
898
|
+
...current,
|
|
899
|
+
[sectionId]: !current[sectionId]
|
|
900
|
+
}));
|
|
901
|
+
};
|
|
902
|
+
const toggleAllSections = (expanded) => setExpandedSections(setEverySection(expanded));
|
|
903
|
+
(0, react.useEffect)(() => {
|
|
904
|
+
writeSectionState(sectionStorage(), expandedSections);
|
|
905
|
+
}, [expandedSections]);
|
|
906
|
+
(0, react.useEffect)(() => {
|
|
907
|
+
if (modelCatalogError) setExpandedSections((current) => openSections(current, [
|
|
908
|
+
"workflow",
|
|
909
|
+
"flash",
|
|
910
|
+
"pro"
|
|
911
|
+
]));
|
|
912
|
+
}, [modelCatalogError]);
|
|
913
|
+
(0, react.useEffect)(() => {
|
|
914
|
+
if (testResult && !testResult.busy && testResult.ok === false) setExpandedSections((current) => openSections(current, ["providers"]));
|
|
915
|
+
}, [testResult]);
|
|
916
|
+
(0, react.useEffect)(() => {
|
|
917
|
+
if (jobs.some((job) => job.status === "running")) setExpandedSections((current) => openSections(current, ["jobs"]));
|
|
918
|
+
}, [jobs]);
|
|
919
|
+
/**
|
|
920
|
+
* The panel is served from disk on every load, but the hub's routes are
|
|
921
|
+
* registered at instance boot — after an upgrade the new UI can call a route
|
|
922
|
+
* the running instance does not have yet, which answers 404/405 with an empty
|
|
923
|
+
* body. Report that as "restart DSH" instead of a JSON parse error.
|
|
924
|
+
*/
|
|
925
|
+
const readJson = (0, react.useCallback)(async (res, path) => {
|
|
926
|
+
const text = await res.text();
|
|
927
|
+
if (text === "") throw new Error(res.status === 404 || res.status === 405 ? `${copy.staleHub}(${path} → HTTP ${res.status})` : copy.emptyResponse(path, res.status));
|
|
928
|
+
try {
|
|
929
|
+
return JSON.parse(text);
|
|
930
|
+
} catch {
|
|
931
|
+
throw new Error(copy.badJson(path, text.slice(0, 160)));
|
|
932
|
+
}
|
|
933
|
+
}, [copy]);
|
|
934
|
+
/** Every request carries the active locale: the panel is the authority on it
|
|
935
|
+
* (DSH's setting may be unset), and the hub localizes its own strings from it. */
|
|
936
|
+
const withLang = (0, react.useCallback)((path) => `${API$1}${path}${path.includes("?") ? "&" : "?"}lang=${locale === "zh" ? "zh" : "en"}`, [locale]);
|
|
937
|
+
const get = (0, react.useCallback)(async (path) => readJson(await fetch(withLang(path), { cache: "no-store" }), path), [readJson, withLang]);
|
|
938
|
+
const post = (0, react.useCallback)(async (path, body) => readJson(await fetch(withLang(path), {
|
|
939
|
+
method: "POST",
|
|
940
|
+
headers: { "content-type": "application/json" },
|
|
941
|
+
body: JSON.stringify({
|
|
942
|
+
...body,
|
|
943
|
+
lang: locale === "zh" ? "zh" : "en"
|
|
944
|
+
})
|
|
945
|
+
}), path), [
|
|
946
|
+
readJson,
|
|
947
|
+
withLang,
|
|
948
|
+
locale
|
|
949
|
+
]);
|
|
950
|
+
const refreshAll = (0, react.useCallback)(async () => {
|
|
951
|
+
try {
|
|
952
|
+
const [j, s, c, pr] = await Promise.all([
|
|
953
|
+
get("/jobs"),
|
|
954
|
+
get("/install/status"),
|
|
955
|
+
get("/config"),
|
|
956
|
+
get("/presets")
|
|
957
|
+
]);
|
|
958
|
+
if (j.ok) setJobs(j.jobs ?? []);
|
|
959
|
+
if (s.ok) setStatus(s.status);
|
|
960
|
+
if (c.ok) setConfig((prev) => prev ?? c.config);
|
|
961
|
+
if (pr.ok) setPresetOptions([{
|
|
962
|
+
value: "default",
|
|
963
|
+
label: copy.presetDefault(pr.defaultId)
|
|
964
|
+
}, ...(pr.presets ?? []).map((x) => ({
|
|
965
|
+
value: x.id,
|
|
966
|
+
label: x.name ?? x.id
|
|
967
|
+
}))]);
|
|
968
|
+
} catch {}
|
|
969
|
+
}, [get]);
|
|
970
|
+
(0, react.useEffect)(() => {
|
|
971
|
+
refreshAll();
|
|
972
|
+
const timer = setInterval(() => {
|
|
973
|
+
get("/jobs").then((j) => {
|
|
974
|
+
if (j.ok) setJobs(j.jobs ?? []);
|
|
975
|
+
}).catch(() => {});
|
|
976
|
+
}, 3e3);
|
|
977
|
+
return () => clearInterval(timer);
|
|
978
|
+
}, [refreshAll, get]);
|
|
979
|
+
const refreshHarnessModels = (0, react.useCallback)(async () => {
|
|
980
|
+
setModelCatalogBusy(true);
|
|
981
|
+
setModelCatalogError("");
|
|
982
|
+
try {
|
|
983
|
+
const result = await get("/models");
|
|
984
|
+
if (!result.ok) throw new Error(result.error ?? copy.catalogFailed);
|
|
985
|
+
setModelCatalog(result);
|
|
986
|
+
} catch (error) {
|
|
987
|
+
setModelCatalogError(error?.message ?? copy.catalogFailed);
|
|
988
|
+
} finally {
|
|
989
|
+
setModelCatalogBusy(false);
|
|
990
|
+
}
|
|
991
|
+
}, [get, copy]);
|
|
992
|
+
(0, react.useEffect)(() => {
|
|
993
|
+
refreshHarnessModels();
|
|
994
|
+
}, [refreshHarnessModels]);
|
|
995
|
+
const act = (0, react.useCallback)(async (target, confirmName) => {
|
|
996
|
+
if (confirmName && !window.confirm(copy.confirmRestore(confirmName))) return;
|
|
997
|
+
setBusy(true);
|
|
998
|
+
setNotice(copy.working);
|
|
999
|
+
try {
|
|
1000
|
+
const r = await post("/install", { target });
|
|
1001
|
+
if (r.ok === false) throw new Error(r.error);
|
|
1002
|
+
setNotice((r.actions ?? []).join(";"));
|
|
1003
|
+
const s = await get("/install/status");
|
|
1004
|
+
if (s.ok) setStatus(s.status);
|
|
1005
|
+
} catch (e) {
|
|
1006
|
+
setNotice(String(e?.message ?? e));
|
|
1007
|
+
} finally {
|
|
1008
|
+
setBusy(false);
|
|
1009
|
+
}
|
|
1010
|
+
}, [
|
|
1011
|
+
post,
|
|
1012
|
+
get,
|
|
1013
|
+
copy
|
|
1014
|
+
]);
|
|
1015
|
+
const applyPatch = (0, react.useCallback)((patch) => {
|
|
1016
|
+
post("/config", patch).then((r) => {
|
|
1017
|
+
if (r.ok) {
|
|
1018
|
+
setConfig(r.config);
|
|
1019
|
+
setNotice(copy.saved);
|
|
1020
|
+
setTimeout(() => setNotice(""), 1500);
|
|
1021
|
+
}
|
|
1022
|
+
}).catch(() => {});
|
|
1023
|
+
}, [post, copy]);
|
|
1024
|
+
/** Selects & checkboxes: apply immediately. */
|
|
1025
|
+
const field = (key, value) => {
|
|
1026
|
+
setConfig((c) => ({
|
|
1027
|
+
...c,
|
|
1028
|
+
[key]: value
|
|
1029
|
+
}));
|
|
1030
|
+
applyPatch({ [key]: value });
|
|
1031
|
+
};
|
|
1032
|
+
/** Text/number inputs: track locally, apply on blur. */
|
|
1033
|
+
const fieldLocal = (key, value) => setConfig((c) => ({
|
|
1034
|
+
...c,
|
|
1035
|
+
[key]: value
|
|
1036
|
+
}));
|
|
1037
|
+
const customProviders = config?.custom_providers ?? [];
|
|
1038
|
+
const extraModels = config?.extra_models ?? {};
|
|
1039
|
+
const RESERVED_IDS = [
|
|
1040
|
+
"claude-code",
|
|
1041
|
+
"codex",
|
|
1042
|
+
"grok",
|
|
1043
|
+
"agy",
|
|
1044
|
+
"off",
|
|
1045
|
+
"custom",
|
|
1046
|
+
"default"
|
|
1047
|
+
];
|
|
1048
|
+
const provType = (p) => p.type ?? (p.vision_command || p.imagegen_command ? "cli" : "api");
|
|
1049
|
+
const canSee = (p) => provType(p) === "cli" ? !!p.vision_command : !!p.base_url && (p.models?.length ?? 0) > 0;
|
|
1050
|
+
const canDraw = (p) => provType(p) === "cli" ? !!p.imagegen_command : !!p.base_url && !!String(p.imagegen_model ?? "").trim();
|
|
1051
|
+
const visionProviderOptions = [
|
|
1052
|
+
{ value: "claude-code" },
|
|
1053
|
+
{ value: "codex" },
|
|
1054
|
+
{ value: "grok" },
|
|
1055
|
+
{ value: "agy" },
|
|
1056
|
+
...customProviders.filter(canSee).map((p) => ({
|
|
1057
|
+
value: p.id,
|
|
1058
|
+
label: p.name || p.id
|
|
1059
|
+
})),
|
|
1060
|
+
{ value: "off" }
|
|
1061
|
+
];
|
|
1062
|
+
const imagegenProviderOptions = [
|
|
1063
|
+
{ value: "codex" },
|
|
1064
|
+
{
|
|
1065
|
+
value: "agy",
|
|
1066
|
+
label: "agy (nano banana)"
|
|
1067
|
+
},
|
|
1068
|
+
{
|
|
1069
|
+
value: "grok",
|
|
1070
|
+
label: "grok (imagine)"
|
|
1071
|
+
},
|
|
1072
|
+
...customProviders.filter(canDraw).map((p) => ({
|
|
1073
|
+
value: p.id,
|
|
1074
|
+
label: p.name || p.id
|
|
1075
|
+
})),
|
|
1076
|
+
{ value: "off" }
|
|
1077
|
+
];
|
|
1078
|
+
const visionModelOptions = (() => {
|
|
1079
|
+
if (!config) return [];
|
|
1080
|
+
const p = config.vision_provider;
|
|
1081
|
+
const custom = customProviders.find((c) => c.id === p);
|
|
1082
|
+
const base = custom ? [{ value: "default" }, ...(custom.models ?? []).map((m) => ({ value: m }))] : dynModels[p] ?? (VISION_MODELS[p] ?? ["default"]).map((m) => ({ value: m }));
|
|
1083
|
+
const extras = (extraModels[p] ?? []).filter((m) => !base.some((o) => o.value === m)).map((m) => ({ value: m }));
|
|
1084
|
+
return [...base, ...extras];
|
|
1085
|
+
})();
|
|
1086
|
+
const commitModelAdd = () => {
|
|
1087
|
+
const m = (modelAdd ?? "").trim();
|
|
1088
|
+
setModelAdd(null);
|
|
1089
|
+
if (m === "" || !config) return;
|
|
1090
|
+
const p = config.vision_provider;
|
|
1091
|
+
const cur = extraModels[p] ?? [];
|
|
1092
|
+
const nextExtra = {
|
|
1093
|
+
...extraModels,
|
|
1094
|
+
[p]: cur.includes(m) ? cur : [...cur, m]
|
|
1095
|
+
};
|
|
1096
|
+
setConfig((c) => ({
|
|
1097
|
+
...c,
|
|
1098
|
+
extra_models: nextExtra,
|
|
1099
|
+
vision_model: m
|
|
1100
|
+
}));
|
|
1101
|
+
applyPatch({
|
|
1102
|
+
extra_models: nextExtra,
|
|
1103
|
+
vision_model: m
|
|
1104
|
+
});
|
|
1105
|
+
};
|
|
1106
|
+
const removeCurrentModel = () => {
|
|
1107
|
+
if (!config) return;
|
|
1108
|
+
const p = config.vision_provider;
|
|
1109
|
+
const m = config.vision_model;
|
|
1110
|
+
const nextExtra = {
|
|
1111
|
+
...extraModels,
|
|
1112
|
+
[p]: (extraModels[p] ?? []).filter((x) => x !== m)
|
|
1113
|
+
};
|
|
1114
|
+
const fallback = visionModelOptions.find((o) => o.value !== m)?.value ?? "default";
|
|
1115
|
+
setConfig((c) => ({
|
|
1116
|
+
...c,
|
|
1117
|
+
extra_models: nextExtra,
|
|
1118
|
+
vision_model: fallback
|
|
1119
|
+
}));
|
|
1120
|
+
applyPatch({
|
|
1121
|
+
extra_models: nextExtra,
|
|
1122
|
+
vision_model: fallback
|
|
1123
|
+
});
|
|
1124
|
+
};
|
|
1125
|
+
const saveProviderForm = () => {
|
|
1126
|
+
if (!provForm || !config) return;
|
|
1127
|
+
const name = provForm.name.trim();
|
|
1128
|
+
if (name === "") {
|
|
1129
|
+
setNotice(copy.needName);
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
const models = provForm.models.split(/[,,\n]+/).map((s) => s.trim()).filter(Boolean);
|
|
1133
|
+
const vision_command = provForm.vision_command.trim();
|
|
1134
|
+
const imagegen_command = provForm.imagegen_command.trim();
|
|
1135
|
+
if (provForm.type === "cli") {
|
|
1136
|
+
if (vision_command === "" && imagegen_command === "") {
|
|
1137
|
+
setNotice(copy.needCmd);
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
} else {
|
|
1141
|
+
if (provForm.base_url.trim() === "") {
|
|
1142
|
+
setNotice(copy.needBaseUrl);
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
if (models.length === 0) {
|
|
1146
|
+
setNotice(copy.needModels);
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
let id = provForm.originalId;
|
|
1151
|
+
if (!id) {
|
|
1152
|
+
const base = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "provider";
|
|
1153
|
+
id = base;
|
|
1154
|
+
let n = 2;
|
|
1155
|
+
while (RESERVED_IDS.includes(id) || customProviders.some((p) => p.id === id)) id = `${base}-${n++}`;
|
|
1156
|
+
}
|
|
1157
|
+
const entry = provForm.type === "api" ? {
|
|
1158
|
+
id,
|
|
1159
|
+
name,
|
|
1160
|
+
type: "api",
|
|
1161
|
+
models,
|
|
1162
|
+
base_url: provForm.base_url.trim(),
|
|
1163
|
+
api_key: provForm.api_key,
|
|
1164
|
+
imagegen_model: provForm.imagegen_model.trim()
|
|
1165
|
+
} : {
|
|
1166
|
+
id,
|
|
1167
|
+
name,
|
|
1168
|
+
type: "cli",
|
|
1169
|
+
models,
|
|
1170
|
+
vision_command,
|
|
1171
|
+
imagegen_command
|
|
1172
|
+
};
|
|
1173
|
+
const next = provForm.originalId ? customProviders.map((p) => p.id === provForm.originalId ? entry : p) : [...customProviders, entry];
|
|
1174
|
+
field("custom_providers", next);
|
|
1175
|
+
setProvForm(null);
|
|
1176
|
+
};
|
|
1177
|
+
const runProviderTest = (key, entry) => {
|
|
1178
|
+
setTestResult({
|
|
1179
|
+
key,
|
|
1180
|
+
busy: true
|
|
1181
|
+
});
|
|
1182
|
+
post("/provider-test", entry).then((r) => setTestResult(r.ok ? {
|
|
1183
|
+
key,
|
|
1184
|
+
busy: false,
|
|
1185
|
+
ok: r.result.ok,
|
|
1186
|
+
steps: r.result.steps
|
|
1187
|
+
} : {
|
|
1188
|
+
key,
|
|
1189
|
+
busy: false,
|
|
1190
|
+
ok: false,
|
|
1191
|
+
error: r.error
|
|
1192
|
+
})).catch((e) => setTestResult({
|
|
1193
|
+
key,
|
|
1194
|
+
busy: false,
|
|
1195
|
+
ok: false,
|
|
1196
|
+
error: String(e?.message ?? e)
|
|
1197
|
+
}));
|
|
1198
|
+
};
|
|
1199
|
+
/** Shared renderer for a test outcome under the button that triggered it. */
|
|
1200
|
+
const testReport = (key) => {
|
|
1201
|
+
if (!testResult || testResult.key !== key) return null;
|
|
1202
|
+
if (testResult.busy) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1203
|
+
style: {
|
|
1204
|
+
fontSize: 11.5,
|
|
1205
|
+
opacity: .6
|
|
1206
|
+
},
|
|
1207
|
+
children: copy.testing
|
|
1208
|
+
});
|
|
1209
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1210
|
+
style: {
|
|
1211
|
+
fontSize: 11.5,
|
|
1212
|
+
display: "flex",
|
|
1213
|
+
flexDirection: "column",
|
|
1214
|
+
gap: 2
|
|
1215
|
+
},
|
|
1216
|
+
children: [
|
|
1217
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1218
|
+
style: { color: testResult.ok ? "#3fb950" : "#f85149" },
|
|
1219
|
+
children: testResult.ok ? `✓ ${copy.testPass}` : `✗ ${copy.testFail}`
|
|
1220
|
+
}),
|
|
1221
|
+
testResult.error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1222
|
+
style: {
|
|
1223
|
+
opacity: .7,
|
|
1224
|
+
wordBreak: "break-all"
|
|
1225
|
+
},
|
|
1226
|
+
children: testResult.error
|
|
1227
|
+
}),
|
|
1228
|
+
(testResult.steps ?? []).map((st, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1229
|
+
style: {
|
|
1230
|
+
opacity: .7,
|
|
1231
|
+
wordBreak: "break-all"
|
|
1232
|
+
},
|
|
1233
|
+
children: [
|
|
1234
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1235
|
+
style: { color: st.ok ? "#3fb950" : "#f85149" },
|
|
1236
|
+
children: st.ok ? "✓" : "✗"
|
|
1237
|
+
}),
|
|
1238
|
+
" ",
|
|
1239
|
+
st.name,
|
|
1240
|
+
": ",
|
|
1241
|
+
st.detail
|
|
1242
|
+
]
|
|
1243
|
+
}, i))
|
|
1244
|
+
]
|
|
1245
|
+
});
|
|
1246
|
+
};
|
|
1247
|
+
const deleteProvider = (id) => {
|
|
1248
|
+
if (!config) return;
|
|
1249
|
+
const patch = { custom_providers: customProviders.filter((p) => p.id !== id) };
|
|
1250
|
+
if (config.vision_provider === id) {
|
|
1251
|
+
patch.vision_provider = "claude-code";
|
|
1252
|
+
patch.vision_model = "haiku";
|
|
1253
|
+
}
|
|
1254
|
+
if (config.imagegen_provider === id) patch.imagegen_provider = "codex";
|
|
1255
|
+
setConfig((c) => ({
|
|
1256
|
+
...c,
|
|
1257
|
+
...patch
|
|
1258
|
+
}));
|
|
1259
|
+
applyPatch(patch);
|
|
1260
|
+
};
|
|
1261
|
+
/** Card: title + one-line description header, then its fields. */
|
|
1262
|
+
const block = (text, fields, gridded = true) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1263
|
+
style: S.block,
|
|
1264
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1265
|
+
style: S.settingTitle,
|
|
1266
|
+
children: text.t
|
|
1267
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1268
|
+
style: S.settingDesc,
|
|
1269
|
+
children: text.d
|
|
1270
|
+
})] }), gridded ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1271
|
+
style: S.blockGrid,
|
|
1272
|
+
children: fields
|
|
1273
|
+
}) : fields]
|
|
1274
|
+
});
|
|
1275
|
+
const integrationRow = (name, installed, extra, installTarget, uninstallTarget, tips) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1276
|
+
style: S.card,
|
|
1277
|
+
children: [
|
|
1278
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1279
|
+
style: {
|
|
1280
|
+
fontWeight: 600,
|
|
1281
|
+
fontSize: 13,
|
|
1282
|
+
minWidth: 92
|
|
1283
|
+
},
|
|
1284
|
+
children: name
|
|
1285
|
+
}),
|
|
1286
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1287
|
+
style: S.chip(installed),
|
|
1288
|
+
children: installed ? `● ${copy.installed}` : `○ ${copy.notInstalled}`
|
|
1289
|
+
}),
|
|
1290
|
+
extra,
|
|
1291
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: { flex: 1 } }),
|
|
1292
|
+
!installed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1293
|
+
style: S.btn,
|
|
1294
|
+
title: tips.install,
|
|
1295
|
+
disabled: busy,
|
|
1296
|
+
onClick: () => {
|
|
1297
|
+
act(installTarget);
|
|
1298
|
+
},
|
|
1299
|
+
children: copy.install
|
|
1300
|
+
}),
|
|
1301
|
+
installed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1302
|
+
style: S.btn,
|
|
1303
|
+
title: tips.update,
|
|
1304
|
+
disabled: busy,
|
|
1305
|
+
onClick: () => {
|
|
1306
|
+
act(installTarget);
|
|
1307
|
+
},
|
|
1308
|
+
children: copy.update
|
|
1309
|
+
}),
|
|
1310
|
+
installed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1311
|
+
style: {
|
|
1312
|
+
...S.btn,
|
|
1313
|
+
opacity: .75
|
|
1314
|
+
},
|
|
1315
|
+
title: tips.restore,
|
|
1316
|
+
disabled: busy,
|
|
1317
|
+
onClick: () => {
|
|
1318
|
+
act(uninstallTarget, name);
|
|
1319
|
+
},
|
|
1320
|
+
children: copy.restore
|
|
1321
|
+
})
|
|
1322
|
+
]
|
|
1323
|
+
});
|
|
1324
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1325
|
+
style: {
|
|
1326
|
+
display: "flex",
|
|
1327
|
+
flexDirection: "column",
|
|
1328
|
+
gap: 8,
|
|
1329
|
+
fontSize: 13,
|
|
1330
|
+
lineHeight: 1.55
|
|
1331
|
+
},
|
|
1332
|
+
children: [
|
|
1333
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1334
|
+
style: {
|
|
1335
|
+
fontSize: 19,
|
|
1336
|
+
fontWeight: 600,
|
|
1337
|
+
marginBottom: -2
|
|
1338
|
+
},
|
|
1339
|
+
children: copy.title
|
|
1340
|
+
}),
|
|
1341
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1342
|
+
style: { opacity: .75 },
|
|
1343
|
+
children: copy.intro
|
|
1344
|
+
}),
|
|
1345
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1346
|
+
style: {
|
|
1347
|
+
display: "flex",
|
|
1348
|
+
alignItems: "center",
|
|
1349
|
+
gap: 7,
|
|
1350
|
+
marginTop: 5
|
|
1351
|
+
},
|
|
1352
|
+
children: [
|
|
1353
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1354
|
+
style: {
|
|
1355
|
+
...S.section,
|
|
1356
|
+
margin: 0,
|
|
1357
|
+
flex: 1
|
|
1358
|
+
},
|
|
1359
|
+
children: copy.globalConfig
|
|
1360
|
+
}),
|
|
1361
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1362
|
+
type: "button",
|
|
1363
|
+
style: {
|
|
1364
|
+
...S.btn,
|
|
1365
|
+
padding: "3px 9px"
|
|
1366
|
+
},
|
|
1367
|
+
onClick: () => toggleAllSections(true),
|
|
1368
|
+
children: copy.expandAll
|
|
1369
|
+
}),
|
|
1370
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1371
|
+
type: "button",
|
|
1372
|
+
style: {
|
|
1373
|
+
...S.btn,
|
|
1374
|
+
padding: "3px 9px"
|
|
1375
|
+
},
|
|
1376
|
+
onClick: () => toggleAllSections(false),
|
|
1377
|
+
children: copy.collapseAll
|
|
1378
|
+
})
|
|
1379
|
+
]
|
|
1380
|
+
}),
|
|
1381
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsibleSection, {
|
|
1382
|
+
sectionId: "integrations",
|
|
1383
|
+
title: copy.sectionNames.integrations,
|
|
1384
|
+
summary: sectionSummary(status?.claude?.installed ? "Claude ✓" : "Claude ○", status?.codex?.installed ? "Codex ✓" : "Codex ○"),
|
|
1385
|
+
expanded: !!expandedSections.integrations,
|
|
1386
|
+
onToggle: () => toggleSection("integrations"),
|
|
1387
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1388
|
+
style: {
|
|
1389
|
+
display: "flex",
|
|
1390
|
+
flexDirection: "column",
|
|
1391
|
+
gap: 8
|
|
1392
|
+
},
|
|
1393
|
+
children: [integrationRow("Claude Code", !!status?.claude?.installed, status?.claude?.installed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1394
|
+
style: S.chip(!!status?.claude?.hud),
|
|
1395
|
+
children: copy.hud
|
|
1396
|
+
}) : null, "claude", "claude-uninstall", copy.tips.claude), integrationRow("Codex", !!status?.codex?.installed, null, "codex", "codex-uninstall", copy.tips.codex)]
|
|
1397
|
+
})
|
|
1398
|
+
}),
|
|
1399
|
+
config && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1400
|
+
(() => {
|
|
1401
|
+
const clampNum = (v) => {
|
|
1402
|
+
const n = Number(v);
|
|
1403
|
+
if (Number.isNaN(n)) return 3;
|
|
1404
|
+
return Math.max(1, Math.min(16, n));
|
|
1405
|
+
};
|
|
1406
|
+
const mode = config.collaboration_mode ?? "flash-only";
|
|
1407
|
+
const nonCustom = mode !== "custom";
|
|
1408
|
+
const states = nonCustom ? {
|
|
1409
|
+
"flash-only": {
|
|
1410
|
+
flash: "auto",
|
|
1411
|
+
pro: "disabled"
|
|
1412
|
+
},
|
|
1413
|
+
"pro-only": {
|
|
1414
|
+
flash: "disabled",
|
|
1415
|
+
pro: "auto"
|
|
1416
|
+
},
|
|
1417
|
+
balanced: {
|
|
1418
|
+
flash: "auto",
|
|
1419
|
+
pro: "auto"
|
|
1420
|
+
},
|
|
1421
|
+
"review-pipeline": {
|
|
1422
|
+
flash: "auto",
|
|
1423
|
+
pro: "auto"
|
|
1424
|
+
},
|
|
1425
|
+
custom: null
|
|
1426
|
+
}[mode] : null;
|
|
1427
|
+
const effState = (tier) => states ? states[tier] : (tier === "flash" ? config.flash_state : config.pro_state) ?? "auto";
|
|
1428
|
+
const roleOptions = [
|
|
1429
|
+
"implementation",
|
|
1430
|
+
"simple_fix",
|
|
1431
|
+
"tests",
|
|
1432
|
+
"search_inspection",
|
|
1433
|
+
"architecture",
|
|
1434
|
+
"complex_debugging",
|
|
1435
|
+
"refactor",
|
|
1436
|
+
"code_review"
|
|
1437
|
+
];
|
|
1438
|
+
const roleKeys = (tier) => {
|
|
1439
|
+
const arr = tier === "flash" ? config.flash_roles ?? [] : config.pro_roles ?? [];
|
|
1440
|
+
const set = new Set(arr);
|
|
1441
|
+
return {
|
|
1442
|
+
arr: roleOptions.filter((r) => set.has(r)),
|
|
1443
|
+
set,
|
|
1444
|
+
save: (list) => field(tier === "flash" ? "flash_roles" : "pro_roles", list)
|
|
1445
|
+
};
|
|
1446
|
+
};
|
|
1447
|
+
const catalogProviders = modelCatalog?.providers ?? [];
|
|
1448
|
+
const refKey = (ref) => `${ref?.provider ?? ""}\0${ref?.model ?? ""}`;
|
|
1449
|
+
const priorityFor = (tier) => Array.isArray(config[`${tier}_model_priority`]) ? config[`${tier}_model_priority`] : [];
|
|
1450
|
+
const savePriority = (tier, list) => {
|
|
1451
|
+
const key = `${tier}_model_priority`;
|
|
1452
|
+
setConfig((current) => ({
|
|
1453
|
+
...current,
|
|
1454
|
+
[key]: list,
|
|
1455
|
+
[`${tier}_model_priority_configured`]: true
|
|
1456
|
+
}));
|
|
1457
|
+
applyPatch({ [key]: list });
|
|
1458
|
+
};
|
|
1459
|
+
const modelMeta = (ref) => {
|
|
1460
|
+
const provider = catalogProviders.find((item) => item.id === ref.provider);
|
|
1461
|
+
return {
|
|
1462
|
+
provider,
|
|
1463
|
+
model: provider?.models?.find((item) => item.id === ref.model)
|
|
1464
|
+
};
|
|
1465
|
+
};
|
|
1466
|
+
const filteredProviders = catalogProviders.map((provider) => ({
|
|
1467
|
+
...provider,
|
|
1468
|
+
models: (provider.models ?? []).filter((model) => {
|
|
1469
|
+
const query = modelQuery.trim().toLowerCase();
|
|
1470
|
+
return !query || [
|
|
1471
|
+
provider.id,
|
|
1472
|
+
provider.name,
|
|
1473
|
+
model.id,
|
|
1474
|
+
model.name
|
|
1475
|
+
].some((value) => String(value ?? "").toLowerCase().includes(query));
|
|
1476
|
+
})
|
|
1477
|
+
})).filter((provider) => provider.models.length > 0);
|
|
1478
|
+
const modelPriorityPanel = (tier) => {
|
|
1479
|
+
const priority = priorityFor(tier);
|
|
1480
|
+
const selected = new Set(priority.map(refKey));
|
|
1481
|
+
const preferredId = tier === "flash" ? "deepseek-v4-flash" : "deepseek-v4-pro";
|
|
1482
|
+
const preferredProviders = catalogProviders.filter((provider) => (provider.models ?? []).some((model) => model.id === preferredId));
|
|
1483
|
+
const ambiguousPreferred = priority.length === 0 && config[`${tier}_model_priority_configured`] !== true && preferredProviders.length > 1 && !preferredProviders.some((provider) => provider.id === modelCatalog?.harness_default?.provider);
|
|
1484
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1485
|
+
style: {
|
|
1486
|
+
gridColumn: "1 / -1",
|
|
1487
|
+
borderTop: "1px solid rgba(128,128,128,0.18)",
|
|
1488
|
+
paddingTop: 9
|
|
1489
|
+
},
|
|
1490
|
+
children: [
|
|
1491
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1492
|
+
style: {
|
|
1493
|
+
...S.fieldLabel,
|
|
1494
|
+
marginBottom: 5
|
|
1495
|
+
},
|
|
1496
|
+
children: copy.modelPriority
|
|
1497
|
+
}),
|
|
1498
|
+
priority.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1499
|
+
style: {
|
|
1500
|
+
fontSize: 12,
|
|
1501
|
+
opacity: .55,
|
|
1502
|
+
marginBottom: 6
|
|
1503
|
+
},
|
|
1504
|
+
children: copy.noPriority
|
|
1505
|
+
}),
|
|
1506
|
+
ambiguousPreferred && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1507
|
+
style: {
|
|
1508
|
+
fontSize: 11.5,
|
|
1509
|
+
color: "#c98735",
|
|
1510
|
+
marginBottom: 6
|
|
1511
|
+
},
|
|
1512
|
+
children: copy.needsModelSelection
|
|
1513
|
+
}),
|
|
1514
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1515
|
+
style: {
|
|
1516
|
+
display: "flex",
|
|
1517
|
+
flexDirection: "column",
|
|
1518
|
+
gap: 5
|
|
1519
|
+
},
|
|
1520
|
+
children: priority.map((ref, index) => {
|
|
1521
|
+
const meta = modelMeta(ref);
|
|
1522
|
+
const warning = !meta.provider ? copy.providerUnavailable : !meta.model ? copy.notAdvertised : "";
|
|
1523
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1524
|
+
style: {
|
|
1525
|
+
display: "flex",
|
|
1526
|
+
gap: 7,
|
|
1527
|
+
alignItems: "center",
|
|
1528
|
+
padding: "6px 8px",
|
|
1529
|
+
border: "1px solid rgba(128,128,128,0.2)",
|
|
1530
|
+
borderRadius: 6
|
|
1531
|
+
},
|
|
1532
|
+
children: [
|
|
1533
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1534
|
+
style: {
|
|
1535
|
+
opacity: .55,
|
|
1536
|
+
width: 18
|
|
1537
|
+
},
|
|
1538
|
+
children: [index + 1, "."]
|
|
1539
|
+
}),
|
|
1540
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1541
|
+
style: {
|
|
1542
|
+
flex: 1,
|
|
1543
|
+
minWidth: 0
|
|
1544
|
+
},
|
|
1545
|
+
children: [
|
|
1546
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1547
|
+
style: {
|
|
1548
|
+
display: "block",
|
|
1549
|
+
fontSize: 12.5
|
|
1550
|
+
},
|
|
1551
|
+
children: meta.model?.name ?? ref.model
|
|
1552
|
+
}),
|
|
1553
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1554
|
+
style: {
|
|
1555
|
+
display: "block",
|
|
1556
|
+
fontSize: 10.5,
|
|
1557
|
+
opacity: .58,
|
|
1558
|
+
fontFamily: "monospace"
|
|
1559
|
+
},
|
|
1560
|
+
children: [
|
|
1561
|
+
ref.provider,
|
|
1562
|
+
" / ",
|
|
1563
|
+
ref.model
|
|
1564
|
+
]
|
|
1565
|
+
}),
|
|
1566
|
+
warning && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1567
|
+
style: {
|
|
1568
|
+
display: "block",
|
|
1569
|
+
fontSize: 10.5,
|
|
1570
|
+
color: "#c98735"
|
|
1571
|
+
},
|
|
1572
|
+
children: warning
|
|
1573
|
+
})
|
|
1574
|
+
]
|
|
1575
|
+
}),
|
|
1576
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1577
|
+
type: "button",
|
|
1578
|
+
style: {
|
|
1579
|
+
...S.btn,
|
|
1580
|
+
padding: "2px 7px"
|
|
1581
|
+
},
|
|
1582
|
+
title: copy.moveUp,
|
|
1583
|
+
disabled: index === 0,
|
|
1584
|
+
onClick: () => {
|
|
1585
|
+
const next = [...priority];
|
|
1586
|
+
[next[index - 1], next[index]] = [next[index], next[index - 1]];
|
|
1587
|
+
savePriority(tier, next);
|
|
1588
|
+
},
|
|
1589
|
+
children: "↑"
|
|
1590
|
+
}),
|
|
1591
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1592
|
+
type: "button",
|
|
1593
|
+
style: {
|
|
1594
|
+
...S.btn,
|
|
1595
|
+
padding: "2px 7px"
|
|
1596
|
+
},
|
|
1597
|
+
title: copy.moveDown,
|
|
1598
|
+
disabled: index === priority.length - 1,
|
|
1599
|
+
onClick: () => {
|
|
1600
|
+
const next = [...priority];
|
|
1601
|
+
[next[index], next[index + 1]] = [next[index + 1], next[index]];
|
|
1602
|
+
savePriority(tier, next);
|
|
1603
|
+
},
|
|
1604
|
+
children: "↓"
|
|
1605
|
+
}),
|
|
1606
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1607
|
+
type: "button",
|
|
1608
|
+
style: {
|
|
1609
|
+
...S.btn,
|
|
1610
|
+
padding: "2px 7px"
|
|
1611
|
+
},
|
|
1612
|
+
title: copy.removePriority,
|
|
1613
|
+
onClick: () => savePriority(tier, priority.filter((_, itemIndex) => itemIndex !== index)),
|
|
1614
|
+
children: "×"
|
|
1615
|
+
})
|
|
1616
|
+
]
|
|
1617
|
+
}, refKey(ref));
|
|
1618
|
+
})
|
|
1619
|
+
}),
|
|
1620
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1621
|
+
style: {
|
|
1622
|
+
display: "flex",
|
|
1623
|
+
gap: 8,
|
|
1624
|
+
alignItems: "center",
|
|
1625
|
+
marginTop: 7
|
|
1626
|
+
},
|
|
1627
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1628
|
+
type: "button",
|
|
1629
|
+
style: S.btn,
|
|
1630
|
+
disabled: !modelCatalog || modelCatalogBusy,
|
|
1631
|
+
onClick: () => {
|
|
1632
|
+
setModelPickerTier(modelPickerTier === tier ? null : tier);
|
|
1633
|
+
setModelQuery("");
|
|
1634
|
+
},
|
|
1635
|
+
children: copy.addPriorityModel
|
|
1636
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1637
|
+
style: {
|
|
1638
|
+
fontSize: 11,
|
|
1639
|
+
opacity: .58
|
|
1640
|
+
},
|
|
1641
|
+
children: [
|
|
1642
|
+
copy.harnessDefault,
|
|
1643
|
+
": ",
|
|
1644
|
+
modelCatalog?.harness_default ? `${modelCatalog.harness_default.provider} / ${modelCatalog.harness_default.model}` : "—"
|
|
1645
|
+
]
|
|
1646
|
+
})]
|
|
1647
|
+
}),
|
|
1648
|
+
modelPickerTier === tier && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1649
|
+
style: {
|
|
1650
|
+
marginTop: 8,
|
|
1651
|
+
border: "1px solid rgba(128,128,128,0.22)",
|
|
1652
|
+
borderRadius: 7,
|
|
1653
|
+
padding: 8,
|
|
1654
|
+
maxHeight: 260,
|
|
1655
|
+
overflow: "auto"
|
|
1656
|
+
},
|
|
1657
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1658
|
+
autoFocus: true,
|
|
1659
|
+
value: modelQuery,
|
|
1660
|
+
placeholder: copy.modelSearch,
|
|
1661
|
+
onChange: (event) => setModelQuery(event.target.value),
|
|
1662
|
+
style: {
|
|
1663
|
+
...S.input,
|
|
1664
|
+
width: "100%",
|
|
1665
|
+
boxSizing: "border-box",
|
|
1666
|
+
marginBottom: 7
|
|
1667
|
+
}
|
|
1668
|
+
}), filteredProviders.map((provider) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1669
|
+
style: { marginBottom: 8 },
|
|
1670
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1671
|
+
style: {
|
|
1672
|
+
fontSize: 11.5,
|
|
1673
|
+
fontWeight: 600,
|
|
1674
|
+
marginBottom: 3
|
|
1675
|
+
},
|
|
1676
|
+
children: [
|
|
1677
|
+
provider.name,
|
|
1678
|
+
" ",
|
|
1679
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1680
|
+
style: {
|
|
1681
|
+
opacity: .5,
|
|
1682
|
+
fontFamily: "monospace"
|
|
1683
|
+
},
|
|
1684
|
+
children: provider.id
|
|
1685
|
+
})
|
|
1686
|
+
]
|
|
1687
|
+
}), (provider.models ?? []).map((model) => {
|
|
1688
|
+
const ref = {
|
|
1689
|
+
provider: provider.id,
|
|
1690
|
+
model: model.id
|
|
1691
|
+
};
|
|
1692
|
+
const duplicate = selected.has(refKey(ref));
|
|
1693
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1694
|
+
type: "button",
|
|
1695
|
+
disabled: duplicate,
|
|
1696
|
+
style: {
|
|
1697
|
+
...S.btn,
|
|
1698
|
+
display: "block",
|
|
1699
|
+
width: "100%",
|
|
1700
|
+
textAlign: "left",
|
|
1701
|
+
marginBottom: 3,
|
|
1702
|
+
opacity: duplicate ? .45 : 1
|
|
1703
|
+
},
|
|
1704
|
+
onClick: () => {
|
|
1705
|
+
savePriority(tier, [...priority, ref]);
|
|
1706
|
+
setModelPickerTier(null);
|
|
1707
|
+
},
|
|
1708
|
+
children: [
|
|
1709
|
+
model.name,
|
|
1710
|
+
" ",
|
|
1711
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1712
|
+
style: {
|
|
1713
|
+
opacity: .55,
|
|
1714
|
+
fontFamily: "monospace"
|
|
1715
|
+
},
|
|
1716
|
+
children: model.id
|
|
1717
|
+
})
|
|
1718
|
+
]
|
|
1719
|
+
}, model.id);
|
|
1720
|
+
})]
|
|
1721
|
+
}, provider.id))]
|
|
1722
|
+
})
|
|
1723
|
+
]
|
|
1724
|
+
});
|
|
1725
|
+
};
|
|
1726
|
+
const tierCard = (tier, card) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1727
|
+
style: S.block,
|
|
1728
|
+
children: [
|
|
1729
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1730
|
+
style: S.settingTitle,
|
|
1731
|
+
children: card.t
|
|
1732
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1733
|
+
style: S.settingDesc,
|
|
1734
|
+
children: card.d
|
|
1735
|
+
})] }),
|
|
1736
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1737
|
+
style: S.blockGrid,
|
|
1738
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1739
|
+
style: S.field,
|
|
1740
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1741
|
+
style: S.fieldLabel,
|
|
1742
|
+
children: copy.tierState
|
|
1743
|
+
}), nonCustom ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1744
|
+
style: {
|
|
1745
|
+
fontSize: 12.5,
|
|
1746
|
+
padding: "3px 8px",
|
|
1747
|
+
borderRadius: 5,
|
|
1748
|
+
border: "1px solid rgba(128,128,128,0.35)",
|
|
1749
|
+
opacity: .8
|
|
1750
|
+
},
|
|
1751
|
+
children: [
|
|
1752
|
+
effState(tier) === "auto" ? copy.tierStateDesc.auto : copy.tierStateDesc.disabled,
|
|
1753
|
+
"(",
|
|
1754
|
+
copy.stateManagedByPreset,
|
|
1755
|
+
")"
|
|
1756
|
+
]
|
|
1757
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
|
|
1758
|
+
value: tier === "flash" ? config.flash_state ?? "auto" : config.pro_state ?? "auto",
|
|
1759
|
+
onChange: (v) => field(tier === "flash" ? "flash_state" : "pro_state", v),
|
|
1760
|
+
options: [
|
|
1761
|
+
"disabled",
|
|
1762
|
+
"manual",
|
|
1763
|
+
"auto"
|
|
1764
|
+
].map((s) => ({
|
|
1765
|
+
value: s,
|
|
1766
|
+
label: copy.tierStateDesc[s]
|
|
1767
|
+
}))
|
|
1768
|
+
})]
|
|
1769
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1770
|
+
style: S.field,
|
|
1771
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1772
|
+
style: S.fieldLabel,
|
|
1773
|
+
children: tier === "flash" ? copy.presetFlash : copy.presetPro
|
|
1774
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
|
|
1775
|
+
value: tier === "flash" ? config.preset_flash ?? "default" : config.preset_pro ?? "default",
|
|
1776
|
+
onChange: (v) => field(tier === "flash" ? "preset_flash" : "preset_pro", v),
|
|
1777
|
+
options: presetOptions
|
|
1778
|
+
})]
|
|
1779
|
+
})]
|
|
1780
|
+
}),
|
|
1781
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1782
|
+
style: {
|
|
1783
|
+
fontSize: 11,
|
|
1784
|
+
opacity: .6,
|
|
1785
|
+
marginTop: 2
|
|
1786
|
+
},
|
|
1787
|
+
children: [
|
|
1788
|
+
copy.statePresetHint,
|
|
1789
|
+
" ",
|
|
1790
|
+
copy.responsibilitiesHint
|
|
1791
|
+
]
|
|
1792
|
+
}),
|
|
1793
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1794
|
+
style: {
|
|
1795
|
+
display: "grid",
|
|
1796
|
+
gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
|
|
1797
|
+
gap: "3px 14px"
|
|
1798
|
+
},
|
|
1799
|
+
children: roleOptions.map((r) => {
|
|
1800
|
+
const k = roleKeys(tier);
|
|
1801
|
+
const on = k.set.has(r);
|
|
1802
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1803
|
+
style: {
|
|
1804
|
+
display: "flex",
|
|
1805
|
+
alignItems: "center",
|
|
1806
|
+
gap: 6,
|
|
1807
|
+
fontSize: 12
|
|
1808
|
+
},
|
|
1809
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1810
|
+
type: "checkbox",
|
|
1811
|
+
checked: on,
|
|
1812
|
+
onChange: () => {
|
|
1813
|
+
k.save(on ? k.arr.filter((x) => x !== r) : [...k.arr, r]);
|
|
1814
|
+
}
|
|
1815
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: copy.roleLabels[r] })]
|
|
1816
|
+
}, r);
|
|
1817
|
+
})
|
|
1818
|
+
}),
|
|
1819
|
+
modelPriorityPanel(tier)
|
|
1820
|
+
]
|
|
1821
|
+
});
|
|
1822
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1823
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CollapsibleSection, {
|
|
1824
|
+
sectionId: "workflow",
|
|
1825
|
+
title: copy.sectionNames.workflow,
|
|
1826
|
+
summary: sectionSummary(config.worker_state ?? "auto", config.review_state ?? "auto", mode, config.isolation ?? "worktree", `×${config.max_parallel ?? 3}`),
|
|
1827
|
+
expanded: !!expandedSections.workflow,
|
|
1828
|
+
onToggle: () => toggleSection("workflow"),
|
|
1829
|
+
children: [
|
|
1830
|
+
block({
|
|
1831
|
+
t: copy.roleWorker,
|
|
1832
|
+
d: copy.roleStateHint
|
|
1833
|
+
}, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1834
|
+
style: S.field,
|
|
1835
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1836
|
+
style: S.fieldLabel,
|
|
1837
|
+
children: copy.workerStateLabel
|
|
1838
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
|
|
1839
|
+
value: config.worker_state ?? "auto",
|
|
1840
|
+
onChange: (v) => field("worker_state", v),
|
|
1841
|
+
options: [
|
|
1842
|
+
"auto",
|
|
1843
|
+
"manual",
|
|
1844
|
+
"disabled"
|
|
1845
|
+
].map((s) => ({
|
|
1846
|
+
value: s,
|
|
1847
|
+
label: copy.tierStateDesc[s]
|
|
1848
|
+
}))
|
|
1849
|
+
})]
|
|
1850
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1851
|
+
style: S.field,
|
|
1852
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1853
|
+
style: S.fieldLabel,
|
|
1854
|
+
children: copy.reviewStateLabel
|
|
1855
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
|
|
1856
|
+
value: config.review_state ?? "auto",
|
|
1857
|
+
onChange: (v) => field("review_state", v),
|
|
1858
|
+
options: [
|
|
1859
|
+
"auto",
|
|
1860
|
+
"manual",
|
|
1861
|
+
"disabled"
|
|
1862
|
+
].map((s) => ({
|
|
1863
|
+
value: s,
|
|
1864
|
+
label: copy.tierStateDesc[s]
|
|
1865
|
+
}))
|
|
1866
|
+
})]
|
|
1867
|
+
})] })),
|
|
1868
|
+
block({
|
|
1869
|
+
t: copy.autoReview,
|
|
1870
|
+
d: copy.autoReviewHint
|
|
1871
|
+
}, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1872
|
+
style: {
|
|
1873
|
+
...S.field,
|
|
1874
|
+
flexDirection: "row",
|
|
1875
|
+
alignItems: "center",
|
|
1876
|
+
gap: 6
|
|
1877
|
+
},
|
|
1878
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1879
|
+
type: "checkbox",
|
|
1880
|
+
checked: config.auto_review ?? !!config.pro_reviews_flash,
|
|
1881
|
+
onChange: (e) => field("auto_review", e.target.checked)
|
|
1882
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1883
|
+
style: { fontSize: 12.5 },
|
|
1884
|
+
children: copy.autoReview
|
|
1885
|
+
})]
|
|
1886
|
+
}), false),
|
|
1887
|
+
block({
|
|
1888
|
+
t: copy.isolation,
|
|
1889
|
+
d: copy.isolationHint
|
|
1890
|
+
}, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1891
|
+
style: S.field,
|
|
1892
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1893
|
+
style: S.fieldLabel,
|
|
1894
|
+
children: copy.isolation
|
|
1895
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
|
|
1896
|
+
value: config.isolation ?? "worktree",
|
|
1897
|
+
onChange: (v) => field("isolation", v),
|
|
1898
|
+
options: ["worktree", "shared"].map((s) => ({
|
|
1899
|
+
value: s,
|
|
1900
|
+
label: copy.isolationDesc[s]
|
|
1901
|
+
}))
|
|
1902
|
+
})]
|
|
1903
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1904
|
+
style: S.field,
|
|
1905
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1906
|
+
style: S.fieldLabel,
|
|
1907
|
+
children: copy.maxParallel
|
|
1908
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1909
|
+
type: "number",
|
|
1910
|
+
min: 1,
|
|
1911
|
+
max: 16,
|
|
1912
|
+
value: config.max_parallel ?? 3,
|
|
1913
|
+
onChange: (e) => fieldLocal("max_parallel", clampNum(e.target.value)),
|
|
1914
|
+
onBlur: () => field("max_parallel", clampNum(config.max_parallel)),
|
|
1915
|
+
style: S.input
|
|
1916
|
+
})]
|
|
1917
|
+
})] })),
|
|
1918
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1919
|
+
style: {
|
|
1920
|
+
fontSize: 11,
|
|
1921
|
+
opacity: .6,
|
|
1922
|
+
marginTop: 3
|
|
1923
|
+
},
|
|
1924
|
+
children: copy.legacyCompatibility
|
|
1925
|
+
}),
|
|
1926
|
+
block({
|
|
1927
|
+
t: copy.orchestration,
|
|
1928
|
+
d: copy.enableSubagentsHint
|
|
1929
|
+
}, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1930
|
+
style: {
|
|
1931
|
+
...S.field,
|
|
1932
|
+
flexDirection: "row",
|
|
1933
|
+
alignItems: "center",
|
|
1934
|
+
gap: 6
|
|
1935
|
+
},
|
|
1936
|
+
title: copy.enableSubagentsHint,
|
|
1937
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1938
|
+
type: "checkbox",
|
|
1939
|
+
checked: !!config.subagents_enabled,
|
|
1940
|
+
onChange: (e) => field("subagents_enabled", e.target.checked)
|
|
1941
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1942
|
+
style: { fontSize: 12.5 },
|
|
1943
|
+
children: config.subagents_enabled === false ? copy.subagentsKept : copy.enableSubagents
|
|
1944
|
+
})]
|
|
1945
|
+
}), false),
|
|
1946
|
+
block({
|
|
1947
|
+
t: copy.mainAgentMode,
|
|
1948
|
+
d: copy.mainAgentModeHint
|
|
1949
|
+
}, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1950
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1951
|
+
style: S.field,
|
|
1952
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1953
|
+
style: S.fieldLabel,
|
|
1954
|
+
children: copy.mainAgentMode
|
|
1955
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
|
|
1956
|
+
value: config.main_agent_mode ?? "direct-allowed",
|
|
1957
|
+
onChange: (v) => field("main_agent_mode", v),
|
|
1958
|
+
options: [
|
|
1959
|
+
"direct-allowed",
|
|
1960
|
+
"coordinator-first",
|
|
1961
|
+
"dispatcher-only"
|
|
1962
|
+
].map((m) => ({
|
|
1963
|
+
value: m,
|
|
1964
|
+
label: copy.mainAgentModeDesc[m]
|
|
1965
|
+
}))
|
|
1966
|
+
})]
|
|
1967
|
+
}),
|
|
1968
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1969
|
+
style: S.field,
|
|
1970
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1971
|
+
style: S.fieldLabel,
|
|
1972
|
+
children: copy.collaborationMode
|
|
1973
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
|
|
1974
|
+
value: mode,
|
|
1975
|
+
onChange: (v) => field("collaboration_mode", v),
|
|
1976
|
+
options: [
|
|
1977
|
+
"flash-only",
|
|
1978
|
+
"pro-only",
|
|
1979
|
+
"balanced",
|
|
1980
|
+
"review-pipeline",
|
|
1981
|
+
"custom"
|
|
1982
|
+
].map((m) => ({
|
|
1983
|
+
value: m,
|
|
1984
|
+
label: copy.collaborationModeDesc[m]
|
|
1985
|
+
}))
|
|
1986
|
+
})]
|
|
1987
|
+
}),
|
|
1988
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1989
|
+
style: S.field,
|
|
1990
|
+
title: copy.workerProviderHint,
|
|
1991
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1992
|
+
style: S.fieldLabel,
|
|
1993
|
+
children: copy.workerProvider
|
|
1994
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
|
|
1995
|
+
value: config.worker_provider_mode ?? "follow-dsh",
|
|
1996
|
+
onChange: (v) => field("worker_provider_mode", v),
|
|
1997
|
+
options: ["follow-dsh", "deepseek-official"].map((m) => ({
|
|
1998
|
+
value: m,
|
|
1999
|
+
label: copy.workerProviderDesc[m]
|
|
2000
|
+
}))
|
|
2001
|
+
})]
|
|
2002
|
+
})
|
|
2003
|
+
] })),
|
|
2004
|
+
block({
|
|
2005
|
+
t: copy.workerModels,
|
|
2006
|
+
d: modelCatalog ? copy.modelPoolSummary(modelCatalog.provider_count ?? 0, modelCatalog.model_count ?? 0) : copy.catalogFailed
|
|
2007
|
+
}, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
2008
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2009
|
+
type: "button",
|
|
2010
|
+
style: S.btn,
|
|
2011
|
+
disabled: modelCatalogBusy,
|
|
2012
|
+
onClick: () => {
|
|
2013
|
+
refreshHarnessModels();
|
|
2014
|
+
},
|
|
2015
|
+
children: modelCatalogBusy ? copy.refreshingModels : copy.refreshHarnessModels
|
|
2016
|
+
}),
|
|
2017
|
+
modelCatalog?.partial && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2018
|
+
style: {
|
|
2019
|
+
fontSize: 11.5,
|
|
2020
|
+
color: "#c98735"
|
|
2021
|
+
},
|
|
2022
|
+
children: copy.partialCatalog
|
|
2023
|
+
}),
|
|
2024
|
+
modelCatalogError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2025
|
+
style: {
|
|
2026
|
+
fontSize: 11.5,
|
|
2027
|
+
color: "#c55"
|
|
2028
|
+
},
|
|
2029
|
+
children: modelCatalogError
|
|
2030
|
+
})
|
|
2031
|
+
] }), false),
|
|
2032
|
+
block({
|
|
2033
|
+
t: copy.routing,
|
|
2034
|
+
d: ""
|
|
2035
|
+
}, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2036
|
+
style: {
|
|
2037
|
+
...S.field,
|
|
2038
|
+
flexDirection: "row",
|
|
2039
|
+
alignItems: "center",
|
|
2040
|
+
gap: 6,
|
|
2041
|
+
paddingBottom: 5
|
|
2042
|
+
},
|
|
2043
|
+
title: copy.escalateHint,
|
|
2044
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2045
|
+
type: "checkbox",
|
|
2046
|
+
checked: !!config.escalate_on_failure,
|
|
2047
|
+
onChange: (e) => field("escalate_on_failure", e.target.checked)
|
|
2048
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2049
|
+
style: { fontSize: 12 },
|
|
2050
|
+
children: copy.escalate
|
|
2051
|
+
})]
|
|
2052
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2053
|
+
style: {
|
|
2054
|
+
...S.field,
|
|
2055
|
+
flexDirection: "row",
|
|
2056
|
+
alignItems: "center",
|
|
2057
|
+
gap: 6,
|
|
2058
|
+
paddingBottom: 5
|
|
2059
|
+
},
|
|
2060
|
+
title: mode === "review-pipeline" ? copy.proReviewsLocked : copy.proReviewsFlashHint,
|
|
2061
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2062
|
+
type: "checkbox",
|
|
2063
|
+
disabled: mode === "review-pipeline",
|
|
2064
|
+
checked: mode === "review-pipeline" ? true : !!config.pro_reviews_flash,
|
|
2065
|
+
onChange: (e) => field("pro_reviews_flash", e.target.checked)
|
|
2066
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2067
|
+
style: { fontSize: 12 },
|
|
2068
|
+
children: [copy.proReviewsFlash, mode === "review-pipeline" ? `(${copy.proReviewsLocked})` : ""]
|
|
2069
|
+
})]
|
|
2070
|
+
})] }))
|
|
2071
|
+
]
|
|
2072
|
+
}),
|
|
2073
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsibleSection, {
|
|
2074
|
+
sectionId: "flash",
|
|
2075
|
+
title: copy.sectionNames.flash,
|
|
2076
|
+
summary: sectionSummary(effState("flash"), copy.modelCount(priorityFor("flash").length), priorityFor("flash")[0]?.model ?? copy.harnessDefault),
|
|
2077
|
+
expanded: !!expandedSections.flash,
|
|
2078
|
+
onToggle: () => toggleSection("flash"),
|
|
2079
|
+
children: tierCard("flash", {
|
|
2080
|
+
t: copy.tierCardFlash,
|
|
2081
|
+
d: "implementation · direct validation · Delivery Report"
|
|
2082
|
+
})
|
|
2083
|
+
}),
|
|
2084
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsibleSection, {
|
|
2085
|
+
sectionId: "pro",
|
|
2086
|
+
title: copy.sectionNames.pro,
|
|
2087
|
+
summary: sectionSummary(effState("pro"), copy.modelCount(priorityFor("pro").length), priorityFor("pro")[0]?.model ?? copy.harnessDefault),
|
|
2088
|
+
expanded: !!expandedSections.pro,
|
|
2089
|
+
onToggle: () => toggleSection("pro"),
|
|
2090
|
+
children: tierCard("pro", {
|
|
2091
|
+
t: copy.tierCardPro,
|
|
2092
|
+
d: "manual advanced work · review pipeline"
|
|
2093
|
+
})
|
|
2094
|
+
})
|
|
2095
|
+
] });
|
|
2096
|
+
})(),
|
|
2097
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsibleSection, {
|
|
2098
|
+
sectionId: "dispatch",
|
|
2099
|
+
title: copy.sectionNames.dispatch,
|
|
2100
|
+
summary: sectionSummary(config.default_tier, config.default_effort, config.tier_policy, config.escalate_on_failure && copy.escalate),
|
|
2101
|
+
expanded: !!expandedSections.dispatch,
|
|
2102
|
+
onToggle: () => toggleSection("dispatch"),
|
|
2103
|
+
children: block(copy.cardDispatch, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
2104
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2105
|
+
style: S.field,
|
|
2106
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2107
|
+
style: S.fieldLabel,
|
|
2108
|
+
children: copy.tier
|
|
2109
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
|
|
2110
|
+
value: config.default_tier,
|
|
2111
|
+
onChange: (v) => field("default_tier", v),
|
|
2112
|
+
options: [{ value: "flash" }, { value: "pro" }]
|
|
2113
|
+
})]
|
|
2114
|
+
}),
|
|
2115
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2116
|
+
style: S.field,
|
|
2117
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2118
|
+
style: S.fieldLabel,
|
|
2119
|
+
children: copy.effort
|
|
2120
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
|
|
2121
|
+
value: config.default_effort,
|
|
2122
|
+
onChange: (v) => field("default_effort", v),
|
|
2123
|
+
options: [
|
|
2124
|
+
{ value: "off" },
|
|
2125
|
+
{ value: "high" },
|
|
2126
|
+
{ value: "max" }
|
|
2127
|
+
]
|
|
2128
|
+
})]
|
|
2129
|
+
}),
|
|
2130
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2131
|
+
style: S.field,
|
|
2132
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2133
|
+
style: S.fieldLabel,
|
|
2134
|
+
children: copy.tierPolicy
|
|
2135
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
|
|
2136
|
+
value: config.tier_policy,
|
|
2137
|
+
onChange: (v) => field("tier_policy", v),
|
|
2138
|
+
options: [
|
|
2139
|
+
"auto",
|
|
2140
|
+
"flash-only",
|
|
2141
|
+
"pro-only"
|
|
2142
|
+
].map((p) => ({
|
|
2143
|
+
value: p,
|
|
2144
|
+
label: copy.tierPolicyDesc[p]
|
|
2145
|
+
}))
|
|
2146
|
+
})]
|
|
2147
|
+
}),
|
|
2148
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2149
|
+
style: {
|
|
2150
|
+
...S.field,
|
|
2151
|
+
flexDirection: "row",
|
|
2152
|
+
alignItems: "center",
|
|
2153
|
+
gap: 6,
|
|
2154
|
+
paddingBottom: 5
|
|
2155
|
+
},
|
|
2156
|
+
title: copy.escalateHint,
|
|
2157
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2158
|
+
type: "checkbox",
|
|
2159
|
+
checked: !!config.escalate_on_failure,
|
|
2160
|
+
onChange: (e) => field("escalate_on_failure", e.target.checked)
|
|
2161
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2162
|
+
style: { fontSize: 12 },
|
|
2163
|
+
children: copy.escalate
|
|
2164
|
+
})]
|
|
2165
|
+
}),
|
|
2166
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2167
|
+
style: S.field,
|
|
2168
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2169
|
+
style: S.fieldLabel,
|
|
2170
|
+
children: copy.presetFlash
|
|
2171
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
|
|
2172
|
+
value: config.preset_flash ?? "default",
|
|
2173
|
+
onChange: (v) => field("preset_flash", v),
|
|
2174
|
+
options: presetOptions
|
|
2175
|
+
})]
|
|
2176
|
+
}),
|
|
2177
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2178
|
+
style: S.field,
|
|
2179
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2180
|
+
style: S.fieldLabel,
|
|
2181
|
+
children: copy.presetPro
|
|
2182
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
|
|
2183
|
+
value: config.preset_pro ?? "default",
|
|
2184
|
+
onChange: (v) => field("preset_pro", v),
|
|
2185
|
+
options: presetOptions
|
|
2186
|
+
})]
|
|
2187
|
+
})
|
|
2188
|
+
] }))
|
|
2189
|
+
}),
|
|
2190
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsibleSection, {
|
|
2191
|
+
sectionId: "runtime",
|
|
2192
|
+
title: copy.sectionNames.runtime,
|
|
2193
|
+
summary: sectionSummary(config.mode, `${config.default_timeout_seconds}s`, config.hub_url),
|
|
2194
|
+
expanded: !!expandedSections.runtime,
|
|
2195
|
+
onToggle: () => toggleSection("runtime"),
|
|
2196
|
+
children: block(copy.cardRuntime, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
2197
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2198
|
+
style: S.field,
|
|
2199
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2200
|
+
style: S.fieldLabel,
|
|
2201
|
+
children: copy.mode
|
|
2202
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
|
|
2203
|
+
value: config.mode,
|
|
2204
|
+
onChange: (v) => field("mode", v),
|
|
2205
|
+
options: [
|
|
2206
|
+
"auto",
|
|
2207
|
+
"hub",
|
|
2208
|
+
"standalone"
|
|
2209
|
+
].map((m) => ({
|
|
2210
|
+
value: m,
|
|
2211
|
+
label: copy.modeDesc[m]
|
|
2212
|
+
}))
|
|
2213
|
+
})]
|
|
2214
|
+
}),
|
|
2215
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2216
|
+
style: S.field,
|
|
2217
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2218
|
+
style: S.fieldLabel,
|
|
2219
|
+
children: copy.timeout
|
|
2220
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2221
|
+
style: {
|
|
2222
|
+
...S.input,
|
|
2223
|
+
width: "100%",
|
|
2224
|
+
boxSizing: "border-box"
|
|
2225
|
+
},
|
|
2226
|
+
type: "number",
|
|
2227
|
+
min: 60,
|
|
2228
|
+
max: 7200,
|
|
2229
|
+
value: config.default_timeout_seconds,
|
|
2230
|
+
onChange: (e) => fieldLocal("default_timeout_seconds", Number(e.target.value)),
|
|
2231
|
+
onBlur: () => applyPatch({ default_timeout_seconds: config.default_timeout_seconds })
|
|
2232
|
+
})]
|
|
2233
|
+
}),
|
|
2234
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2235
|
+
style: S.field,
|
|
2236
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2237
|
+
style: S.fieldLabel,
|
|
2238
|
+
children: copy.hubUrl
|
|
2239
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2240
|
+
style: {
|
|
2241
|
+
...S.input,
|
|
2242
|
+
width: "100%",
|
|
2243
|
+
boxSizing: "border-box"
|
|
2244
|
+
},
|
|
2245
|
+
value: config.hub_url,
|
|
2246
|
+
onChange: (e) => fieldLocal("hub_url", e.target.value),
|
|
2247
|
+
onBlur: () => applyPatch({ hub_url: config.hub_url }),
|
|
2248
|
+
onKeyDown: (e) => {
|
|
2249
|
+
if (e.key === "Enter") e.currentTarget.blur();
|
|
2250
|
+
}
|
|
2251
|
+
})]
|
|
2252
|
+
})
|
|
2253
|
+
] }))
|
|
2254
|
+
}),
|
|
2255
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsibleSection, {
|
|
2256
|
+
sectionId: "multimodal",
|
|
2257
|
+
title: copy.sectionNames.multimodal,
|
|
2258
|
+
summary: sectionSummary(config.vision_enabled ? `${copy.capVision}: ${config.vision_provider}` : `${copy.capVision}: off`, config.imagegen_enabled ? `${copy.capImagegen}: ${config.imagegen_provider}` : `${copy.capImagegen}: off`),
|
|
2259
|
+
expanded: !!expandedSections.multimodal,
|
|
2260
|
+
onToggle: () => toggleSection("multimodal"),
|
|
2261
|
+
children: block(copy.cardMM, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
2262
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2263
|
+
style: {
|
|
2264
|
+
...S.field,
|
|
2265
|
+
flexDirection: "row",
|
|
2266
|
+
alignItems: "center",
|
|
2267
|
+
gap: 6,
|
|
2268
|
+
gridColumn: "1 / -1"
|
|
2269
|
+
},
|
|
2270
|
+
title: copy.capRestartHint,
|
|
2271
|
+
children: [
|
|
2272
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2273
|
+
type: "checkbox",
|
|
2274
|
+
checked: !!config.vision_enabled,
|
|
2275
|
+
onChange: (e) => field("vision_enabled", e.target.checked)
|
|
2276
|
+
}),
|
|
2277
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2278
|
+
style: { fontSize: 12.5 },
|
|
2279
|
+
children: copy.enableCrewVision
|
|
2280
|
+
}),
|
|
2281
|
+
config.vision_enabled === false && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2282
|
+
style: {
|
|
2283
|
+
fontSize: 11,
|
|
2284
|
+
opacity: .55
|
|
2285
|
+
},
|
|
2286
|
+
children: copy.capDisabledNote
|
|
2287
|
+
})
|
|
2288
|
+
]
|
|
2289
|
+
}),
|
|
2290
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2291
|
+
style: S.field,
|
|
2292
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2293
|
+
style: S.fieldLabel,
|
|
2294
|
+
children: copy.visionProvider
|
|
2295
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
|
|
2296
|
+
value: config.vision_provider,
|
|
2297
|
+
onChange: (p) => {
|
|
2298
|
+
const custom = customProviders.find((c) => c.id === p);
|
|
2299
|
+
const m = custom ? custom.models?.[0] ?? "default" : (VISION_MODELS[p] ?? ["default"])[0];
|
|
2300
|
+
setModelAdd(null);
|
|
2301
|
+
setConfig((c) => ({
|
|
2302
|
+
...c,
|
|
2303
|
+
vision_provider: p,
|
|
2304
|
+
vision_model: m
|
|
2305
|
+
}));
|
|
2306
|
+
applyPatch({
|
|
2307
|
+
vision_provider: p,
|
|
2308
|
+
vision_model: m
|
|
2309
|
+
});
|
|
2310
|
+
if ((p === "grok" || p === "agy") && !dynModels[p]) get(`/vision-models?provider=${p}`).then((r) => {
|
|
2311
|
+
if (r.ok) setDynModels((d) => ({
|
|
2312
|
+
...d,
|
|
2313
|
+
[p]: r.models
|
|
2314
|
+
}));
|
|
2315
|
+
}).catch(() => {});
|
|
2316
|
+
},
|
|
2317
|
+
options: visionProviderOptions
|
|
2318
|
+
})]
|
|
2319
|
+
}),
|
|
2320
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2321
|
+
style: S.field,
|
|
2322
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2323
|
+
style: S.fieldLabel,
|
|
2324
|
+
children: copy.visionModel
|
|
2325
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2326
|
+
style: {
|
|
2327
|
+
display: "flex",
|
|
2328
|
+
gap: 6,
|
|
2329
|
+
alignItems: "center"
|
|
2330
|
+
},
|
|
2331
|
+
children: [
|
|
2332
|
+
modelAdd === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
|
|
2333
|
+
value: config.vision_model,
|
|
2334
|
+
onChange: (v) => field("vision_model", v),
|
|
2335
|
+
options: visionModelOptions
|
|
2336
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2337
|
+
autoFocus: true,
|
|
2338
|
+
style: {
|
|
2339
|
+
...S.input,
|
|
2340
|
+
width: "100%",
|
|
2341
|
+
boxSizing: "border-box",
|
|
2342
|
+
...S.mono
|
|
2343
|
+
},
|
|
2344
|
+
value: modelAdd,
|
|
2345
|
+
placeholder: copy.modelPlaceholder,
|
|
2346
|
+
onChange: (e) => setModelAdd(e.target.value),
|
|
2347
|
+
onBlur: commitModelAdd,
|
|
2348
|
+
onKeyDown: (e) => {
|
|
2349
|
+
if (e.key === "Enter") e.currentTarget.blur();
|
|
2350
|
+
if (e.key === "Escape") setModelAdd(null);
|
|
2351
|
+
}
|
|
2352
|
+
}),
|
|
2353
|
+
modelAdd === null && (config.vision_provider === "grok" || config.vision_provider === "agy") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2354
|
+
type: "button",
|
|
2355
|
+
title: copy.refreshModels,
|
|
2356
|
+
disabled: busy,
|
|
2357
|
+
style: {
|
|
2358
|
+
...S.btn,
|
|
2359
|
+
padding: "3px 8px",
|
|
2360
|
+
flexShrink: 0
|
|
2361
|
+
},
|
|
2362
|
+
onClick: () => {
|
|
2363
|
+
const p = config.vision_provider;
|
|
2364
|
+
setBusy(true);
|
|
2365
|
+
get(`/vision-models?provider=${p}&refresh=1`).then((r) => {
|
|
2366
|
+
if (r.ok) setDynModels((d) => ({
|
|
2367
|
+
...d,
|
|
2368
|
+
[p]: r.models
|
|
2369
|
+
}));
|
|
2370
|
+
}).catch(() => {}).finally(() => setBusy(false));
|
|
2371
|
+
},
|
|
2372
|
+
children: "↻"
|
|
2373
|
+
}),
|
|
2374
|
+
modelAdd === null && config.vision_provider !== "off" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2375
|
+
type: "button",
|
|
2376
|
+
title: copy.addModelTip,
|
|
2377
|
+
style: {
|
|
2378
|
+
...S.btn,
|
|
2379
|
+
padding: "3px 8px",
|
|
2380
|
+
flexShrink: 0
|
|
2381
|
+
},
|
|
2382
|
+
onClick: () => setModelAdd(""),
|
|
2383
|
+
children: "+"
|
|
2384
|
+
}),
|
|
2385
|
+
modelAdd === null && (extraModels[config.vision_provider] ?? []).includes(config.vision_model) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2386
|
+
type: "button",
|
|
2387
|
+
title: copy.removeModelTip,
|
|
2388
|
+
style: {
|
|
2389
|
+
...S.btn,
|
|
2390
|
+
padding: "3px 8px",
|
|
2391
|
+
flexShrink: 0
|
|
2392
|
+
},
|
|
2393
|
+
onClick: removeCurrentModel,
|
|
2394
|
+
children: "−"
|
|
2395
|
+
})
|
|
2396
|
+
]
|
|
2397
|
+
})]
|
|
2398
|
+
}),
|
|
2399
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2400
|
+
style: {
|
|
2401
|
+
...S.field,
|
|
2402
|
+
flexDirection: "row",
|
|
2403
|
+
alignItems: "center",
|
|
2404
|
+
gap: 6,
|
|
2405
|
+
gridColumn: "1 / -1"
|
|
2406
|
+
},
|
|
2407
|
+
title: copy.capRestartHint,
|
|
2408
|
+
children: [
|
|
2409
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2410
|
+
type: "checkbox",
|
|
2411
|
+
checked: !!config.imagegen_enabled,
|
|
2412
|
+
onChange: (e) => field("imagegen_enabled", e.target.checked)
|
|
2413
|
+
}),
|
|
2414
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2415
|
+
style: { fontSize: 12.5 },
|
|
2416
|
+
children: copy.enableImagegen
|
|
2417
|
+
}),
|
|
2418
|
+
config.imagegen_enabled === false && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2419
|
+
style: {
|
|
2420
|
+
fontSize: 11,
|
|
2421
|
+
opacity: .55
|
|
2422
|
+
},
|
|
2423
|
+
children: copy.capDisabledNote
|
|
2424
|
+
})
|
|
2425
|
+
]
|
|
2426
|
+
}),
|
|
2427
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2428
|
+
style: S.field,
|
|
2429
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2430
|
+
style: S.fieldLabel,
|
|
2431
|
+
children: copy.imagegenProvider
|
|
2432
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
|
|
2433
|
+
value: config.imagegen_provider,
|
|
2434
|
+
onChange: (v) => field("imagegen_provider", v),
|
|
2435
|
+
options: imagegenProviderOptions
|
|
2436
|
+
})]
|
|
2437
|
+
})
|
|
2438
|
+
] }))
|
|
2439
|
+
}),
|
|
2440
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsibleSection, {
|
|
2441
|
+
sectionId: "providers",
|
|
2442
|
+
title: copy.sectionNames.providers,
|
|
2443
|
+
summary: sectionSummary(copy.providerCount(customProviders.length), testResult?.ok === false && copy.testFail),
|
|
2444
|
+
expanded: !!expandedSections.providers,
|
|
2445
|
+
onToggle: () => toggleSection("providers"),
|
|
2446
|
+
children: block(copy.cardCustomProv, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
2447
|
+
customProviders.length === 0 && provForm === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2448
|
+
style: {
|
|
2449
|
+
fontSize: 12,
|
|
2450
|
+
opacity: .5
|
|
2451
|
+
},
|
|
2452
|
+
children: copy.noCustomProviders
|
|
2453
|
+
}),
|
|
2454
|
+
customProviders.map((p) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2455
|
+
style: {
|
|
2456
|
+
display: "flex",
|
|
2457
|
+
alignItems: "center",
|
|
2458
|
+
gap: 8,
|
|
2459
|
+
fontSize: 12.5
|
|
2460
|
+
},
|
|
2461
|
+
children: [
|
|
2462
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2463
|
+
style: { fontWeight: 500 },
|
|
2464
|
+
children: p.name || p.id
|
|
2465
|
+
}),
|
|
2466
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2467
|
+
style: {
|
|
2468
|
+
...S.chip(false),
|
|
2469
|
+
opacity: .7
|
|
2470
|
+
},
|
|
2471
|
+
children: provType(p) === "api" ? "API" : "CLI"
|
|
2472
|
+
}),
|
|
2473
|
+
canSee(p) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2474
|
+
style: S.chip(true),
|
|
2475
|
+
children: copy.capVision
|
|
2476
|
+
}),
|
|
2477
|
+
canDraw(p) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2478
|
+
style: S.chip(true),
|
|
2479
|
+
children: copy.capImagegen
|
|
2480
|
+
}),
|
|
2481
|
+
provType(p) === "api" && !!p.api_key && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2482
|
+
style: {
|
|
2483
|
+
fontSize: 11.5,
|
|
2484
|
+
opacity: .5
|
|
2485
|
+
},
|
|
2486
|
+
children: ["key ", copy.keySet]
|
|
2487
|
+
}),
|
|
2488
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: { flex: 1 } }),
|
|
2489
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2490
|
+
style: {
|
|
2491
|
+
...S.btn,
|
|
2492
|
+
padding: "2px 8px"
|
|
2493
|
+
},
|
|
2494
|
+
title: copy.testTip,
|
|
2495
|
+
disabled: !!testResult?.busy,
|
|
2496
|
+
onClick: () => runProviderTest(`row:${p.id}`, p),
|
|
2497
|
+
children: testResult?.key === `row:${p.id}` && testResult.busy ? copy.testing : copy.testProvider
|
|
2498
|
+
}),
|
|
2499
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2500
|
+
style: {
|
|
2501
|
+
...S.btn,
|
|
2502
|
+
padding: "2px 8px"
|
|
2503
|
+
},
|
|
2504
|
+
onClick: () => setProvForm({
|
|
2505
|
+
originalId: p.id,
|
|
2506
|
+
name: p.name ?? p.id,
|
|
2507
|
+
type: provType(p),
|
|
2508
|
+
models: (p.models ?? []).join(", "),
|
|
2509
|
+
base_url: p.base_url ?? "",
|
|
2510
|
+
api_key: p.api_key ?? "",
|
|
2511
|
+
imagegen_model: p.imagegen_model ?? "",
|
|
2512
|
+
vision_command: p.vision_command ?? "",
|
|
2513
|
+
imagegen_command: p.imagegen_command ?? ""
|
|
2514
|
+
}),
|
|
2515
|
+
children: copy.edit
|
|
2516
|
+
}),
|
|
2517
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2518
|
+
style: {
|
|
2519
|
+
...S.btn,
|
|
2520
|
+
padding: "2px 8px",
|
|
2521
|
+
opacity: .75
|
|
2522
|
+
},
|
|
2523
|
+
onClick: () => {
|
|
2524
|
+
if (confirm(copy.confirmDeleteProvider(p.name || p.id))) deleteProvider(p.id);
|
|
2525
|
+
},
|
|
2526
|
+
children: copy.del
|
|
2527
|
+
})
|
|
2528
|
+
]
|
|
2529
|
+
}, p.id)),
|
|
2530
|
+
customProviders.some((p) => testResult?.key === `row:${p.id}`) && testReport(testResult.key),
|
|
2531
|
+
provForm === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2532
|
+
style: S.btn,
|
|
2533
|
+
onClick: () => setProvForm({
|
|
2534
|
+
name: "",
|
|
2535
|
+
type: "api",
|
|
2536
|
+
models: "",
|
|
2537
|
+
base_url: "",
|
|
2538
|
+
api_key: "",
|
|
2539
|
+
imagegen_model: "",
|
|
2540
|
+
vision_command: "",
|
|
2541
|
+
imagegen_command: ""
|
|
2542
|
+
}),
|
|
2543
|
+
children: copy.addProvider
|
|
2544
|
+
}) }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2545
|
+
style: {
|
|
2546
|
+
borderTop: "1px solid rgba(128,128,128,0.16)",
|
|
2547
|
+
paddingTop: 10,
|
|
2548
|
+
display: "flex",
|
|
2549
|
+
flexDirection: "column",
|
|
2550
|
+
gap: 10
|
|
2551
|
+
},
|
|
2552
|
+
children: [
|
|
2553
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2554
|
+
style: S.blockGrid,
|
|
2555
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2556
|
+
style: S.field,
|
|
2557
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2558
|
+
style: S.fieldLabel,
|
|
2559
|
+
children: copy.providerName
|
|
2560
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2561
|
+
autoFocus: true,
|
|
2562
|
+
style: {
|
|
2563
|
+
...S.input,
|
|
2564
|
+
width: "100%",
|
|
2565
|
+
boxSizing: "border-box"
|
|
2566
|
+
},
|
|
2567
|
+
value: provForm.name,
|
|
2568
|
+
onChange: (e) => setProvForm({
|
|
2569
|
+
...provForm,
|
|
2570
|
+
name: e.target.value
|
|
2571
|
+
})
|
|
2572
|
+
})]
|
|
2573
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2574
|
+
style: S.field,
|
|
2575
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2576
|
+
style: S.fieldLabel,
|
|
2577
|
+
children: copy.providerType
|
|
2578
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
|
|
2579
|
+
value: provForm.type,
|
|
2580
|
+
onChange: (v) => setProvForm({
|
|
2581
|
+
...provForm,
|
|
2582
|
+
type: v
|
|
2583
|
+
}),
|
|
2584
|
+
options: [{
|
|
2585
|
+
value: "api",
|
|
2586
|
+
label: copy.typeApi
|
|
2587
|
+
}, {
|
|
2588
|
+
value: "cli",
|
|
2589
|
+
label: copy.typeCli
|
|
2590
|
+
}]
|
|
2591
|
+
})]
|
|
2592
|
+
})]
|
|
2593
|
+
}),
|
|
2594
|
+
provForm.type === "api" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2595
|
+
style: S.blockGrid,
|
|
2596
|
+
children: [
|
|
2597
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2598
|
+
style: S.field,
|
|
2599
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2600
|
+
style: S.fieldLabel,
|
|
2601
|
+
children: copy.baseUrl
|
|
2602
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2603
|
+
style: {
|
|
2604
|
+
...S.input,
|
|
2605
|
+
width: "100%",
|
|
2606
|
+
boxSizing: "border-box",
|
|
2607
|
+
...S.mono
|
|
2608
|
+
},
|
|
2609
|
+
value: provForm.base_url,
|
|
2610
|
+
placeholder: "https://api.example.com/v1",
|
|
2611
|
+
onChange: (e) => setProvForm({
|
|
2612
|
+
...provForm,
|
|
2613
|
+
base_url: e.target.value
|
|
2614
|
+
})
|
|
2615
|
+
})]
|
|
2616
|
+
}),
|
|
2617
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2618
|
+
style: S.field,
|
|
2619
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2620
|
+
style: S.fieldLabel,
|
|
2621
|
+
children: copy.apiKey
|
|
2622
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2623
|
+
type: "password",
|
|
2624
|
+
autoComplete: "off",
|
|
2625
|
+
style: {
|
|
2626
|
+
...S.input,
|
|
2627
|
+
width: "100%",
|
|
2628
|
+
boxSizing: "border-box",
|
|
2629
|
+
...S.mono
|
|
2630
|
+
},
|
|
2631
|
+
value: provForm.api_key,
|
|
2632
|
+
placeholder: copy.keyPlaceholder,
|
|
2633
|
+
onChange: (e) => setProvForm({
|
|
2634
|
+
...provForm,
|
|
2635
|
+
api_key: e.target.value
|
|
2636
|
+
})
|
|
2637
|
+
})]
|
|
2638
|
+
}),
|
|
2639
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2640
|
+
style: S.field,
|
|
2641
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2642
|
+
style: S.fieldLabel,
|
|
2643
|
+
children: copy.modelsField
|
|
2644
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2645
|
+
style: {
|
|
2646
|
+
...S.input,
|
|
2647
|
+
width: "100%",
|
|
2648
|
+
boxSizing: "border-box",
|
|
2649
|
+
...S.mono
|
|
2650
|
+
},
|
|
2651
|
+
value: provForm.models,
|
|
2652
|
+
placeholder: "gpt-4o-mini, qwen-vl-max",
|
|
2653
|
+
onChange: (e) => setProvForm({
|
|
2654
|
+
...provForm,
|
|
2655
|
+
models: e.target.value
|
|
2656
|
+
})
|
|
2657
|
+
})]
|
|
2658
|
+
}),
|
|
2659
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2660
|
+
style: S.field,
|
|
2661
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2662
|
+
style: S.fieldLabel,
|
|
2663
|
+
children: copy.imagegenModel
|
|
2664
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2665
|
+
style: {
|
|
2666
|
+
...S.input,
|
|
2667
|
+
width: "100%",
|
|
2668
|
+
boxSizing: "border-box",
|
|
2669
|
+
...S.mono
|
|
2670
|
+
},
|
|
2671
|
+
value: provForm.imagegen_model,
|
|
2672
|
+
placeholder: "dall-e-3",
|
|
2673
|
+
onChange: (e) => setProvForm({
|
|
2674
|
+
...provForm,
|
|
2675
|
+
imagegen_model: e.target.value
|
|
2676
|
+
})
|
|
2677
|
+
})]
|
|
2678
|
+
})
|
|
2679
|
+
]
|
|
2680
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2681
|
+
style: {
|
|
2682
|
+
fontSize: 11.5,
|
|
2683
|
+
opacity: .55
|
|
2684
|
+
},
|
|
2685
|
+
children: copy.apiFormHint
|
|
2686
|
+
})] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
2687
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2688
|
+
style: S.field,
|
|
2689
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2690
|
+
style: S.fieldLabel,
|
|
2691
|
+
children: copy.visionCmd
|
|
2692
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2693
|
+
style: {
|
|
2694
|
+
...S.input,
|
|
2695
|
+
width: "100%",
|
|
2696
|
+
boxSizing: "border-box",
|
|
2697
|
+
...S.mono
|
|
2698
|
+
},
|
|
2699
|
+
value: provForm.vision_command,
|
|
2700
|
+
placeholder: "mycli see {image} --ask {question} --model {model}",
|
|
2701
|
+
onChange: (e) => setProvForm({
|
|
2702
|
+
...provForm,
|
|
2703
|
+
vision_command: e.target.value
|
|
2704
|
+
})
|
|
2705
|
+
})]
|
|
2706
|
+
}),
|
|
2707
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2708
|
+
style: S.field,
|
|
2709
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2710
|
+
style: S.fieldLabel,
|
|
2711
|
+
children: copy.imagegenCmd
|
|
2712
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2713
|
+
style: {
|
|
2714
|
+
...S.input,
|
|
2715
|
+
width: "100%",
|
|
2716
|
+
boxSizing: "border-box",
|
|
2717
|
+
...S.mono
|
|
2718
|
+
},
|
|
2719
|
+
value: provForm.imagegen_command,
|
|
2720
|
+
placeholder: "mycli gen {prompt} -o {output}",
|
|
2721
|
+
onChange: (e) => setProvForm({
|
|
2722
|
+
...provForm,
|
|
2723
|
+
imagegen_command: e.target.value
|
|
2724
|
+
})
|
|
2725
|
+
})]
|
|
2726
|
+
}),
|
|
2727
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2728
|
+
style: S.field,
|
|
2729
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2730
|
+
style: S.fieldLabel,
|
|
2731
|
+
children: copy.modelsField
|
|
2732
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2733
|
+
style: {
|
|
2734
|
+
...S.input,
|
|
2735
|
+
width: "100%",
|
|
2736
|
+
boxSizing: "border-box",
|
|
2737
|
+
...S.mono
|
|
2738
|
+
},
|
|
2739
|
+
value: provForm.models,
|
|
2740
|
+
placeholder: "model-a, model-b",
|
|
2741
|
+
onChange: (e) => setProvForm({
|
|
2742
|
+
...provForm,
|
|
2743
|
+
models: e.target.value
|
|
2744
|
+
})
|
|
2745
|
+
})]
|
|
2746
|
+
}),
|
|
2747
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2748
|
+
style: {
|
|
2749
|
+
fontSize: 11.5,
|
|
2750
|
+
opacity: .55
|
|
2751
|
+
},
|
|
2752
|
+
children: copy.cliFormHint
|
|
2753
|
+
})
|
|
2754
|
+
] }),
|
|
2755
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2756
|
+
style: {
|
|
2757
|
+
display: "flex",
|
|
2758
|
+
gap: 8,
|
|
2759
|
+
alignItems: "center"
|
|
2760
|
+
},
|
|
2761
|
+
children: [
|
|
2762
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2763
|
+
style: S.btn,
|
|
2764
|
+
onClick: saveProviderForm,
|
|
2765
|
+
children: copy.saveProvider
|
|
2766
|
+
}),
|
|
2767
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2768
|
+
style: S.btn,
|
|
2769
|
+
title: copy.testTip,
|
|
2770
|
+
disabled: !!testResult?.busy,
|
|
2771
|
+
onClick: () => runProviderTest("form", {
|
|
2772
|
+
id: provForm.originalId ?? "draft",
|
|
2773
|
+
name: provForm.name || "draft",
|
|
2774
|
+
type: provForm.type,
|
|
2775
|
+
models: provForm.models.split(/[,,\n]+/).map((x) => x.trim()).filter(Boolean),
|
|
2776
|
+
base_url: provForm.base_url.trim(),
|
|
2777
|
+
api_key: provForm.api_key,
|
|
2778
|
+
imagegen_model: provForm.imagegen_model.trim(),
|
|
2779
|
+
vision_command: provForm.vision_command.trim(),
|
|
2780
|
+
imagegen_command: provForm.imagegen_command.trim()
|
|
2781
|
+
}),
|
|
2782
|
+
children: testResult?.key === "form" && testResult.busy ? copy.testing : copy.testProvider
|
|
2783
|
+
}),
|
|
2784
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2785
|
+
style: {
|
|
2786
|
+
...S.btn,
|
|
2787
|
+
opacity: .75
|
|
2788
|
+
},
|
|
2789
|
+
onClick: () => setProvForm(null),
|
|
2790
|
+
children: copy.cancel
|
|
2791
|
+
})
|
|
2792
|
+
]
|
|
2793
|
+
}),
|
|
2794
|
+
testReport("form")
|
|
2795
|
+
]
|
|
2796
|
+
})
|
|
2797
|
+
] }), false)
|
|
2798
|
+
})
|
|
2799
|
+
] }),
|
|
2800
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2801
|
+
style: {
|
|
2802
|
+
fontSize: 11.5,
|
|
2803
|
+
opacity: .55,
|
|
2804
|
+
marginTop: 2
|
|
2805
|
+
},
|
|
2806
|
+
children: copy.globalHint
|
|
2807
|
+
}),
|
|
2808
|
+
notice !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2809
|
+
style: {
|
|
2810
|
+
fontSize: 12,
|
|
2811
|
+
opacity: .8,
|
|
2812
|
+
whiteSpace: "pre-wrap"
|
|
2813
|
+
},
|
|
2814
|
+
children: notice
|
|
2815
|
+
}),
|
|
2816
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsibleSection, {
|
|
2817
|
+
sectionId: "jobs",
|
|
2818
|
+
title: copy.sectionNames.jobs,
|
|
2819
|
+
summary: sectionSummary(copy.jobCount(jobs.length), jobs.some((job) => job.status === "running") && copy.runningCount(jobs.filter((job) => job.status === "running").length)),
|
|
2820
|
+
expanded: !!expandedSections.jobs,
|
|
2821
|
+
onToggle: () => toggleSection("jobs"),
|
|
2822
|
+
children: jobs.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2823
|
+
style: { opacity: .55 },
|
|
2824
|
+
children: copy.empty
|
|
2825
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2826
|
+
style: { overflowX: "auto" },
|
|
2827
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
|
|
2828
|
+
style: {
|
|
2829
|
+
borderCollapse: "collapse",
|
|
2830
|
+
width: "100%",
|
|
2831
|
+
tableLayout: "fixed",
|
|
2832
|
+
minWidth: 760
|
|
2833
|
+
},
|
|
2834
|
+
children: [
|
|
2835
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("colgroup", { children: [
|
|
2836
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { style: { width: 64 } }),
|
|
2837
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { style: { width: 58 } }),
|
|
2838
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { style: { width: 72 } }),
|
|
2839
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { style: { width: 40 } }),
|
|
2840
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { style: { width: 100 } }),
|
|
2841
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { style: { width: 84 } }),
|
|
2842
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { style: { width: 340 } })
|
|
2843
|
+
] }),
|
|
2844
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", { children: [
|
|
2845
|
+
copy.col.id,
|
|
2846
|
+
copy.col.source,
|
|
2847
|
+
copy.col.tier,
|
|
2848
|
+
copy.col.status,
|
|
2849
|
+
copy.col.progress,
|
|
2850
|
+
copy.col.tokens,
|
|
2851
|
+
copy.col.task
|
|
2852
|
+
].map((h) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
2853
|
+
style: {
|
|
2854
|
+
...S.tight,
|
|
2855
|
+
fontSize: 11,
|
|
2856
|
+
opacity: .55,
|
|
2857
|
+
fontWeight: 500
|
|
2858
|
+
},
|
|
2859
|
+
children: h
|
|
2860
|
+
}, h)) }) }),
|
|
2861
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: jobs.map((job) => {
|
|
2862
|
+
const color = job.status === "running" ? "#4a9eff" : job.status === "done" ? "#3fb950" : "#f85149";
|
|
2863
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
|
|
2864
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
2865
|
+
style: {
|
|
2866
|
+
...S.tight,
|
|
2867
|
+
...S.mono
|
|
2868
|
+
},
|
|
2869
|
+
title: job.id,
|
|
2870
|
+
children: String(job.id).replace(/-[a-z0-9]+$/, "")
|
|
2871
|
+
}),
|
|
2872
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
2873
|
+
style: S.tight,
|
|
2874
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2875
|
+
style: S.chip(job.source === "claude-code" || job.source === "codex"),
|
|
2876
|
+
children: sourceLabel(job.source)
|
|
2877
|
+
})
|
|
2878
|
+
}),
|
|
2879
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", {
|
|
2880
|
+
style: S.tight,
|
|
2881
|
+
children: [
|
|
2882
|
+
job.tier,
|
|
2883
|
+
"/",
|
|
2884
|
+
job.effort
|
|
2885
|
+
]
|
|
2886
|
+
}),
|
|
2887
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
2888
|
+
style: {
|
|
2889
|
+
...S.tight,
|
|
2890
|
+
textAlign: "center"
|
|
2891
|
+
},
|
|
2892
|
+
title: `${job.status}${job.currentTool ? ` · ${job.currentTool}` : ""}`,
|
|
2893
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2894
|
+
style: {
|
|
2895
|
+
color,
|
|
2896
|
+
cursor: "default"
|
|
2897
|
+
},
|
|
2898
|
+
children: "●"
|
|
2899
|
+
})
|
|
2900
|
+
}),
|
|
2901
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", {
|
|
2902
|
+
style: {
|
|
2903
|
+
...S.tight,
|
|
2904
|
+
...S.mono
|
|
2905
|
+
},
|
|
2906
|
+
title: copy.progressTip(elapsed(job.startedAt, job.endedAt), job.turn, job.step, job.toolCalls ?? "-"),
|
|
2907
|
+
children: [
|
|
2908
|
+
elapsed(job.startedAt, job.endedAt),
|
|
2909
|
+
" ",
|
|
2910
|
+
job.turn,
|
|
2911
|
+
".",
|
|
2912
|
+
job.step,
|
|
2913
|
+
" ",
|
|
2914
|
+
job.toolCalls ?? "-",
|
|
2915
|
+
"t"
|
|
2916
|
+
]
|
|
2917
|
+
}),
|
|
2918
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
2919
|
+
style: {
|
|
2920
|
+
...S.tight,
|
|
2921
|
+
...S.mono
|
|
2922
|
+
},
|
|
2923
|
+
children: job.tokens ? `${ktok(job.tokens.input)}/${ktok(job.tokens.output)}` : "-"
|
|
2924
|
+
}),
|
|
2925
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
2926
|
+
style: {
|
|
2927
|
+
...S.cell,
|
|
2928
|
+
position: "relative"
|
|
2929
|
+
},
|
|
2930
|
+
onMouseEnter: (e) => {
|
|
2931
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
2932
|
+
setHoverTask({
|
|
2933
|
+
id: job.id,
|
|
2934
|
+
text: job.task,
|
|
2935
|
+
right: Math.max(8, window.innerWidth - rect.right),
|
|
2936
|
+
top: rect.bottom + 4,
|
|
2937
|
+
bottom: window.innerHeight - rect.top + 4,
|
|
2938
|
+
flip: rect.top < 240
|
|
2939
|
+
});
|
|
2940
|
+
},
|
|
2941
|
+
onMouseLeave: () => setHoverTask(null),
|
|
2942
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2943
|
+
style: {
|
|
2944
|
+
whiteSpace: "nowrap",
|
|
2945
|
+
overflow: "hidden",
|
|
2946
|
+
textOverflow: "ellipsis",
|
|
2947
|
+
lineHeight: 1.45,
|
|
2948
|
+
fontSize: 12.5
|
|
2949
|
+
},
|
|
2950
|
+
children: job.task
|
|
2951
|
+
})
|
|
2952
|
+
})
|
|
2953
|
+
] }, job.id);
|
|
2954
|
+
}) })
|
|
2955
|
+
]
|
|
2956
|
+
})
|
|
2957
|
+
})
|
|
2958
|
+
}),
|
|
2959
|
+
hoverTask && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2960
|
+
style: {
|
|
2961
|
+
position: "fixed",
|
|
2962
|
+
right: hoverTask.right,
|
|
2963
|
+
zIndex: 9999,
|
|
2964
|
+
...hoverTask.flip ? { top: hoverTask.top } : { bottom: hoverTask.bottom },
|
|
2965
|
+
minWidth: 260,
|
|
2966
|
+
maxWidth: 420,
|
|
2967
|
+
maxHeight: 180,
|
|
2968
|
+
overflowY: "auto",
|
|
2969
|
+
padding: "8px 12px",
|
|
2970
|
+
borderRadius: 8,
|
|
2971
|
+
border: "1px solid rgba(128,128,128,0.35)",
|
|
2972
|
+
background: "Canvas",
|
|
2973
|
+
color: "CanvasText",
|
|
2974
|
+
boxShadow: "0 8px 28px rgba(0,0,0,0.22)",
|
|
2975
|
+
whiteSpace: "pre-wrap",
|
|
2976
|
+
fontSize: 12,
|
|
2977
|
+
lineHeight: 1.5
|
|
2978
|
+
},
|
|
2979
|
+
children: hoverTask.text
|
|
2980
|
+
}), document.body)
|
|
2981
|
+
]
|
|
2982
|
+
});
|
|
2983
|
+
}
|
|
2984
|
+
function apply$1(ctx) {
|
|
2985
|
+
let runningCount = 0;
|
|
2986
|
+
ctx.slots.inject("settings.section", () => {
|
|
2987
|
+
let dispose = register();
|
|
2988
|
+
function register() {
|
|
2989
|
+
return ctx.slots.register({
|
|
2990
|
+
name: "settings.section",
|
|
2991
|
+
id: "dsh-crew",
|
|
2992
|
+
order: 65,
|
|
2993
|
+
label: () => {
|
|
2994
|
+
const base = COPY[ctx.locale.getLocale().active === "zh" ? "zh" : "en"].label;
|
|
2995
|
+
if (runningCount <= 0) return base;
|
|
2996
|
+
return `${base} ${[
|
|
2997
|
+
"❶",
|
|
2998
|
+
"❷",
|
|
2999
|
+
"❸",
|
|
3000
|
+
"❹",
|
|
3001
|
+
"❺",
|
|
3002
|
+
"❻",
|
|
3003
|
+
"❼",
|
|
3004
|
+
"❽",
|
|
3005
|
+
"❾",
|
|
3006
|
+
"❿"
|
|
3007
|
+
][runningCount - 1] ?? `(${runningCount})`}`;
|
|
3008
|
+
}
|
|
3009
|
+
}, () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkersPanel, { ctx }));
|
|
3010
|
+
}
|
|
3011
|
+
const poll = async () => {
|
|
3012
|
+
if (document.visibilityState !== "visible") return;
|
|
3013
|
+
try {
|
|
3014
|
+
const r = await (await fetch(`${API$1}/jobs`, { cache: "no-store" })).json();
|
|
3015
|
+
const n = r.ok ? (r.jobs ?? []).filter((j) => j.status === "running").length : 0;
|
|
3016
|
+
if (n !== runningCount) {
|
|
3017
|
+
runningCount = n;
|
|
3018
|
+
dispose();
|
|
3019
|
+
dispose = register();
|
|
3020
|
+
}
|
|
3021
|
+
} catch {}
|
|
3022
|
+
};
|
|
3023
|
+
poll();
|
|
3024
|
+
const timer = setInterval(() => {
|
|
3025
|
+
poll();
|
|
3026
|
+
}, 1e4);
|
|
3027
|
+
return () => {
|
|
3028
|
+
clearInterval(timer);
|
|
3029
|
+
dispose();
|
|
3030
|
+
};
|
|
3031
|
+
});
|
|
3032
|
+
}
|
|
3033
|
+
//#endregion
|
|
3034
|
+
//#region src/client/activation-summary.tsx
|
|
3035
|
+
const ORDER = [
|
|
3036
|
+
"live",
|
|
3037
|
+
"next-workflow",
|
|
3038
|
+
"next-session",
|
|
3039
|
+
"restart-required"
|
|
3040
|
+
];
|
|
3041
|
+
const LABELS = {
|
|
3042
|
+
zh: {
|
|
3043
|
+
title: "配置生效边界",
|
|
3044
|
+
hint: "这里显示全局 Settings 保存后的实际生效时机;会话内 dsh_worker_config 覆盖可能更早生效。",
|
|
3045
|
+
boundary: {
|
|
3046
|
+
live: "Live · 当前运行时",
|
|
3047
|
+
"next-workflow": "Next workflow · 下一个任务",
|
|
3048
|
+
"next-session": "Next session · 新 CC / Codex 会话",
|
|
3049
|
+
"restart-required": "Restart required · 重启 DSH / MCP"
|
|
3050
|
+
}
|
|
3051
|
+
},
|
|
3052
|
+
en: {
|
|
3053
|
+
title: "Configuration activation boundaries",
|
|
3054
|
+
hint: "Shows when persisted Settings changes actually take effect. Session-level dsh_worker_config overrides may activate earlier.",
|
|
3055
|
+
boundary: {
|
|
3056
|
+
live: "Live · current runtime",
|
|
3057
|
+
"next-workflow": "Next workflow",
|
|
3058
|
+
"next-session": "Next session · new CC / Codex session",
|
|
3059
|
+
"restart-required": "Restart required · restart DSH / MCP"
|
|
3060
|
+
}
|
|
3061
|
+
}
|
|
3062
|
+
};
|
|
3063
|
+
function groupActivationBoundaries(activation = {}) {
|
|
3064
|
+
const grouped = Object.fromEntries(ORDER.map((boundary) => [boundary, []]));
|
|
3065
|
+
for (const [key, entry] of Object.entries(activation)) if (entry?.global && grouped[entry.global]) grouped[entry.global].push(key);
|
|
3066
|
+
for (const boundary of ORDER) grouped[boundary].sort();
|
|
3067
|
+
return grouped;
|
|
3068
|
+
}
|
|
3069
|
+
function ActivationSummary({ activation, locale }) {
|
|
3070
|
+
if (!activation || Object.keys(activation).length === 0) return null;
|
|
3071
|
+
const copy = LABELS[locale === "zh" ? "zh" : "en"];
|
|
3072
|
+
const grouped = groupActivationBoundaries(activation);
|
|
3073
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3074
|
+
style: {
|
|
3075
|
+
border: "1px solid rgba(128,128,128,0.22)",
|
|
3076
|
+
borderRadius: 8,
|
|
3077
|
+
padding: "9px 12px",
|
|
3078
|
+
display: "flex",
|
|
3079
|
+
flexDirection: "column",
|
|
3080
|
+
gap: 5
|
|
3081
|
+
},
|
|
3082
|
+
children: [
|
|
3083
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3084
|
+
style: {
|
|
3085
|
+
fontWeight: 600,
|
|
3086
|
+
fontSize: 12.5
|
|
3087
|
+
},
|
|
3088
|
+
children: copy.title
|
|
3089
|
+
}),
|
|
3090
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3091
|
+
style: {
|
|
3092
|
+
fontSize: 11,
|
|
3093
|
+
opacity: .6
|
|
3094
|
+
},
|
|
3095
|
+
children: copy.hint
|
|
3096
|
+
}),
|
|
3097
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3098
|
+
style: {
|
|
3099
|
+
display: "grid",
|
|
3100
|
+
gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
|
|
3101
|
+
gap: "5px 12px"
|
|
3102
|
+
},
|
|
3103
|
+
children: ORDER.map((boundary) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3104
|
+
style: { minWidth: 0 },
|
|
3105
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3106
|
+
style: {
|
|
3107
|
+
fontSize: 10.5,
|
|
3108
|
+
opacity: .7,
|
|
3109
|
+
fontWeight: 600
|
|
3110
|
+
},
|
|
3111
|
+
children: copy.boundary[boundary]
|
|
3112
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3113
|
+
style: {
|
|
3114
|
+
fontSize: 10.5,
|
|
3115
|
+
opacity: .55,
|
|
3116
|
+
wordBreak: "break-word"
|
|
3117
|
+
},
|
|
3118
|
+
children: grouped[boundary].length ? grouped[boundary].join(" · ") : "—"
|
|
3119
|
+
})]
|
|
3120
|
+
}, boundary))
|
|
3121
|
+
})
|
|
3122
|
+
]
|
|
3123
|
+
});
|
|
3124
|
+
}
|
|
3125
|
+
//#endregion
|
|
3126
|
+
//#region src/client/entry.tsx
|
|
3127
|
+
const inject = inject$1;
|
|
3128
|
+
const API = "/_dsh/dsh-crew";
|
|
3129
|
+
const DEFAULT_ADAPTIVE = {
|
|
3130
|
+
enabled: false,
|
|
3131
|
+
window_size: 8,
|
|
3132
|
+
min_samples: 2
|
|
3133
|
+
};
|
|
3134
|
+
function clampInt(value, fallback, min, max) {
|
|
3135
|
+
const parsed = Number(value);
|
|
3136
|
+
if (!Number.isInteger(parsed)) return fallback;
|
|
3137
|
+
return Math.max(min, Math.min(max, parsed));
|
|
3138
|
+
}
|
|
3139
|
+
function normalizeAdaptive(value) {
|
|
3140
|
+
const windowSize = clampInt(value?.window_size, DEFAULT_ADAPTIVE.window_size, 1, 32);
|
|
3141
|
+
return {
|
|
3142
|
+
enabled: value?.enabled === true,
|
|
3143
|
+
window_size: windowSize,
|
|
3144
|
+
min_samples: clampInt(value?.min_samples, DEFAULT_ADAPTIVE.min_samples, 1, windowSize)
|
|
3145
|
+
};
|
|
3146
|
+
}
|
|
3147
|
+
function useLocale(ctx) {
|
|
3148
|
+
return (0, react.useSyncExternalStore)((notify) => ctx.on("locale/change", notify), () => ctx.locale.getLocale().active, () => ctx.locale.getLocale().active);
|
|
3149
|
+
}
|
|
3150
|
+
function AdaptiveRoutingPanel({ ctx }) {
|
|
3151
|
+
const zh = useLocale(ctx) === "zh";
|
|
3152
|
+
const [adaptive, setAdaptive] = (0, react.useState)(DEFAULT_ADAPTIVE);
|
|
3153
|
+
const [loaded, setLoaded] = (0, react.useState)(false);
|
|
3154
|
+
const [saving, setSaving] = (0, react.useState)(false);
|
|
3155
|
+
const [message, setMessage] = (0, react.useState)("");
|
|
3156
|
+
const copy = zh ? {
|
|
3157
|
+
title: "自适应模型路由(实验)",
|
|
3158
|
+
hint: "默认关闭。仅对系统自动产生的候选做健康排序;显式 Provider / Model 优先级永远保持原序。信号只来自本进程内 Crew 已观察到的成功、失败、超时与粗粒度延迟,不读取额度、价格或凭据。重启 Hub 会清空健康历史。",
|
|
3159
|
+
enabled: "启用自适应路由",
|
|
3160
|
+
window: "健康窗口",
|
|
3161
|
+
minSamples: "最少样本",
|
|
3162
|
+
windowHint: "每个 role/provider/model 最多参考最近 1–32 次结果。",
|
|
3163
|
+
minHint: "达到该样本数后才允许健康分数影响自动候选顺序。",
|
|
3164
|
+
boundary: "生效边界:下一工作流",
|
|
3165
|
+
saved: "已保存",
|
|
3166
|
+
loading: "加载中…"
|
|
3167
|
+
} : {
|
|
3168
|
+
title: "Adaptive Model Routing (experimental)",
|
|
3169
|
+
hint: "Off by default. Health ordering applies only to automatically derived candidates; explicit Provider / Model priorities always keep their order. Signals are limited to Crew-observed success, failure, timeout, and coarse latency in this process—never quota, pricing, or credentials. Restarting the Hub clears the history.",
|
|
3170
|
+
enabled: "Enable adaptive routing",
|
|
3171
|
+
window: "Health window",
|
|
3172
|
+
minSamples: "Minimum samples",
|
|
3173
|
+
windowHint: "Use at most the most recent 1–32 outcomes per role/provider/model.",
|
|
3174
|
+
minHint: "Health may affect automatic candidate order only after this many samples.",
|
|
3175
|
+
boundary: "Activation boundary: next workflow",
|
|
3176
|
+
saved: "Saved",
|
|
3177
|
+
loading: "Loading…"
|
|
3178
|
+
};
|
|
3179
|
+
(0, react.useEffect)(() => {
|
|
3180
|
+
let cancelled = false;
|
|
3181
|
+
const load = async () => {
|
|
3182
|
+
try {
|
|
3183
|
+
const res = await fetch(`${API}/config?lang=${zh ? "zh" : "en"}`, { cache: "no-store" });
|
|
3184
|
+
const body = await res.json();
|
|
3185
|
+
if (!res.ok || body?.ok === false) throw new Error(body?.error ?? `HTTP ${res.status}`);
|
|
3186
|
+
if (!cancelled) {
|
|
3187
|
+
setAdaptive(normalizeAdaptive(body?.config?.worker?.model_policy?.adaptive));
|
|
3188
|
+
setMessage("");
|
|
3189
|
+
setLoaded(true);
|
|
3190
|
+
}
|
|
3191
|
+
} catch (err) {
|
|
3192
|
+
if (!cancelled) {
|
|
3193
|
+
setMessage(err?.message ?? String(err));
|
|
3194
|
+
setLoaded(true);
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
};
|
|
3198
|
+
load();
|
|
3199
|
+
return () => {
|
|
3200
|
+
cancelled = true;
|
|
3201
|
+
};
|
|
3202
|
+
}, [zh]);
|
|
3203
|
+
const save = async (candidate) => {
|
|
3204
|
+
const next = normalizeAdaptive(candidate);
|
|
3205
|
+
setAdaptive(next);
|
|
3206
|
+
setSaving(true);
|
|
3207
|
+
try {
|
|
3208
|
+
const res = await fetch(`${API}/config?lang=${zh ? "zh" : "en"}`, {
|
|
3209
|
+
method: "POST",
|
|
3210
|
+
headers: { "content-type": "application/json" },
|
|
3211
|
+
body: JSON.stringify({ worker: { model_policy: { adaptive: next } } })
|
|
3212
|
+
});
|
|
3213
|
+
const body = await res.json();
|
|
3214
|
+
if (!res.ok || body?.ok === false) throw new Error(body?.error ?? `HTTP ${res.status}`);
|
|
3215
|
+
setAdaptive(normalizeAdaptive(body?.config?.worker?.model_policy?.adaptive));
|
|
3216
|
+
setMessage(copy.saved);
|
|
3217
|
+
setTimeout(() => setMessage(""), 1500);
|
|
3218
|
+
} catch (err) {
|
|
3219
|
+
setMessage(err?.message ?? String(err));
|
|
3220
|
+
} finally {
|
|
3221
|
+
setSaving(false);
|
|
3222
|
+
}
|
|
3223
|
+
};
|
|
3224
|
+
if (!loaded) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3225
|
+
style: {
|
|
3226
|
+
fontSize: 12,
|
|
3227
|
+
opacity: .6
|
|
3228
|
+
},
|
|
3229
|
+
children: copy.loading
|
|
3230
|
+
});
|
|
3231
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3232
|
+
style: {
|
|
3233
|
+
display: "flex",
|
|
3234
|
+
flexDirection: "column",
|
|
3235
|
+
gap: 10,
|
|
3236
|
+
fontSize: 13,
|
|
3237
|
+
lineHeight: 1.55
|
|
3238
|
+
},
|
|
3239
|
+
children: [
|
|
3240
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3241
|
+
style: {
|
|
3242
|
+
fontWeight: 600,
|
|
3243
|
+
fontSize: 14
|
|
3244
|
+
},
|
|
3245
|
+
children: copy.title
|
|
3246
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3247
|
+
style: {
|
|
3248
|
+
opacity: .68,
|
|
3249
|
+
fontSize: 12,
|
|
3250
|
+
marginTop: 2
|
|
3251
|
+
},
|
|
3252
|
+
children: copy.hint
|
|
3253
|
+
})] }),
|
|
3254
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3255
|
+
style: {
|
|
3256
|
+
display: "flex",
|
|
3257
|
+
alignItems: "center",
|
|
3258
|
+
gap: 7
|
|
3259
|
+
},
|
|
3260
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3261
|
+
type: "checkbox",
|
|
3262
|
+
checked: adaptive.enabled,
|
|
3263
|
+
disabled: saving,
|
|
3264
|
+
onChange: (event) => {
|
|
3265
|
+
save({
|
|
3266
|
+
...adaptive,
|
|
3267
|
+
enabled: event.target.checked
|
|
3268
|
+
});
|
|
3269
|
+
}
|
|
3270
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: copy.enabled })]
|
|
3271
|
+
}),
|
|
3272
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3273
|
+
style: {
|
|
3274
|
+
display: "grid",
|
|
3275
|
+
gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
|
|
3276
|
+
gap: 10
|
|
3277
|
+
},
|
|
3278
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3279
|
+
style: {
|
|
3280
|
+
display: "flex",
|
|
3281
|
+
flexDirection: "column",
|
|
3282
|
+
gap: 4
|
|
3283
|
+
},
|
|
3284
|
+
children: [
|
|
3285
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3286
|
+
style: {
|
|
3287
|
+
fontSize: 12,
|
|
3288
|
+
fontWeight: 500
|
|
3289
|
+
},
|
|
3290
|
+
children: copy.window
|
|
3291
|
+
}),
|
|
3292
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3293
|
+
type: "number",
|
|
3294
|
+
min: 1,
|
|
3295
|
+
max: 32,
|
|
3296
|
+
value: adaptive.window_size,
|
|
3297
|
+
disabled: saving,
|
|
3298
|
+
onChange: (event) => {
|
|
3299
|
+
const windowSize = clampInt(event.target.value, adaptive.window_size, 1, 32);
|
|
3300
|
+
setAdaptive((current) => ({
|
|
3301
|
+
...current,
|
|
3302
|
+
window_size: windowSize,
|
|
3303
|
+
min_samples: Math.min(current.min_samples, windowSize)
|
|
3304
|
+
}));
|
|
3305
|
+
},
|
|
3306
|
+
onBlur: () => {
|
|
3307
|
+
save(adaptive);
|
|
3308
|
+
},
|
|
3309
|
+
style: {
|
|
3310
|
+
padding: "5px 7px",
|
|
3311
|
+
borderRadius: 5,
|
|
3312
|
+
border: "1px solid rgba(128,128,128,0.35)",
|
|
3313
|
+
background: "transparent",
|
|
3314
|
+
color: "inherit"
|
|
3315
|
+
}
|
|
3316
|
+
}),
|
|
3317
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3318
|
+
style: {
|
|
3319
|
+
fontSize: 10.5,
|
|
3320
|
+
opacity: .55
|
|
3321
|
+
},
|
|
3322
|
+
children: copy.windowHint
|
|
3323
|
+
})
|
|
3324
|
+
]
|
|
3325
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3326
|
+
style: {
|
|
3327
|
+
display: "flex",
|
|
3328
|
+
flexDirection: "column",
|
|
3329
|
+
gap: 4
|
|
3330
|
+
},
|
|
3331
|
+
children: [
|
|
3332
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3333
|
+
style: {
|
|
3334
|
+
fontSize: 12,
|
|
3335
|
+
fontWeight: 500
|
|
3336
|
+
},
|
|
3337
|
+
children: copy.minSamples
|
|
3338
|
+
}),
|
|
3339
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3340
|
+
type: "number",
|
|
3341
|
+
min: 1,
|
|
3342
|
+
max: adaptive.window_size,
|
|
3343
|
+
value: adaptive.min_samples,
|
|
3344
|
+
disabled: saving,
|
|
3345
|
+
onChange: (event) => setAdaptive((current) => ({
|
|
3346
|
+
...current,
|
|
3347
|
+
min_samples: clampInt(event.target.value, current.min_samples, 1, current.window_size)
|
|
3348
|
+
})),
|
|
3349
|
+
onBlur: () => {
|
|
3350
|
+
save(adaptive);
|
|
3351
|
+
},
|
|
3352
|
+
style: {
|
|
3353
|
+
padding: "5px 7px",
|
|
3354
|
+
borderRadius: 5,
|
|
3355
|
+
border: "1px solid rgba(128,128,128,0.35)",
|
|
3356
|
+
background: "transparent",
|
|
3357
|
+
color: "inherit"
|
|
3358
|
+
}
|
|
3359
|
+
}),
|
|
3360
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3361
|
+
style: {
|
|
3362
|
+
fontSize: 10.5,
|
|
3363
|
+
opacity: .55
|
|
3364
|
+
},
|
|
3365
|
+
children: copy.minHint
|
|
3366
|
+
})
|
|
3367
|
+
]
|
|
3368
|
+
})]
|
|
3369
|
+
}),
|
|
3370
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3371
|
+
style: {
|
|
3372
|
+
display: "flex",
|
|
3373
|
+
gap: 8,
|
|
3374
|
+
alignItems: "center",
|
|
3375
|
+
fontSize: 11.5,
|
|
3376
|
+
opacity: .62
|
|
3377
|
+
},
|
|
3378
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: copy.boundary }), message && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: ["· ", message] })]
|
|
3379
|
+
})
|
|
3380
|
+
]
|
|
3381
|
+
});
|
|
3382
|
+
}
|
|
3383
|
+
function ActivationBoundaryPanel({ ctx }) {
|
|
3384
|
+
const locale = useLocale(ctx);
|
|
3385
|
+
const [activation, setActivation] = (0, react.useState)(null);
|
|
3386
|
+
const [error, setError] = (0, react.useState)("");
|
|
3387
|
+
(0, react.useEffect)(() => {
|
|
3388
|
+
let cancelled = false;
|
|
3389
|
+
const load = async () => {
|
|
3390
|
+
try {
|
|
3391
|
+
const res = await fetch(`${API}/config?lang=${locale === "zh" ? "zh" : "en"}`, { cache: "no-store" });
|
|
3392
|
+
const body = await res.json();
|
|
3393
|
+
if (!cancelled) {
|
|
3394
|
+
if (!res.ok || body?.ok === false) throw new Error(body?.error ?? `HTTP ${res.status}`);
|
|
3395
|
+
setActivation(body?.config?.config_activation ?? null);
|
|
3396
|
+
setError("");
|
|
3397
|
+
}
|
|
3398
|
+
} catch (err) {
|
|
3399
|
+
if (!cancelled) setError(err?.message ?? String(err));
|
|
3400
|
+
}
|
|
3401
|
+
};
|
|
3402
|
+
load();
|
|
3403
|
+
return () => {
|
|
3404
|
+
cancelled = true;
|
|
3405
|
+
};
|
|
3406
|
+
}, [locale]);
|
|
3407
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3408
|
+
style: {
|
|
3409
|
+
display: "flex",
|
|
3410
|
+
flexDirection: "column",
|
|
3411
|
+
gap: 8,
|
|
3412
|
+
fontSize: 13,
|
|
3413
|
+
lineHeight: 1.55
|
|
3414
|
+
},
|
|
3415
|
+
children: [error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3416
|
+
style: {
|
|
3417
|
+
fontSize: 11.5,
|
|
3418
|
+
opacity: .6
|
|
3419
|
+
},
|
|
3420
|
+
children: error
|
|
3421
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ActivationSummary, {
|
|
3422
|
+
activation: activation ?? void 0,
|
|
3423
|
+
locale
|
|
3424
|
+
})]
|
|
3425
|
+
});
|
|
3426
|
+
}
|
|
3427
|
+
function apply(ctx) {
|
|
3428
|
+
apply$1(ctx);
|
|
3429
|
+
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
3430
|
+
name: "settings.section",
|
|
3431
|
+
id: "dsh-crew-adaptive-routing",
|
|
3432
|
+
order: 65,
|
|
3433
|
+
label: () => ctx.locale.getLocale().active === "zh" ? "DSH Crew · 自适应路由" : "DSH Crew · Adaptive Routing"
|
|
3434
|
+
}, () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AdaptiveRoutingPanel, { ctx })));
|
|
3435
|
+
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
3436
|
+
name: "settings.section",
|
|
3437
|
+
id: "dsh-crew-runtime-controls",
|
|
3438
|
+
order: 66,
|
|
3439
|
+
label: () => ctx.locale.getLocale().active === "zh" ? "DSH Crew · 生效边界" : "DSH Crew · Activation"
|
|
3440
|
+
}, () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ActivationBoundaryPanel, { ctx })));
|
|
3441
|
+
}
|
|
3442
|
+
//#endregion
|
|
3443
|
+
exports.apply = apply;
|
|
3444
|
+
exports.inject = inject;
|
|
3445
|
+
|
|
3446
|
+
return module.exports; } });
|