@ran-sh/dsh-crew 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -37,9 +37,7 @@ const SETTINGS_SECTION_IDS = Object.freeze([
37
37
  "dispatch",
38
38
  "adaptive",
39
39
  "runtime",
40
- "multimodal",
41
40
  "harnessProviders",
42
- "providers",
43
41
  "jobs"
44
42
  ]);
45
43
  function createDefaultSectionState() {
@@ -415,7 +413,7 @@ function classifyCrewSurface({ bridgeStatus, runtime } = {}) {
415
413
  /**
416
414
  * Surface capability model. The NATIVE 3210 Crew harness is the single full
417
415
  * control plane; the OFFICIAL 3080 surface is a narrow quick-controls panel
418
- * (total switch, flash/pro model priority, vision/imagegen toggles) plus a
416
+ * (total switch and the flash/pro model priority lists) plus a
419
417
  * deep link back to 3210. Unknown surfaces get diagnostics only — never write
420
418
  * authority. This replaces the old binary full-vs-minimal assumption.
421
419
  */
@@ -1180,9 +1178,7 @@ const COPY = {
1180
1178
  schedule: "波峰波谷调度",
1181
1179
  adaptive: "自适应路由",
1182
1180
  runtime: "运行 / 生效边界",
1183
- multimodal: "视觉与生图",
1184
1181
  harnessProviders: "Harness Providers",
1185
- providers: "多模态适配器",
1186
1182
  jobs: "任务状态"
1187
1183
  },
1188
1184
  harnessHint: "DeepSeek 官方 Harness 的 dsh-crew profile:Provider、Harness Models 与运行时配置",
@@ -1275,12 +1271,6 @@ const COPY = {
1275
1271
  proReviewsFlash: "Pro 复审 Flash 改动",
1276
1272
  proReviewsFlashHint: "Flash 成功后自动追加一次 Pro 复审(仅 blocking 派发)",
1277
1273
  proReviewsLocked: "Review Pipeline 模式固定开启",
1278
- enableCrewVision: "启用 Crew 视觉",
1279
- enableCrewVisionHint: "关闭后不注册 Crew 的 describe_image 与视觉 route(可与你的 dsh-vision 等共存)。",
1280
- enableImagegen: "启用生图",
1281
- enableImagegenHint: "关闭后不注册 Crew 的 generate_image。",
1282
- capRestartHint: "该开关改变工具注册,需重启 DSH 生效",
1283
- capDisabledNote: "Crew 工具/route 已禁用(重启 DSH 后生效)",
1284
1274
  tier: "默认档位",
1285
1275
  effort: "默认推理",
1286
1276
  mode: "执行模式",
@@ -1296,10 +1286,6 @@ const COPY = {
1296
1286
  escalateHint: "flash 失败自动用 pro 重试一次",
1297
1287
  presetFlash: "flash 模式",
1298
1288
  presetPro: "pro 模式",
1299
- multimodal: "多模态",
1300
- visionProvider: "视觉 provider",
1301
- visionModel: "视觉模型",
1302
- imagegenProvider: "生图 provider",
1303
1289
  harnessProviders: "Harness Providers",
1304
1290
  harnessProviderHint: "DeepSeek 官方 Harness 的 3210 dsh-crew profile 中的真实 Provider 注册与生命周期;不是视觉 / 生图适配器。",
1305
1291
  providerState: (state) => `状态:${state}`,
@@ -1340,48 +1326,11 @@ const COPY = {
1340
1326
  credentialPurging: "清理中…",
1341
1327
  credentialPurgeUnavailable: "凭据清理不可用",
1342
1328
  credentialPurgeUnverified: "已执行清理,但验证失败;引用仍保留用于恢复检查",
1343
- customProviders: "多模态适配器",
1344
- addProvider: "+ 添加多模态适配器",
1345
- noCustomProviders: "暂无。添加后会出现在上方的视觉 / 生图 provider 选择里。",
1346
- providerName: "名称",
1347
- modelsField: "模型列表(逗号分隔)",
1348
- providerType: "接入方式",
1349
- typeApi: "API · 兼容 OpenAI 接口",
1350
- typeCli: "CLI · 本地命令",
1351
- baseUrl: "Base URL",
1352
- apiKey: "API Key",
1353
- imagegenModel: "生图模型(留空=不提供生图)",
1354
- visionCmd: "视觉命令(可选)",
1355
- imagegenCmd: "生图命令(可选)",
1356
- apiFormHint: "走 OpenAI 兼容接口:视觉调 {base}/chat/completions(图片以 base64 内联),生图调 {base}/images/generations。Key 保存在本机 ~/.config/dsh-crew/config.json,不会外发到第三方。",
1357
- cliFormHint: "命令经 bash 执行,占位符会被安全引用后代入。视觉: {image} {question} {model},stdout 即答案;生图: {prompt} {output} {size},命令须把图片写到 {output}。两条命令至少填一条。",
1358
- saveProvider: "保存",
1359
- cancel: "取消",
1360
- edit: "编辑",
1361
- del: "删除",
1362
- testProvider: "连通测试",
1363
- testing: "测试中…",
1364
- testTip: "按当前填写的内容实测:API 会检查可达性与鉴权并真发一次视觉请求;CLI 会检查可执行文件并真跑一次视觉命令。生图只校验配置,不实际出图。",
1365
- testPass: "连通正常",
1366
- testFail: "连通失败",
1367
1329
  staleHub: "本 DSH 实例还在跑旧版插件,缺少该接口 —— 重启 DSH 后再试",
1368
1330
  emptyResponse: (path, status) => `${path} → HTTP ${status},响应为空`,
1369
1331
  badJson: (path, body) => `${path} → 非 JSON 响应: ${body}`,
1370
- refreshModels: "重新从 CLI 获取模型列表",
1371
1332
  presetDefault: (id) => `default · 跟随 DSH${id ? ` (${id})` : ""}`,
1372
1333
  progressTip: (t, turn, step, calls) => `耗时 ${t} · 第${turn}轮第${step}步 · ${calls} 次工具调用`,
1373
- confirmDeleteProvider: (n) => `删除自定义 provider「${n}」?`,
1374
- needName: "⚠ 名称必填",
1375
- needCmd: "⚠ 视觉/生图命令至少填一条",
1376
- needBaseUrl: "⚠ Base URL 必填",
1377
- needModels: "⚠ 模型列表至少填一个",
1378
- keySet: "已配置",
1379
- keyPlaceholder: "sk-…(留空则不发送 Authorization)",
1380
- addModelTip: "添加模型到列表",
1381
- removeModelTip: "从列表移除当前模型",
1382
- modelPlaceholder: "模型 id,回车确认",
1383
- capVision: "视觉",
1384
- capImagegen: "生图",
1385
1334
  groupDispatch: "派发",
1386
1335
  groupRuntime: "运行",
1387
1336
  groupMultimodal: "多模态",
@@ -1427,14 +1376,6 @@ const COPY = {
1427
1376
  t: "执行与连接",
1428
1377
  d: "worker 会话跑在本实例内还是独立进程,单个任务的超时上限,以及 CC / Codex 探测本实例的地址。"
1429
1378
  },
1430
- cardMM: {
1431
- t: "视觉与生图",
1432
- d: "给纯文本的 DSH 模型借来眼睛和画笔:describe_image、会话贴图转写与 generate_image 都用这里的设置。"
1433
- },
1434
- cardCustomProv: {
1435
- t: "自定义 Provider",
1436
- d: "接入自己的 API 或本地命令;保存后会出现在上面的视觉 / 生图 provider 选择里。"
1437
- },
1438
1379
  modeDesc: {
1439
1380
  auto: "auto · 优先 3210",
1440
1381
  hub: "hub · 必须 3210"
@@ -1541,9 +1482,7 @@ const COPY = {
1541
1482
  schedule: "Peak / off-peak scheduling",
1542
1483
  adaptive: "Adaptive routing",
1543
1484
  runtime: "Runtime / activation boundaries",
1544
- multimodal: "Vision & image generation",
1545
1485
  harnessProviders: "Harness Providers",
1546
- providers: "Multimodal adapters",
1547
1486
  jobs: "Task status"
1548
1487
  },
1549
1488
  harnessHint: "The official DeepSeek Harness dsh-crew profile: providers, Harness Models, and runtime configuration",
@@ -1636,12 +1575,6 @@ const COPY = {
1636
1575
  proReviewsFlash: "Pro reviews Flash changes",
1637
1576
  proReviewsFlashHint: "Automatically follow a successful Flash run with one Pro review (blocking dispatch only)",
1638
1577
  proReviewsLocked: "always on in Review Pipeline mode",
1639
- enableCrewVision: "Enable Crew Vision",
1640
- enableCrewVisionHint: "When off, the Crew describe_image tool and vision route are not registered (coexist with your own dsh-vision or other plugins).",
1641
- enableImagegen: "Enable Image Generation",
1642
- enableImagegenHint: "When off, the Crew generate_image tool is not registered.",
1643
- capRestartHint: "This switch changes tool registration and takes effect after a DSH restart",
1644
- capDisabledNote: "Crew tool/route disabled (effective after DSH restart)",
1645
1578
  tier: "Default tier",
1646
1579
  effort: "Default effort",
1647
1580
  mode: "Mode",
@@ -1657,12 +1590,8 @@ const COPY = {
1657
1590
  escalateHint: "retry a failed flash run once on pro",
1658
1591
  presetFlash: "flash preset",
1659
1592
  presetPro: "pro preset",
1660
- multimodal: "Multimodal",
1661
- visionProvider: "Vision provider",
1662
- visionModel: "Vision model",
1663
- imagegenProvider: "Image-gen provider",
1664
1593
  harnessProviders: "Harness Providers",
1665
- harnessProviderHint: "Live Provider registration and lifecycle in the official DeepSeek Harness dsh-crew profile on 3210; separate from vision / image-gen adapters.",
1594
+ harnessProviderHint: "Live Provider registration and lifecycle in the official DeepSeek Harness dsh-crew profile on 3210.",
1666
1595
  providerState: (state) => `State: ${state}`,
1667
1596
  providerModels: (count) => `${count} models`,
1668
1597
  providerJobs: (count) => `${count} running jobs`,
@@ -1701,48 +1630,11 @@ const COPY = {
1701
1630
  credentialPurging: "Purging…",
1702
1631
  credentialPurgeUnavailable: "Credential purge unavailable",
1703
1632
  credentialPurgeUnverified: "Purge ran but verification failed; the reference remains visible for recovery",
1704
- customProviders: "Multimodal adapters",
1705
- addProvider: "+ Add multimodal adapter",
1706
- noCustomProviders: "None yet. Added providers appear in the vision / image-gen selects above.",
1707
- providerName: "Name",
1708
- modelsField: "Models (comma-separated)",
1709
- providerType: "Connection",
1710
- typeApi: "API · OpenAI-compatible",
1711
- typeCli: "CLI · local command",
1712
- baseUrl: "Base URL",
1713
- apiKey: "API key",
1714
- imagegenModel: "Image-gen model (empty = no image generation)",
1715
- visionCmd: "Vision command (optional)",
1716
- imagegenCmd: "Image-gen command (optional)",
1717
- 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.",
1718
- 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.",
1719
- saveProvider: "Save",
1720
- cancel: "Cancel",
1721
- edit: "Edit",
1722
- del: "Delete",
1723
- testProvider: "Test",
1724
- testing: "Testing…",
1725
- 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.",
1726
- testPass: "Connection OK",
1727
- testFail: "Connection failed",
1728
1633
  staleHub: "This DSH instance is still running an older build of the plugin and lacks this route — restart DSH and retry",
1729
1634
  emptyResponse: (path, status) => `${path} → HTTP ${status}, empty response`,
1730
1635
  badJson: (path, body) => `${path} → non-JSON response: ${body}`,
1731
- refreshModels: "Re-fetch the model list from the CLI",
1732
1636
  presetDefault: (id) => `default · follow DSH${id ? ` (${id})` : ""}`,
1733
1637
  progressTip: (t, turn, step, calls) => `${t} elapsed · turn ${turn}, step ${step} · ${calls} tool calls`,
1734
- confirmDeleteProvider: (n) => `Delete custom provider "${n}"?`,
1735
- needName: "⚠ Name is required",
1736
- needCmd: "⚠ Fill in at least one command",
1737
- needBaseUrl: "⚠ Base URL is required",
1738
- needModels: "⚠ Add at least one model",
1739
- keySet: "set",
1740
- keyPlaceholder: "sk-… (leave empty to send no Authorization header)",
1741
- addModelTip: "Add a model to the list",
1742
- removeModelTip: "Remove the current model from the list",
1743
- modelPlaceholder: "model id, Enter to confirm",
1744
- capVision: "vision",
1745
- capImagegen: "image-gen",
1746
1638
  groupDispatch: "Dispatch",
1747
1639
  groupRuntime: "Runtime",
1748
1640
  groupMultimodal: "Multimodal",
@@ -1788,14 +1680,6 @@ const COPY = {
1788
1680
  t: "Execution & connection",
1789
1681
  d: "Whether worker sessions run inside this instance or a separate process, the per-job timeout, and the address CC / Codex probes."
1790
1682
  },
1791
- cardMM: {
1792
- t: "Vision & image generation",
1793
- d: "Lends the harness's text-only models eyes and a brush: describe_image, pasted-image transcription and generate_image all use these settings."
1794
- },
1795
- cardCustomProv: {
1796
- t: "Custom providers",
1797
- d: "Bring your own API or local command; saved providers appear in the vision / image-gen selects above."
1798
- },
1799
1683
  modeDesc: {
1800
1684
  auto: "auto · prefer 3210",
1801
1685
  hub: "hub · require 3210"
@@ -2002,20 +1886,6 @@ const S = {
2002
1886
  fontVariantNumeric: "tabular-nums"
2003
1887
  }
2004
1888
  };
2005
- const VISION_MODELS = {
2006
- "claude-code": [
2007
- "haiku",
2008
- "sonnet",
2009
- "opus"
2010
- ],
2011
- codex: [
2012
- "default",
2013
- "gpt-5.4-mini",
2014
- "gpt-5.6"
2015
- ],
2016
- grok: ["default"],
2017
- agy: ["default"]
2018
- };
2019
1889
  function CustomSelect({ value, options, onChange, minWidth }) {
2020
1890
  const [open, setOpen] = (0, react.useState)(null);
2021
1891
  (0, react.useEffect)(() => {
@@ -2284,7 +2154,6 @@ function WorkersPanel({ ctx }) {
2284
2154
  const [presetOptions, setPresetOptions] = (0, react.useState)([{ value: "default" }]);
2285
2155
  const [notice, setNotice] = (0, react.useState)("");
2286
2156
  const [busy, setBusy] = (0, react.useState)(false);
2287
- const [provForm, setProvForm] = (0, react.useState)(null);
2288
2157
  const [modelAdd, setModelAdd] = (0, react.useState)(null);
2289
2158
  const [modelCatalog, setModelCatalog] = (0, react.useState)(null);
2290
2159
  const [modelCatalogError, setModelCatalogError] = (0, react.useState)("");
@@ -2298,7 +2167,6 @@ function WorkersPanel({ ctx }) {
2298
2167
  const [providerLifecycleBusy, setProviderLifecycleBusy] = (0, react.useState)(null);
2299
2168
  const [modelPickerTier, setModelPickerTier] = (0, react.useState)(null);
2300
2169
  const [modelQuery, setModelQuery] = (0, react.useState)("");
2301
- const [testResult, setTestResult] = (0, react.useState)(null);
2302
2170
  const [expandedSections, setExpandedSections] = (0, react.useState)(() => readSectionState(sectionStorage()));
2303
2171
  const [surface, setSurface] = (0, react.useState)("detecting");
2304
2172
  const [readinessEnvelope, setReadinessEnvelope] = (0, react.useState)({});
@@ -2332,9 +2200,6 @@ function WorkersPanel({ ctx }) {
2332
2200
  (0, react.useEffect)(() => {
2333
2201
  if (providerInventoryError) setExpandedSections((current) => openSections(current, ["harnessProviders"]));
2334
2202
  }, [providerInventoryError]);
2335
- (0, react.useEffect)(() => {
2336
- if (testResult && !testResult.busy && testResult.ok === false) setExpandedSections((current) => openSections(current, ["providers"]));
2337
- }, [testResult]);
2338
2203
  (0, react.useEffect)(() => {
2339
2204
  if (jobs.some((job) => job.status === "running")) setExpandedSections((current) => openSections(current, ["jobs"]));
2340
2205
  }, [jobs]);
@@ -2651,230 +2516,6 @@ function WorkersPanel({ ctx }) {
2651
2516
  }));
2652
2517
  applyPatch({ review: patch });
2653
2518
  };
2654
- const customProviders = config?.custom_providers ?? [];
2655
- const extraModels = config?.extra_models ?? {};
2656
- const RESERVED_IDS = [
2657
- "claude-code",
2658
- "codex",
2659
- "grok",
2660
- "agy",
2661
- "off",
2662
- "custom",
2663
- "default"
2664
- ];
2665
- const provType = (p) => p.type ?? (p.vision_command || p.imagegen_command ? "cli" : "api");
2666
- const canSee = (p) => provType(p) === "cli" ? !!p.vision_command : !!p.base_url && (p.models?.length ?? 0) > 0;
2667
- const canDraw = (p) => provType(p) === "cli" ? !!p.imagegen_command : !!p.base_url && !!String(p.imagegen_model ?? "").trim();
2668
- const visionProviderOptions = [
2669
- { value: "claude-code" },
2670
- { value: "codex" },
2671
- { value: "grok" },
2672
- { value: "agy" },
2673
- ...customProviders.filter(canSee).map((p) => ({
2674
- value: p.id,
2675
- label: p.name || p.id
2676
- })),
2677
- { value: "off" }
2678
- ];
2679
- const imagegenProviderOptions = [
2680
- { value: "codex" },
2681
- {
2682
- value: "agy",
2683
- label: "agy (nano banana)"
2684
- },
2685
- {
2686
- value: "grok",
2687
- label: "grok (imagine)"
2688
- },
2689
- ...customProviders.filter(canDraw).map((p) => ({
2690
- value: p.id,
2691
- label: p.name || p.id
2692
- })),
2693
- { value: "off" }
2694
- ];
2695
- const visionModelOptions = (() => {
2696
- if (!config) return [];
2697
- const p = config.vision_provider;
2698
- const custom = customProviders.find((c) => c.id === p);
2699
- const base = custom ? [{ value: "default" }, ...(custom.models ?? []).map((m) => ({ value: m }))] : dynModels[p] ?? (VISION_MODELS[p] ?? ["default"]).map((m) => ({ value: m }));
2700
- const extras = (extraModels[p] ?? []).filter((m) => !base.some((o) => o.value === m)).map((m) => ({ value: m }));
2701
- return [...base, ...extras];
2702
- })();
2703
- const commitModelAdd = () => {
2704
- const m = (modelAdd ?? "").trim();
2705
- setModelAdd(null);
2706
- if (m === "" || !config) return;
2707
- const p = config.vision_provider;
2708
- const cur = extraModels[p] ?? [];
2709
- const nextExtra = {
2710
- ...extraModels,
2711
- [p]: cur.includes(m) ? cur : [...cur, m]
2712
- };
2713
- setConfig((c) => ({
2714
- ...c,
2715
- extra_models: nextExtra,
2716
- vision_model: m
2717
- }));
2718
- applyPatch({
2719
- extra_models: nextExtra,
2720
- vision_model: m
2721
- });
2722
- };
2723
- const removeCurrentModel = () => {
2724
- if (!config) return;
2725
- const p = config.vision_provider;
2726
- const m = config.vision_model;
2727
- const nextExtra = {
2728
- ...extraModels,
2729
- [p]: (extraModels[p] ?? []).filter((x) => x !== m)
2730
- };
2731
- const fallback = visionModelOptions.find((o) => o.value !== m)?.value ?? "default";
2732
- setConfig((c) => ({
2733
- ...c,
2734
- extra_models: nextExtra,
2735
- vision_model: fallback
2736
- }));
2737
- applyPatch({
2738
- extra_models: nextExtra,
2739
- vision_model: fallback
2740
- });
2741
- };
2742
- const saveProviderForm = () => {
2743
- if (!provForm || !config) return;
2744
- const name = provForm.name.trim();
2745
- if (name === "") {
2746
- setNotice(copy.needName);
2747
- return;
2748
- }
2749
- const models = provForm.models.split(/[,,\n]+/).map((s) => s.trim()).filter(Boolean);
2750
- const vision_command = provForm.vision_command.trim();
2751
- const imagegen_command = provForm.imagegen_command.trim();
2752
- if (provForm.type === "cli") {
2753
- if (vision_command === "" && imagegen_command === "") {
2754
- setNotice(copy.needCmd);
2755
- return;
2756
- }
2757
- } else {
2758
- if (provForm.base_url.trim() === "") {
2759
- setNotice(copy.needBaseUrl);
2760
- return;
2761
- }
2762
- if (models.length === 0) {
2763
- setNotice(copy.needModels);
2764
- return;
2765
- }
2766
- }
2767
- let id = provForm.originalId;
2768
- if (!id) {
2769
- const base = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "provider";
2770
- id = base;
2771
- let n = 2;
2772
- while (RESERVED_IDS.includes(id) || customProviders.some((p) => p.id === id)) id = `${base}-${n++}`;
2773
- }
2774
- const entry = provForm.type === "api" ? {
2775
- id,
2776
- name,
2777
- type: "api",
2778
- models,
2779
- base_url: provForm.base_url.trim(),
2780
- api_key: provForm.api_key,
2781
- imagegen_model: provForm.imagegen_model.trim()
2782
- } : {
2783
- id,
2784
- name,
2785
- type: "cli",
2786
- models,
2787
- vision_command,
2788
- imagegen_command
2789
- };
2790
- const next = provForm.originalId ? customProviders.map((p) => p.id === provForm.originalId ? entry : p) : [...customProviders, entry];
2791
- field("custom_providers", next);
2792
- setProvForm(null);
2793
- };
2794
- const runProviderTest = (key, entry) => {
2795
- setTestResult({
2796
- key,
2797
- busy: true
2798
- });
2799
- post("/provider-test", entry).then((r) => setTestResult(r.ok ? {
2800
- key,
2801
- busy: false,
2802
- ok: r.result.ok,
2803
- steps: r.result.steps
2804
- } : {
2805
- key,
2806
- busy: false,
2807
- ok: false,
2808
- error: r.error
2809
- })).catch((e) => setTestResult({
2810
- key,
2811
- busy: false,
2812
- ok: false,
2813
- error: String(e?.message ?? e)
2814
- }));
2815
- };
2816
- /** Shared renderer for a test outcome under the button that triggered it. */
2817
- const testReport = (key) => {
2818
- if (!testResult || testResult.key !== key) return null;
2819
- if (testResult.busy) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2820
- style: {
2821
- fontSize: 11.5,
2822
- opacity: .6
2823
- },
2824
- children: copy.testing
2825
- });
2826
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2827
- style: {
2828
- fontSize: 11.5,
2829
- display: "flex",
2830
- flexDirection: "column",
2831
- gap: 2
2832
- },
2833
- children: [
2834
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2835
- style: { color: testResult.ok ? "#3fb950" : "#f85149" },
2836
- children: testResult.ok ? `✓ ${copy.testPass}` : `✗ ${copy.testFail}`
2837
- }),
2838
- testResult.error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2839
- style: {
2840
- opacity: .7,
2841
- wordBreak: "break-all"
2842
- },
2843
- children: testResult.error
2844
- }),
2845
- (testResult.steps ?? []).map((st, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2846
- style: {
2847
- opacity: .7,
2848
- wordBreak: "break-all"
2849
- },
2850
- children: [
2851
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2852
- style: { color: st.ok ? "#3fb950" : "#f85149" },
2853
- children: st.ok ? "✓" : "✗"
2854
- }),
2855
- " ",
2856
- st.name,
2857
- ": ",
2858
- st.detail
2859
- ]
2860
- }, i))
2861
- ]
2862
- });
2863
- };
2864
- const deleteProvider = (id) => {
2865
- if (!config) return;
2866
- const patch = { custom_providers: customProviders.filter((p) => p.id !== id) };
2867
- if (config.vision_provider === id) {
2868
- patch.vision_provider = "claude-code";
2869
- patch.vision_model = "haiku";
2870
- }
2871
- if (config.imagegen_provider === id) patch.imagegen_provider = "codex";
2872
- setConfig((c) => ({
2873
- ...c,
2874
- ...patch
2875
- }));
2876
- applyPatch(patch);
2877
- };
2878
2519
  const deleteHarnessProvider = async (record) => {
2879
2520
  if (!record || record.delete_capability !== "supported" || record.desired_state === "absent" || Number(record.references?.active_jobs ?? 0) > 0) {
2880
2521
  setNotice(copy.providerDeleteBlocked);
@@ -4636,191 +4277,6 @@ function WorkersPanel({ ctx }) {
4636
4277
  locale
4637
4278
  })]
4638
4279
  }),
