codebee 0.1.15 → 0.1.16
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 +5 -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/modelhub.py +23 -3
- package/app/core/notify.py +11 -1
- package/app/core/paihang.py +5 -2
- package/app/core/pipeline.py +80 -4
- 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/zentao.py +26 -26
- package/app/main.py +20 -0
- package/app/ui/app.js +411 -52
- package/app/ui/i18n.js +77 -7
- package/app/ui/index.html +25 -47
- 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();
|
|
@@ -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("本次运行进行中:报告结束后在这里生成,实时进度看「步骤」页签"))
|
|
@@ -3854,14 +3901,24 @@ function renderBookMetaPanel(task) {
|
|
|
3854
3901
|
pbBlock(task, p.id) +
|
|
3855
3902
|
"</div>";
|
|
3856
3903
|
}).join("") + "</div>";
|
|
3857
|
-
// 封面卡(covergen,借鉴 oh-story 封面图环节):curl 落盘运行目录,
|
|
3904
|
+
// 封面卡(covergen,借鉴 oh-story 封面图环节):curl 落盘运行目录,
|
|
3905
|
+
// done 后卡片内直接嵌缩略图(/api/runs/<id>/cover 专用通道),点开新页看大图
|
|
3858
3906
|
const cg = task.cover_gen || {};
|
|
3859
3907
|
const cgSt = cg.status || "";
|
|
3860
3908
|
let cgBody = "";
|
|
3861
3909
|
if (cgSt === "running") {
|
|
3862
3910
|
cgBody = '<div class="bm-empty"><svg class="ico spin" aria-hidden="true"><use href="#i-refresh"/></svg> ' + esc(t("生成中…")) + "</div>";
|
|
3911
|
+
} else if (cgSt === "done" && cg.run_id) {
|
|
3912
|
+
const covUrl = "/api/runs/" + encodeURIComponent(cg.run_id) + "/cover" +
|
|
3913
|
+
(cg.at ? "?ts=" + encodeURIComponent(cg.at) : ""); // at 当缓存戳,重生成后缩略图跟着换
|
|
3914
|
+
cgBody = '<div class="bm-cover-wrap">' +
|
|
3915
|
+
'<a href="' + esc(urlAuth(covUrl)) + '" target="_blank" rel="noopener" title="' + esc(t("点击查看大图")) + '">' +
|
|
3916
|
+
'<img class="bm-cover-img" src="' + esc(urlAuth(covUrl)) + '" alt="cover.png" ' +
|
|
3917
|
+
'onerror="this.closest(".bm-cover-wrap").classList.add("noimg")"></a>' +
|
|
3918
|
+
'<span class="bm-cover-fallback">' + esc(t("cover.png 已不在运行目录")) + "</span>" +
|
|
3919
|
+
'<div class="bm-cover-meta">' + esc([cg.model, cg.provider, cg.size].filter(Boolean).join(" · ")) + "</div></div>";
|
|
3863
3920
|
} else if (cgSt === "done") {
|
|
3864
|
-
cgBody = '<div class="bm-empty">' + esc(t("
|
|
3921
|
+
cgBody = '<div class="bm-empty">' + esc(t("封面已生成,但缺少运行记录归属")) + "</div>";
|
|
3865
3922
|
} else if (cgSt === "failed") {
|
|
3866
3923
|
cgBody = '<div class="bm-errhint">' + esc(cg.error || t("生成失败")) + "</div>";
|
|
3867
3924
|
} else {
|
|
@@ -4076,7 +4133,7 @@ window.pbUploadChapter = async function (taskId, platform) {
|
|
|
4076
4133
|
};
|
|
4077
4134
|
|
|
4078
4135
|
window.pbSendChapter = async function (taskId, platform, relName) {
|
|
4079
|
-
if (!confirm(t("将把《"
|
|
4136
|
+
if (!confirm(t("将把《{0}》填进平台章节编辑器,填好后由你人工提交。继续?", relName))) return;
|
|
4080
4137
|
try {
|
|
4081
4138
|
await api("/api/publish/task/" + encodeURIComponent(taskId) + "/chapter",
|
|
4082
4139
|
{ method: "POST", body: JSON.stringify({ platform, file: relName }) });
|
|
@@ -4091,8 +4148,7 @@ window.pbPublishAll = async function (taskId, platform) {
|
|
|
4091
4148
|
const au = (((S.pubAuto && S.pubAuto.books) || [])
|
|
4092
4149
|
.find((b) => b.platform === platform)) || {};
|
|
4093
4150
|
if (!au.pending) return;
|
|
4094
|
-
if (!confirm(t("将从最靠前的待发章节开始填稿(共 "
|
|
4095
|
-
" 章待发)。本轮只填一章并停在表单页,由你在浏览器里确认提交;提交后再点一次即发下一章。护栏(每日上限/连续失败暂停)生效。继续?"))) return;
|
|
4151
|
+
if (!confirm(t("将从最靠前的待发章节开始填稿(共 {0} 章待发)。本轮只填一章并停在表单页,由你在浏览器里确认提交;提交后再点一次即发下一章。护栏(每日上限/连续失败暂停)生效。继续?", au.pending))) return;
|
|
4096
4152
|
try {
|
|
4097
4153
|
await api("/api/publish/task/" + encodeURIComponent(taskId) + "/publish-all",
|
|
4098
4154
|
{ method: "POST", body: JSON.stringify({ platform }) });
|
|
@@ -6548,6 +6604,17 @@ function provAdaptedProto(p, allow, kind) {
|
|
|
6548
6604
|
&& !chatBlocked((caps[pr] || {}).wire_api)) || "";
|
|
6549
6605
|
}
|
|
6550
6606
|
|
|
6607
|
+
/* 死因细化:协议适配失败里最常见的一种是「openai 面实测只探到 chat 形态」
|
|
6608
|
+
* (codex 0.154+ 只讲 responses,其余 CLI 不受影响)。按真实死因说话——
|
|
6609
|
+
* 模型接入页明明亮着 openai ✓,链上却笼统说「协议不匹配」,看着就像自相矛盾。 */
|
|
6610
|
+
function chatShapedFace(pv, allow) {
|
|
6611
|
+
if (!pv) return false;
|
|
6612
|
+
const chat = (w) => (w || "responses") === "chat";
|
|
6613
|
+
if (allow.indexOf(pv.protocol) >= 0 && chat(pv.wire_api)) return true;
|
|
6614
|
+
const caps = pv.wire_caps || {};
|
|
6615
|
+
return allow.some((pr) => (caps[pr] || {}).base && chat((caps[pr] || {}).wire_api));
|
|
6616
|
+
}
|
|
6617
|
+
|
|
6551
6618
|
/* CLI 允许注入的供应商 wire 协议——与 modelhub.resolve_binding 的 allowed 规则
|
|
6552
6619
|
* 保持一致(codex 只吃 openai wire、claude 只吃 anthropic、dsh 只吃 OpenAI
|
|
6553
6620
|
* 兼容端点,其余两种皆可)。后端会跳过不匹配的链条目,这里在选择层就挡住。 */
|
|
@@ -6674,7 +6741,10 @@ function chainDeadReasons(c, chain) {
|
|
|
6674
6741
|
const pv = (S.providers || []).find((p) => p.id === x.p);
|
|
6675
6742
|
if (!pv) return t("供应商已删除");
|
|
6676
6743
|
if (pv.enabled === false) return t("供应商已停用");
|
|
6677
|
-
if (!provAdaptedProto(pv, allow, c.orch_kind))
|
|
6744
|
+
if (!provAdaptedProto(pv, allow, c.orch_kind))
|
|
6745
|
+
return chatShapedFace(pv, allow)
|
|
6746
|
+
? t("openai 面仅 chat 形态(codex 只讲 responses)")
|
|
6747
|
+
: t("协议不匹配");
|
|
6678
6748
|
const ks = pv.keys || [];
|
|
6679
6749
|
if (ks.length && !ks.some((k) => k.enabled !== false && !k.cooling))
|
|
6680
6750
|
return t("密钥全部冷却中");
|
|
@@ -6820,7 +6890,9 @@ function bindModelBox(c, provId) {
|
|
|
6820
6890
|
"<b>" + (i === 0 ? t("主") : t("备")) + "</b>" + esc(pname) + " · " + esc(c2.m) +
|
|
6821
6891
|
(modelHasImage(c2.p, c2.m)
|
|
6822
6892
|
? ' <span class="tag ok" title="' + esc(t("支持图片输入")) + '">' + t("图") + "</span>" : "") +
|
|
6823
|
-
(dead ? ' <span class="hint warn">⚠ ' + esc(
|
|
6893
|
+
(dead ? ' <span class="hint warn">⚠ ' + esc(chatShapedFace(pv, allow)
|
|
6894
|
+
? t("openai 面仅 chat 形态(codex 只讲 responses),解析时跳过")
|
|
6895
|
+
: t("协议不匹配,解析时跳过")) + "</span>" : badge) +
|
|
6824
6896
|
(i > 0 ? '<button class="mini" data-m="' + esc(c2.m) + '" data-p="' + esc(c2.p) +
|
|
6825
6897
|
'" title="' + t("设为主模型") + '" onclick="bindPromote(\'' + esc(c.id) + '\', this)">' +
|
|
6826
6898
|
'<svg class="ico" aria-hidden="true"><use href="#i-arrow-up"></use></svg></button>' : "") +
|
|
@@ -7683,8 +7755,8 @@ function autoTemplate(tplId) {
|
|
|
7683
7755
|
}
|
|
7684
7756
|
|
|
7685
7757
|
/* ---------------------------------------------------------- 禅道 Bug 自动修复 */
|
|
7686
|
-
/* /api/zentao → {config(
|
|
7687
|
-
*
|
|
7758
|
+
/* /api/zentao → {config(脱敏,含 product_profiles), claims, last_scan, next_scan, last_error}。
|
|
7759
|
+
* 产品档案卡:结构增删走「先采集 DOM 回 S.ztProfiles → 改 → 重渲染」,保住已输入值。 */
|
|
7688
7760
|
|
|
7689
7761
|
async function loadZentao() {
|
|
7690
7762
|
let v = null;
|
|
@@ -7696,35 +7768,180 @@ async function loadZentao() {
|
|
|
7696
7768
|
set("zt-account", cfg.account);
|
|
7697
7769
|
const pw = $("zt-password");
|
|
7698
7770
|
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
7771
|
const tg = (id, on) => { const el = $(id); if (el) el.checked = !!on; };
|
|
7772
|
+
tg("zt-triageai", cfg.triage_ai);
|
|
7706
7773
|
tg("zt-autoresolve", cfg.auto_resolve);
|
|
7707
7774
|
tg("zt-automerge", cfg.auto_merge);
|
|
7708
7775
|
tg("zt-poll", cfg.poll_enabled);
|
|
7709
7776
|
set("zt-interval", cfg.interval_hours || 2);
|
|
7777
|
+
S.ztProfiles = JSON.parse(JSON.stringify(cfg.product_profiles || []));
|
|
7778
|
+
renderZentaoProfiles();
|
|
7710
7779
|
renderZentaoStatus();
|
|
7711
7780
|
renderZentaoClaims();
|
|
7712
7781
|
}
|
|
7713
7782
|
|
|
7783
|
+
/* ---- 产品档案:渲染与采集 ---- */
|
|
7784
|
+
|
|
7785
|
+
const ZT_SIDES = ["backend", "frontend"];
|
|
7786
|
+
|
|
7787
|
+
function ztRepoHtml(i, side) {
|
|
7788
|
+
const cn = side === "backend" ? t("后端") : t("前端");
|
|
7789
|
+
const p = (S.ztProfiles[i] || {});
|
|
7790
|
+
const repo = (p.repos || {})[side] || {};
|
|
7791
|
+
const hint = (p.repo_hints || {})[side] || "";
|
|
7792
|
+
return '<label>' + cn + t("仓库·工作目录") + '</label><input class="zt-p-wd" data-side="' + side + '" value="' + esc(repo.workdir || "") + '" placeholder="' + t("空 = 不用该端") + '">' +
|
|
7793
|
+
'<label>' + cn + t("仓库·基线分支") + '</label><input class="zt-p-rev" data-side="' + side + '" value="' + esc(repo.git_rev || "") + '" placeholder="' + t("如 main;空 = 直接改工作目录") + '">' +
|
|
7794
|
+
'<label>' + cn + t("仓库·验证命令") + '</label><input class="zt-p-verify" data-side="' + side + '" value="' + esc(repo.verify_command || "") + '" placeholder="' + t("如 npm test;空 = 靠评审把关") + '">' +
|
|
7795
|
+
'<label>' + cn + t("仓库·一句话描述") + '</label><input class="zt-p-hint" data-side="' + side + '" value="' + esc(hint) + '" placeholder="' + t("给 AI 排查看,如:Vue3 管理台前端") + '">';
|
|
7796
|
+
}
|
|
7797
|
+
|
|
7798
|
+
function ztProfCardHtml(p, i) {
|
|
7799
|
+
const ours = p.our_sides || [];
|
|
7800
|
+
const routes = p.module_routes || [];
|
|
7801
|
+
const SIDE_OPTS = [["backend", "后端"], ["frontend", "前端"], ["both", "双端"], ["not_ours", "非我方"]];
|
|
7802
|
+
const rows = routes.map((r, j) =>
|
|
7803
|
+
'<div class="zt-mr-row" data-j="' + j + '">' +
|
|
7804
|
+
'<input class="zt-mr-module zt-num" list="zt-mods-' + i + '" placeholder="' + t("模块 ID") + '" value="' + esc(r.module || "") + '">' +
|
|
7805
|
+
'<select class="zt-mr-side">' + SIDE_OPTS.map((o) =>
|
|
7806
|
+
'<option value="' + o[0] + '"' + (r.side === o[0] ? " selected" : "") + ">" + t(o[1]) + "</option>").join("") + "</select>" +
|
|
7807
|
+
'<input class="zt-mr-account" placeholder="' + t("转给谁(禅道账号,可空)") + '" value="' + esc(r.account || "") + '">' +
|
|
7808
|
+
'<button class="ghost small" onclick="ztMrDel(' + i + "," + j + ')">' + t("删") + "</button>" +
|
|
7809
|
+
"</div>").join("");
|
|
7810
|
+
const owners = p.owners || {};
|
|
7811
|
+
return '<div class="card zt-prof" data-i="' + i + '"><div class="head">' +
|
|
7812
|
+
'<span class="name">' + t("产品") + ' <input class="zt-p-product zt-num" type="number" min="1" value="' + esc(p.product || "") + '" placeholder="ID"></span>' +
|
|
7813
|
+
'<button class="danger small" onclick="ztProfDel(' + i + ')">' + t("删除产品") + "</button></div>" +
|
|
7814
|
+
'<div class="zt-grid">' +
|
|
7815
|
+
'<label data-i18n="只认领指派给">只认领指派给</label><input class="zt-p-assigned" value="' + esc(p.assigned_to || "") + '" placeholder="' + t("禅道账号名,空 = 不按指派过滤") + '">' +
|
|
7816
|
+
'<label data-i18n="严重度上限">严重度上限</label><select class="zt-p-sev">' +
|
|
7817
|
+
[0, 1, 2, 3, 4].map((n) => '<option value="' + n + '"' + (Number(p.severity_cap || 0) === n ? " selected" : "") + ">" +
|
|
7818
|
+
(n === 0 ? t("不限") : n + (n === 1 ? t("(最严重)") : "")) + "</option>").join("") + "</select>" +
|
|
7819
|
+
"</div>" +
|
|
7820
|
+
'<div class="zt-ours"><span>' + t("我方端(自动修):") + "</span>" +
|
|
7821
|
+
ZT_SIDES.map((s) =>
|
|
7822
|
+
'<label class="toggle"><input type="checkbox" class="zt-p-ours" data-side="' + s + '"' + (ours.indexOf(s) >= 0 ? " checked" : "") + "><span>" +
|
|
7823
|
+
(s === "backend" ? t("后端") : t("前端")) + "</span></label>").join("") +
|
|
7824
|
+
'<span class="hint">' + t("都不勾 = 该产品只排查转派,不自动修") + "</span></div>" +
|
|
7825
|
+
'<div class="zt-grid">' + ZT_SIDES.map((s) => ztRepoHtml(i, s)).join("") + "</div>" +
|
|
7826
|
+
'<div class="zt-grid">' +
|
|
7827
|
+
'<label data-i18n="后端负责人">后端负责人</label><input class="zt-p-owner" data-side="backend" value="' + esc(owners.backend || "") + '" placeholder="' + t("禅道账号,转派/升级用") + '">' +
|
|
7828
|
+
'<label data-i18n="前端负责人">前端负责人</label><input class="zt-p-owner" data-side="frontend" value="' + esc(owners.frontend || "") + '" placeholder="' + t("禅道账号,转派/升级用") + '">' +
|
|
7829
|
+
'<label data-i18n="非我方转派给">非我方转派给</label><input class="zt-p-owner" data-side="not_ours" value="' + esc(owners.not_ours || "") + '" placeholder="' + t("空 = 指回报告人") + '">' +
|
|
7830
|
+
"</div>" +
|
|
7831
|
+
'<div class="zt-mrs"><div class="zt-mr-head">' + t("模块路由(模块 → 端/人,排查优先级最高)") +
|
|
7832
|
+
'<button class="ghost small" onclick="ztFetchMods(' + i + ')">' + t("拉取模块清单") + "</button>" +
|
|
7833
|
+
'<button class="ghost small" onclick="ztMrAdd(' + i + ')">' + t("+ 加路由") + "</button></div>" +
|
|
7834
|
+
(rows || '<div class="hint">' + t("未配路由——模块不在路由里的 Bug 走 AI 排查") + "</div>") +
|
|
7835
|
+
'<datalist id="zt-mods-' + i + '"></datalist>' +
|
|
7836
|
+
"</div></div>";
|
|
7837
|
+
}
|
|
7838
|
+
|
|
7839
|
+
function renderZentaoProfiles() {
|
|
7840
|
+
const box = $("zt-profiles");
|
|
7841
|
+
if (!box) return;
|
|
7842
|
+
box.innerHTML = (S.ztProfiles || []).map((p, i) => ztProfCardHtml(p, i)).join("") ||
|
|
7843
|
+
'<div class="empty">' + t("还没有产品档案——点「+ 添加产品」,填产品 ID 与两端仓库。") + "</div>";
|
|
7844
|
+
}
|
|
7845
|
+
|
|
7846
|
+
/* DOM 卡片 → profiles 数组(结构变化前采集,保住已输入值) */
|
|
7847
|
+
function ztHarvestProfiles() {
|
|
7848
|
+
return Array.from(document.querySelectorAll("#zt-profiles .zt-prof")).map((card) => {
|
|
7849
|
+
const gv = (sel) => { const el = card.querySelector(sel); return el ? (el.value || "").trim() : ""; };
|
|
7850
|
+
const routes = Array.from(card.querySelectorAll(".zt-mr-row")).map((row) => ({
|
|
7851
|
+
module: parseInt((row.querySelector(".zt-mr-module").value || ""), 10) || 0,
|
|
7852
|
+
side: row.querySelector(".zt-mr-side").value,
|
|
7853
|
+
account: (row.querySelector(".zt-mr-account").value || "").trim(),
|
|
7854
|
+
})).filter((r) => r.module > 0);
|
|
7855
|
+
return {
|
|
7856
|
+
product: parseInt(gv(".zt-p-product"), 10) || 0,
|
|
7857
|
+
assigned_to: gv(".zt-p-assigned"),
|
|
7858
|
+
severity_cap: parseInt(gv(".zt-p-sev"), 10) || 0,
|
|
7859
|
+
our_sides: Array.from(card.querySelectorAll(".zt-p-ours:checked")).map((el) => el.dataset.side),
|
|
7860
|
+
repos: {
|
|
7861
|
+
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"]') },
|
|
7862
|
+
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"]') },
|
|
7863
|
+
},
|
|
7864
|
+
repo_hints: {
|
|
7865
|
+
backend: gv('.zt-p-hint[data-side="backend"]'),
|
|
7866
|
+
frontend: gv('.zt-p-hint[data-side="frontend"]'),
|
|
7867
|
+
},
|
|
7868
|
+
owners: {
|
|
7869
|
+
backend: gv('.zt-p-owner[data-side="backend"]'),
|
|
7870
|
+
frontend: gv('.zt-p-owner[data-side="frontend"]'),
|
|
7871
|
+
not_ours: gv('.zt-p-owner[data-side="not_ours"]'),
|
|
7872
|
+
},
|
|
7873
|
+
module_routes: routes,
|
|
7874
|
+
};
|
|
7875
|
+
});
|
|
7876
|
+
}
|
|
7877
|
+
|
|
7878
|
+
function ztProfAdd() {
|
|
7879
|
+
S.ztProfiles = ztHarvestProfiles();
|
|
7880
|
+
S.ztProfiles.push({ product: 0, assigned_to: "", severity_cap: 0, our_sides: ["backend"],
|
|
7881
|
+
repos: { backend: {}, frontend: {} }, repo_hints: { backend: "", frontend: "" },
|
|
7882
|
+
owners: { backend: "", frontend: "", not_ours: "" }, module_routes: [] });
|
|
7883
|
+
renderZentaoProfiles();
|
|
7884
|
+
}
|
|
7885
|
+
|
|
7886
|
+
function ztProfDel(i) {
|
|
7887
|
+
S.ztProfiles = ztHarvestProfiles();
|
|
7888
|
+
S.ztProfiles.splice(i, 1);
|
|
7889
|
+
renderZentaoProfiles();
|
|
7890
|
+
}
|
|
7891
|
+
|
|
7892
|
+
function ztMrAdd(i) {
|
|
7893
|
+
S.ztProfiles = ztHarvestProfiles();
|
|
7894
|
+
(S.ztProfiles[i].module_routes = S.ztProfiles[i].module_routes || []).push({ module: "", side: "backend", account: "" });
|
|
7895
|
+
renderZentaoProfiles();
|
|
7896
|
+
}
|
|
7897
|
+
|
|
7898
|
+
function ztMrDel(i, j) {
|
|
7899
|
+
S.ztProfiles = ztHarvestProfiles();
|
|
7900
|
+
S.ztProfiles[i].module_routes.splice(j, 1);
|
|
7901
|
+
renderZentaoProfiles();
|
|
7902
|
+
}
|
|
7903
|
+
|
|
7904
|
+
async function ztFetchMods(i) {
|
|
7905
|
+
const pid = parseInt(($("zt-profiles").querySelectorAll(".zt-prof")[i]?.querySelector(".zt-p-product").value) || "", 10);
|
|
7906
|
+
if (!pid) { toast(t("先填产品 ID 再拉模块清单"), true); return; }
|
|
7907
|
+
let r;
|
|
7908
|
+
try {
|
|
7909
|
+
r = await api("/api/zentao/modules", { method: "POST", body: JSON.stringify({ product: pid }) });
|
|
7910
|
+
} catch (e) { toast(e.message, true); return; }
|
|
7911
|
+
if (!r.ok) { toast(r.error || t("拉取失败"), true); return; }
|
|
7912
|
+
const dl = $("zt-mods-" + i);
|
|
7913
|
+
if (dl) dl.innerHTML = (r.modules || []).map((m) =>
|
|
7914
|
+
'<option value="' + esc(m.id) + '">' + esc(m.name || "") + "</option>").join("");
|
|
7915
|
+
toast(t("模块清单已拉到 ") + (r.modules || []).length + t(" 条,输入框可下拉选择"));
|
|
7916
|
+
}
|
|
7917
|
+
|
|
7918
|
+
/* ---- 状态行 / 修复记录 ---- */
|
|
7919
|
+
|
|
7714
7920
|
function zentaoStateTag(st) {
|
|
7715
7921
|
const MAP = {
|
|
7716
7922
|
fixing: ["修复中", "zt-tag-fix"],
|
|
7717
7923
|
resolved: ["已解决", "zt-tag-ok"],
|
|
7924
|
+
transferred: ["已转派", "zt-tag-fix"],
|
|
7925
|
+
escalated: ["已升级转派", "zt-tag-warn"],
|
|
7718
7926
|
merge_failed: ["合并失败", "zt-tag-warn"],
|
|
7719
7927
|
commented: ["已评论留人工", "zt-tag-warn"],
|
|
7720
7928
|
resolve_failed: ["回写失败", "zt-tag-warn"],
|
|
7721
7929
|
done_manual: ["待人工确认", "zt-tag-warn"],
|
|
7930
|
+
need_manual: ["需人工", "zt-tag-warn"],
|
|
7722
7931
|
lost: ["记录丢失", "zt-tag-err"],
|
|
7723
7932
|
};
|
|
7724
7933
|
const m = MAP[st] || [st || "?", ""];
|
|
7725
7934
|
return '<span class="tag ' + m[1] + '">' + t(m[0]) + "</span>";
|
|
7726
7935
|
}
|
|
7727
7936
|
|
|
7937
|
+
function zentaoTriText(c) {
|
|
7938
|
+
const tri = c.triage || {};
|
|
7939
|
+
if (!tri.side) return "";
|
|
7940
|
+
const NAME = { backend: "后端问题", frontend: "前端问题", both: "双端问题", not_ours: "非我方", unknown: "待人工" };
|
|
7941
|
+
const by = tri.by === "rule" ? t("模块路由") : tri.by === "ai" ? "AI" : "";
|
|
7942
|
+
return t("排查:") + t(NAME[tri.side] || tri.side) + (by ? "(" + by + ")" : "");
|
|
7943
|
+
}
|
|
7944
|
+
|
|
7728
7945
|
function renderZentaoStatus() {
|
|
7729
7946
|
const box = $("zentao-status");
|
|
7730
7947
|
if (!box) return;
|
|
@@ -7745,37 +7962,35 @@ function renderZentaoClaims() {
|
|
|
7745
7962
|
const box = $("zentao-claims");
|
|
7746
7963
|
if (!box) return;
|
|
7747
7964
|
const claims = (S.zentao && S.zentao.claims) || [];
|
|
7748
|
-
box.innerHTML = claims.map((c) =>
|
|
7749
|
-
|
|
7750
|
-
|
|
7751
|
-
|
|
7752
|
-
|
|
7753
|
-
|
|
7754
|
-
|
|
7755
|
-
|
|
7756
|
-
|
|
7757
|
-
|
|
7965
|
+
box.innerHTML = claims.map((c) => {
|
|
7966
|
+
const tasks = (c.tasks || []).map((tk) =>
|
|
7967
|
+
"<span>【" + (tk.side === "frontend" ? t("前端") : t("后端")) + "】" +
|
|
7968
|
+
'<a href="#" onclick="zentaoOpenRun(\'' + esc(tk.run_id || "") + '\');return false;">' + esc(tk.task_id || "?") + "</a></span>").join("");
|
|
7969
|
+
return '<div class="card"><div class="head"><span class="name">#' + esc(c.bug_id) + " " + esc(c.title || "") + "</span>" +
|
|
7970
|
+
zentaoStateTag(c.state) + "</div>" +
|
|
7971
|
+
'<div class="auto-meta">' +
|
|
7972
|
+
(zentaoTriText(c) ? "<span>" + esc(zentaoTriText(c)) + "</span>" : "") +
|
|
7973
|
+
(tasks || "") +
|
|
7974
|
+
(c.claimed_at ? "<span>" + t("认领于 ") + esc(c.claimed_at) + "</span>" : "") +
|
|
7975
|
+
"</div>" +
|
|
7976
|
+
(c.note ? '<div class="note">' + esc(c.note) + "</div>" : "") +
|
|
7977
|
+
"</div>";
|
|
7978
|
+
}).join("") || '<div class="empty">' + t("还没有认领过 Bug——配置好连接与产品档案后点「立即扫描」。") + "</div>";
|
|
7758
7979
|
}
|
|
7759
7980
|
|
|
7760
|
-
function
|
|
7761
|
-
|
|
7762
|
-
if (c && c.run_id) { openRun(c.run_id); return; }
|
|
7981
|
+
function zentaoOpenRun(runId) {
|
|
7982
|
+
if (runId) { openRun(runId); return; }
|
|
7763
7983
|
switchTab("tasks");
|
|
7764
7984
|
}
|
|
7765
7985
|
|
|
7766
7986
|
/* 表单 → 保存配置。password 只在用户输入了才提交(空 = 保持已存密码)。 */
|
|
7767
7987
|
async function saveZentao() {
|
|
7768
7988
|
const val = (id) => (($(id).value || "").trim());
|
|
7769
|
-
const products = val("zt-products").split(/[,,\s]+/).filter(Boolean).map(Number);
|
|
7770
7989
|
const payload = {
|
|
7771
7990
|
base_url: val("zt-base-url"),
|
|
7772
7991
|
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"),
|
|
7992
|
+
product_profiles: ztHarvestProfiles(),
|
|
7993
|
+
triage_ai: $("zt-triageai").checked,
|
|
7779
7994
|
auto_resolve: $("zt-autoresolve").checked,
|
|
7780
7995
|
auto_merge: $("zt-automerge").checked,
|
|
7781
7996
|
poll_enabled: $("zt-poll").checked,
|
|
@@ -8352,13 +8567,139 @@ function maybeWhatsnew() {
|
|
|
8352
8567
|
}
|
|
8353
8568
|
}
|
|
8354
8569
|
|
|
8355
|
-
/* =====
|
|
8356
|
-
*
|
|
8357
|
-
*
|
|
8358
|
-
|
|
8570
|
+
/* ===== 帮助中心(首启欢迎引导升级版):左目录右内容 =====
|
|
8571
|
+
* 首启自动弹(orch.welcomed 记账只弹一次,徽章「首次启动」,落在「快速上手」章);
|
|
8572
|
+
* 手动开(设置导航/关于页/Ctrl+K)徽章「帮助中心」。加新章 = HELP_CHAPTERS
|
|
8573
|
+
* 追加一条 + helpChapterBody 加一个分支;todo:true 的章自动渲染「整理中」占位。 */
|
|
8574
|
+
var HELP_CHAPTERS = [
|
|
8575
|
+
{ id: "quickstart", icon: "i-rocket", label: "快速上手" },
|
|
8576
|
+
{ id: "models", icon: "i-blocks", label: "模型接入与绑定" },
|
|
8577
|
+
{ id: "features", icon: "i-bee", label: "功能一览" },
|
|
8578
|
+
{ id: "serial", icon: "i-book-open", label: "连载创作流程" },
|
|
8579
|
+
{ id: "publish", icon: "i-clapper", label: "发布上架" },
|
|
8580
|
+
{ id: "auto", icon: "i-calendar-days", label: "自动化与技能市场" },
|
|
8581
|
+
{ id: "faq", icon: "i-search", label: "常见问题" },
|
|
8582
|
+
];
|
|
8583
|
+
var helpCur = "quickstart";
|
|
8584
|
+
var helpFirst = false; // 本次打开是否首启自动弹(决定徽章文案;语言切换时也要用)
|
|
8585
|
+
|
|
8586
|
+
function helpChapterBody(id) {
|
|
8587
|
+
if (id === "quickstart") {
|
|
8588
|
+
return '<div class="welcome-steps">' +
|
|
8589
|
+
'<div class="wstep"><b>1</b><span>' + t("添加模型供应商:设置 → 模型接入,填入 API Key") + '</span></div>' +
|
|
8590
|
+
'<div class="wstep"><b>2</b><span>' + t("绑定 CLI 智能体:点「一键推荐绑定」自动配好") + '</span></div>' +
|
|
8591
|
+
'<div class="wstep"><b>3</b><span>' + t("新建任务:选工作目录、写目标,蜂群开工") + '</span></div>' +
|
|
8592
|
+
'</div>' +
|
|
8593
|
+
'<p class="help-note">' + t("任务跑起来后,详情页能看到步骤、蜂巢、成果文件与 Git 版本;「直接执行」类任务还能像聊天一样边跑边追加消息。") + '</p>' +
|
|
8594
|
+
'<div class="welcome-acts">' +
|
|
8595
|
+
'<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>' +
|
|
8596
|
+
'<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>' +
|
|
8597
|
+
'<button class="wl-btn ghost" onclick="welcomeClose()"><span>' + t("先跳过,直接体验") + '</span></button>' +
|
|
8598
|
+
'</div>';
|
|
8599
|
+
}
|
|
8600
|
+
if (id === "models") {
|
|
8601
|
+
return '<h3 class="help-h3">' + t("第一步:添加供应商") + '</h3>' +
|
|
8602
|
+
'<p class="help-p">' + t("「设置 → 模型接入 → 添加供应商」:填名称、API Key、接口地址(一般用默认)。保存后点「获取模型列表」自动拉取该厂商的模型;网关不提供列表接口时直接手工填模型名即可。同一厂商可配多把 Key,按顺序轮换,某把欠费自动切下一把。") + '</p>' +
|
|
8603
|
+
'<h3 class="help-h3">' + t("第二步:认识协议徽章") + '</h3>' +
|
|
8604
|
+
'<p class="help-p">' + t("新供应商默认自动识别协议,保存后在后台实测支持哪些调用形态,结果以徽章标在卡片上。带「· chat」表示该模型只适合站内直连对话;codex 这类只讲 responses 协议的 CLI 用不了它,绑定页会自动剔除,不用自己排查。") + '</p>' +
|
|
8605
|
+
'<h3 class="help-h3">' + t("第三步:绑定 CLI 智能体") + '</h3>' +
|
|
8606
|
+
'<p class="help-p">' + t("「设置 → CLI 绑定」给每个智能体配一条模型链,运行时按顺序故障转移。「一键推荐绑定」只填空缺,不覆盖手工配置;删除或停用厂商后链会自动补绑。") + '</p>' +
|
|
8607
|
+
'<h3 class="help-h3">' + t("报错速查") + '</h3>' +
|
|
8608
|
+
'<ul class="help-list">' +
|
|
8609
|
+
'<li><b>401</b><span>' + t("Key 无效或过期——检查或更换 Key。") + '</span></li>' +
|
|
8610
|
+
'<li><b>404</b><span>' + t("模型名不对;若发生在「获取模型列表」,是网关不提供列表接口,能正常对话就不用管。") + '</span></li>' +
|
|
8611
|
+
'<li><b>503</b><span>' + t("该模型当前无可用渠道——查余额或换一个模型。") + '</span></li>' +
|
|
8612
|
+
'<li><b>' + t("冷却中") + '</b><span>' + t("这把 Key 连续失败被暂停 30 分钟,到期自动恢复,也可在密钥旁手动重置。") + '</span></li>' +
|
|
8613
|
+
'</ul>' +
|
|
8614
|
+
'<p class="help-note">' + t("想零成本先跑通:挑有免费档位的厂商配一个模型,跑通第一个任务后再按需加码。") + '</p>';
|
|
8615
|
+
}
|
|
8616
|
+
if (id === "features") {
|
|
8617
|
+
return '<ul class="help-feats">' +
|
|
8618
|
+
'<li><svg class="ico" aria-hidden="true"><use href="#i-workflow"></use></svg><div><b>' + t("多智能体编排") + '</b><span>' + t("规划 · 实现 · 评审 · 打磨,接棒交付") + '</span></div></li>' +
|
|
8619
|
+
'<li><svg class="ico" aria-hidden="true"><use href="#i-book-open"></use></svg><div><b>' + t("连载写作") + '</b><span>' + t("断点续跑 · 经验沉淀 · 作品资料") + '</span></div></li>' +
|
|
8620
|
+
'<li><svg class="ico" aria-hidden="true"><use href="#i-clapper"></use></svg><div><b>' + t("发布上架") + '</b><span>' + t("一键发布到番茄、七猫,含封面与作品资料") + '</span></div></li>' +
|
|
8621
|
+
'<li><svg class="ico" aria-hidden="true"><use href="#i-git-branch"></use></svg><div><b>' + t("版本工作台") + '</b><span>' + t("任务分支隔离,改动可审可合") + '</span></div></li>' +
|
|
8622
|
+
'<li><svg class="ico" aria-hidden="true"><use href="#i-calendar-days"></use></svg><div><b>' + t("自动化") + '</b><span>' + t("定时任务,无人值守") + '</span></div></li>' +
|
|
8623
|
+
'<li><svg class="ico" aria-hidden="true"><use href="#i-blocks"></use></svg><div><b>' + t("插件市场") + '</b><span>' + t("600+ 技能,一键安装") + '</span></div></li>' +
|
|
8624
|
+
'</ul>';
|
|
8625
|
+
}
|
|
8626
|
+
if (id === "serial") {
|
|
8627
|
+
return '<h3 class="help-h3">' + t("开一本新书") + '</h3>' +
|
|
8628
|
+
'<p class="help-p">' + t("新建任务选「连载写作」类型,目标里写清书名、题材和计划篇幅(如「都市异能,先写 20 章」)。目标写得太笼统时,蜂群会先反问几个澄清问题再开工。开书前会先准备作品资料:书简介与分类标签可一键生成,按平台官方选项实抓,不用自己去查规则。") + '</p>' +
|
|
8629
|
+
'<h3 class="help-h3">' + t("每章怎么跑") + '</h3>' +
|
|
8630
|
+
'<p class="help-p">' + t("大纲确认后分章推进:起草 → 跨厂商评审 → 打磨 → 定稿。评审不过会自动换将重写,不带着问题过关;需要你拍板的地方会亮起「待裁决」。写作时详情页可实时预览章节,蜂巢页每个智能体一格,点开能看谁在干什么。") + '</p>' +
|
|
8631
|
+
'<h3 class="help-h3">' + t("中断了怎么办") + '</h3>' +
|
|
8632
|
+
'<p class="help-p">' + t("任务随时可续跑:已写章节都落了盘,续跑接着往下写,不会从头再来。单章失败会自动退避重试,任务状态里会显示「将于 HH:MM 自动续跑」,不用盯着等。") + '</p>' +
|
|
8633
|
+
'<h3 class="help-h3">' + t("递话与经验") + '</h3>' +
|
|
8634
|
+
'<p class="help-p">' + t("跑的过程中可以在详情页给蜂群递话(文字/截图/附件),下一轮生效。每轮结束复盘官会把踩过的坑沉淀进经验库,后续任务自动复用,越用越顺手。") + '</p>';
|
|
8635
|
+
}
|
|
8636
|
+
if (id === "publish") {
|
|
8637
|
+
return '<h3 class="help-h3">' + t("第一次发布前:登录平台") + '</h3>' +
|
|
8638
|
+
'<p class="help-p">' + t("目前支持番茄小说与七猫。第一次发布会引导你在浏览器里登录平台账号(扫码或账密),登录态保存在本机,之后发布免登录;过期了重新登一次即可。") + '</p>' +
|
|
8639
|
+
'<h3 class="help-h3">' + t("建书:资料与封面") + '</h3>' +
|
|
8640
|
+
'<p class="help-p">' + t("连载任务的建书面板里,作品简介、分类标签可以按平台官方选项一键生成;封面点「生成封面」自动出竖版图(cover.png 落在任务运行目录)。") + '</p>' +
|
|
8641
|
+
'<h3 class="help-h3">' + t("发章与确认") + '</h3>' +
|
|
8642
|
+
'<p class="help-p">' + t("章稿定稿后一键发布:自动填标题正文、处理平台的各类弹层,但提交前会停下来让你确认——绝不静默替你上架。发布完成后任务详情里能看到结果与章节链接。") + '</p>' +
|
|
8643
|
+
'<h3 class="help-h3">' + t("发布失败") + '</h3>' +
|
|
8644
|
+
'<p class="help-p">' + t("平台改版或登录态过期会导致失败,失败信息会写明原因。重新登录后再点重试即可;反复失败可以到 GitHub 提 Issue 附上失败截图。") + '</p>';
|
|
8645
|
+
}
|
|
8646
|
+
if (id === "auto") {
|
|
8647
|
+
return '<h3 class="help-h3">' + t("定时任务") + '</h3>' +
|
|
8648
|
+
'<p class="help-p">' + t("设置 → 自动化:把任意任务类型挂上时间表(每天/每周固定时刻),到点自动按真实运行链开跑,结果照常落盘。配合群机器人(钉钉/飞书/企微自动适配)可以把完成状态推到群里。") + '</p>' +
|
|
8649
|
+
'<h3 class="help-h3">' + t("技能市场") + '</h3>' +
|
|
8650
|
+
'<p class="help-p">' + t("设置 → 插件市场:600+ 技能一键安装,来源包括内置库和 ZCode、Anthropic 等多个外部目录。装上的技能会注入智能体能力,按任务类型生效。") + '</p>' +
|
|
8651
|
+
'<h3 class="help-h3">' + t("用量台账") + '</h3>' +
|
|
8652
|
+
'<p class="help-p">' + t("设置 → 用量:每次调用的模型、token 与费用按多个维度聚合展示,历史运行会在启动时自动回填——每个任务花了多少,一目了然。") + '</p>';
|
|
8653
|
+
}
|
|
8654
|
+
if (id === "faq") {
|
|
8655
|
+
return '<div class="help-qa"><b>' + t("点「获取模型列表」报 404?") + '</b><p>' + t("部分网关不提供模型列表接口,属正常现象;只要能正常对话就不用管,模型名手工填即可。") + '</p></div>' +
|
|
8656
|
+
'<div class="help-qa"><b>' + t("报 503 / 无可用渠道?") + '</b><p>' + t("该模型当前没有可用渠道,最常见是余额耗尽。查一下余额或换个模型;同一厂商配了多把 Key 会自动轮换。") + '</p></div>' +
|
|
8657
|
+
'<div class="help-qa"><b>' + t("「冷却中」是什么意思?") + '</b><p>' + t("一把 Key 连续失败会被暂停 30 分钟,防止反复撞墙烧钱,到期自动恢复;也可以在密钥旁手动重置。") + '</p></div>' +
|
|
8658
|
+
'<div class="help-qa"><b>' + t("任务一直显示「排队」?") + '</b><p>' + t("本地默认最多 6 个任务同时跑,排满就排队。排队时可以打开详情看前面还有几个;个别卡死的任务,看门狗会自动清理补队。") + '</p></div>' +
|
|
8659
|
+
'<div class="help-qa"><b>' + t("状态里写着「将于 HH:MM 自动续跑」?") + '</b><p>' + t("这一步失败了,正在退避等待自动重试,到点会接着跑,不需要手动干预;等不及也可以在详情页手动重试。") + '</p></div>' +
|
|
8660
|
+
'<div class="help-qa"><b>' + t("生成的文件在哪?") + '</b><p>' + t("写作类任务的章节、封面、报告都落在任务的运行目录,详情页「成果」页签可浏览和预览。代码类任务则在仓库的任务分支上改代码,详情页「版本」里审阅后再合并。") + '</p></div>' +
|
|
8661
|
+
'<div class="help-qa"><b>' + t("忘了令牌 / 手机打不开页面?") + '</b><p>' + t("服务启动日志里有带令牌的完整访问地址;远程设备必须用带令牌的 URL 打开(或在令牌门里输入一次),否则会一直要求授权。") + '</p></div>' +
|
|
8662
|
+
'<div class="help-qa"><b>' + t("升级后数据会丢吗?") + '</b><p>' + t("不会。任务、配置、用量台账都在独立的数据目录里,升级只替换程序本体;老版本装新版会自动沿用原数据目录。") + '</p></div>' +
|
|
8663
|
+
'<div class="help-qa"><b>' + t("页面打不开 / 服务起不来?") + '</b><p>' + t("多半是默认端口 8765 被旧进程占着:结束旧的 python 进程,或启动时用 --port 换个端口。还不行就到 GitHub 提 Issue。") + '</p></div>';
|
|
8664
|
+
}
|
|
8665
|
+
const ch = HELP_CHAPTERS.find((c) => c.id === id);
|
|
8666
|
+
if (ch && ch.todo) {
|
|
8667
|
+
return '<div class="help-todo"><b>' + t(ch.label || "") + '</b><p>' +
|
|
8668
|
+
t("本章节正在整理,可先看「快速上手」与「模型接入与绑定」,或到 GitHub 提 Issue 提问。") + '</p></div>';
|
|
8669
|
+
}
|
|
8670
|
+
return "";
|
|
8671
|
+
}
|
|
8672
|
+
|
|
8673
|
+
function renderHelp() {
|
|
8674
|
+
const toc = $("help-toc");
|
|
8675
|
+
if (toc) {
|
|
8676
|
+
toc.innerHTML = HELP_CHAPTERS.map((c) =>
|
|
8677
|
+
'<button class="hitem' + (c.id === helpCur ? " active" : "") + (c.todo ? " todo" : "") +
|
|
8678
|
+
'" data-ch="' + c.id + '" onclick="helpGo(\'' + c.id + '\')">' +
|
|
8679
|
+
'<svg class="ico" aria-hidden="true"><use href="#' + c.icon + '"></use></svg><span>' + t(c.label) + '</span>' +
|
|
8680
|
+
(c.todo ? '<em class="h-todo">' + t("整理中") + '</em>' : "") +
|
|
8681
|
+
'</button>').join("");
|
|
8682
|
+
}
|
|
8683
|
+
const body = $("help-body");
|
|
8684
|
+
if (body) {
|
|
8685
|
+
body.innerHTML = helpChapterBody(helpCur);
|
|
8686
|
+
}
|
|
8687
|
+
}
|
|
8688
|
+
function setHelpChapter(id) {
|
|
8689
|
+
helpCur = HELP_CHAPTERS.some((c) => c.id === id) ? id : "quickstart";
|
|
8690
|
+
renderHelp();
|
|
8691
|
+
}
|
|
8692
|
+
function helpGo(id) { setHelpChapter(id); }
|
|
8693
|
+
function welcomeOpen(opts) {
|
|
8359
8694
|
const w = $("welcome");
|
|
8360
8695
|
if (!w) return;
|
|
8361
8696
|
if (!$("token-gate").classList.contains("hidden")) return;
|
|
8697
|
+
helpFirst = !!(opts && opts.firstRun);
|
|
8698
|
+
if (helpFirst) helpCur = "quickstart";
|
|
8699
|
+
else if (opts && opts.topic && HELP_CHAPTERS.some((c) => c.id === opts.topic)) helpCur = opts.topic;
|
|
8700
|
+
const badge = $("welcome-badge");
|
|
8701
|
+
if (badge) badge.textContent = t(helpFirst ? "首次启动" : "帮助中心");
|
|
8702
|
+
renderHelp();
|
|
8362
8703
|
w.classList.remove("hidden");
|
|
8363
8704
|
document.body.classList.add("welcome-open");
|
|
8364
8705
|
}
|
|
@@ -8376,11 +8717,22 @@ function welcomeGo(tab) {
|
|
|
8376
8717
|
function maybeWelcome() {
|
|
8377
8718
|
let seen = "";
|
|
8378
8719
|
try { seen = localStorage.getItem("orch.welcomed"); } catch (e) { /* ignore */ }
|
|
8379
|
-
if (!seen) welcomeOpen();
|
|
8720
|
+
if (!seen) welcomeOpen({ firstRun: true });
|
|
8380
8721
|
}
|
|
8381
8722
|
window.welcomeOpen = welcomeOpen;
|
|
8382
8723
|
window.welcomeClose = welcomeClose;
|
|
8383
8724
|
window.welcomeGo = welcomeGo;
|
|
8725
|
+
window.helpGo = helpGo;
|
|
8726
|
+
// 空状态里的「看帮助」链接用:打开帮助中心对应章并吞掉默认跳转
|
|
8727
|
+
window.helpOpenTopic = function (topic) { welcomeOpen({ topic: topic }); return false; };
|
|
8728
|
+
|
|
8729
|
+
/* 就地帮助问号:页面里任何 .hhelp(data-help-topic)点击直达帮助中心对应章。
|
|
8730
|
+
* 事件委托挂在 document 上,动态渲染出来的问号无需逐个接线。 */
|
|
8731
|
+
document.addEventListener("click", (e) => {
|
|
8732
|
+
const btn = e.target && e.target.closest ? e.target.closest(".hhelp") : null;
|
|
8733
|
+
if (!btn) return;
|
|
8734
|
+
welcomeOpen({ topic: btn.dataset.helpTopic });
|
|
8735
|
+
});
|
|
8384
8736
|
|
|
8385
8737
|
function renderSu() {
|
|
8386
8738
|
const info = $("su-info");
|
|
@@ -8485,7 +8837,7 @@ async function suCheck() {
|
|
|
8485
8837
|
finally { if (b) b.disabled = false; }
|
|
8486
8838
|
if (!s) { toast(t("检查更新失败:服务未连接"), true); return; }
|
|
8487
8839
|
if (s.has_update) toast(t("发现新版本 v") + s.latest + t(",点「升级到新版」即可"));
|
|
8488
|
-
else if (s.note) toast(s.note);
|
|
8840
|
+
else if (s.note) toast(t(s.note));
|
|
8489
8841
|
else toast(t("已是最新版"));
|
|
8490
8842
|
}
|
|
8491
8843
|
|
|
@@ -8718,6 +9070,12 @@ function setLangBtn(lang) {
|
|
|
8718
9070
|
paintArchToggle();
|
|
8719
9071
|
syncSideExpandBtn();
|
|
8720
9072
|
renderCodePreviews(); // 预览徽章是动态文案:语言切换时若停在皮肤页要跟着换
|
|
9073
|
+
// 帮助中心若开着:徽章/目录/正文跟着换语言重画
|
|
9074
|
+
if (!$("welcome").classList.contains("hidden")) {
|
|
9075
|
+
const wbadge = $("welcome-badge");
|
|
9076
|
+
if (wbadge) wbadge.textContent = t(helpFirst ? "首次启动" : "帮助中心");
|
|
9077
|
+
renderHelp();
|
|
9078
|
+
}
|
|
8721
9079
|
// 重画还在缓存里的页面标题
|
|
8722
9080
|
const title = $("page-title");
|
|
8723
9081
|
if (title && S.tab) title.textContent = tabTitle(S.tab);
|
|
@@ -9715,6 +10073,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
9715
10073
|
$("btn-zentao-test").addEventListener("click", testZentao);
|
|
9716
10074
|
$("btn-zentao-scan").addEventListener("click", scanZentao);
|
|
9717
10075
|
$("btn-zentao-save").addEventListener("click", saveZentao);
|
|
10076
|
+
$("btn-zentao-addprofile").addEventListener("click", ztProfAdd);
|
|
9718
10077
|
$("auto-filter").addEventListener("click", (e) => {
|
|
9719
10078
|
const b = e.target.closest("[data-f]");
|
|
9720
10079
|
if (!b) return;
|