codebee 0.1.15 → 0.1.17
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 +14 -0
- package/README.md +9 -8
- package/app/core/bookmeta.py +10 -3
- package/app/core/bookmeta_catalog.py +16 -0
- package/app/core/covergen.py +176 -56
- package/app/core/jobs.py +23 -10
- package/app/core/modelhub.py +23 -3
- package/app/core/notify.py +11 -1
- package/app/core/paihang.py +5 -2
- package/app/core/pipeline.py +159 -10
- package/app/core/publish/browser.py +21 -7
- package/app/core/publish/fanqie.py +16 -2
- package/app/core/publish/flow.py +25 -1
- package/app/core/publish/flows-fanqie-calibrated.json +324 -37
- package/app/core/publish/manager.py +25 -9
- package/app/core/publish/qimao.py +18 -3
- package/app/core/runner.py +19 -4
- package/app/core/settings.py +3 -1
- package/app/core/store.py +13 -1
- package/app/core/zentao.py +26 -26
- package/app/main.py +20 -0
- package/app/ui/app.js +441 -60
- package/app/ui/i18n.js +243 -9
- package/app/ui/index.html +34 -52
- package/app/ui/style.css +116 -24
- package/package.json +1 -1
package/app/ui/app.js
CHANGED
|
@@ -412,7 +412,7 @@ function runStatusText(run) {
|
|
|
412
412
|
if (st === "queued" && run && run.resume_enqueue_at) {
|
|
413
413
|
const at = Date.parse(String(run.resume_enqueue_at).replace(" ", "T"));
|
|
414
414
|
if (!isNaN(at) && Date.now() < at)
|
|
415
|
-
return t("将于 "
|
|
415
|
+
return t("将于 {0} 自动续跑", String(run.resume_enqueue_at).slice(11, 16));
|
|
416
416
|
}
|
|
417
417
|
return { queued: t("排队中"), running: t("运行中"), done: t("完成"),
|
|
418
418
|
failed: t("失败"), cancelled: t("已取消"), timeout: t("超时") }[st] || st;
|
|
@@ -822,6 +822,11 @@ function renderBindings() {
|
|
|
822
822
|
if (sig === S.bindSig) return;
|
|
823
823
|
S.bindSig = sig;
|
|
824
824
|
syncSaveAllBtn();
|
|
825
|
+
if (!targets.length) {
|
|
826
|
+
bbox.innerHTML = '<div class="empty">' + t("还没有装好的 CLI 智能体(codex / claude / kimi 等)。") +
|
|
827
|
+
'<a href="#" onclick="return helpOpenTopic(\'quickstart\')">' + t("看看快速上手") + '</a></div>';
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
825
830
|
bbox.innerHTML = targets.map((c) => {
|
|
826
831
|
const b = (S.bindings || {})[c.id] || {};
|
|
827
832
|
const opts = '<option value="">' + t("不绑定(用 CLI 自身的凭据与配置)") + '</option>' + bindable.map((p) => {
|
|
@@ -900,6 +905,7 @@ function renderProvList() {
|
|
|
900
905
|
'<div class="pi-body"><div class="pi-top"><div class="pi-name">' + esc(p.name) + "</div>" +
|
|
901
906
|
'<span class="pi-n">' + st + "</span></div>" +
|
|
902
907
|
'<div class="pi-meta"><span class="tag">' + esc(protoLabel(p)) + "</span>" +
|
|
908
|
+
(p.protocol === "auto" ? "" : wireCapTags(p, p.protocol)) +
|
|
903
909
|
((p.keys || []).length > 1 ? '<span class="tag">' + t("%1 把密钥").replace("%1", (p.keys || []).length) + "</span>" : "") +
|
|
904
910
|
(off ? '<span class="tag">' + t("已停用") + "</span>" : "") +
|
|
905
911
|
"</div></div></div>";
|
|
@@ -970,17 +976,18 @@ function renderProvDetail() {
|
|
|
970
976
|
const p = (S.providers || []).find((x) => x.id === S.selProv);
|
|
971
977
|
if (!p) {
|
|
972
978
|
box.innerHTML = '<div class="empty">' + t("左侧选择供应商;还没有供应商时点左上角「导入」,") +
|
|
973
|
-
t("可从 CCSwitch / Codex / Claude Code / ZCode / Qwen / Gemini / OpenCode / Continue / Cursor / Trae 扫描带入。") +
|
|
979
|
+
t("可从 CCSwitch / Codex / Claude Code / ZCode / Qwen / Gemini / OpenCode / Continue / Cursor / Trae 扫描带入。") +
|
|
980
|
+
'<br><a href="#" onclick="return helpOpenTopic(\'models\')">' + t("还没配过模型?看「模型接入与绑定」帮助") + '</a></div>';
|
|
974
981
|
return;
|
|
975
982
|
}
|
|
976
983
|
const tp = (S.testProvState || {})[p.id];
|
|
977
984
|
const tpHtml = tp ? (tp.ok
|
|
978
|
-
? '<span class="badge ok"
|
|
985
|
+
? '<span class="badge ok"' + (tp.note ? ' title="' + esc(tp.note) + '"' : "") + '>' +
|
|
986
|
+
t("✓ 连通 ") + tp.latency_ms + "ms" +
|
|
987
|
+
(tp.note ? " · " + t("无模型列表接口") : " · " + tp.count + t(" 个模型")) + "</span>"
|
|
979
988
|
: '<span class="badge bad" title="' + esc(tp.error || "") + '">✗ ' + esc((tp.error || t("失败")).slice(0, 60)) + "</span>") : "";
|
|
980
|
-
// 适配测试通过的 wire
|
|
981
|
-
const capTags =
|
|
982
|
-
'<span class="tag ok" title="' + esc(t("适配测试通过:同密钥实测支持该协议注入") +
|
|
983
|
-
(cap.checked_at ? " · " + cap.checked_at : "")) + '">' + esc(pr) + " ✓</span>").join("");
|
|
989
|
+
// 适配测试通过的 wire(同密钥实测可注入的另一条协议面);形态随徽章标出
|
|
990
|
+
const capTags = wireCapTags(p, "");
|
|
984
991
|
const pb = (S.probeState || {})[p.id];
|
|
985
992
|
const pbHtml = !pb ? "" : (pb.busy
|
|
986
993
|
? '<span class="badge">' + esc(t("适配测试中…")) + "</span>"
|
|
@@ -1161,6 +1168,24 @@ async function duplicateProvider(pid) {
|
|
|
1161
1168
|
poll();
|
|
1162
1169
|
}
|
|
1163
1170
|
|
|
1171
|
+
/* 多 wire 网关的「另一条协议面」徽章:适配测试实测通过、又不是当前分组的 wire。
|
|
1172
|
+
* 同一份模型列表在两条 wire 上都能注入(解析走适配端点),列表不重复摆两份,
|
|
1173
|
+
* 但得把另一面亮出来——否则页面看上去只有一种协议能用,OpenAI 面永远被忽略。
|
|
1174
|
+
* 形态必须标出:chat 形态除 codex 外都能用(codex 0.154+ 只讲 responses),
|
|
1175
|
+
* 不标的话模型接入页一片 ✓、codex 链上却 ⚠ 协议不匹配,两个页面自相矛盾。 */
|
|
1176
|
+
function wireCapTags(p, excludeProto) {
|
|
1177
|
+
return Object.keys((p || {}).wire_caps || {}).filter((pr) => pr !== excludeProto).map((pr) => {
|
|
1178
|
+
const cap = (p.wire_caps || {})[pr] || {};
|
|
1179
|
+
const chat = (cap.wire_api || "responses") === "chat";
|
|
1180
|
+
const tip = (chat
|
|
1181
|
+
? t("适配测试实测:该面为 chat 形态——除 codex 外的 openai 系 CLI 可用;codex 只讲 responses,链上注入会被跳过")
|
|
1182
|
+
: t("适配测试实测:同密钥也可走 {0} wire——这组模型同样可注入 {0} 系 CLI(codex/dsh 等),运行时自动走适配端点", pr)) +
|
|
1183
|
+
(cap.checked_at ? " · " + cap.checked_at : "");
|
|
1184
|
+
return '<span class="tag ok" title="' + esc(tip) + '">' +
|
|
1185
|
+
esc(pr) + " ✓" + (chat ? " · chat" : "") + "</span>";
|
|
1186
|
+
}).join("");
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1164
1189
|
/* 模型分组区 HTML(全量重绘与过滤重绘共用;过滤时暂停拖拽排序) */
|
|
1165
1190
|
function provModelGroupsHtml(p) {
|
|
1166
1191
|
const kw = ((S.modelFilter || {})[p.id] || "").trim().toLowerCase();
|
|
@@ -1187,6 +1212,7 @@ function provModelGroupsHtml(p) {
|
|
|
1187
1212
|
'<label class="pcheck-all"><input type="checkbox"' + (allSel ? " checked" : "") +
|
|
1188
1213
|
' onchange="toggleGroupSel(\'' + esc(p.id) + '\', \'' + esc(g) + '\', this.checked)">' + t("全选") + '</label>' +
|
|
1189
1214
|
esc(g) + t(" 协议 · ") + gm.length + t(" 个") +
|
|
1215
|
+
wireCapTags(p, g) +
|
|
1190
1216
|
(kw ? t("(过滤中,拖拽排序暂停)") : t("(拖动 ☰ 调序;启用的排最前)")) + "</div>";
|
|
1191
1217
|
for (const m of gm) html += provModelRow(p.id, m, g);
|
|
1192
1218
|
html += "</div>";
|
|
@@ -1497,6 +1523,7 @@ async function refreshProviderModels(id) {
|
|
|
1497
1523
|
try {
|
|
1498
1524
|
const r = await api("/api/models/refresh", { method: "POST", body: JSON.stringify({ id }) });
|
|
1499
1525
|
if (!r.ok && r.message) toast(t("获取失败:") + r.message, true);
|
|
1526
|
+
else if (r.note) toast(r.note); // 网关无模型列表接口(404):良性提示,不算失败
|
|
1500
1527
|
else toast(t("已获取模型列表 · wire 适配测试后台进行中"));
|
|
1501
1528
|
} catch (e) { toast(t("获取失败:") + e.message, true); }
|
|
1502
1529
|
poll();
|
|
@@ -1652,7 +1679,7 @@ async function openImportDialog() {
|
|
|
1652
1679
|
'<input type="checkbox" value="' + esc(s.id) + '"' + (ok ? " checked" : " disabled") +
|
|
1653
1680
|
(ok ? ' onchange="updateImportSelHint()"' : "") + ">" +
|
|
1654
1681
|
'<div class="src-main">' +
|
|
1655
|
-
'<div class="src-name">' + esc(s.name) + status + "</div>" +
|
|
1682
|
+
'<div class="src-name">' + esc(t(s.name)) + status + "</div>" +
|
|
1656
1683
|
'<div class="src-desc">' + esc(t(s.desc)) + "</div>" +
|
|
1657
1684
|
'<div class="src-path">' + esc((s.paths || []).join(" · ")) + "</div>" +
|
|
1658
1685
|
(s.note ? '<div class="src-note">' + esc(t.apply(null, [s.note].concat(s.note_args || []))) + "</div>" : "") +
|
|
@@ -1803,6 +1830,26 @@ function renderClarify(questions, goal, resetSubmit) {
|
|
|
1803
1830
|
ta.value = ta.value.trim() + (ta.value.includes(line) ? "" : (ta.value ? "\n" : "") + line);
|
|
1804
1831
|
b.classList.add("picked");
|
|
1805
1832
|
b.disabled = true;
|
|
1833
|
+
// spec 分段签核(借鉴 superpowers「分段短块逐段消化」):全部问题选完后
|
|
1834
|
+
// 显示已确认摘要条 + 「确认开跑」——用户看一眼再动手,不蒙头就创建
|
|
1835
|
+
const allPicked = questions.every((it) =>
|
|
1836
|
+
Array.from(box.querySelectorAll(".cl-opt")).some(
|
|
1837
|
+
(x) => x.dataset.q === it.q && x.classList.contains("picked")));
|
|
1838
|
+
let bar = box.querySelector("#clarify-confirm");
|
|
1839
|
+
if (allPicked && !bar) {
|
|
1840
|
+
bar = document.createElement("div");
|
|
1841
|
+
bar.id = "clarify-confirm";
|
|
1842
|
+
bar.className = "cl-confirm";
|
|
1843
|
+
bar.innerHTML = '<button type="button" class="primary small" id="clarify-go">' +
|
|
1844
|
+
esc(t("已确认,开跑")) + "</button></div>";
|
|
1845
|
+
box.appendChild(bar);
|
|
1846
|
+
bar.querySelector("#clarify-go").addEventListener("click", () => {
|
|
1847
|
+
box.classList.add("hidden");
|
|
1848
|
+
box.innerHTML = "";
|
|
1849
|
+
S.clarifyDone = true;
|
|
1850
|
+
$("btn-create").click();
|
|
1851
|
+
});
|
|
1852
|
+
}
|
|
1806
1853
|
}));
|
|
1807
1854
|
function it0safe(q, o) { return q + ":" + o; }
|
|
1808
1855
|
const skip = $("clarify-skip");
|
|
@@ -3266,12 +3313,12 @@ function drawTaskDetail(key, runs) {
|
|
|
3266
3313
|
} catch (e) {
|
|
3267
3314
|
// 拉取失败不再静默留白(手机端「报告空空」的来源之一):给可见提示,
|
|
3268
3315
|
// 不落缓存,下一轮轮询签名变化时自动重试
|
|
3269
|
-
hint(t("报告读取失败("
|
|
3316
|
+
hint(t("报告读取失败({0}),稍后自动重试", (e || {}).message || t("网络异常")));
|
|
3270
3317
|
}
|
|
3271
3318
|
} else if (latest.status === "running" || latest.status === "queued") {
|
|
3272
3319
|
hint(t("本次运行进行中:报告结束后在这里生成,实时进度看「步骤」页签"));
|
|
3273
3320
|
} else {
|
|
3274
|
-
const st = { failed: "失败", cancelled: "已取消", timeout: "超时" }[latest.status] || latest.status || "";
|
|
3321
|
+
const st = { failed: t("失败"), cancelled: t("已取消"), timeout: t("超时") }[latest.status] || latest.status || "";
|
|
3275
3322
|
hint(t("本次运行没有生成报告") + (st ? t("(状态:") + st + t(")") : "") +
|
|
3276
3323
|
t(";各步骤日志在「步骤」页签"));
|
|
3277
3324
|
}
|
|
@@ -3630,11 +3677,11 @@ async function renderRunDetail() {
|
|
|
3630
3677
|
// 拉取失败不再静默留白:给可见提示(下轮轮询重画会重试)
|
|
3631
3678
|
if (S.detailRunId === id && !S.detailTaskKey)
|
|
3632
3679
|
$("rd-report").innerHTML = '<div class="hint">' +
|
|
3633
|
-
esc(t("报告读取失败("
|
|
3680
|
+
esc(t("报告读取失败({0}),稍后自动重试", (e || {}).message || t("网络异常"))) + "</div>";
|
|
3634
3681
|
}
|
|
3635
3682
|
loadArtifacts(id);
|
|
3636
3683
|
} else {
|
|
3637
|
-
const st = { failed: "失败", cancelled: "已取消", timeout: "超时" }[run.status] || run.status || "";
|
|
3684
|
+
const st = { failed: t("失败"), cancelled: t("已取消"), timeout: t("超时") }[run.status] || run.status || "";
|
|
3638
3685
|
$("rd-report").innerHTML = '<div class="hint">' +
|
|
3639
3686
|
(run.status === "running" || run.status === "queued"
|
|
3640
3687
|
? esc(t("本次运行进行中:报告结束后在这里生成,实时进度看「步骤」页签"))
|
|
@@ -3799,6 +3846,7 @@ function renderBookMetaPanel(task) {
|
|
|
3799
3846
|
let html = '<div class="bm-head"><svg class="ico" aria-hidden="true"><use href="#i-idcard"/></svg>' +
|
|
3800
3847
|
'<span class="sec-title">' + t("作品信息") + "</span>" +
|
|
3801
3848
|
'<span class="hint">' + esc(t("按发布平台生成建书表单资料,逐字段复制过去")) + "</span>" +
|
|
3849
|
+
'<button class="hhelp" data-help-topic="publish" title="' + esc(t("这是什么?点开看帮助")) + '" aria-label="' + esc(t("帮助")) + '"><svg class="ico" aria-hidden="true"><use href="#i-help"></use></svg></button>' +
|
|
3802
3850
|
'<span class="flex1"></span>';
|
|
3803
3851
|
if (taskBusy) html += '<span class="bm-warn">' + esc(t("任务运行中,生成将在本轮结束后可用")) + "</span>";
|
|
3804
3852
|
html += "</div>";
|
|
@@ -3854,14 +3902,24 @@ function renderBookMetaPanel(task) {
|
|
|
3854
3902
|
pbBlock(task, p.id) +
|
|
3855
3903
|
"</div>";
|
|
3856
3904
|
}).join("") + "</div>";
|
|
3857
|
-
// 封面卡(covergen,借鉴 oh-story 封面图环节):curl 落盘运行目录,
|
|
3905
|
+
// 封面卡(covergen,借鉴 oh-story 封面图环节):curl 落盘运行目录,
|
|
3906
|
+
// done 后卡片内直接嵌缩略图(/api/runs/<id>/cover 专用通道),点开新页看大图
|
|
3858
3907
|
const cg = task.cover_gen || {};
|
|
3859
3908
|
const cgSt = cg.status || "";
|
|
3860
3909
|
let cgBody = "";
|
|
3861
3910
|
if (cgSt === "running") {
|
|
3862
3911
|
cgBody = '<div class="bm-empty"><svg class="ico spin" aria-hidden="true"><use href="#i-refresh"/></svg> ' + esc(t("生成中…")) + "</div>";
|
|
3912
|
+
} else if (cgSt === "done" && cg.run_id) {
|
|
3913
|
+
const covUrl = "/api/runs/" + encodeURIComponent(cg.run_id) + "/cover" +
|
|
3914
|
+
(cg.at ? "?ts=" + encodeURIComponent(cg.at) : ""); // at 当缓存戳,重生成后缩略图跟着换
|
|
3915
|
+
cgBody = '<div class="bm-cover-wrap">' +
|
|
3916
|
+
'<a href="' + esc(urlAuth(covUrl)) + '" target="_blank" rel="noopener" title="' + esc(t("点击查看大图")) + '">' +
|
|
3917
|
+
'<img class="bm-cover-img" src="' + esc(urlAuth(covUrl)) + '" alt="cover.png" ' +
|
|
3918
|
+
'onerror="this.closest(".bm-cover-wrap").classList.add("noimg")"></a>' +
|
|
3919
|
+
'<span class="bm-cover-fallback">' + esc(t("cover.png 已不在运行目录")) + "</span>" +
|
|
3920
|
+
'<div class="bm-cover-meta">' + esc([cg.model, cg.provider, cg.size].filter(Boolean).join(" · ")) + "</div></div>";
|
|
3863
3921
|
} else if (cgSt === "done") {
|
|
3864
|
-
cgBody = '<div class="bm-empty">' + esc(t("
|
|
3922
|
+
cgBody = '<div class="bm-empty">' + esc(t("封面已生成,但缺少运行记录归属")) + "</div>";
|
|
3865
3923
|
} else if (cgSt === "failed") {
|
|
3866
3924
|
cgBody = '<div class="bm-errhint">' + esc(cg.error || t("生成失败")) + "</div>";
|
|
3867
3925
|
} else {
|
|
@@ -4076,7 +4134,7 @@ window.pbUploadChapter = async function (taskId, platform) {
|
|
|
4076
4134
|
};
|
|
4077
4135
|
|
|
4078
4136
|
window.pbSendChapter = async function (taskId, platform, relName) {
|
|
4079
|
-
if (!confirm(t("将把《"
|
|
4137
|
+
if (!confirm(t("将把《{0}》填进平台章节编辑器,填好后由你人工提交。继续?", relName))) return;
|
|
4080
4138
|
try {
|
|
4081
4139
|
await api("/api/publish/task/" + encodeURIComponent(taskId) + "/chapter",
|
|
4082
4140
|
{ method: "POST", body: JSON.stringify({ platform, file: relName }) });
|
|
@@ -4091,8 +4149,7 @@ window.pbPublishAll = async function (taskId, platform) {
|
|
|
4091
4149
|
const au = (((S.pubAuto && S.pubAuto.books) || [])
|
|
4092
4150
|
.find((b) => b.platform === platform)) || {};
|
|
4093
4151
|
if (!au.pending) return;
|
|
4094
|
-
if (!confirm(t("将从最靠前的待发章节开始填稿(共 "
|
|
4095
|
-
" 章待发)。本轮只填一章并停在表单页,由你在浏览器里确认提交;提交后再点一次即发下一章。护栏(每日上限/连续失败暂停)生效。继续?"))) return;
|
|
4152
|
+
if (!confirm(t("将从最靠前的待发章节开始填稿(共 {0} 章待发)。本轮只填一章并停在表单页,由你在浏览器里确认提交;提交后再点一次即发下一章。护栏(每日上限/连续失败暂停)生效。继续?", au.pending))) return;
|
|
4096
4153
|
try {
|
|
4097
4154
|
await api("/api/publish/task/" + encodeURIComponent(taskId) + "/publish-all",
|
|
4098
4155
|
{ method: "POST", body: JSON.stringify({ platform }) });
|
|
@@ -4228,6 +4285,7 @@ function _renderGitSnapshot(run, task) {
|
|
|
4228
4285
|
let html =
|
|
4229
4286
|
'<div class="git-head"><svg class="ico" aria-hidden="true"><use href="#i-git-branch"></use></svg>' +
|
|
4230
4287
|
'<span class="sec-title">' + t("代码版本隔离") + '</span>' +
|
|
4288
|
+
'<button class="hhelp" data-help-topic="code" title="' + esc(t("这是什么?点开看帮助")) + '" aria-label="' + esc(t("帮助")) + '"><svg class="ico" aria-hidden="true"><use href="#i-help"></use></svg></button>' +
|
|
4231
4289
|
'<code class="git-branch" title="' + esc(t("任务分支:产物提交在此,原分支未受影响")) + '">' + esc(g.branch) + "</code>";
|
|
4232
4290
|
const addT = ch.add_total, delT = ch.del_total;
|
|
4233
4291
|
if (addT != null && delT != null) {
|
|
@@ -4323,7 +4381,8 @@ function renderGitWbMain(d, task) {
|
|
|
4323
4381
|
sel += "</select>";
|
|
4324
4382
|
let html =
|
|
4325
4383
|
'<div class="git-head"><svg class="ico" aria-hidden="true"><use href="#i-git-branch"></use></svg>' +
|
|
4326
|
-
'<span class="sec-title">' + t("Git 工作台") + "</span>" +
|
|
4384
|
+
'<span class="sec-title">' + t("Git 工作台") + "</span>" +
|
|
4385
|
+
'<button class="hhelp" data-help-topic="code" title="' + esc(t("这是什么?点开看帮助")) + '" aria-label="' + esc(t("帮助")) + '"><svg class="ico" aria-hidden="true"><use href="#i-help"></use></svg></button>' + sel +
|
|
4327
4386
|
'<code class="git-branch" title="HEAD">' + esc(d.head || "") + "</code>" +
|
|
4328
4387
|
(d.upstream ? '<span class="gm" title="' + esc(d.upstream) + '">↑' + (d.ahead || 0) + " ↓" + (d.behind || 0) + "</span>" : "") +
|
|
4329
4388
|
'<span class="flex1"></span>' +
|
|
@@ -6548,6 +6607,17 @@ function provAdaptedProto(p, allow, kind) {
|
|
|
6548
6607
|
&& !chatBlocked((caps[pr] || {}).wire_api)) || "";
|
|
6549
6608
|
}
|
|
6550
6609
|
|
|
6610
|
+
/* 死因细化:协议适配失败里最常见的一种是「openai 面实测只探到 chat 形态」
|
|
6611
|
+
* (codex 0.154+ 只讲 responses,其余 CLI 不受影响)。按真实死因说话——
|
|
6612
|
+
* 模型接入页明明亮着 openai ✓,链上却笼统说「协议不匹配」,看着就像自相矛盾。 */
|
|
6613
|
+
function chatShapedFace(pv, allow) {
|
|
6614
|
+
if (!pv) return false;
|
|
6615
|
+
const chat = (w) => (w || "responses") === "chat";
|
|
6616
|
+
if (allow.indexOf(pv.protocol) >= 0 && chat(pv.wire_api)) return true;
|
|
6617
|
+
const caps = pv.wire_caps || {};
|
|
6618
|
+
return allow.some((pr) => (caps[pr] || {}).base && chat((caps[pr] || {}).wire_api));
|
|
6619
|
+
}
|
|
6620
|
+
|
|
6551
6621
|
/* CLI 允许注入的供应商 wire 协议——与 modelhub.resolve_binding 的 allowed 规则
|
|
6552
6622
|
* 保持一致(codex 只吃 openai wire、claude 只吃 anthropic、dsh 只吃 OpenAI
|
|
6553
6623
|
* 兼容端点,其余两种皆可)。后端会跳过不匹配的链条目,这里在选择层就挡住。 */
|
|
@@ -6674,7 +6744,10 @@ function chainDeadReasons(c, chain) {
|
|
|
6674
6744
|
const pv = (S.providers || []).find((p) => p.id === x.p);
|
|
6675
6745
|
if (!pv) return t("供应商已删除");
|
|
6676
6746
|
if (pv.enabled === false) return t("供应商已停用");
|
|
6677
|
-
if (!provAdaptedProto(pv, allow, c.orch_kind))
|
|
6747
|
+
if (!provAdaptedProto(pv, allow, c.orch_kind))
|
|
6748
|
+
return chatShapedFace(pv, allow)
|
|
6749
|
+
? t("openai 面仅 chat 形态(codex 只讲 responses)")
|
|
6750
|
+
: t("协议不匹配");
|
|
6678
6751
|
const ks = pv.keys || [];
|
|
6679
6752
|
if (ks.length && !ks.some((k) => k.enabled !== false && !k.cooling))
|
|
6680
6753
|
return t("密钥全部冷却中");
|
|
@@ -6820,7 +6893,9 @@ function bindModelBox(c, provId) {
|
|
|
6820
6893
|
"<b>" + (i === 0 ? t("主") : t("备")) + "</b>" + esc(pname) + " · " + esc(c2.m) +
|
|
6821
6894
|
(modelHasImage(c2.p, c2.m)
|
|
6822
6895
|
? ' <span class="tag ok" title="' + esc(t("支持图片输入")) + '">' + t("图") + "</span>" : "") +
|
|
6823
|
-
(dead ? ' <span class="hint warn">⚠ ' + esc(
|
|
6896
|
+
(dead ? ' <span class="hint warn">⚠ ' + esc(chatShapedFace(pv, allow)
|
|
6897
|
+
? t("openai 面仅 chat 形态(codex 只讲 responses),解析时跳过")
|
|
6898
|
+
: t("协议不匹配,解析时跳过")) + "</span>" : badge) +
|
|
6824
6899
|
(i > 0 ? '<button class="mini" data-m="' + esc(c2.m) + '" data-p="' + esc(c2.p) +
|
|
6825
6900
|
'" title="' + t("设为主模型") + '" onclick="bindPromote(\'' + esc(c.id) + '\', this)">' +
|
|
6826
6901
|
'<svg class="ico" aria-hidden="true"><use href="#i-arrow-up"></use></svg></button>' : "") +
|
|
@@ -7683,8 +7758,8 @@ function autoTemplate(tplId) {
|
|
|
7683
7758
|
}
|
|
7684
7759
|
|
|
7685
7760
|
/* ---------------------------------------------------------- 禅道 Bug 自动修复 */
|
|
7686
|
-
/* /api/zentao → {config(
|
|
7687
|
-
*
|
|
7761
|
+
/* /api/zentao → {config(脱敏,含 product_profiles), claims, last_scan, next_scan, last_error}。
|
|
7762
|
+
* 产品档案卡:结构增删走「先采集 DOM 回 S.ztProfiles → 改 → 重渲染」,保住已输入值。 */
|
|
7688
7763
|
|
|
7689
7764
|
async function loadZentao() {
|
|
7690
7765
|
let v = null;
|
|
@@ -7696,35 +7771,180 @@ async function loadZentao() {
|
|
|
7696
7771
|
set("zt-account", cfg.account);
|
|
7697
7772
|
const pw = $("zt-password");
|
|
7698
7773
|
if (pw) { pw.value = ""; pw.placeholder = cfg.has_password ? t("已保存(不改就留空)") : ""; }
|
|
7699
|
-
set("zt-products", (cfg.products || []).join(","));
|
|
7700
|
-
set("zt-assigned", cfg.assigned_to);
|
|
7701
|
-
const sev = $("zt-sev"); if (sev) sev.value = String(cfg.severity_cap || 0);
|
|
7702
|
-
set("zt-workdir", cfg.workdir);
|
|
7703
|
-
set("zt-gitrev", cfg.git_rev);
|
|
7704
|
-
set("zt-verify", cfg.verify_command);
|
|
7705
7774
|
const tg = (id, on) => { const el = $(id); if (el) el.checked = !!on; };
|
|
7775
|
+
tg("zt-triageai", cfg.triage_ai);
|
|
7706
7776
|
tg("zt-autoresolve", cfg.auto_resolve);
|
|
7707
7777
|
tg("zt-automerge", cfg.auto_merge);
|
|
7708
7778
|
tg("zt-poll", cfg.poll_enabled);
|
|
7709
7779
|
set("zt-interval", cfg.interval_hours || 2);
|
|
7780
|
+
S.ztProfiles = JSON.parse(JSON.stringify(cfg.product_profiles || []));
|
|
7781
|
+
renderZentaoProfiles();
|
|
7710
7782
|
renderZentaoStatus();
|
|
7711
7783
|
renderZentaoClaims();
|
|
7712
7784
|
}
|
|
7713
7785
|
|
|
7786
|
+
/* ---- 产品档案:渲染与采集 ---- */
|
|
7787
|
+
|
|
7788
|
+
const ZT_SIDES = ["backend", "frontend"];
|
|
7789
|
+
|
|
7790
|
+
function ztRepoHtml(i, side) {
|
|
7791
|
+
const cn = side === "backend" ? t("后端") : t("前端");
|
|
7792
|
+
const p = (S.ztProfiles[i] || {});
|
|
7793
|
+
const repo = (p.repos || {})[side] || {};
|
|
7794
|
+
const hint = (p.repo_hints || {})[side] || "";
|
|
7795
|
+
return '<label>' + cn + t("仓库·工作目录") + '</label><input class="zt-p-wd" data-side="' + side + '" value="' + esc(repo.workdir || "") + '" placeholder="' + t("空 = 不用该端") + '">' +
|
|
7796
|
+
'<label>' + cn + t("仓库·基线分支") + '</label><input class="zt-p-rev" data-side="' + side + '" value="' + esc(repo.git_rev || "") + '" placeholder="' + t("如 main;空 = 直接改工作目录") + '">' +
|
|
7797
|
+
'<label>' + cn + t("仓库·验证命令") + '</label><input class="zt-p-verify" data-side="' + side + '" value="' + esc(repo.verify_command || "") + '" placeholder="' + t("如 npm test;空 = 靠评审把关") + '">' +
|
|
7798
|
+
'<label>' + cn + t("仓库·一句话描述") + '</label><input class="zt-p-hint" data-side="' + side + '" value="' + esc(hint) + '" placeholder="' + t("给 AI 排查看,如:Vue3 管理台前端") + '">';
|
|
7799
|
+
}
|
|
7800
|
+
|
|
7801
|
+
function ztProfCardHtml(p, i) {
|
|
7802
|
+
const ours = p.our_sides || [];
|
|
7803
|
+
const routes = p.module_routes || [];
|
|
7804
|
+
const SIDE_OPTS = [["backend", "后端"], ["frontend", "前端"], ["both", "双端"], ["not_ours", "非我方"]];
|
|
7805
|
+
const rows = routes.map((r, j) =>
|
|
7806
|
+
'<div class="zt-mr-row" data-j="' + j + '">' +
|
|
7807
|
+
'<input class="zt-mr-module zt-num" list="zt-mods-' + i + '" placeholder="' + t("模块 ID") + '" value="' + esc(r.module || "") + '">' +
|
|
7808
|
+
'<select class="zt-mr-side">' + SIDE_OPTS.map((o) =>
|
|
7809
|
+
'<option value="' + o[0] + '"' + (r.side === o[0] ? " selected" : "") + ">" + t(o[1]) + "</option>").join("") + "</select>" +
|
|
7810
|
+
'<input class="zt-mr-account" placeholder="' + t("转给谁(禅道账号,可空)") + '" value="' + esc(r.account || "") + '">' +
|
|
7811
|
+
'<button class="ghost small" onclick="ztMrDel(' + i + "," + j + ')">' + t("删") + "</button>" +
|
|
7812
|
+
"</div>").join("");
|
|
7813
|
+
const owners = p.owners || {};
|
|
7814
|
+
return '<div class="card zt-prof" data-i="' + i + '"><div class="head">' +
|
|
7815
|
+
'<span class="name">' + t("产品") + ' <input class="zt-p-product zt-num" type="number" min="1" value="' + esc(p.product || "") + '" placeholder="ID"></span>' +
|
|
7816
|
+
'<button class="danger small" onclick="ztProfDel(' + i + ')">' + t("删除产品") + "</button></div>" +
|
|
7817
|
+
'<div class="zt-grid">' +
|
|
7818
|
+
'<label data-i18n="只认领指派给">只认领指派给</label><input class="zt-p-assigned" value="' + esc(p.assigned_to || "") + '" placeholder="' + t("禅道账号名,空 = 不按指派过滤") + '">' +
|
|
7819
|
+
'<label data-i18n="严重度上限">严重度上限</label><select class="zt-p-sev">' +
|
|
7820
|
+
[0, 1, 2, 3, 4].map((n) => '<option value="' + n + '"' + (Number(p.severity_cap || 0) === n ? " selected" : "") + ">" +
|
|
7821
|
+
(n === 0 ? t("不限") : n + (n === 1 ? t("(最严重)") : "")) + "</option>").join("") + "</select>" +
|
|
7822
|
+
"</div>" +
|
|
7823
|
+
'<div class="zt-ours"><span>' + t("我方端(自动修):") + "</span>" +
|
|
7824
|
+
ZT_SIDES.map((s) =>
|
|
7825
|
+
'<label class="toggle"><input type="checkbox" class="zt-p-ours" data-side="' + s + '"' + (ours.indexOf(s) >= 0 ? " checked" : "") + "><span>" +
|
|
7826
|
+
(s === "backend" ? t("后端") : t("前端")) + "</span></label>").join("") +
|
|
7827
|
+
'<span class="hint">' + t("都不勾 = 该产品只排查转派,不自动修") + "</span></div>" +
|
|
7828
|
+
'<div class="zt-grid">' + ZT_SIDES.map((s) => ztRepoHtml(i, s)).join("") + "</div>" +
|
|
7829
|
+
'<div class="zt-grid">' +
|
|
7830
|
+
'<label data-i18n="后端负责人">后端负责人</label><input class="zt-p-owner" data-side="backend" value="' + esc(owners.backend || "") + '" placeholder="' + t("禅道账号,转派/升级用") + '">' +
|
|
7831
|
+
'<label data-i18n="前端负责人">前端负责人</label><input class="zt-p-owner" data-side="frontend" value="' + esc(owners.frontend || "") + '" placeholder="' + t("禅道账号,转派/升级用") + '">' +
|
|
7832
|
+
'<label data-i18n="非我方转派给">非我方转派给</label><input class="zt-p-owner" data-side="not_ours" value="' + esc(owners.not_ours || "") + '" placeholder="' + t("空 = 指回报告人") + '">' +
|
|
7833
|
+
"</div>" +
|
|
7834
|
+
'<div class="zt-mrs"><div class="zt-mr-head">' + t("模块路由(模块 → 端/人,排查优先级最高)") +
|
|
7835
|
+
'<button class="ghost small" onclick="ztFetchMods(' + i + ')">' + t("拉取模块清单") + "</button>" +
|
|
7836
|
+
'<button class="ghost small" onclick="ztMrAdd(' + i + ')">' + t("+ 加路由") + "</button></div>" +
|
|
7837
|
+
(rows || '<div class="hint">' + t("未配路由——模块不在路由里的 Bug 走 AI 排查") + "</div>") +
|
|
7838
|
+
'<datalist id="zt-mods-' + i + '"></datalist>' +
|
|
7839
|
+
"</div></div>";
|
|
7840
|
+
}
|
|
7841
|
+
|
|
7842
|
+
function renderZentaoProfiles() {
|
|
7843
|
+
const box = $("zt-profiles");
|
|
7844
|
+
if (!box) return;
|
|
7845
|
+
box.innerHTML = (S.ztProfiles || []).map((p, i) => ztProfCardHtml(p, i)).join("") ||
|
|
7846
|
+
'<div class="empty">' + t("还没有产品档案——点「+ 添加产品」,填产品 ID 与两端仓库。") + "</div>";
|
|
7847
|
+
}
|
|
7848
|
+
|
|
7849
|
+
/* DOM 卡片 → profiles 数组(结构变化前采集,保住已输入值) */
|
|
7850
|
+
function ztHarvestProfiles() {
|
|
7851
|
+
return Array.from(document.querySelectorAll("#zt-profiles .zt-prof")).map((card) => {
|
|
7852
|
+
const gv = (sel) => { const el = card.querySelector(sel); return el ? (el.value || "").trim() : ""; };
|
|
7853
|
+
const routes = Array.from(card.querySelectorAll(".zt-mr-row")).map((row) => ({
|
|
7854
|
+
module: parseInt((row.querySelector(".zt-mr-module").value || ""), 10) || 0,
|
|
7855
|
+
side: row.querySelector(".zt-mr-side").value,
|
|
7856
|
+
account: (row.querySelector(".zt-mr-account").value || "").trim(),
|
|
7857
|
+
})).filter((r) => r.module > 0);
|
|
7858
|
+
return {
|
|
7859
|
+
product: parseInt(gv(".zt-p-product"), 10) || 0,
|
|
7860
|
+
assigned_to: gv(".zt-p-assigned"),
|
|
7861
|
+
severity_cap: parseInt(gv(".zt-p-sev"), 10) || 0,
|
|
7862
|
+
our_sides: Array.from(card.querySelectorAll(".zt-p-ours:checked")).map((el) => el.dataset.side),
|
|
7863
|
+
repos: {
|
|
7864
|
+
backend: { workdir: gv('.zt-p-wd[data-side="backend"]'), git_rev: gv('.zt-p-rev[data-side="backend"]'), verify_command: gv('.zt-p-verify[data-side="backend"]') },
|
|
7865
|
+
frontend: { workdir: gv('.zt-p-wd[data-side="frontend"]'), git_rev: gv('.zt-p-rev[data-side="frontend"]'), verify_command: gv('.zt-p-verify[data-side="frontend"]') },
|
|
7866
|
+
},
|
|
7867
|
+
repo_hints: {
|
|
7868
|
+
backend: gv('.zt-p-hint[data-side="backend"]'),
|
|
7869
|
+
frontend: gv('.zt-p-hint[data-side="frontend"]'),
|
|
7870
|
+
},
|
|
7871
|
+
owners: {
|
|
7872
|
+
backend: gv('.zt-p-owner[data-side="backend"]'),
|
|
7873
|
+
frontend: gv('.zt-p-owner[data-side="frontend"]'),
|
|
7874
|
+
not_ours: gv('.zt-p-owner[data-side="not_ours"]'),
|
|
7875
|
+
},
|
|
7876
|
+
module_routes: routes,
|
|
7877
|
+
};
|
|
7878
|
+
});
|
|
7879
|
+
}
|
|
7880
|
+
|
|
7881
|
+
function ztProfAdd() {
|
|
7882
|
+
S.ztProfiles = ztHarvestProfiles();
|
|
7883
|
+
S.ztProfiles.push({ product: 0, assigned_to: "", severity_cap: 0, our_sides: ["backend"],
|
|
7884
|
+
repos: { backend: {}, frontend: {} }, repo_hints: { backend: "", frontend: "" },
|
|
7885
|
+
owners: { backend: "", frontend: "", not_ours: "" }, module_routes: [] });
|
|
7886
|
+
renderZentaoProfiles();
|
|
7887
|
+
}
|
|
7888
|
+
|
|
7889
|
+
function ztProfDel(i) {
|
|
7890
|
+
S.ztProfiles = ztHarvestProfiles();
|
|
7891
|
+
S.ztProfiles.splice(i, 1);
|
|
7892
|
+
renderZentaoProfiles();
|
|
7893
|
+
}
|
|
7894
|
+
|
|
7895
|
+
function ztMrAdd(i) {
|
|
7896
|
+
S.ztProfiles = ztHarvestProfiles();
|
|
7897
|
+
(S.ztProfiles[i].module_routes = S.ztProfiles[i].module_routes || []).push({ module: "", side: "backend", account: "" });
|
|
7898
|
+
renderZentaoProfiles();
|
|
7899
|
+
}
|
|
7900
|
+
|
|
7901
|
+
function ztMrDel(i, j) {
|
|
7902
|
+
S.ztProfiles = ztHarvestProfiles();
|
|
7903
|
+
S.ztProfiles[i].module_routes.splice(j, 1);
|
|
7904
|
+
renderZentaoProfiles();
|
|
7905
|
+
}
|
|
7906
|
+
|
|
7907
|
+
async function ztFetchMods(i) {
|
|
7908
|
+
const pid = parseInt(($("zt-profiles").querySelectorAll(".zt-prof")[i]?.querySelector(".zt-p-product").value) || "", 10);
|
|
7909
|
+
if (!pid) { toast(t("先填产品 ID 再拉模块清单"), true); return; }
|
|
7910
|
+
let r;
|
|
7911
|
+
try {
|
|
7912
|
+
r = await api("/api/zentao/modules", { method: "POST", body: JSON.stringify({ product: pid }) });
|
|
7913
|
+
} catch (e) { toast(e.message, true); return; }
|
|
7914
|
+
if (!r.ok) { toast(r.error || t("拉取失败"), true); return; }
|
|
7915
|
+
const dl = $("zt-mods-" + i);
|
|
7916
|
+
if (dl) dl.innerHTML = (r.modules || []).map((m) =>
|
|
7917
|
+
'<option value="' + esc(m.id) + '">' + esc(m.name || "") + "</option>").join("");
|
|
7918
|
+
toast(t("模块清单已拉到 ") + (r.modules || []).length + t(" 条,输入框可下拉选择"));
|
|
7919
|
+
}
|
|
7920
|
+
|
|
7921
|
+
/* ---- 状态行 / 修复记录 ---- */
|
|
7922
|
+
|
|
7714
7923
|
function zentaoStateTag(st) {
|
|
7715
7924
|
const MAP = {
|
|
7716
7925
|
fixing: ["修复中", "zt-tag-fix"],
|
|
7717
7926
|
resolved: ["已解决", "zt-tag-ok"],
|
|
7927
|
+
transferred: ["已转派", "zt-tag-fix"],
|
|
7928
|
+
escalated: ["已升级转派", "zt-tag-warn"],
|
|
7718
7929
|
merge_failed: ["合并失败", "zt-tag-warn"],
|
|
7719
7930
|
commented: ["已评论留人工", "zt-tag-warn"],
|
|
7720
7931
|
resolve_failed: ["回写失败", "zt-tag-warn"],
|
|
7721
7932
|
done_manual: ["待人工确认", "zt-tag-warn"],
|
|
7933
|
+
need_manual: ["需人工", "zt-tag-warn"],
|
|
7722
7934
|
lost: ["记录丢失", "zt-tag-err"],
|
|
7723
7935
|
};
|
|
7724
7936
|
const m = MAP[st] || [st || "?", ""];
|
|
7725
7937
|
return '<span class="tag ' + m[1] + '">' + t(m[0]) + "</span>";
|
|
7726
7938
|
}
|
|
7727
7939
|
|
|
7940
|
+
function zentaoTriText(c) {
|
|
7941
|
+
const tri = c.triage || {};
|
|
7942
|
+
if (!tri.side) return "";
|
|
7943
|
+
const NAME = { backend: "后端问题", frontend: "前端问题", both: "双端问题", not_ours: "非我方", unknown: "待人工" };
|
|
7944
|
+
const by = tri.by === "rule" ? t("模块路由") : tri.by === "ai" ? "AI" : "";
|
|
7945
|
+
return t("排查:") + t(NAME[tri.side] || tri.side) + (by ? "(" + by + ")" : "");
|
|
7946
|
+
}
|
|
7947
|
+
|
|
7728
7948
|
function renderZentaoStatus() {
|
|
7729
7949
|
const box = $("zentao-status");
|
|
7730
7950
|
if (!box) return;
|
|
@@ -7745,37 +7965,35 @@ function renderZentaoClaims() {
|
|
|
7745
7965
|
const box = $("zentao-claims");
|
|
7746
7966
|
if (!box) return;
|
|
7747
7967
|
const claims = (S.zentao && S.zentao.claims) || [];
|
|
7748
|
-
box.innerHTML = claims.map((c) =>
|
|
7749
|
-
|
|
7750
|
-
|
|
7751
|
-
|
|
7752
|
-
|
|
7753
|
-
|
|
7754
|
-
|
|
7755
|
-
|
|
7756
|
-
|
|
7757
|
-
|
|
7968
|
+
box.innerHTML = claims.map((c) => {
|
|
7969
|
+
const tasks = (c.tasks || []).map((tk) =>
|
|
7970
|
+
"<span>【" + (tk.side === "frontend" ? t("前端") : t("后端")) + "】" +
|
|
7971
|
+
'<a href="#" onclick="zentaoOpenRun(\'' + esc(tk.run_id || "") + '\');return false;">' + esc(tk.task_id || "?") + "</a></span>").join("");
|
|
7972
|
+
return '<div class="card"><div class="head"><span class="name">#' + esc(c.bug_id) + " " + esc(c.title || "") + "</span>" +
|
|
7973
|
+
zentaoStateTag(c.state) + "</div>" +
|
|
7974
|
+
'<div class="auto-meta">' +
|
|
7975
|
+
(zentaoTriText(c) ? "<span>" + esc(zentaoTriText(c)) + "</span>" : "") +
|
|
7976
|
+
(tasks || "") +
|
|
7977
|
+
(c.claimed_at ? "<span>" + t("认领于 ") + esc(c.claimed_at) + "</span>" : "") +
|
|
7978
|
+
"</div>" +
|
|
7979
|
+
(c.note ? '<div class="note">' + esc(c.note) + "</div>" : "") +
|
|
7980
|
+
"</div>";
|
|
7981
|
+
}).join("") || '<div class="empty">' + t("还没有认领过 Bug——配置好连接与产品档案后点「立即扫描」。") + "</div>";
|
|
7758
7982
|
}
|
|
7759
7983
|
|
|
7760
|
-
function
|
|
7761
|
-
|
|
7762
|
-
if (c && c.run_id) { openRun(c.run_id); return; }
|
|
7984
|
+
function zentaoOpenRun(runId) {
|
|
7985
|
+
if (runId) { openRun(runId); return; }
|
|
7763
7986
|
switchTab("tasks");
|
|
7764
7987
|
}
|
|
7765
7988
|
|
|
7766
7989
|
/* 表单 → 保存配置。password 只在用户输入了才提交(空 = 保持已存密码)。 */
|
|
7767
7990
|
async function saveZentao() {
|
|
7768
7991
|
const val = (id) => (($(id).value || "").trim());
|
|
7769
|
-
const products = val("zt-products").split(/[,,\s]+/).filter(Boolean).map(Number);
|
|
7770
7992
|
const payload = {
|
|
7771
7993
|
base_url: val("zt-base-url"),
|
|
7772
7994
|
account: val("zt-account"),
|
|
7773
|
-
|
|
7774
|
-
|
|
7775
|
-
severity_cap: parseInt($("zt-sev").value, 10) || 0,
|
|
7776
|
-
workdir: val("zt-workdir"),
|
|
7777
|
-
git_rev: val("zt-gitrev"),
|
|
7778
|
-
verify_command: val("zt-verify"),
|
|
7995
|
+
product_profiles: ztHarvestProfiles(),
|
|
7996
|
+
triage_ai: $("zt-triageai").checked,
|
|
7779
7997
|
auto_resolve: $("zt-autoresolve").checked,
|
|
7780
7998
|
auto_merge: $("zt-automerge").checked,
|
|
7781
7999
|
poll_enabled: $("zt-poll").checked,
|
|
@@ -7976,7 +8194,7 @@ function mkrCardHtml(p) {
|
|
|
7976
8194
|
const meta = [p.author, p.version ? "v" + p.version : ""].filter(Boolean).join(" · ");
|
|
7977
8195
|
return '<div class="card mk-card' + (p.compat === "blocked" ? " blocked" : "") + '"><div class="head">' +
|
|
7978
8196
|
'<span class="name">' + esc(t(p.title || p.name)) + "</span>" +
|
|
7979
|
-
'<span class="tag mk-src">' + esc(p.source_name || "") + "</span>" +
|
|
8197
|
+
'<span class="tag mk-src">' + esc(t(p.source_name || "")) + "</span>" +
|
|
7980
8198
|
(p.category ? '<span class="tag">' + esc(p.category) + "</span>" : "") +
|
|
7981
8199
|
"</div>" +
|
|
7982
8200
|
'<div class="note">' + esc(t(p.desc || "")) + "</div>" +
|
|
@@ -7999,7 +8217,7 @@ function renderMarketRemote() {
|
|
|
7999
8217
|
if (srcSel && !srcSel.options.length) {
|
|
8000
8218
|
srcSel.innerHTML = '<option value="">' + t("全部来源") + "</option>" +
|
|
8001
8219
|
(data.sources || []).map((s) =>
|
|
8002
|
-
'<option value="' + esc(s.id) + '">' + esc(s.name) + t("(") + s.count + t(")") + "</option>").join("");
|
|
8220
|
+
'<option value="' + esc(s.id) + '">' + esc(t(s.name)) + t("(") + s.count + t(")") + "</option>").join("");
|
|
8003
8221
|
}
|
|
8004
8222
|
const times = (data.sources || []).map((s) => s.fetched_at).filter(Boolean);
|
|
8005
8223
|
const meta = $("mkr-meta");
|
|
@@ -8054,7 +8272,7 @@ async function mkrRefresh() {
|
|
|
8054
8272
|
const keep = srcSel.value;
|
|
8055
8273
|
srcSel.innerHTML = '<option value="">' + t("全部来源") + "</option>" +
|
|
8056
8274
|
(r.sources || []).map((s) =>
|
|
8057
|
-
'<option value="' + esc(s.id) + '">' + esc(s.name) + t("(") + s.count + t(")") + "</option>").join("");
|
|
8275
|
+
'<option value="' + esc(s.id) + '">' + esc(t(s.name)) + t("(") + s.count + t(")") + "</option>").join("");
|
|
8058
8276
|
srcSel.value = keep;
|
|
8059
8277
|
}
|
|
8060
8278
|
mkrLoadPage(true);
|
|
@@ -8174,7 +8392,7 @@ async function loadSettingsV2() {
|
|
|
8174
8392
|
inp = '<input id="' + iid + '" type="text" value="' + esc(String(cur)) + '">';
|
|
8175
8393
|
}
|
|
8176
8394
|
html += '<div class="set-v2-row">' + inp +
|
|
8177
|
-
'<span class="set-v2-lb" title="' + esc(f.description || f.path) + '">' + esc(f.description || f.path) + "</span></div>";
|
|
8395
|
+
'<span class="set-v2-lb" title="' + esc(t(f.description || f.path)) + '">' + esc(t(f.description || f.path)) + "</span></div>";
|
|
8178
8396
|
}
|
|
8179
8397
|
html += '<div class="input-row" style="margin-top:6px"><button class="ghost small" onclick="saveSettingsV2(\'' + esc(ns) + '\')">' + t("保存") + "</button>" +
|
|
8180
8398
|
'<span class="set-v2-msg msg"></span></div></div>';
|
|
@@ -8352,13 +8570,158 @@ function maybeWhatsnew() {
|
|
|
8352
8570
|
}
|
|
8353
8571
|
}
|
|
8354
8572
|
|
|
8355
|
-
/* =====
|
|
8356
|
-
*
|
|
8357
|
-
*
|
|
8358
|
-
|
|
8573
|
+
/* ===== 帮助中心(首启欢迎引导升级版):左目录右内容 =====
|
|
8574
|
+
* 首启自动弹(orch.welcomed 记账只弹一次,徽章「首次启动」,落在「快速上手」章);
|
|
8575
|
+
* 手动开(设置导航/关于页/Ctrl+K)徽章「帮助中心」。加新章 = HELP_CHAPTERS
|
|
8576
|
+
* 追加一条 + helpChapterBody 加一个分支;todo:true 的章自动渲染「整理中」占位。 */
|
|
8577
|
+
var HELP_CHAPTERS = [
|
|
8578
|
+
{ id: "quickstart", icon: "i-rocket", label: "快速上手" },
|
|
8579
|
+
{ id: "models", icon: "i-blocks", label: "模型接入与绑定" },
|
|
8580
|
+
{ id: "features", icon: "i-bee", label: "功能一览" },
|
|
8581
|
+
{ id: "serial", icon: "i-book-open", label: "连载创作流程" },
|
|
8582
|
+
{ id: "publish", icon: "i-clapper", label: "发布上架" },
|
|
8583
|
+
{ id: "code", icon: "i-git-branch", label: "代码任务与版本" },
|
|
8584
|
+
{ id: "auto", icon: "i-calendar-days", label: "自动化与技能市场" },
|
|
8585
|
+
{ id: "faq", icon: "i-search", label: "常见问题" },
|
|
8586
|
+
];
|
|
8587
|
+
var helpCur = "quickstart";
|
|
8588
|
+
var helpFirst = false; // 本次打开是否首启自动弹(决定徽章文案;语言切换时也要用)
|
|
8589
|
+
|
|
8590
|
+
function helpChapterBody(id) {
|
|
8591
|
+
if (id === "quickstart") {
|
|
8592
|
+
return '<div class="welcome-steps">' +
|
|
8593
|
+
'<div class="wstep"><b>1</b><span>' + t("添加模型供应商:设置 → 模型接入,填入 API Key") + '</span></div>' +
|
|
8594
|
+
'<div class="wstep"><b>2</b><span>' + t("绑定 CLI 智能体:点「一键推荐绑定」自动配好") + '</span></div>' +
|
|
8595
|
+
'<div class="wstep"><b>3</b><span>' + t("新建任务:选工作目录、写目标,蜂群开工") + '</span></div>' +
|
|
8596
|
+
'</div>' +
|
|
8597
|
+
'<p class="help-note">' + t("任务跑起来后,详情页能看到步骤、蜂巢、成果文件与 Git 版本;「直接执行」类任务还能像聊天一样边跑边追加消息。") + '</p>' +
|
|
8598
|
+
'<div class="welcome-acts">' +
|
|
8599
|
+
'<button class="wl-btn primary" onclick="welcomeGo(\'models\')"><span>' + t("开始配置模型") + '</span><svg class="ico" aria-hidden="true"><use href="#i-chevron-r"></use></svg></button>' +
|
|
8600
|
+
'<button class="wl-btn" onclick="welcomeGo(\'bindings\')"><span>' + t("绑定 CLI 智能体") + '</span><svg class="ico" aria-hidden="true"><use href="#i-chevron-r"></use></svg></button>' +
|
|
8601
|
+
'<button class="wl-btn ghost" onclick="welcomeClose()"><span>' + t("先跳过,直接体验") + '</span></button>' +
|
|
8602
|
+
'</div>';
|
|
8603
|
+
}
|
|
8604
|
+
if (id === "models") {
|
|
8605
|
+
return '<h3 class="help-h3">' + t("第一步:添加供应商") + '</h3>' +
|
|
8606
|
+
'<p class="help-p">' + t("「设置 → 模型接入 → 添加供应商」:填名称、API Key、接口地址(一般用默认)。保存后点「获取模型列表」自动拉取该厂商的模型;网关不提供列表接口时直接手工填模型名即可。同一厂商可配多把 Key,按顺序轮换,某把欠费自动切下一把。") + '</p>' +
|
|
8607
|
+
'<h3 class="help-h3">' + t("第二步:认识协议徽章") + '</h3>' +
|
|
8608
|
+
'<p class="help-p">' + t("新供应商默认自动识别协议,保存后在后台实测支持哪些调用形态,结果以徽章标在卡片上。带「· chat」表示该模型只适合站内直连对话;codex 这类只讲 responses 协议的 CLI 用不了它,绑定页会自动剔除,不用自己排查。") + '</p>' +
|
|
8609
|
+
'<h3 class="help-h3">' + t("第三步:绑定 CLI 智能体") + '</h3>' +
|
|
8610
|
+
'<p class="help-p">' + t("「设置 → CLI 绑定」给每个智能体配一条模型链,运行时按顺序故障转移。「一键推荐绑定」只填空缺,不覆盖手工配置;删除或停用厂商后链会自动补绑。") + '</p>' +
|
|
8611
|
+
'<h3 class="help-h3">' + t("报错速查") + '</h3>' +
|
|
8612
|
+
'<ul class="help-list">' +
|
|
8613
|
+
'<li><b>401</b><span>' + t("Key 无效或过期——检查或更换 Key。") + '</span></li>' +
|
|
8614
|
+
'<li><b>404</b><span>' + t("模型名不对;若发生在「获取模型列表」,是网关不提供列表接口,能正常对话就不用管。") + '</span></li>' +
|
|
8615
|
+
'<li><b>503</b><span>' + t("该模型当前无可用渠道——查余额或换一个模型。") + '</span></li>' +
|
|
8616
|
+
'<li><b>' + t("冷却中") + '</b><span>' + t("这把 Key 连续失败被暂停 30 分钟,到期自动恢复,也可在密钥旁手动重置。") + '</span></li>' +
|
|
8617
|
+
'</ul>' +
|
|
8618
|
+
'<p class="help-note">' + t("想零成本先跑通:挑有免费档位的厂商配一个模型,跑通第一个任务后再按需加码。") + '</p>';
|
|
8619
|
+
}
|
|
8620
|
+
if (id === "features") {
|
|
8621
|
+
return '<ul class="help-feats">' +
|
|
8622
|
+
'<li><svg class="ico" aria-hidden="true"><use href="#i-workflow"></use></svg><div><b>' + t("多智能体编排") + '</b><span>' + t("规划 · 实现 · 评审 · 打磨,接棒交付") + '</span></div></li>' +
|
|
8623
|
+
'<li><svg class="ico" aria-hidden="true"><use href="#i-book-open"></use></svg><div><b>' + t("连载写作") + '</b><span>' + t("断点续跑 · 经验沉淀 · 作品资料") + '</span></div></li>' +
|
|
8624
|
+
'<li><svg class="ico" aria-hidden="true"><use href="#i-clapper"></use></svg><div><b>' + t("发布上架") + '</b><span>' + t("一键发布到番茄、七猫,含封面与作品资料") + '</span></div></li>' +
|
|
8625
|
+
'<li><svg class="ico" aria-hidden="true"><use href="#i-git-branch"></use></svg><div><b>' + t("版本工作台") + '</b><span>' + t("任务分支隔离,改动可审可合") + '</span></div></li>' +
|
|
8626
|
+
'<li><svg class="ico" aria-hidden="true"><use href="#i-calendar-days"></use></svg><div><b>' + t("自动化") + '</b><span>' + t("定时任务,无人值守") + '</span></div></li>' +
|
|
8627
|
+
'<li><svg class="ico" aria-hidden="true"><use href="#i-library"></use></svg><div><b>' + t("经验库") + '</b><span>' + t("内置规范包 + 自动沉淀教训,越用越顺手") + '</span></div></li>' +
|
|
8628
|
+
'<li><svg class="ico" aria-hidden="true"><use href="#i-blocks"></use></svg><div><b>' + t("插件市场") + '</b><span>' + t("600+ 技能,一键安装") + '</span></div></li>' +
|
|
8629
|
+
'</ul>';
|
|
8630
|
+
}
|
|
8631
|
+
if (id === "serial") {
|
|
8632
|
+
return '<h3 class="help-h3">' + t("开一本新书") + '</h3>' +
|
|
8633
|
+
'<p class="help-p">' + t("新建任务选「连载写作」类型,目标里写清书名、题材和计划篇幅(如「都市异能,先写 20 章」)。目标写得太笼统时,蜂群会先反问几个澄清问题再开工。开书前会先准备作品资料:书简介与分类标签可一键生成,按平台官方选项实抓,不用自己去查规则。") + '</p>' +
|
|
8634
|
+
'<h3 class="help-h3">' + t("每章怎么跑") + '</h3>' +
|
|
8635
|
+
'<p class="help-p">' + t("大纲确认后分章推进:起草 → 跨厂商评审 → 打磨 → 定稿。评审不过会自动换将重写,不带着问题过关;需要你拍板的地方会亮起「待裁决」。写作时详情页可实时预览章节,蜂巢页每个智能体一格,点开能看谁在干什么。") + '</p>' +
|
|
8636
|
+
'<h3 class="help-h3">' + t("中断了怎么办") + '</h3>' +
|
|
8637
|
+
'<p class="help-p">' + t("任务随时可续跑:已写章节都落了盘,续跑接着往下写,不会从头再来。单章失败会自动退避重试,任务状态里会显示「将于 HH:MM 自动续跑」,不用盯着等。") + '</p>' +
|
|
8638
|
+
'<h3 class="help-h3">' + t("递话与经验") + '</h3>' +
|
|
8639
|
+
'<p class="help-p">' + t("跑的过程中可以在详情页给蜂群递话(文字/截图/附件),下一轮生效。每轮结束复盘官会把踩过的坑沉淀进经验库,后续任务自动复用,越用越顺手。") + '</p>';
|
|
8640
|
+
}
|
|
8641
|
+
if (id === "publish") {
|
|
8642
|
+
return '<h3 class="help-h3">' + t("第一次发布前:登录平台") + '</h3>' +
|
|
8643
|
+
'<p class="help-p">' + t("目前支持番茄小说与七猫。第一次发布会引导你在浏览器里登录平台账号(扫码或账密),登录态保存在本机,之后发布免登录;过期了重新登一次即可。") + '</p>' +
|
|
8644
|
+
'<h3 class="help-h3">' + t("建书:资料与封面") + '</h3>' +
|
|
8645
|
+
'<p class="help-p">' + t("连载任务的建书面板里,作品简介、分类标签可以按平台官方选项一键生成;封面点「生成封面」自动出竖版图(cover.png 落在任务运行目录)。") + '</p>' +
|
|
8646
|
+
'<h3 class="help-h3">' + t("发章与确认") + '</h3>' +
|
|
8647
|
+
'<p class="help-p">' + t("章稿定稿后一键发布:自动填标题正文、处理平台的各类弹层,但提交前会停下来让你确认——绝不静默替你上架。发布完成后任务详情里能看到结果与章节链接。") + '</p>' +
|
|
8648
|
+
'<h3 class="help-h3">' + t("发布失败") + '</h3>' +
|
|
8649
|
+
'<p class="help-p">' + t("平台改版或登录态过期会导致失败,失败信息会写明原因。重新登录后再点重试即可;反复失败可以到 GitHub 提 Issue 附上失败截图。") + '</p>';
|
|
8650
|
+
}
|
|
8651
|
+
if (id === "code") {
|
|
8652
|
+
return '<h3 class="help-h3">' + t("代码任务怎么跑") + '</h3>' +
|
|
8653
|
+
'<p class="help-p">' + t("新建任务选开发类类型,目标写清楚要做什么(可以附需求文档、截图,附件会进入上下文)。蜂群先出计划再实现:实现 → 客观验证 → 跨厂商评审 → 修复,单路实现失败还有多候选赛马——各候选在隔离的 worktree 里各写各的,择优采纳。跑的过程中可以在详情页递话(文字/截图),下一轮生效。") + '</p>' +
|
|
8654
|
+
'<h3 class="help-h3">' + t("任务分支隔离:你的工作区不受影响") + '</h3>' +
|
|
8655
|
+
'<p class="help-p">' + t("启用代码版本隔离的任务会检出独立的任务分支(codebee/任务ID),所有改动只落在那条分支上,你当前的分支和未提交的改动完全不动——开跑前工作区有脏改动会先帮你收起(stash),跑完自动还原。") + '</p>' +
|
|
8656
|
+
'<h3 class="help-h3">' + t("版本裁决:合并还是丢弃") + '</h3>' +
|
|
8657
|
+
'<p class="help-p">' + t("任务跑完后,「版本」页签会亮起「待裁决」等你拍板:先看变更清单,双击文件能看 diff;满意点「合并」,任务分支合回基线分支;不满意点「丢弃」,分支和工作现场自动清场。中途停下的任务也会留好提交,可以继续跑完再裁决。合并与丢弃绝不静默替你决定。") + '</p>' +
|
|
8658
|
+
'<h3 class="help-h3">' + t("哪里看细节") + '</h3>' +
|
|
8659
|
+
'<p class="help-p">' + t("「蜂巢」页签看每个智能体实时在干什么(点格子看实时日志);「步骤」页签看任务拆解、每步实际用的模型、耗时与失败原因,评审分数与打磨记录也在这里。") + '</p>';
|
|
8660
|
+
}
|
|
8661
|
+
if (id === "auto") {
|
|
8662
|
+
return '<h3 class="help-h3">' + t("定时任务") + '</h3>' +
|
|
8663
|
+
'<p class="help-p">' + t("设置 → 自动化:把任意任务类型挂上时间表(每天/每周固定时刻),到点自动按真实运行链开跑,结果照常落盘。配合群机器人(钉钉/飞书/企微自动适配)可以把完成状态推到群里。") + '</p>' +
|
|
8664
|
+
'<h3 class="help-h3">' + t("禅道 Bug 自动修复") + '</h3>' +
|
|
8665
|
+
'<p class="help-p">' + t("设置 → 禅道:配好地址与产品档案后,定时扫描激活 Bug,按模块路由 / AI 排查定责(前端/后端/双端/非我方);我方端的 Bug 自动创建代码修复任务,修完自动合并、resolve 并回写报告到群里;测试指错人的也会按排查结论改派。合并失败不 resolve 不转派,留人工兜底。") + '</p>' +
|
|
8666
|
+
'<h3 class="help-h3">' + t("技能市场") + '</h3>' +
|
|
8667
|
+
'<p class="help-p">' + t("设置 → 插件市场:600+ 技能一键安装,来源包括内置库和 ZCode、Anthropic 等多个外部目录。装上的技能会注入智能体能力,按任务类型生效。") + '</p>' +
|
|
8668
|
+
'<h3 class="help-h3">' + t("用量台账") + '</h3>' +
|
|
8669
|
+
'<p class="help-p">' + t("设置 → 用量统计:每次调用的模型、token 与费用按多个维度聚合展示,历史运行会在启动时自动回填——每个任务花了多少,一目了然。") + '</p>';
|
|
8670
|
+
}
|
|
8671
|
+
if (id === "faq") {
|
|
8672
|
+
return '<div class="help-qa"><b>' + t("点「获取模型列表」报 404?") + '</b><p>' + t("部分网关不提供模型列表接口,属正常现象;只要能正常对话就不用管,模型名手工填即可。") + '</p></div>' +
|
|
8673
|
+
'<div class="help-qa"><b>' + t("报 503 / 无可用渠道?") + '</b><p>' + t("该模型当前没有可用渠道,最常见是余额耗尽。查一下余额或换个模型;同一厂商配了多把 Key 会自动轮换。") + '</p></div>' +
|
|
8674
|
+
'<div class="help-qa"><b>' + t("「冷却中」是什么意思?") + '</b><p>' + t("一把 Key 连续失败会被暂停 30 分钟,防止反复撞墙烧钱,到期自动恢复;也可以在密钥旁手动重置。") + '</p></div>' +
|
|
8675
|
+
'<div class="help-qa"><b>' + t("任务一直显示「排队」?") + '</b><p>' + t("本地默认最多 6 个任务同时跑,排满就排队。排队时可以打开详情看前面还有几个;个别卡死的任务,看门狗会自动清理补队。") + '</p></div>' +
|
|
8676
|
+
'<div class="help-qa"><b>' + t("状态里写着「将于 HH:MM 自动续跑」?") + '</b><p>' + t("这一步失败了,正在退避等待自动重试,到点会接着跑,不需要手动干预;等不及也可以在详情页手动重试。") + '</p></div>' +
|
|
8677
|
+
'<div class="help-qa"><b>' + t("生成的文件在哪?") + '</b><p>' + t("写作类任务的章节、封面、报告都落在任务的运行目录,详情页「成果」页签可浏览和预览。代码类任务则在仓库的任务分支上改代码,详情页「版本」里审阅后再合并。") + '</p></div>' +
|
|
8678
|
+
'<div class="help-qa"><b>' + t("忘了令牌 / 手机打不开页面?") + '</b><p>' + t("服务启动日志里有带令牌的完整访问地址;远程设备必须用带令牌的 URL 打开(或在令牌门里输入一次),否则会一直要求授权。") + '</p></div>' +
|
|
8679
|
+
'<div class="help-qa"><b>' + t("升级后数据会丢吗?") + '</b><p>' + t("不会。任务、配置、用量台账都在独立的数据目录里,升级只替换程序本体;老版本装新版会自动沿用原数据目录。") + '</p></div>' +
|
|
8680
|
+
'<div class="help-qa"><b>' + t("页面打不开 / 服务起不来?") + '</b><p>' + t("多半是默认端口 8765 被旧进程占着:结束旧的 python 进程,或启动时用 --port 换个端口。还不行就到 GitHub 提 Issue。") + '</p></div>';
|
|
8681
|
+
}
|
|
8682
|
+
const ch = HELP_CHAPTERS.find((c) => c.id === id);
|
|
8683
|
+
if (ch && ch.todo) {
|
|
8684
|
+
return '<div class="help-todo"><b>' + t(ch.label || "") + '</b><p>' +
|
|
8685
|
+
t("本章节正在整理,可先看「快速上手」与「模型接入与绑定」,或到 GitHub 提 Issue 提问。") + '</p></div>';
|
|
8686
|
+
}
|
|
8687
|
+
return "";
|
|
8688
|
+
}
|
|
8689
|
+
|
|
8690
|
+
function renderHelp() {
|
|
8691
|
+
const toc = $("help-toc");
|
|
8692
|
+
if (toc) {
|
|
8693
|
+
toc.innerHTML = HELP_CHAPTERS.map((c) =>
|
|
8694
|
+
'<button class="hitem' + (c.id === helpCur ? " active" : "") + (c.todo ? " todo" : "") +
|
|
8695
|
+
'" data-ch="' + c.id + '" onclick="helpGo(\'' + c.id + '\')">' +
|
|
8696
|
+
'<svg class="ico" aria-hidden="true"><use href="#' + c.icon + '"></use></svg><span>' + t(c.label) + '</span>' +
|
|
8697
|
+
(c.todo ? '<em class="h-todo">' + t("整理中") + '</em>' : "") +
|
|
8698
|
+
'</button>').join("");
|
|
8699
|
+
}
|
|
8700
|
+
const body = $("help-body");
|
|
8701
|
+
if (body) {
|
|
8702
|
+
body.innerHTML = helpChapterBody(helpCur);
|
|
8703
|
+
}
|
|
8704
|
+
}
|
|
8705
|
+
function setHelpChapter(id) {
|
|
8706
|
+
helpCur = HELP_CHAPTERS.some((c) => c.id === id) ? id : "quickstart";
|
|
8707
|
+
renderHelp();
|
|
8708
|
+
}
|
|
8709
|
+
function helpGo(id) { setHelpChapter(id); }
|
|
8710
|
+
function paintHelpChrome() {
|
|
8711
|
+
const badge = $("welcome-badge");
|
|
8712
|
+
if (badge) badge.textContent = t(helpFirst ? "首次启动" : "帮助中心");
|
|
8713
|
+
const title = $("welcome-title");
|
|
8714
|
+
if (title) title.textContent = t(helpFirst ? "欢迎使用 CodeBee" : "帮助中心");
|
|
8715
|
+
}
|
|
8716
|
+
function welcomeOpen(opts) {
|
|
8359
8717
|
const w = $("welcome");
|
|
8360
8718
|
if (!w) return;
|
|
8361
8719
|
if (!$("token-gate").classList.contains("hidden")) return;
|
|
8720
|
+
helpFirst = !!(opts && opts.firstRun);
|
|
8721
|
+
if (helpFirst) helpCur = "quickstart";
|
|
8722
|
+
else if (opts && opts.topic && HELP_CHAPTERS.some((c) => c.id === opts.topic)) helpCur = opts.topic;
|
|
8723
|
+
paintHelpChrome();
|
|
8724
|
+
renderHelp();
|
|
8362
8725
|
w.classList.remove("hidden");
|
|
8363
8726
|
document.body.classList.add("welcome-open");
|
|
8364
8727
|
}
|
|
@@ -8376,11 +8739,22 @@ function welcomeGo(tab) {
|
|
|
8376
8739
|
function maybeWelcome() {
|
|
8377
8740
|
let seen = "";
|
|
8378
8741
|
try { seen = localStorage.getItem("orch.welcomed"); } catch (e) { /* ignore */ }
|
|
8379
|
-
if (!seen) welcomeOpen();
|
|
8742
|
+
if (!seen) welcomeOpen({ firstRun: true });
|
|
8380
8743
|
}
|
|
8381
8744
|
window.welcomeOpen = welcomeOpen;
|
|
8382
8745
|
window.welcomeClose = welcomeClose;
|
|
8383
8746
|
window.welcomeGo = welcomeGo;
|
|
8747
|
+
window.helpGo = helpGo;
|
|
8748
|
+
// 空状态里的「看帮助」链接用:打开帮助中心对应章并吞掉默认跳转
|
|
8749
|
+
window.helpOpenTopic = function (topic) { welcomeOpen({ topic: topic }); return false; };
|
|
8750
|
+
|
|
8751
|
+
/* 就地帮助问号:页面里任何 .hhelp(data-help-topic)点击直达帮助中心对应章。
|
|
8752
|
+
* 事件委托挂在 document 上,动态渲染出来的问号无需逐个接线。 */
|
|
8753
|
+
document.addEventListener("click", (e) => {
|
|
8754
|
+
const btn = e.target && e.target.closest ? e.target.closest(".hhelp") : null;
|
|
8755
|
+
if (!btn) return;
|
|
8756
|
+
welcomeOpen({ topic: btn.dataset.helpTopic });
|
|
8757
|
+
});
|
|
8384
8758
|
|
|
8385
8759
|
function renderSu() {
|
|
8386
8760
|
const info = $("su-info");
|
|
@@ -8485,7 +8859,7 @@ async function suCheck() {
|
|
|
8485
8859
|
finally { if (b) b.disabled = false; }
|
|
8486
8860
|
if (!s) { toast(t("检查更新失败:服务未连接"), true); return; }
|
|
8487
8861
|
if (s.has_update) toast(t("发现新版本 v") + s.latest + t(",点「升级到新版」即可"));
|
|
8488
|
-
else if (s.note) toast(s.note);
|
|
8862
|
+
else if (s.note) toast(t(s.note));
|
|
8489
8863
|
else toast(t("已是最新版"));
|
|
8490
8864
|
}
|
|
8491
8865
|
|
|
@@ -8718,6 +9092,11 @@ function setLangBtn(lang) {
|
|
|
8718
9092
|
paintArchToggle();
|
|
8719
9093
|
syncSideExpandBtn();
|
|
8720
9094
|
renderCodePreviews(); // 预览徽章是动态文案:语言切换时若停在皮肤页要跟着换
|
|
9095
|
+
// 帮助中心若开着:徽章/标题/目录/正文跟着换语言重画
|
|
9096
|
+
if (!$("welcome").classList.contains("hidden")) {
|
|
9097
|
+
paintHelpChrome();
|
|
9098
|
+
renderHelp();
|
|
9099
|
+
}
|
|
8721
9100
|
// 重画还在缓存里的页面标题
|
|
8722
9101
|
const title = $("page-title");
|
|
8723
9102
|
if (title && S.tab) title.textContent = tabTitle(S.tab);
|
|
@@ -9196,7 +9575,7 @@ function collapseDrawerIfMobile() {
|
|
|
9196
9575
|
|
|
9197
9576
|
function switchTab(name) {
|
|
9198
9577
|
if (name === "__phone") { openPhoneConnect(); return; } // 手机连接是弹框,不切页
|
|
9199
|
-
if (name === "__guide") { welcomeOpen(); return; } //
|
|
9578
|
+
if (name === "__guide") { welcomeOpen(); return; } // 帮助中心是弹层,不切页(设置导航「软件」组)
|
|
9200
9579
|
// 导航收进「设置」:进设置后左栏整体换成设置导航,内容铺满
|
|
9201
9580
|
S.tab = name;
|
|
9202
9581
|
if (SET_TABS.has(name)) localStorage.setItem("orch.setTab", name);
|
|
@@ -9683,6 +10062,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
9683
10062
|
&& e.target.tagName !== "TEXTAREA") { _askClose(true); return; }
|
|
9684
10063
|
// 文件内容弹窗(240) 夹在 ask(250) 与 modal(200) 之间,Esc 同样逐层关
|
|
9685
10064
|
if (e.key === "Escape" && filePopIsOpen()) { window.filePopClose(); return; }
|
|
10065
|
+
if (e.key === "F1") { e.preventDefault(); welcomeOpen(); return; } // F1 = 帮助中心(浏览器帮助被拦下)
|
|
9686
10066
|
if (e.key === "Escape" && !$("welcome").classList.contains("hidden")) { welcomeClose(); return; }
|
|
9687
10067
|
if (e.key === "Escape" && !$("modal").classList.contains("hidden")) closeModal();
|
|
9688
10068
|
// 日志抽屉:Esc 收起(最底层,放在弹窗之后)
|
|
@@ -9715,6 +10095,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
9715
10095
|
$("btn-zentao-test").addEventListener("click", testZentao);
|
|
9716
10096
|
$("btn-zentao-scan").addEventListener("click", scanZentao);
|
|
9717
10097
|
$("btn-zentao-save").addEventListener("click", saveZentao);
|
|
10098
|
+
$("btn-zentao-addprofile").addEventListener("click", ztProfAdd);
|
|
9718
10099
|
$("auto-filter").addEventListener("click", (e) => {
|
|
9719
10100
|
const b = e.target.closest("[data-f]");
|
|
9720
10101
|
if (!b) return;
|
|
@@ -9780,7 +10161,7 @@ function cmdkOps() {
|
|
|
9780
10161
|
{ icon: "i-cpu", label: t("智能体管理"), run: () => switchTab("agents") },
|
|
9781
10162
|
{ icon: "i-chart", label: t("用量统计"), run: () => switchTab("usage") },
|
|
9782
10163
|
{ icon: "i-gear", label: t("设置"), run: () => enterSettings() },
|
|
9783
|
-
{ icon: "i-bee", label: t("
|
|
10164
|
+
{ icon: "i-bee", label: t("帮助中心"), run: () => welcomeOpen() },
|
|
9784
10165
|
{ icon: "i-phone", label: t("手机连接"), run: () => switchTab("__phone") },
|
|
9785
10166
|
];
|
|
9786
10167
|
}
|