4639
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsibleSection, {
4640
- sectionId: "multimodal",
4641
- title: copy.sectionNames.multimodal,
4642
- summary: sectionSummary(config.vision_enabled ? `${copy.capVision}: ${config.vision_provider}` : `${copy.capVision}: off`, config.imagegen_enabled ? `${copy.capImagegen}: ${config.imagegen_provider}` : `${copy.capImagegen}: off`),
4643
- expanded: !!expandedSections.multimodal,
4644
- onToggle: () => toggleSection("multimodal"),
4645
- children: block(copy.cardMM, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
4646
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
4647
- style: {
4648
- ...S.field,
4649
- flexDirection: "row",
4650
- alignItems: "center",
4651
- gap: 6,
4652
- gridColumn: "1 / -1"
4653
- },
4654
- title: copy.capRestartHint,
4655
- children: [
4656
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4657
- type: "checkbox",
4658
- checked: !!config.vision_enabled,
4659
- onChange: (e) => field("vision_enabled", e.target.checked)
4660
- }),
4661
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4662
- style: { fontSize: 12.5 },
4663
- children: copy.enableCrewVision
4664
- }),
4665
- config.vision_enabled === false && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4666
- style: {
4667
- fontSize: 11,
4668
- opacity: .55
4669
- },
4670
- children: copy.capDisabledNote
4671
- })
4672
- ]
4673
- }),
4674
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
4675
- style: S.field,
4676
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4677
- style: S.fieldLabel,
4678
- children: copy.visionProvider
4679
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
4680
- value: config.vision_provider,
4681
- onChange: (p) => {
4682
- const custom = customProviders.find((c) => c.id === p);
4683
- const m = custom ? custom.models?.[0] ?? "default" : (VISION_MODELS[p] ?? ["default"])[0];
4684
- setModelAdd(null);
4685
- setConfig((c) => ({
4686
- ...c,
4687
- vision_provider: p,
4688
- vision_model: m
4689
- }));
4690
- applyPatch({
4691
- vision_provider: p,
4692
- vision_model: m
4693
- });
4694
- if ((p === "grok" || p === "agy") && !dynModels[p]) get(`/vision-models?provider=${p}`).then((r) => {
4695
- if (r.ok) setDynModels((d) => ({
4696
- ...d,
4697
- [p]: r.models
4698
- }));
4699
- }).catch(() => {});
4700
- },
4701
- options: visionProviderOptions
4702
- })]
4703
- }),
4704
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
4705
- style: S.field,
4706
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4707
- style: S.fieldLabel,
4708
- children: copy.visionModel
4709
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4710
- style: {
4711
- display: "flex",
4712
- gap: 6,
4713
- alignItems: "center"
4714
- },
4715
- children: [
4716
- modelAdd === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
4717
- value: config.vision_model,
4718
- onChange: (v) => field("vision_model", v),
4719
- options: visionModelOptions
4720
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4721
- autoFocus: true,
4722
- style: {
4723
- ...S.input,
4724
- width: "100%",
4725
- boxSizing: "border-box",
4726
- ...S.mono
4727
- },
4728
- value: modelAdd,
4729
- placeholder: copy.modelPlaceholder,
4730
- onChange: (e) => setModelAdd(e.target.value),
4731
- onBlur: commitModelAdd,
4732
- onKeyDown: (e) => {
4733
- if (e.key === "Enter") e.currentTarget.blur();
4734
- if (e.key === "Escape") setModelAdd(null);
4735
- }
4736
- }),
4737
- modelAdd === null && (config.vision_provider === "grok" || config.vision_provider === "agy") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4738
- type: "button",
4739
- title: copy.refreshModels,
4740
- disabled: busy,
4741
- style: {
4742
- ...S.btn,
4743
- padding: "3px 8px",
4744
- flexShrink: 0
4745
- },
4746
- onClick: () => {
4747
- const p = config.vision_provider;
4748
- setBusy(true);
4749
- get(`/vision-models?provider=${p}&refresh=1`).then((r) => {
4750
- if (r.ok) setDynModels((d) => ({
4751
- ...d,
4752
- [p]: r.models
4753
- }));
4754
- }).catch(() => {}).finally(() => setBusy(false));
4755
- },
4756
- children: "↻"
4757
- }),
4758
- modelAdd === null && config.vision_provider !== "off" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4759
- type: "button",
4760
- title: copy.addModelTip,
4761
- style: {
4762
- ...S.btn,
4763
- padding: "3px 8px",
4764
- flexShrink: 0
4765
- },
4766
- onClick: () => setModelAdd(""),
4767
- children: "+"
4768
- }),
4769
- modelAdd === null && (extraModels[config.vision_provider] ?? []).includes(config.vision_model) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4770
- type: "button",
4771
- title: copy.removeModelTip,
4772
- style: {
4773
- ...S.btn,
4774
- padding: "3px 8px",
4775
- flexShrink: 0
4776
- },
4777
- onClick: removeCurrentModel,
4778
- children: "−"
4779
- })
4780
- ]
4781
- })]
4782
- }),
4783
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
4784
- style: {
4785
- ...S.field,
4786
- flexDirection: "row",
4787
- alignItems: "center",
4788
- gap: 6,
4789
- gridColumn: "1 / -1"
4790
- },
4791
- title: copy.capRestartHint,
4792
- children: [
4793
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4794
- type: "checkbox",
4795
- checked: !!config.imagegen_enabled,
4796
- onChange: (e) => field("imagegen_enabled", e.target.checked)
4797
- }),
4798
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4799
- style: { fontSize: 12.5 },
4800
- children: copy.enableImagegen
4801
- }),
4802
- config.imagegen_enabled === false && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4803
- style: {
4804
- fontSize: 11,
4805
- opacity: .55
4806
- },
4807
- children: copy.capDisabledNote
4808
- })
4809
- ]
4810
- }),
4811
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
4812
- style: S.field,
4813
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4814
- style: S.fieldLabel,
4815
- children: copy.imagegenProvider
4816
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
4817
- value: config.imagegen_provider,
4818
- onChange: (v) => field("imagegen_provider", v),
4819
- options: imagegenProviderOptions
4820
- })]
4821
- })
4822
- ] }))
4823
- }),
4824
4280
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CollapsibleSection, {
4825
4281
  sectionId: "harnessProviders",
4826
4282
  title: copy.sectionNames.harnessProviders,
@@ -5149,365 +4605,6 @@ function WorkersPanel({ ctx }) {
5149
4605
  ]
5150
4606
  })
