codebee 0.1.5 → 0.1.6
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/CHANGELOG.md +8 -0
- package/README.md +4 -5
- package/app/core/health.py +55 -1
- package/app/core/jobs.py +10 -2
- package/app/core/manager.py +9 -1
- package/app/core/modelhub.py +66 -15
- package/app/core/pipeline.py +126 -60
- package/app/core/registry.py +4 -0
- package/app/core/router.py +5 -4
- package/app/core/runner.py +12 -3
- package/app/core/selfupdate.py +7 -1
- package/app/ui/app.js +168 -66
- package/app/ui/i18n.js +13 -5
- package/app/ui/icons/bee.svg +79 -0
- package/app/ui/index.html +2 -1
- package/app/ui/style.css +189 -342
- package/package.json +1 -1
package/app/ui/app.js
CHANGED
|
@@ -567,7 +567,9 @@ function renderHealthBanner(health) {
|
|
|
567
567
|
el.className = "health-banner alerting";
|
|
568
568
|
el.title = alerts.map((p) =>
|
|
569
569
|
p.provider + (p.model ? " · " + p.model : "") +
|
|
570
|
-
|
|
570
|
+
(p.static
|
|
571
|
+
? t(":") + (p.last_error || t("未知错误"))
|
|
572
|
+
: t(":连续失败 ") + p.consecutive_failures + t(" 次(") + (p.last_error || t("未知错误")) + t(")"))
|
|
571
573
|
).join("\n");
|
|
572
574
|
if (!_healthBeeped) { _healthBeeped = true; beepAttention(); }
|
|
573
575
|
}
|
|
@@ -575,7 +577,7 @@ function renderHealthBanner(health) {
|
|
|
575
577
|
function healthBannerText(health) {
|
|
576
578
|
return ((health && health.alerts) || []).map((p) => {
|
|
577
579
|
const m = p.model ? " · " + p.model : "";
|
|
578
|
-
return "⚠ " + p.provider + m + " " + t("连接异常");
|
|
580
|
+
return "⚠ " + p.provider + m + " " + (p.static ? t("绑定链失效") : t("连接异常"));
|
|
579
581
|
}).join(t(" "));
|
|
580
582
|
}
|
|
581
583
|
|
|
@@ -584,19 +586,21 @@ function healthBannerClick() {
|
|
|
584
586
|
if (!alerts.length) return;
|
|
585
587
|
const rows = alerts.map((p) => {
|
|
586
588
|
const model = p.model ? " · " + p.model : "";
|
|
587
|
-
|
|
588
|
-
"<b>" + esc(p.provider) + (p.model ? " · " + esc(p.model) : "") + "</b>" +
|
|
589
|
+
const statLine = p.static ? "" :
|
|
589
590
|
"<div style='opacity:.75;font-size:12px;margin-top:2px'>" +
|
|
590
591
|
t("连续失败") + " " + p.consecutive_failures + " " + t("次 · 首次失败 ") + (p.first_fail_at || "-") +
|
|
591
|
-
"</div>"
|
|
592
|
+
"</div>";
|
|
593
|
+
return "<div style='margin-bottom:10px'>" +
|
|
594
|
+
"<b>" + esc(p.provider) + (p.model ? " · " + esc(p.model) : "") + "</b>" +
|
|
595
|
+
statLine +
|
|
592
596
|
"<div style='color:#dc2626;font-size:12px;margin-top:2px;word-break:break-all'>" +
|
|
593
597
|
esc(p.last_error || "") + "</div></div>";
|
|
594
598
|
}).join("");
|
|
595
599
|
const pid0 = alerts[0].provider_id || "";
|
|
596
600
|
const model0 = alerts[0].model || "";
|
|
597
601
|
const foot =
|
|
598
|
-
(model0 ? "<button class='btn ghost' onclick=\"healthDisableModel('" + esc(pid0) + "','" + esc(model0) + "')\">" + t("禁用该模型") + "</button>" : "") +
|
|
599
|
-
"<button class='btn ghost' onclick=\"healthDisableProvider('" + esc(pid0) + "')\">" + t("禁用该厂商") + "</button>" +
|
|
602
|
+
(pid0 && model0 ? "<button class='btn ghost' onclick=\"healthDisableModel('" + esc(pid0) + "','" + esc(model0) + "')\">" + t("禁用该模型") + "</button>" : "") +
|
|
603
|
+
(pid0 ? "<button class='btn ghost' onclick=\"healthDisableProvider('" + esc(pid0) + "')\">" + t("禁用该厂商") + "</button>" : "") +
|
|
600
604
|
"<button class='btn ghost' onclick=\"healthOp('silence')\">" + t("静默本次告警") + "</button>" +
|
|
601
605
|
"<button class='btn ghost' onclick=\"healthOp('reset')\">" + t("手动标记恢复") + "</button>" +
|
|
602
606
|
"<button class='primary' onclick='closeModal()'>" + t("关闭") + "</button>";
|
|
@@ -640,6 +644,7 @@ async function healthDisableModel(pid, model) {
|
|
|
640
644
|
S.provSig = "";
|
|
641
645
|
closeModal(); render(); loadOrchestrator();
|
|
642
646
|
toast(t("已禁用模型 {0} · {1},链降级自动跳过;绑定页可重新启用").replace("{0}", pid).replace("{1}", model));
|
|
647
|
+
autoRebindSoon();
|
|
643
648
|
} catch (e) {
|
|
644
649
|
toast(t("操作失败:") + e.message, true);
|
|
645
650
|
}
|
|
@@ -663,6 +668,7 @@ async function healthDisableProvider(pid) {
|
|
|
663
668
|
S.provSig = "";
|
|
664
669
|
closeModal(); render(); loadOrchestrator();
|
|
665
670
|
toast(t("已禁用厂商 {0}:链降级自动跳过,绑定页可重新启用").replace("{0}", pid));
|
|
671
|
+
autoRebindSoon();
|
|
666
672
|
} catch (e) {
|
|
667
673
|
toast(t("操作失败:") + e.message, true);
|
|
668
674
|
}
|
|
@@ -848,19 +854,19 @@ function renderBindings() {
|
|
|
848
854
|
}).join("");
|
|
849
855
|
const bound = provs.find((p) => p.id === b.provider_id);
|
|
850
856
|
const offWarn = bound && bound.enabled === false
|
|
851
|
-
? '<p class="hint warn">' + t("
|
|
857
|
+
? '<p class="hint warn">' + t("该供应商已停用:编排时不会注入它,模型链因此失效时相关步骤会直接判失败。") + '</p>' : "";
|
|
852
858
|
const protoWarn = bound && !bindable.some((p) => p.id === bound.id)
|
|
853
859
|
? '<p class="hint warn">' + t("该供应商协议为 ") + esc(protoLabel(bound)) +
|
|
854
|
-
t(",当前没有可注入的 CLI
|
|
855
|
-
//
|
|
856
|
-
//
|
|
857
|
-
//
|
|
860
|
+
t(",当前没有可注入的 CLI,编排时相关步骤会直接判失败。") + '</p>' : "";
|
|
861
|
+
// 供应商停用但链上勾选了它家模型:链条目在解析时也会被整条跳过,链空了
|
|
862
|
+
// 相关步骤会直接判失败(2026-09-17 起;此前的静默回落本机默认正是 2026-09-16
|
|
863
|
+
// 配额事故的隐藏形态)——单说「供应商下拉」警告不够,链本身的死活也要点名。
|
|
858
864
|
const chainDead = bindChain(b).some((c2) => c2.p && (() => {
|
|
859
865
|
const pv = provs.find((p) => p.id === c2.p);
|
|
860
866
|
return !pv || pv.enabled === false;
|
|
861
867
|
})());
|
|
862
868
|
const chainWarn = (!bound || bound.enabled !== false) && chainDead
|
|
863
|
-
? '<p class="hint warn">' + t("
|
|
869
|
+
? '<p class="hint warn">' + t("模型链里有已停用/已删除的供应商:这些条目解析时会被跳过,链可能因此整体失效,相关步骤将判失败。") + '</p>' : "";
|
|
864
870
|
return '<div class="card"><div class="head"><span class="name">' + esc(c.name) + "</span>" +
|
|
865
871
|
'<span class="tag">' + esc(c.orch_kind) + "</span></div>" +
|
|
866
872
|
'<div class="field"><label>' + t("供应商") + '</label><select id="bindprov-' + esc(c.id) + '">' + opts + "</select></div>" +
|
|
@@ -964,6 +970,7 @@ async function batchProvOp(op) {
|
|
|
964
970
|
try {
|
|
965
971
|
await api("/api/models/provider-op", { method: "POST",
|
|
966
972
|
body: JSON.stringify({ ids, op }) });
|
|
973
|
+
if (op !== "duplicate") autoRebindSoon();
|
|
967
974
|
} catch (e) { toast(t("操作失败:") + e.message, true); }
|
|
968
975
|
S.selProvs = {};
|
|
969
976
|
S.modelsSig = null;
|
|
@@ -1150,6 +1157,7 @@ async function keyOpCall(pid, op, keyId, key, label) {
|
|
|
1150
1157
|
try {
|
|
1151
1158
|
await api("/api/models/key-op", { method: "POST",
|
|
1152
1159
|
body: JSON.stringify({ provider_id: pid, op, key_id: keyId, key, label }) });
|
|
1160
|
+
autoRebindSoon(); // 密钥启停/删除/解冻会改变厂商「推荐可用」判定,自动补绑一次
|
|
1153
1161
|
} catch (e) { toast(t("操作失败:") + e.message, true); }
|
|
1154
1162
|
// 操作期间轮询照跑(uiPrompt 弹框不阻塞轮询),S.modelsSig 还是旧值:必须
|
|
1155
1163
|
// 清签名强制重绘,否则 KEY 写盘成功界面也看不到。
|
|
@@ -1359,6 +1367,7 @@ async function modelOp(pid, name, op) {
|
|
|
1359
1367
|
try {
|
|
1360
1368
|
await api("/api/models/model-op", { method: "POST",
|
|
1361
1369
|
body: JSON.stringify({ provider_id: pid, name, op }) });
|
|
1370
|
+
autoRebindSoon(); // 模型停用/启用/删除会改变推荐源与死链判定,自动补绑一次
|
|
1362
1371
|
const s = modelSel(pid); // 单行操作后同步清掉该行勾选
|
|
1363
1372
|
if (s[name]) { delete modelSelSet(pid)[name]; }
|
|
1364
1373
|
} catch (e) { toast(t("操作失败:") + e.message, true); }
|
|
@@ -1425,6 +1434,7 @@ async function batchModelOp(pid, op) {
|
|
|
1425
1434
|
try {
|
|
1426
1435
|
await api("/api/models/model-op", { method: "POST",
|
|
1427
1436
|
body: JSON.stringify({ provider_id: pid, names, op }) });
|
|
1437
|
+
autoRebindSoon();
|
|
1428
1438
|
} catch (e) { toast(t("操作失败:") + e.message, true); }
|
|
1429
1439
|
clearModelSel(pid);
|
|
1430
1440
|
poll();
|
|
@@ -1443,6 +1453,7 @@ async function toggleProviderEnabled(pid, enabled) {
|
|
|
1443
1453
|
try {
|
|
1444
1454
|
await api("/api/models/provider-op", { method: "POST",
|
|
1445
1455
|
body: JSON.stringify({ ids: [pid], op: off ? "enable" : "disable" }) });
|
|
1456
|
+
autoRebindSoon();
|
|
1446
1457
|
} catch (e) { toast(t("操作失败:") + e.message, true); }
|
|
1447
1458
|
S.modelsSig = null;
|
|
1448
1459
|
poll();
|
|
@@ -1466,7 +1477,8 @@ async function refreshAllModels() {
|
|
|
1466
1477
|
btn.disabled = false; btn.classList.remove("loading");
|
|
1467
1478
|
btn.title = t("全部重新拉取模型列表");
|
|
1468
1479
|
}, 1500);
|
|
1469
|
-
setTimeout(poll, 2500);
|
|
1480
|
+
setTimeout(poll, 2500);
|
|
1481
|
+
setTimeout(autoRebindSoon, 8000); // 模型列表落盘后推荐源才完整,补绑一次
|
|
1470
1482
|
}
|
|
1471
1483
|
|
|
1472
1484
|
async function refreshProviderModels(id) {
|
|
@@ -1477,7 +1489,7 @@ async function refreshProviderModels(id) {
|
|
|
1477
1489
|
else toast(t("已获取模型列表 · wire 适配测试后台进行中"));
|
|
1478
1490
|
} catch (e) { toast(t("获取失败:") + e.message, true); }
|
|
1479
1491
|
poll();
|
|
1480
|
-
setTimeout(
|
|
1492
|
+
setTimeout(autoRebindSoon, 4000); // 模型列表落盘后补绑一次(后台 wire 探测同步进行)
|
|
1481
1493
|
}
|
|
1482
1494
|
|
|
1483
1495
|
/* 供应商编辑卡:协议可选「自动」;显式选定时它是主协议(用于需要唯一协议的
|
|
@@ -1533,6 +1545,7 @@ async function delProvider(id) {
|
|
|
1533
1545
|
try {
|
|
1534
1546
|
await api("/api/models/provider-op", { method: "POST",
|
|
1535
1547
|
body: JSON.stringify({ ids: [id], op: "delete" }) });
|
|
1548
|
+
autoRebindSoon();
|
|
1536
1549
|
} catch (e) { toast(t("操作失败:") + e.message, true); }
|
|
1537
1550
|
if (S.selModels) delete S.selModels[id];
|
|
1538
1551
|
if (S.selProvs) delete S.selProvs[id];
|
|
@@ -1596,6 +1609,7 @@ async function doAddProvider() {
|
|
|
1596
1609
|
S.modelsSig = null;
|
|
1597
1610
|
closeModal();
|
|
1598
1611
|
poll();
|
|
1612
|
+
autoRebindSoon(); // 新增即启用:马上给空链/死链一次推荐机会
|
|
1599
1613
|
if (p && body.api_key) refreshProviderModels(p.id); // 有密钥才自动拉模型列表
|
|
1600
1614
|
} catch (e) {
|
|
1601
1615
|
res.textContent = t("保存失败:") + e.message;
|
|
@@ -1691,7 +1705,7 @@ async function doImport() {
|
|
|
1691
1705
|
btn.textContent = t("完成");
|
|
1692
1706
|
btn.disabled = false;
|
|
1693
1707
|
btn.onclick = closeModal;
|
|
1694
|
-
if (r.imported) { S.selProv = null; S.modelsSig = null; poll(); }
|
|
1708
|
+
if (r.imported) { S.selProv = null; S.modelsSig = null; poll(); autoRebindSoon(); }
|
|
1695
1709
|
} catch (e) {
|
|
1696
1710
|
$("import-result").innerHTML = '<div class="msg bad">' + t("导入失败:") + esc(e.message) + "</div>";
|
|
1697
1711
|
btn.disabled = false; btn.textContent = t("导入选中");
|
|
@@ -4805,7 +4819,9 @@ window.renderHive = function (run) {
|
|
|
4805
4819
|
const key = run.id + "|" + (s.log || "");
|
|
4806
4820
|
if (st !== "running") delete hiveTails[key];
|
|
4807
4821
|
const tailText = st === "running" ? (hiveTails[key] || concl) : concl;
|
|
4808
|
-
const title = [s.role, who,
|
|
4822
|
+
const title = [s.role, who,
|
|
4823
|
+
(s.duration_s != null ? s.duration_s + "s" : ""),
|
|
4824
|
+
s.started_at ? t("开始于 ") + s.started_at : "",
|
|
4809
4825
|
(s.note ? "◆ " + s.note : "")].filter(Boolean).join(" · ")
|
|
4810
4826
|
+ (concl && st !== "running" ? "\n" + t("结论:") + concl : "");
|
|
4811
4827
|
// --d:入场瀑布逐格延迟(泳道间 110ms + 同泳道逐格 45ms),样式端消费
|
|
@@ -4814,7 +4830,7 @@ window.renderHive = function (run) {
|
|
|
4814
4830
|
' title="' + esc(title) + '" ' +
|
|
4815
4831
|
'onclick="hiveOpenLog(\'' + esc(run.id) + "', '" + esc(s.log || "") + '\')">' +
|
|
4816
4832
|
'<div class="hc-head">' +
|
|
4817
|
-
'<
|
|
4833
|
+
'<img class="hc-bee" src="icons/bee.svg" alt="" aria-hidden="true">' +
|
|
4818
4834
|
'<span class="hc-role">' + esc(s.role || "") + "</span>" +
|
|
4819
4835
|
'<span class="hc-who">' + esc(who) + "</span></div>" +
|
|
4820
4836
|
'<div class="hc-tail" data-log="' + esc(s.log || "") + '">' +
|
|
@@ -4852,7 +4868,13 @@ window.renderHive = function (run) {
|
|
|
4852
4868
|
(hasRun ? '<span class="lane-live"><i class="live-dot"></i>' + t("进行中") + "</span>" : "") + "</div>" +
|
|
4853
4869
|
'<div class="lane-track">' + dots(list, i) + "</div>" +
|
|
4854
4870
|
'<div class="lane-cells">' + list.slice(-8).map((s, ci) => cell(s, i, ci)).join("") + "</div></div>";
|
|
4855
|
-
}).join('<div class="lane-flow" aria-hidden="true"
|
|
4871
|
+
}).join('<div class="lane-flow" aria-hidden="true">'
|
|
4872
|
+
+ '<svg class="flow-line flow-line-down" viewBox="0 0 36 44" aria-hidden="true">'
|
|
4873
|
+
+ '<path class="flow-path" d="M14 2 C14 16 22 28 22 42"/><path class="flow-tip" d="M16 36 L22 43 L28 36"/></svg>'
|
|
4874
|
+
+ '<svg class="flow-line flow-line-up" viewBox="0 0 36 44" aria-hidden="true">'
|
|
4875
|
+
+ '<path class="flow-path" d="M22 2 C22 16 14 28 14 42"/><path class="flow-tip" d="M8 36 L14 43 L20 36"/></svg>'
|
|
4876
|
+
+ '<span class="flow-bee"><img src="icons/bee.svg" alt="" aria-hidden="true"></span>'
|
|
4877
|
+
+ '</div>');
|
|
4856
4878
|
// 入场瀑布闸门:只按"结构"(泳道数/格数/状态构成)签名;运行中尾巴/秒表每 2s
|
|
4857
4879
|
// 变化不能触重播。签名没变就不动 DOM——格子不闪、动画不重启、悬停不弹手。
|
|
4858
4880
|
const sig = lanes.map((l) => l + ":" + byStage[l].length + "(" +
|
|
@@ -5761,20 +5783,20 @@ function bindSelById(id) {
|
|
|
5761
5783
|
function bindRepaint() { S.bindSig = null; renderBindings(); }
|
|
5762
5784
|
|
|
5763
5785
|
/* 一键推荐绑定:给每个链为空的 CLI 按协议适配规则预填一条主模型。
|
|
5764
|
-
* 推荐顺序:显式协议原生匹配 > auto 且 wire_caps
|
|
5765
|
-
*
|
|
5766
|
-
*
|
|
5786
|
+
* 推荐顺序:显式协议原生匹配 > auto 且 wire_caps 命中;同分按 priority /
|
|
5787
|
+
* 数组序稳定。默认模型停用的厂商直接不推荐——它当前键位就是坏的,绑上去
|
|
5788
|
+
* 解析照样失败;没有合适的就什么都不绑(2026-09-17 用户拍板,不再保留
|
|
5789
|
+
* 「只剩它也上」的兜底)。只预填草稿态(dirty),逐条看清后各自保存
|
|
5767
5790
|
* 或「全部保存」,健康链绝不被覆盖。 */
|
|
5768
5791
|
function recommendFor(c) {
|
|
5769
|
-
|
|
5770
|
-
|
|
5792
|
+
// 目录页「默认模型」下拉用 orch_kind || id 判协议(仅管理条目没有 orch_kind),
|
|
5793
|
+
// 推荐口径与下拉过滤保持一致;绑定页条目恒有 orch_kind,不受影响
|
|
5794
|
+
const allow = bindAllowedProtocols(c.orch_kind || c.id);
|
|
5795
|
+
const badDefault = (p) => (p.models || []).some((m) => m.name === p.model &&
|
|
5796
|
+
(m.hidden || m.enabled === false));
|
|
5797
|
+
const provs = (S.providers || []).filter((p) => !badDefault(p) && chainProvUsable(p, allow));
|
|
5771
5798
|
if (!provs.length) return null;
|
|
5772
|
-
const score = (p) =>
|
|
5773
|
-
const native = allow.indexOf(p.protocol) >= 0;
|
|
5774
|
-
const badDefault = (p.models || []).some((m) => m.name === p.model &&
|
|
5775
|
-
(m.hidden || m.enabled === false)) ? 500 : 0;
|
|
5776
|
-
return (native ? 0 : 1) * 1000 + badDefault + (p.priority || 0);
|
|
5777
|
-
};
|
|
5799
|
+
const score = (p) => (allow.indexOf(p.protocol) >= 0 ? 0 : 1) * 1000 + (p.priority || 0);
|
|
5778
5800
|
provs.sort((a, b) => score(a) - score(b));
|
|
5779
5801
|
const p = provs[0];
|
|
5780
5802
|
const models = (p.models || []).filter((m) => !m.hidden && m.enabled !== false);
|
|
@@ -5812,48 +5834,38 @@ function chainDeadReasons(c, chain) {
|
|
|
5812
5834
|
return dead;
|
|
5813
5835
|
}
|
|
5814
5836
|
|
|
5837
|
+
/* 单条绑定链的推荐修复动作:需要改返回新链,不动返回 null。
|
|
5838
|
+
* 空链 → 预填推荐;整条死透(或只有链首且已死)→ 重推荐;链首死但链内还有
|
|
5839
|
+
* 活的备选 → 新链首插最前(原降级序保留,推荐项已在链内则升首去重)。 */
|
|
5840
|
+
function bindRepairAction(c, st) {
|
|
5841
|
+
const rec = recommendFor(c);
|
|
5842
|
+
if (!st.chain.length) return rec ? [rec] : null;
|
|
5843
|
+
const dead = chainDeadReasons(c, st.chain);
|
|
5844
|
+
const allDead = dead.every((d) => d);
|
|
5845
|
+
const headDead = dead[0] !== ""; // 空 p = CLI 默认凭据,是有意配置不算死
|
|
5846
|
+
if (allDead) return rec ? [rec] : null;
|
|
5847
|
+
if (headDead && rec && chainKey(st.chain) !== chainKey([rec]))
|
|
5848
|
+
return [rec].concat(st.chain.filter((x) => !(x.p === rec.p && x.m === rec.m)));
|
|
5849
|
+
return null;
|
|
5850
|
+
}
|
|
5851
|
+
|
|
5815
5852
|
function autoBindAll() {
|
|
5816
5853
|
const targets = (S.catalog || []).filter((c) => c.installed && c.orch_kind);
|
|
5817
5854
|
let filled = 0, skipped = 0, noProv = 0, refilled = 0;
|
|
5818
5855
|
for (const c of targets) {
|
|
5819
5856
|
const st = bindSelById(c.id);
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
st.chain
|
|
5824
|
-
|
|
5825
|
-
|
|
5857
|
+
const act = bindRepairAction(c, st);
|
|
5858
|
+
if (!act) {
|
|
5859
|
+
// 没动它:分清「健康链无需推荐」和「没有可推荐的」两种落空
|
|
5860
|
+
if (!st.chain.length) { noProv++; continue; }
|
|
5861
|
+
const dead = chainDeadReasons(c, st.chain);
|
|
5862
|
+
if (dead.every((d) => d)) noProv++; else skipped++;
|
|
5826
5863
|
continue;
|
|
5827
5864
|
}
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
|
|
5832
|
-
const allDead = aliveN === 0;
|
|
5833
|
-
const headDead = dead[0] !== ""; // 空 p = CLI 默认凭据,是有意配置不算死
|
|
5834
|
-
const rec = recommendFor(c);
|
|
5835
|
-
if (allDead && rec) {
|
|
5836
|
-
st.chain = [rec];
|
|
5837
|
-
st.dirty = true;
|
|
5838
|
-
refilled++;
|
|
5839
|
-
} else if (allDead) {
|
|
5840
|
-
noProv++;
|
|
5841
|
-
} else if (headDead && rec && chainKey(st.chain) !== chainKey([rec])) {
|
|
5842
|
-
// 链首死但链内还有活的备选:只把推荐的新链首插到最前面(原降级序保留)
|
|
5843
|
-
const hasRec = st.chain.some((x) => x.p === rec.p && x.m === rec.m);
|
|
5844
|
-
if (!hasRec) {
|
|
5845
|
-
st.chain = [rec].concat(st.chain);
|
|
5846
|
-
st.dirty = true;
|
|
5847
|
-
refilled++;
|
|
5848
|
-
} else {
|
|
5849
|
-
// 推荐项已在链内:把它升到链首
|
|
5850
|
-
st.chain = [rec].concat(st.chain.filter((x) => x !== rec));
|
|
5851
|
-
st.dirty = true;
|
|
5852
|
-
refilled++;
|
|
5853
|
-
}
|
|
5854
|
-
} else {
|
|
5855
|
-
skipped++;
|
|
5856
|
-
}
|
|
5865
|
+
const wasEmpty = !st.chain.length;
|
|
5866
|
+
st.chain = act;
|
|
5867
|
+
st.dirty = true;
|
|
5868
|
+
if (wasEmpty) filled++; else refilled++;
|
|
5857
5869
|
}
|
|
5858
5870
|
bindRepaint();
|
|
5859
5871
|
if (filled && refilled) {
|
|
@@ -5871,6 +5883,56 @@ function autoBindAll() {
|
|
|
5871
5883
|
}
|
|
5872
5884
|
}
|
|
5873
5885
|
|
|
5886
|
+
/* 厂商/模型停用·启用·删除后自动补一次推荐绑定(2026-09-17 用户拍板):
|
|
5887
|
+
* 绑定链——空链预填、死链重推荐、死链首插新首,与「一键推荐绑定」同规则,
|
|
5888
|
+
* 但直接落盘(自动场景没有人工确认环节);目录页——空默认模型直填。
|
|
5889
|
+
* 没有合适的推荐就保持原样,什么都不绑。绑定页上用户手改中的草稿(dirty)
|
|
5890
|
+
* 不碰;一处都没改成静默返回,不打扰停用/启用的操作反馈。 */
|
|
5891
|
+
let _autoRebindRunning = false, _autoRebindAgain = false;
|
|
5892
|
+
async function autoRebindSoon() {
|
|
5893
|
+
if (_autoRebindRunning) { _autoRebindAgain = true; return; }
|
|
5894
|
+
_autoRebindRunning = true;
|
|
5895
|
+
try {
|
|
5896
|
+
await poll(); // 拿停用/启用落盘后的最新 providers / bindings / catalog
|
|
5897
|
+
let fixed = 0;
|
|
5898
|
+
for (const c of (S.catalog || []).filter((x) => x.installed && x.orch_kind)) {
|
|
5899
|
+
const st = bindSelById(c.id);
|
|
5900
|
+
if (st.dirty) continue; // 用户手改中,不覆盖草稿
|
|
5901
|
+
const act = bindRepairAction(c, st);
|
|
5902
|
+
if (!act) continue;
|
|
5903
|
+
const b = (S.bindings || {})[c.id] || {};
|
|
5904
|
+
try {
|
|
5905
|
+
await api("/api/models/binding", { method: "POST", body: JSON.stringify({
|
|
5906
|
+
agent_id: c.id, provider_id: act[0].p,
|
|
5907
|
+
chain: act.map((x) => ({ provider_id: x.p, model: x.m })),
|
|
5908
|
+
difficulty_routing: !!b.difficulty_routing }) });
|
|
5909
|
+
st.chain = act; st.dirty = false; st.key = chainKey(act);
|
|
5910
|
+
fixed++;
|
|
5911
|
+
} catch (e) { /* 单条失败不打断,等下次变更再补 */ }
|
|
5912
|
+
}
|
|
5913
|
+
for (const c of (S.catalog || []).filter((x) =>
|
|
5914
|
+
x.installed && x.config_writable && !fmtModel(x.model))) {
|
|
5915
|
+
const rec = recommendFor(c);
|
|
5916
|
+
if (!rec) continue;
|
|
5917
|
+
try {
|
|
5918
|
+
const r = await api("/api/catalog/" + encodeURIComponent(c.id) + "/model",
|
|
5919
|
+
{ method: "POST", body: JSON.stringify({ model: rec.m }) });
|
|
5920
|
+
c.model = r.model || rec.m;
|
|
5921
|
+
fixed++;
|
|
5922
|
+
} catch (e) { /* 同上 */ }
|
|
5923
|
+
}
|
|
5924
|
+
if (fixed) {
|
|
5925
|
+
S.catSig = null; S.bindSig = null;
|
|
5926
|
+
render();
|
|
5927
|
+
toast(t("厂商/模型变动,已自动重绑 %1 处。").replace("%1", fixed));
|
|
5928
|
+
}
|
|
5929
|
+
} catch (e) { /* 自动补绑失败静默:不打断用户的停用/启用操作 */ }
|
|
5930
|
+
finally {
|
|
5931
|
+
_autoRebindRunning = false;
|
|
5932
|
+
if (_autoRebindAgain) { _autoRebindAgain = false; autoRebindSoon(); }
|
|
5933
|
+
}
|
|
5934
|
+
}
|
|
5935
|
+
|
|
5874
5936
|
async function saveAllBindings() {
|
|
5875
5937
|
const targets = (S.catalog || []).filter((c) => c.installed && c.orch_kind);
|
|
5876
5938
|
const dirty = targets.filter((c) => (S.bindSel[c.id] || {}).dirty);
|
|
@@ -5916,7 +5978,7 @@ function bindModelBox(c, provId) {
|
|
|
5916
5978
|
"</span>";
|
|
5917
5979
|
}).join("")
|
|
5918
5980
|
: '<span class="hint">' + t("未设置") + (provId
|
|
5919
|
-
? t("
|
|
5981
|
+
? t("(按供应商/难度自动解析——供应商协议不匹配或被停用时解析为空,相关步骤将判失败)")
|
|
5920
5982
|
: t("(用 CLI 默认模型——不会注入任何供应商凭据)")) + "</span>";
|
|
5921
5983
|
return '<div class="field"><label>' + t("运行时模型链(跨厂商,最多 ") + MAX_ORCH_MODELS + t(" 条)") + "</label>" +
|
|
5922
5984
|
'<div class="orch-row">' + chips +
|
|
@@ -6106,6 +6168,45 @@ async function saveModel(id) {
|
|
|
6106
6168
|
poll();
|
|
6107
6169
|
}
|
|
6108
6170
|
|
|
6171
|
+
/* 一键绑定推荐模型(智能体目录页):给「默认模型」还空着的已安装智能体按
|
|
6172
|
+
* 协议适配规则(复用绑定页 recommendFor 评分)挑一家厂商的启用模型并直接
|
|
6173
|
+
* 写入该 CLI 自身配置(写入侧先自动 .bak 备份)。已配置的不动——目录页的
|
|
6174
|
+
* 默认模型会写进 CLI 全局配置,手工设置过的值(含 CLI 订阅自带的默认)不
|
|
6175
|
+
* 该被一键覆盖;要换模型逐卡下拉改就行。 */
|
|
6176
|
+
async function catAutoBindAll() {
|
|
6177
|
+
const targets = (S.catalog || []).filter((c) => c.installed && c.config_writable);
|
|
6178
|
+
if (!targets.length) { toast(t("没有支持写入默认模型的已安装智能体。"), true); return; }
|
|
6179
|
+
const empty = targets.filter((c) => !fmtModel(c.model));
|
|
6180
|
+
if (!empty.length) { toast(t("所有已安装智能体都已配置默认模型,无需绑定。")); return; }
|
|
6181
|
+
let done = 0, failed = 0, noProv = 0, firstErr = "";
|
|
6182
|
+
for (const c of empty) {
|
|
6183
|
+
const rec = recommendFor(c);
|
|
6184
|
+
if (!rec) { noProv++; continue; }
|
|
6185
|
+
try {
|
|
6186
|
+
const r = await api("/api/catalog/" + encodeURIComponent(c.id) + "/model",
|
|
6187
|
+
{ method: "POST", body: JSON.stringify({ model: rec.m }) });
|
|
6188
|
+
c.model = r.model || rec.m;
|
|
6189
|
+
done++;
|
|
6190
|
+
} catch (e) {
|
|
6191
|
+
failed++;
|
|
6192
|
+
if (!firstErr) firstErr = c.name + ":" + e.message;
|
|
6193
|
+
}
|
|
6194
|
+
}
|
|
6195
|
+
S.catSig = null;
|
|
6196
|
+
renderCatalog();
|
|
6197
|
+
if (done && failed) {
|
|
6198
|
+
toast(t("已为 %1 个智能体写入推荐模型,%2 个失败。").replace("%1", done).replace("%2", failed) +
|
|
6199
|
+
(firstErr ? " " + firstErr : ""), true);
|
|
6200
|
+
} else if (done) {
|
|
6201
|
+
toast(t("已为 %1 个智能体写入推荐模型(原配置已自动备份 .bak)。").replace("%1", done));
|
|
6202
|
+
} else if (failed) {
|
|
6203
|
+
toast(t("推荐模型写入失败:") + firstErr, true);
|
|
6204
|
+
} else {
|
|
6205
|
+
toast(t("没有可推荐的:先到「模型接入」页导入与 CLI 协议匹配的供应商。"), true);
|
|
6206
|
+
}
|
|
6207
|
+
poll();
|
|
6208
|
+
}
|
|
6209
|
+
|
|
6109
6210
|
/* 一键打开:web 类后台起服务并自动开浏览器;console 类新开终端窗口跑交互 TUI。
|
|
6110
6211
|
用的模型就是目录页「默认模型」已写入该 CLI 配置文件的那份;密钥由服务端按编排同款规则注入 */
|
|
6111
6212
|
async function openAgent(id) {
|
|
@@ -8258,6 +8359,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
8258
8359
|
$("btn-reload-catalog").addEventListener("click", async () => {
|
|
8259
8360
|
await api("/api/catalog/reload", { method: "POST" }); poll(); refreshSessionAgents();
|
|
8260
8361
|
});
|
|
8362
|
+
$("btn-catalog-autobind").addEventListener("click", catAutoBindAll);
|
|
8261
8363
|
$("btn-import").addEventListener("click", openImportDialog);
|
|
8262
8364
|
$("btn-add-provider").addEventListener("click", openAddProviderDialog);
|
|
8263
8365
|
$("prov-search").addEventListener("input", () => {
|
package/app/ui/i18n.js
CHANGED
|
@@ -577,8 +577,8 @@
|
|
|
577
577
|
"(用 CLI 默认模型)": " (use CLI default model)",
|
|
578
578
|
"(用 CLI 默认模型——不会注入任何供应商凭据)": " (use CLI default model — no provider credentials injected)",
|
|
579
579
|
"(按供应商/难度自动解析)": " (auto by provider/difficulty)",
|
|
580
|
-
"
|
|
581
|
-
"
|
|
580
|
+
"(按供应商/难度自动解析——供应商协议不匹配或被停用时解析为空,相关步骤将判失败)": " (auto by provider/difficulty — resolves to empty and the step fails hard if the provider's protocol mismatches or is disabled)",
|
|
581
|
+
"模型链里有已停用/已删除的供应商:这些条目解析时会被跳过,链可能因此整体失效,相关步骤将判失败。": "The model chain contains disabled/deleted providers: those entries are skipped at resolve time, which can invalidate the whole chain and make affected steps fail hard.",
|
|
582
582
|
"名称": "Name",
|
|
583
583
|
"默认模型": "Default model",
|
|
584
584
|
"API 地址": "API endpoint",
|
|
@@ -839,7 +839,7 @@
|
|
|
839
839
|
"重试": "Retry",
|
|
840
840
|
",点「升级到新版」即可": " — click \"Upgrade to new version\" to proceed",
|
|
841
841
|
"后点重新加载。": " and click Reload.",
|
|
842
|
-
"启用「参与编排」后,该智能体会出现在任务表单与评审组中。进入本页会自动检查各 CLI 是否有新版本;「默认模型」写入 CLI 自己的配置文件(CodeBee 之外也生效),CodeBee 编排运行时用哪个供应商 / 模型请到「CLI
|
|
842
|
+
"启用「参与编排」后,该智能体会出现在任务表单与评审组中。进入本页会自动检查各 CLI 是否有新版本;「默认模型」写入 CLI 自己的配置文件(CodeBee 之外也生效),CodeBee 编排运行时用哪个供应商 / 模型请到「CLI 绑定」页配置。点「一键绑定推荐模型」可为默认模型还空着的智能体自动填入推荐;厂商 / 模型停用或启用后也会自动补绑一次。安装命令不明的条目可直接编辑": "With \"Takes part in orchestration\" enabled, this agent shows up in the task form and review groups. Opening this page auto-checks each CLI for updates; \"default model\" is written into the CLI's own config file (takes effect outside CodeBee too) — to pick which provider/model CodeBee uses at runtime, go to the \"CLI Bindings\" page. \"Auto-bind recommended models\" fills in a recommendation for agents whose default model is empty; rebinding also runs automatically when providers/models are disabled or enabled. Entries with an unknown install command can be edited directly in",
|
|
843
843
|
"是默认皮肤,即原本的界面。": " is the default skin — the original interface.",
|
|
844
844
|
"每套皮肤都是一份完整配色,并且各带「日间 / 夜间」两版,明暗切换不会丢皮肤。选中的皮肤立即生效并记在这台设备上(手机与电脑各自独立)。": "Each skin is a complete palette, with day / night variants — switching brightness never loses your skin. The selected skin applies instantly and is remembered on this device (phone and computer are independent).",
|
|
845
845
|
"系统自己的经验沉淀:": "The system's own distilled experience:",
|
|
@@ -869,6 +869,13 @@
|
|
|
869
869
|
"模型已停用": "Model disabled",
|
|
870
870
|
"没有可推荐的:先到「模型接入」页导入与 CLI 协议匹配的供应商。": "Nothing to recommend — import providers matching the CLI protocols on the \"Models\" page first.",
|
|
871
871
|
"所有 CLI 都已配置模型链,无需推荐。": "All CLIs already have model chains — no recommendation needed.",
|
|
872
|
+
"一键绑定推荐模型": "Auto-bind recommended models",
|
|
873
|
+
"没有支持写入默认模型的已安装智能体。": "No installed agents support writing a default model.",
|
|
874
|
+
"所有已安装智能体都已配置默认模型,无需绑定。": "All installed agents already have a default model — nothing to bind.",
|
|
875
|
+
"已为 %1 个智能体写入推荐模型,%2 个失败。": "Wrote recommended models to %1 agent(s), %2 failed.",
|
|
876
|
+
"已为 %1 个智能体写入推荐模型(原配置已自动备份 .bak)。": "Wrote recommended models to %1 agent(s) (original configs backed up as .bak).",
|
|
877
|
+
"推荐模型写入失败:": "Failed to write recommended models: ",
|
|
878
|
+
"厂商/模型变动,已自动重绑 %1 处。": "Providers/models changed — %1 binding(s) auto-rebound.",
|
|
872
879
|
"已保存 %1 个 CLI 的绑定。": "Saved bindings for %1 CLI(s).",
|
|
873
880
|
"未安装": "Not installed",
|
|
874
881
|
"有新版本 ": "New version ",
|
|
@@ -1164,6 +1171,7 @@
|
|
|
1164
1171
|
"接管": "Take over",
|
|
1165
1172
|
"打开网页": "Open web",
|
|
1166
1173
|
"连接异常": "connection issue",
|
|
1174
|
+
"绑定链失效": "binding chain dead",
|
|
1167
1175
|
"连续失败": "consecutive failures",
|
|
1168
1176
|
"一致性": "Consistency",
|
|
1169
1177
|
"人物塑造": "Characters",
|
|
@@ -1193,10 +1201,10 @@
|
|
|
1193
1201
|
"),无需升级。": ") — no update needed.",
|
|
1194
1202
|
"+ 新建自定义流程": "+ New custom flow",
|
|
1195
1203
|
",可在「关于与更新」一键升级": " — one-click upgrade in About & Updates",
|
|
1196
|
-
",当前没有可注入的 CLI
|
|
1204
|
+
",当前没有可注入的 CLI,编排时相关步骤会直接判失败。": ", but no injectable CLI matches it — affected orchestration steps fail hard.",
|
|
1197
1205
|
":连续失败 ": ": consecutive failures ",
|
|
1198
1206
|
"该供应商协议为 ": "This provider's protocol is ",
|
|
1199
|
-
"
|
|
1207
|
+
"该供应商已停用:编排时不会注入它,模型链因此失效时相关步骤会直接判失败。": "This provider is disabled: orchestration won't inject it, and affected steps fail hard when the model chain is invalidated.",
|
|
1200
1208
|
"绑定后编排调用会注入该供应商的 API key 与地址;不绑定则只按下方模型链传 -m 参数。": "When bound, orchestrated calls inject this provider's API key and URL; otherwise only the model chain below is passed via -m.",
|
|
1201
1209
|
"还没有可选模型:先到「模型接入」页导入供应商并获取模型列表。": "No models available yet — import a provider and fetch its model list on the \"Models\" page first.",
|
|
1202
1210
|
"还没有已安装且可编排的 CLI——先到「智能体管理」页安装并启用。": "No installed, orchestration-capable CLI yet — install and enable one on the \"Agents\" page.",
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
|
2
|
+
<!-- CodeBee 写实小蜂:琥珀黑条纹腹 + 绒毛胸背 + 半透明翅脉 + 扇翅动画。
|
|
3
|
+
经 <img> 引用(独立文档,渐变 id 互不串扰);翅动媒体查询随系统 reduce-motion 关停。 -->
|
|
4
|
+
<defs>
|
|
5
|
+
<linearGradient id="cbAbd" x1="0" y1="0" x2="0" y2="1">
|
|
6
|
+
<stop offset="0" stop-color="#ffcd66"/>
|
|
7
|
+
<stop offset=".55" stop-color="#f0a92c"/>
|
|
8
|
+
<stop offset="1" stop-color="#c67c12"/>
|
|
9
|
+
</linearGradient>
|
|
10
|
+
<radialGradient id="cbThx" cx=".38" cy=".3" r="1.05">
|
|
11
|
+
<stop offset="0" stop-color="#7a5a26"/>
|
|
12
|
+
<stop offset=".6" stop-color="#3d2c10"/>
|
|
13
|
+
<stop offset="1" stop-color="#1f1505"/>
|
|
14
|
+
</radialGradient>
|
|
15
|
+
<linearGradient id="cbWing" x1="0" y1="0" x2="1" y2="1">
|
|
16
|
+
<stop offset="0" stop-color="#ffffff" stop-opacity=".85"/>
|
|
17
|
+
<stop offset="1" stop-color="#cfe0f5" stop-opacity=".16"/>
|
|
18
|
+
</linearGradient>
|
|
19
|
+
<clipPath id="cbAbdClip"><ellipse cx="32" cy="44" rx="11.5" ry="13.5"/></clipPath>
|
|
20
|
+
</defs>
|
|
21
|
+
<style>
|
|
22
|
+
.wingL, .wingR { transform-box: view-box; }
|
|
23
|
+
.wingL { transform-origin: 27px 21px; animation: cbFlapL .16s ease-in-out infinite alternate; }
|
|
24
|
+
.wingR { transform-origin: 37px 21px; animation: cbFlapR .16s ease-in-out infinite alternate; }
|
|
25
|
+
@keyframes cbFlapL { from { transform: rotate(-16deg); } to { transform: rotate(9deg); } }
|
|
26
|
+
@keyframes cbFlapR { from { transform: rotate(16deg); } to { transform: rotate(-9deg); } }
|
|
27
|
+
@media (prefers-reduced-motion: reduce) { .wingL, .wingR { animation: none; } }
|
|
28
|
+
</style>
|
|
29
|
+
|
|
30
|
+
<!-- 触角 -->
|
|
31
|
+
<path d="M29.5 8C27.5 4.6 24.6 3 21.8 3.6" fill="none" stroke="#241a05" stroke-width="1.4" stroke-linecap="round"/>
|
|
32
|
+
<path d="M34.5 8c2-3.4 4.9-5 7.7-4.4" fill="none" stroke="#241a05" stroke-width="1.4" stroke-linecap="round"/>
|
|
33
|
+
<circle cx="21.6" cy="3.7" r="1.5" fill="#241a05"/>
|
|
34
|
+
<circle cx="42.4" cy="3.7" r="1.5" fill="#241a05"/>
|
|
35
|
+
|
|
36
|
+
<!-- 左翅(前后两片,扇翅绕胸侧基点) -->
|
|
37
|
+
<g class="wingL">
|
|
38
|
+
<ellipse cx="16.5" cy="19.5" rx="11.5" ry="4.8" transform="rotate(-34 26 20.5)"
|
|
39
|
+
fill="url(#cbWing)" stroke="#e8f1fb" stroke-opacity=".65" stroke-width=".7"/>
|
|
40
|
+
<ellipse cx="20" cy="24.5" rx="8.2" ry="3.5" transform="rotate(-22 26.5 24.5)"
|
|
41
|
+
fill="url(#cbWing)" stroke="#e8f1fb" stroke-opacity=".55" stroke-width=".6"/>
|
|
42
|
+
<g transform="rotate(-34 26 20.5)" fill="none" stroke="#a9c1dd" stroke-opacity=".4" stroke-width=".5">
|
|
43
|
+
<path d="M26 20.5 8 15.5"/><path d="M26 21.5 7.5 20.5"/>
|
|
44
|
+
</g>
|
|
45
|
+
</g>
|
|
46
|
+
<!-- 右翅 -->
|
|
47
|
+
<g class="wingR">
|
|
48
|
+
<ellipse cx="47.5" cy="19.5" rx="11.5" ry="4.8" transform="rotate(34 38 20.5)"
|
|
49
|
+
fill="url(#cbWing)" stroke="#e8f1fb" stroke-opacity=".65" stroke-width=".7"/>
|
|
50
|
+
<ellipse cx="44" cy="24.5" rx="8.2" ry="3.5" transform="rotate(22 37.5 24.5)"
|
|
51
|
+
fill="url(#cbWing)" stroke="#e8f1fb" stroke-opacity=".55" stroke-width=".6"/>
|
|
52
|
+
<g transform="rotate(34 38 20.5)" fill="none" stroke="#a9c1dd" stroke-opacity=".4" stroke-width=".5">
|
|
53
|
+
<path d="M38 20.5 56 15.5"/><path d="M38 21.5 56.5 20.5"/>
|
|
54
|
+
</g>
|
|
55
|
+
</g>
|
|
56
|
+
|
|
57
|
+
<!-- 腹部:琥珀底 + 黑条纹 + 高光 + 螫针 -->
|
|
58
|
+
<ellipse cx="32" cy="44" rx="11.5" ry="13.5" fill="url(#cbAbd)"/>
|
|
59
|
+
<g clip-path="url(#cbAbdClip)" fill="#241a05">
|
|
60
|
+
<path d="M20 40.5 Q32 36.5 44 40.5 L44 46.5 Q32 42.5 20 46.5 Z"/>
|
|
61
|
+
<path d="M20.5 49 Q32 45.5 43.5 49 L43.2 54.5 Q32 51.5 20.8 54.5 Z" opacity=".92"/>
|
|
62
|
+
</g>
|
|
63
|
+
<path d="M25.2 36q-2.6 4-2 9.5" stroke="#ffe9ad" stroke-opacity=".7" stroke-width="1.6" stroke-linecap="round" fill="none"/>
|
|
64
|
+
<ellipse cx="32" cy="44" rx="11.5" ry="13.5" fill="none" stroke="#8a5a10" stroke-opacity=".35" stroke-width=".8"/>
|
|
65
|
+
<path d="M32 58.5 29.8 54h4.4Z" fill="#241a05"/>
|
|
66
|
+
|
|
67
|
+
<!-- 胸背:绒毛渐变 + 虚线描边模拟绒毛缘 -->
|
|
68
|
+
<ellipse cx="32" cy="25.5" rx="9.2" ry="9.8" fill="url(#cbThx)"/>
|
|
69
|
+
<ellipse cx="32" cy="25.5" rx="9.2" ry="9.8" fill="none" stroke="#8a6a2e" stroke-opacity=".45"
|
|
70
|
+
stroke-width="1" stroke-dasharray="1.2 1.6"/>
|
|
71
|
+
|
|
72
|
+
<!-- 头:复眼 + 高光 -->
|
|
73
|
+
<circle cx="32" cy="12.8" r="6.3" fill="#241a05"/>
|
|
74
|
+
<ellipse cx="28" cy="12.6" rx="2.4" ry="3" fill="#0e0a03"/>
|
|
75
|
+
<ellipse cx="36" cy="12.6" rx="2.4" ry="3" fill="#0e0a03"/>
|
|
76
|
+
<circle cx="28.9" cy="11.3" r=".7" fill="#7d6538" opacity=".8"/>
|
|
77
|
+
<circle cx="36.9" cy="11.3" r=".7" fill="#7d6538" opacity=".8"/>
|
|
78
|
+
<path d="M28.5 8.6q3.5-1.6 7 0" stroke="#8a6a2e" stroke-opacity=".55" stroke-width=".8" fill="none"/>
|
|
79
|
+
</svg>
|
package/app/ui/index.html
CHANGED
|
@@ -519,11 +519,12 @@
|
|
|
519
519
|
<div class="panel-head">
|
|
520
520
|
<h2 data-i18n="智能体目录">智能体目录</h2>
|
|
521
521
|
<div class="actions">
|
|
522
|
+
<button data-i18n="一键绑定推荐模型" id="btn-catalog-autobind" class="ghost">一键绑定推荐模型</button>
|
|
522
523
|
<button data-i18n="重新加载 catalog" id="btn-reload-catalog" class="ghost">重新加载 catalog</button>
|
|
523
524
|
<button data-i18n="恢复默认 catalog" id="btn-reset-catalog" class="ghost">恢复默认 catalog</button>
|
|
524
525
|
</div>
|
|
525
526
|
</div>
|
|
526
|
-
<p class="hint"><span data-i18n="启用「参与编排」后,该智能体会出现在任务表单与评审组中。进入本页会自动检查各 CLI 是否有新版本;「默认模型」写入 CLI 自己的配置文件(CodeBee 之外也生效),CodeBee 编排运行时用哪个供应商 / 模型请到「CLI
|
|
527
|
+
<p class="hint"><span data-i18n="启用「参与编排」后,该智能体会出现在任务表单与评审组中。进入本页会自动检查各 CLI 是否有新版本;「默认模型」写入 CLI 自己的配置文件(CodeBee 之外也生效),CodeBee 编排运行时用哪个供应商 / 模型请到「CLI 绑定」页配置。点「一键绑定推荐模型」可为默认模型还空着的智能体自动填入推荐;厂商 / 模型停用或启用后也会自动补绑一次。安装命令不明的条目可直接编辑">启用「参与编排」后,该智能体会出现在任务表单与评审组中。进入本页会自动检查各 CLI 是否有新版本;「默认模型」写入 CLI 自己的配置文件(CodeBee 之外也生效),CodeBee 编排运行时用哪个供应商 / 模型请到「CLI 绑定」页配置。点「一键绑定推荐模型」可为默认模型还空着的智能体自动填入推荐;厂商 / 模型停用或启用后也会自动补绑一次。安装命令不明的条目可直接编辑</span> <code>data/catalog.json</code> <span data-i18n="后点重新加载。">后点重新加载。</span></p>
|
|
527
528
|
<h3 data-i18n="已安装" class="sec-title">已安装</h3>
|
|
528
529
|
<div id="ag-installed" class="cards"></div>
|
|
529
530
|
<h3 data-i18n="可安装" class="sec-title">可安装</h3>
|