5151
4607
  ]
5152
- }),
5153
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollapsibleSection, {
5154
- sectionId: "providers",
5155
- title: copy.sectionNames.providers,
5156
- summary: sectionSummary(copy.providerCount(customProviders.length), testResult?.ok === false && copy.testFail),
5157
- expanded: !!expandedSections.providers,
5158
- onToggle: () => toggleSection("providers"),
5159
- children: block(copy.cardCustomProv, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
5160
- customProviders.length === 0 && provForm === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5161
- style: {
5162
- fontSize: 12,
5163
- opacity: .5
5164
- },
5165
- children: copy.noCustomProviders
5166
- }),
5167
- customProviders.map((p) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5168
- style: {
5169
- display: "flex",
5170
- alignItems: "center",
5171
- gap: 8,
5172
- fontSize: 12.5
5173
- },
5174
- children: [
5175
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5176
- style: { fontWeight: 500 },
5177
- children: p.name || p.id
5178
- }),
5179
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5180
- style: {
5181
- ...S.chip(false),
5182
- opacity: .7
5183
- },
5184
- children: provType(p) === "api" ? "API" : "CLI"
5185
- }),
5186
- canSee(p) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5187
- style: S.chip(true),
5188
- children: copy.capVision
5189
- }),
5190
- canDraw(p) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5191
- style: S.chip(true),
5192
- children: copy.capImagegen
5193
- }),
5194
- provType(p) === "api" && !!p.api_key && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5195
- style: {
5196
- fontSize: 11.5,
5197
- opacity: .5
5198
- },
5199
- children: ["key ", copy.keySet]
5200
- }),
5201
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: { flex: 1 } }),
5202
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5203
- style: {
5204
- ...S.btn,
5205
- padding: "2px 8px"
5206
- },
5207
- title: copy.testTip,
5208
- disabled: !!testResult?.busy,
5209
- onClick: () => runProviderTest(`row:${p.id}`, p),
5210
- children: testResult?.key === `row:${p.id}` && testResult.busy ? copy.testing : copy.testProvider
5211
- }),
5212
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5213
- style: {
5214
- ...S.btn,
5215
- padding: "2px 8px"
5216
- },
5217
- onClick: () => setProvForm({
5218
- originalId: p.id,
5219
- name: p.name ?? p.id,
5220
- type: provType(p),
5221
- models: (p.models ?? []).join(", "),
5222
- base_url: p.base_url ?? "",
5223
- api_key: p.api_key ?? "",
5224
- imagegen_model: p.imagegen_model ?? "",
5225
- vision_command: p.vision_command ?? "",
5226
- imagegen_command: p.imagegen_command ?? ""
5227
- }),
5228
- children: copy.edit
5229
- }),
5230
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5231
- style: {
5232
- ...S.btn,
5233
- padding: "2px 8px",
5234
- opacity: .75
5235
- },
5236
- onClick: () => {
5237
- if (confirm(copy.confirmDeleteProvider(p.name || p.id))) deleteProvider(p.id);
5238
- },
5239
- children: copy.del
5240
- })
5241
- ]
5242
- }, p.id)),
5243
- customProviders.some((p) => testResult?.key === `row:${p.id}`) && testReport(testResult.key),
5244
- provForm === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5245
- style: S.btn,
5246
- onClick: () => setProvForm({
5247
- name: "",
5248
- type: "api",
5249
- models: "",
5250
- base_url: "",
5251
- api_key: "",
5252
- imagegen_model: "",
5253
- vision_command: "",
5254
- imagegen_command: ""
5255
- }),
5256
- children: copy.addProvider
5257
- }) }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5258
- style: {
5259
- borderTop: "1px solid rgba(128,128,128,0.16)",
5260
- paddingTop: 10,
5261
- display: "flex",
5262
- flexDirection: "column",
5263
- gap: 10
5264
- },
5265
- children: [
5266
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5267
- style: S.blockGrid,
5268
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
5269
- style: S.field,
5270
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5271
- style: S.fieldLabel,
5272
- children: copy.providerName
5273
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5274
- autoFocus: true,
5275
- style: {
5276
- ...S.input,
5277
- width: "100%",
5278
- boxSizing: "border-box"
5279
- },
5280
- value: provForm.name,
5281
- onChange: (e) => setProvForm({
5282
- ...provForm,
5283
- name: e.target.value
5284
- })
5285
- })]
5286
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
5287
- style: S.field,
5288
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5289
- style: S.fieldLabel,
5290
- children: copy.providerType
5291
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomSelect, {
5292
- value: provForm.type,
5293
- onChange: (v) => setProvForm({
5294
- ...provForm,
5295
- type: v
5296
- }),
5297
- options: [{
5298
- value: "api",
5299
- label: copy.typeApi
5300
- }, {
5301
- value: "cli",
5302
- label: copy.typeCli
5303
- }]
5304
- })]
5305
- })]
5306
- }),
5307
- provForm.type === "api" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5308
- style: S.blockGrid,
5309
- children: [
5310
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
5311
- style: S.field,
5312
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5313
- style: S.fieldLabel,
5314
- children: copy.baseUrl
5315
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5316
- style: {
5317
- ...S.input,
5318
- width: "100%",
5319
- boxSizing: "border-box",
5320
- ...S.mono
5321
- },
5322
- value: provForm.base_url,
5323
- placeholder: "https://api.example.com/v1",
5324
- onChange: (e) => setProvForm({
5325
- ...provForm,
5326
- base_url: e.target.value
5327
- })
5328
- })]
5329
- }),
5330
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
5331
- style: S.field,
5332
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5333
- style: S.fieldLabel,
5334
- children: copy.apiKey
5335
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5336
- type: "password",
5337
- autoComplete: "off",
5338
- style: {
5339
- ...S.input,
5340
- width: "100%",
5341
- boxSizing: "border-box",
5342
- ...S.mono
5343
- },
5344
- value: provForm.api_key,
5345
- placeholder: copy.keyPlaceholder,
5346
- onChange: (e) => setProvForm({
5347
- ...provForm,
5348
- api_key: e.target.value
5349
- })
5350
- })]
5351
- }),
5352
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
5353
- style: S.field,
5354
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5355
- style: S.fieldLabel,
5356
- children: copy.modelsField
5357
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5358
- style: {
5359
- ...S.input,
5360
- width: "100%",
5361
- boxSizing: "border-box",
5362
- ...S.mono
5363
- },
5364
- value: provForm.models,
5365
- placeholder: "gpt-4o-mini, qwen-vl-max",
5366
- onChange: (e) => setProvForm({
5367
- ...provForm,
5368
- models: e.target.value
5369
- })
5370
- })]
5371
- }),
5372
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
5373
- style: S.field,
5374
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5375
- style: S.fieldLabel,
5376
- children: copy.imagegenModel
5377
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5378
- style: {
5379
- ...S.input,
5380
- width: "100%",
5381
- boxSizing: "border-box",
5382
- ...S.mono
5383
- },
5384
- value: provForm.imagegen_model,
5385
- placeholder: "dall-e-3",
5386
- onChange: (e) => setProvForm({
5387
- ...provForm,
5388
- imagegen_model: e.target.value
5389
- })
5390
- })]
5391
- })
5392
- ]
5393
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5394
- style: {
5395
- fontSize: 11.5,
5396
- opacity: .55
5397
- },
5398
- children: copy.apiFormHint
5399
- })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
5400
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
5401
- style: S.field,
5402
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5403
- style: S.fieldLabel,
5404
- children: copy.visionCmd
5405
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5406
- style: {
5407
- ...S.input,
5408
- width: "100%",
5409
- boxSizing: "border-box",
5410
- ...S.mono
5411
- },
5412
- value: provForm.vision_command,
5413
- placeholder: "mycli see {image} --ask {question} --model {model}",
5414
- onChange: (e) => setProvForm({
5415
- ...provForm,
5416
- vision_command: e.target.value
5417
- })
5418
- })]
5419
- }),
5420
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
5421
- style: S.field,
5422
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5423
- style: S.fieldLabel,
5424
- children: copy.imagegenCmd
5425
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5426
- style: {
5427
- ...S.input,
5428
- width: "100%",
5429
- boxSizing: "border-box",
5430
- ...S.mono
5431
- },
5432
- value: provForm.imagegen_command,
5433
- placeholder: "mycli gen {prompt} -o {output}",
5434
- onChange: (e) => setProvForm({
5435
- ...provForm,
5436
- imagegen_command: e.target.value
5437
- })
5438
- })]
5439
- }),
5440
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
5441
- style: S.field,
5442
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5443
- style: S.fieldLabel,
5444
- children: copy.modelsField
5445
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5446
- style: {
5447
- ...S.input,
5448
- width: "100%",
5449
- boxSizing: "border-box",
5450
- ...S.mono
5451
- },
5452
- value: provForm.models,
5453
- placeholder: "model-a, model-b",
5454
- onChange: (e) => setProvForm({
5455
- ...provForm,
5456
- models: e.target.value
5457
- })
5458
- })]
5459
- }),
5460
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5461
- style: {
5462
- fontSize: 11.5,
5463
- opacity: .55
5464
- },
5465
- children: copy.cliFormHint
5466
- })
5467
- ] }),
5468
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5469
- style: {
5470
- display: "flex",
5471
- gap: 8,
5472
- alignItems: "center"
5473
- },
5474
- children: [
5475
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5476
- style: S.btn,
5477
- onClick: saveProviderForm,
5478
- children: copy.saveProvider
5479
- }),
5480
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5481
- style: S.btn,
5482
- title: copy.testTip,
5483
- disabled: !!testResult?.busy,
5484
- onClick: () => runProviderTest("form", {
5485
- id: provForm.originalId ?? "draft",
5486
- name: provForm.name || "draft",
5487
- type: provForm.type,
5488
- models: provForm.models.split(/[,,\n]+/).map((x) => x.trim()).filter(Boolean),
5489
- base_url: provForm.base_url.trim(),
5490
- api_key: provForm.api_key,
5491
- imagegen_model: provForm.imagegen_model.trim(),
5492
- vision_command: provForm.vision_command.trim(),
5493
- imagegen_command: provForm.imagegen_command.trim()
5494
- }),
5495
- children: testResult?.key === "form" && testResult.busy ? copy.testing : copy.testProvider
5496
- }),
5497
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5498
- style: {
5499
- ...S.btn,
5500
- opacity: .75
5501
- },
5502
- onClick: () => setProvForm(null),
5503
- children: copy.cancel
5504
- })
5505
- ]
5506
- }),
5507
- testReport("form")
5508
- ]
5509
- })
5510
- ] }), false)
5511
4608
  })
5512
4609
  ] }),
5513
4610
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {