codebee 0.1.71 → 0.1.72
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 +595 -589
- package/LICENSE +21 -21
- package/README.md +508 -509
- package/app/core/acceptance.py +100 -100
- package/app/core/aiflavor.py +197 -197
- package/app/core/artifacts.py +148 -148
- package/app/core/assets.py +110 -110
- package/app/core/attachments.py +623 -623
- package/app/core/audit.py +30 -30
- package/app/core/backup.py +470 -470
- package/app/core/beekeeper.py +183 -183
- package/app/core/benchstore.py +95 -95
- package/app/core/board.py +209 -209
- package/app/core/bookmeta.py +935 -935
- package/app/core/bookmeta_catalog.py +180 -180
- package/app/core/branching.py +137 -137
- package/app/core/builtin_agent.py +1680 -1673
- package/app/core/capability.py +144 -144
- package/app/core/chaptersafe.py +32 -32
- package/app/core/cleanup.py +303 -303
- package/app/core/compaction.py +201 -201
- package/app/core/covergen.py +356 -356
- package/app/core/defectretro.py +43 -43
- package/app/core/diagnostics.py +114 -114
- package/app/core/dispatch.py +190 -190
- package/app/core/dispatch_log.py +128 -128
- package/app/core/env_scrub.py +85 -85
- package/app/core/errorlog.py +137 -137
- package/app/core/evalbench.py +836 -720
- package/app/core/evaluation.py +174 -174
- package/app/core/failure_notify.py +290 -290
- package/app/core/flows.py +7 -7
- package/app/core/goal_service.py +167 -167
- package/app/core/health.py +374 -374
- package/app/core/history.py +38 -38
- package/app/core/hooks.py +162 -162
- package/app/core/jobs.py +21 -0
- package/app/core/manager.py +2143 -2143
- package/app/core/market.py +88 -2
- package/app/core/market_remote.py +16 -0
- package/app/core/mocks.py +64 -64
- package/app/core/modelhub.py +3 -2
- package/app/core/notify.py +201 -201
- package/app/core/novel_quality.py +189 -189
- package/app/core/operations.py +228 -228
- package/app/core/paihang.py +138 -138
- package/app/core/pipeline.py +102 -30
- package/app/core/portguard.py +111 -111
- package/app/core/preview.py +293 -293
- package/app/core/prompt_guard.py +78 -0
- package/app/core/publish/__init__.py +7 -7
- package/app/core/publish/auto.py +396 -367
- package/app/core/publish/browser.py +655 -655
- package/app/core/publish/fanqie.py +165 -165
- package/app/core/publish/flow.py +589 -589
- package/app/core/publish/flows-fanqie-calibrated.json +336 -336
- package/app/core/publish/ledger.py +307 -229
- package/app/core/publish/manager.py +931 -824
- package/app/core/publish/qimao.py +146 -146
- package/app/core/publish/ws.py +139 -139
- package/app/core/quality_gate.py +194 -0
- package/app/core/redact.py +38 -38
- package/app/core/repeat_guard.py +129 -129
- package/app/core/router.py +398 -398
- package/app/core/runner.py +2142 -2142
- package/app/core/selfupdate.py +384 -384
- package/app/core/session_log.py +161 -161
- package/app/core/sessions.py +312 -312
- package/app/core/settings_schema.py +270 -270
- package/app/core/share_page.py +114 -114
- package/app/core/skill_scan.py +139 -122
- package/app/core/skillpacks/fanqie-novel.md +80 -80
- package/app/core/skillpacks/market/character-bible.md +66 -66
- package/app/core/skillpacks/market/code-risk-checklist.md +58 -58
- package/app/core/skillpacks/market/git-workflow.md +57 -57
- package/app/core/skillpacks/market/release-notes.md +72 -72
- package/app/core/skillpacks/market/weekly-report.md +71 -71
- package/app/core/skillpacks/market/worldview-consistency.md +70 -70
- package/app/core/skills.py +58 -8
- package/app/core/step_runner.py +132 -132
- package/app/core/store.py +51 -0
- package/app/core/task_compile.py +199 -199
- package/app/core/telemetry.py +292 -292
- package/app/core/tlsctx.py +64 -64
- package/app/core/token_meter.py +147 -147
- package/app/core/tracing.py +144 -144
- package/app/core/upstream.py +41 -41
- package/app/core/usage.py +1035 -1035
- package/app/core/volumes.py +389 -389
- package/app/core/wxdigest.py +858 -858
- package/app/core/zentao.py +2692 -2614
- package/app/main.py +86 -11
- package/app/mcp_server.py +175 -175
- package/app/pick_dialog.py +110 -110
- package/app/ui/app.js +258 -54
- package/app/ui/artdiff.js +74 -74
- package/app/ui/board.html +162 -162
- package/app/ui/board.js +305 -305
- package/app/ui/i18n.js +24 -0
- package/app/ui/icons/bee.svg +79 -79
- package/app/ui/index.html +5 -0
- package/app/ui/manifest.json +16 -16
- package/app/ui/qrcode.js +2297 -2297
- package/app/ui/style.css +7051 -7012
- package/bin/tutti.js +147 -147
- package/package.json +48 -47
- package/tools/wx_reader.py +261 -261
- package/app/.playwright-cli/console-2026-09-23T08-26-03-635Z.log +0 -88
- package/app/.playwright-cli/page-2026-09-23T08-26-03-845Z.yml +0 -126
- package/app/.playwright-cli/page-2026-09-23T08-29-44-203Z.png +0 -0
- package/app/.playwright-cli/page-2026-09-23T08-35-42-046Z.yml +0 -210
- package/app/.playwright-cli/page-2026-09-23T08-36-52-858Z.png +0 -0
- package/app/.playwright-cli/page-2026-09-23T08-37-33-938Z.png +0 -0
- package/app/.playwright-cli/run-detail-after.png +0 -0
- package/app//344/275/234/345/223/201/344/277/241/346/201/257-/344/270/203/347/214/253.md +0 -18
package/app/ui/app.js
CHANGED
|
@@ -10,7 +10,7 @@ function codebeeDocumentTitle(lang) {
|
|
|
10
10
|
document.title = codebeeDocumentTitle((localStorage.getItem("orch.lang") || "zh").toLowerCase());
|
|
11
11
|
|
|
12
12
|
const $ = (id) => document.getElementById(id);
|
|
13
|
-
const S = { state: null, catalog: null, catSig: "", providers: null, bindings: null, modelsSig: "", bindSig: "", tab: "tasks", mainPage: "", /* 主栏正显示的全局页("" = 新建任务表单);左栏是任务树还是设置导航看 body.settings-mode */ detailRunId: null, pollTimer: null, showArchived: false, /* 会话内开关:每次加载默认隐藏已归档、图标不选中(不持久化,见 btn-side-arch) */ selProvs: {}, selModels: {}, selRuns: {}, bindSel: {}, catalogChecking: false, updateCheckAt: 0, control: null, sseLive: false, es: null, flows: null, orch: null, skills: null, orchSig: "", settings: null, sessionAgents: new Set(), atts: [], gitInfo: null, gitWb: null, gitWbKey: "", gitWbAt: 0, gitWbBusy: false, creatingTask: false, inspKey: null, inspData: null, inspSig: "", inspAt: 0, inspTab: "git", inspAutoSig: "", rdTab: null, rdTabSig: "", rdTabPin: false, _rdCtx: {}, lastPrefs: null, prefsApplied: false, sideUsage: null, sideUsageAt: 0, ovUsage: null, ovDays: undefined };
|
|
13
|
+
const S = { state: null, catalog: null, catSig: "", providers: null, bindings: null, modelsSig: "", bindSig: "", tab: "tasks", mainPage: "", /* 主栏正显示的全局页("" = 新建任务表单);左栏是任务树还是设置导航看 body.settings-mode */ detailRunId: null, pollTimer: null, showArchived: false, /* 会话内开关:每次加载默认隐藏已归档、图标不选中(不持久化,见 btn-side-arch) */ selProvs: {}, selModels: {}, selRuns: {}, bindSel: {}, catalogChecking: false, updateCheckAt: 0, control: null, sseLive: false, es: null, flows: null, orch: null, skills: null, orchSig: "", settings: null, sessionAgents: new Set(), atts: [], gitInfo: null, gitWb: null, gitWbKey: "", gitWbAt: 0, gitWbBusy: false, creatingTask: false, inspKey: null, inspData: null, inspSig: "", inspAt: 0, inspTab: "git", inspAutoSig: "", rdTab: null, rdTabSig: "", rdTabPin: false, rdHiveFirst: false, _rdCtx: {}, lastPrefs: null, prefsApplied: false, sideUsage: null, sideUsageAt: 0, ovUsage: null, ovDays: undefined };
|
|
14
14
|
|
|
15
15
|
/* ---------------------------------------------------------- 内置浏览器工作区 */
|
|
16
16
|
const BROWSER_STORAGE_KEY = "orch.browser.workspace.v1";
|
|
@@ -167,7 +167,11 @@ function browserRenderTaskContext() {
|
|
|
167
167
|
const verifyIcon = verifyPass === true ? "i-check" : (verifyPass === false ? "i-x" : "i-gauge");
|
|
168
168
|
const verifyText = verifyPass === true ? "验证通过" : (verifyPass === false ? "验证未通过" : "等待验证");
|
|
169
169
|
const command = task.verify_command || "";
|
|
170
|
+
const qualityLabels = { continue_only: "继续打磨(暂不可发布)",
|
|
171
|
+
publish_ready: "可发布,签约检查点待完成", signing_ready: "可申请签约" };
|
|
172
|
+
const qualityStatus = verdict.quality_status && qualityLabels[verdict.quality_status];
|
|
170
173
|
verify.innerHTML = '<div class="browser-verify-line ' + verifyClass + '"><svg class="ico" aria-hidden="true"><use href="#' + verifyIcon + '"></use></svg><span>' + esc(t(verifyText)) + "</span></div>" +
|
|
174
|
+
(qualityStatus ? '<div class="browser-verify-command quality-status">' + esc(t(qualityStatus)) + "</div>" : "") +
|
|
171
175
|
(command ? '<div class="browser-verify-command" title="' + esc(command) + '">' + esc(command) + "</div>" : '<span>' + esc(t("未配置验证命令")) + "</span>");
|
|
172
176
|
const platforms = ["fanqie", "qimao"], ps = (S.pubState && S.pubState.platforms) || {};
|
|
173
177
|
publish.innerHTML = '<div class="browser-publish-platforms">' + platforms.map((platform) => {
|
|
@@ -1243,7 +1247,12 @@ async function api(path, opts) {
|
|
|
1243
1247
|
}
|
|
1244
1248
|
}
|
|
1245
1249
|
}
|
|
1246
|
-
if (!res.ok)
|
|
1250
|
+
if (!res.ok) {
|
|
1251
|
+
const failure = new Error((data && data.error) || ("HTTP " + res.status));
|
|
1252
|
+
failure.status = res.status;
|
|
1253
|
+
failure.data = data;
|
|
1254
|
+
throw failure;
|
|
1255
|
+
}
|
|
1247
1256
|
const method = String(fetchOpts.method || "GET").toUpperCase();
|
|
1248
1257
|
if (method !== "GET") {
|
|
1249
1258
|
if (String(path).startsWith("/api/catalog/")) S._catalogLoaded = false;
|
|
@@ -1260,6 +1269,21 @@ async function api(path, opts) {
|
|
|
1260
1269
|
}
|
|
1261
1270
|
}
|
|
1262
1271
|
|
|
1272
|
+
async function pbApiWithQualityOverride(path, body, prompt) {
|
|
1273
|
+
try {
|
|
1274
|
+
return await api(path, { method: "POST", body: JSON.stringify(body) });
|
|
1275
|
+
} catch (e) {
|
|
1276
|
+
const gate = e && e.data && e.data.quality_gate;
|
|
1277
|
+
if (!gate) throw e;
|
|
1278
|
+
if (!confirm((prompt || "质量门禁未通过") + "\n\n" + (e.message || "") +
|
|
1279
|
+
"\n\n确认后将记录为人工强制放行。继续?")) throw e;
|
|
1280
|
+
const reason = window.prompt("请输入人工复核原因(必填)", "已人工复核稿件与平台版本") || "";
|
|
1281
|
+
if (!reason.trim()) throw new Error("未填写人工复核原因,已取消强制放行");
|
|
1282
|
+
return await api(path, { method: "POST", body: JSON.stringify({ ...body,
|
|
1283
|
+
force: true, force_confirmed: true, force_reason: reason.trim() }) });
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1263
1287
|
/* 轻提示:3.5s 自动消失 */
|
|
1264
1288
|
function toast(msg, bad) {
|
|
1265
1289
|
let el = $("toast");
|
|
@@ -3368,7 +3392,8 @@ async function archiveTask(id, archived) {
|
|
|
3368
3392
|
S.sideSig = "";
|
|
3369
3393
|
}
|
|
3370
3394
|
render();
|
|
3371
|
-
|
|
3395
|
+
return true;
|
|
3396
|
+
} catch (e) { toast(t("操作失败:") + e.message, true); return false; }
|
|
3372
3397
|
poll();
|
|
3373
3398
|
}
|
|
3374
3399
|
|
|
@@ -3982,7 +4007,14 @@ async function removeSideDir(ids, dir) {
|
|
|
3982
4007
|
.replace("%1", ids.length),
|
|
3983
4008
|
{ ok: t("移除") });
|
|
3984
4009
|
if (!ok) return;
|
|
3985
|
-
|
|
4010
|
+
// 有归档失败就不能藏文件夹:藏是 localStorage 假成功,服务端任务没归档,
|
|
4011
|
+
// 手机端等其他设备照样看得到这批任务(服务重启窗口实测案)。留文件夹让用户重试。
|
|
4012
|
+
let failed = 0;
|
|
4013
|
+
for (const id of ids) if (!await archiveTask(id, true)) failed++;
|
|
4014
|
+
if (failed) {
|
|
4015
|
+
toast(t("有 {0} 个任务归档失败,文件夹先保留;任务与文件都没动,稍后重试即可").replace("{0}", failed), true);
|
|
4016
|
+
return;
|
|
4017
|
+
}
|
|
3986
4018
|
if (dir) hideSideDir(dir);
|
|
3987
4019
|
}
|
|
3988
4020
|
|
|
@@ -4394,7 +4426,8 @@ window.sideOpenTask = function (key) {
|
|
|
4394
4426
|
detailSideReset(); // 换详情目标:任务级 side 缓存作废,等首拉
|
|
4395
4427
|
const olderRunsButton = $("rd-load-earlier");
|
|
4396
4428
|
if (olderRunsButton) olderRunsButton.hidden = true;
|
|
4397
|
-
S.
|
|
4429
|
+
S.rdHiveFirst = true; // 侧栏点开任务详情:首拉后默认落「蜂巢」(用户 2026-09-29)
|
|
4430
|
+
S.rdTab = null;
|
|
4398
4431
|
S.rdTabSig = "";
|
|
4399
4432
|
applyRdTabs();
|
|
4400
4433
|
syncInspectorVis(); // 详情已铺开:检查器让位(选中保留,返回列表自动滑回)
|
|
@@ -4621,8 +4654,10 @@ function drawTaskDetail(key, runs, appendFrom) {
|
|
|
4621
4654
|
if (!runs.length) return;
|
|
4622
4655
|
// 消息数/未消费数也入签名:新指令或被 drain 后重画指挥区;步骤状态入签名:
|
|
4623
4656
|
// 取消收尾把僵尸步骤落成「已取消」时要立即重画,不等条数变化;
|
|
4624
|
-
// 作品信息状态入签名:后台一键生成 running→done
|
|
4657
|
+
// 作品信息状态入签名:后台一键生成 running→done 要立刻反映到成果区面板。
|
|
4658
|
+
// 续写批次签名盯的是链上根任务的 book_meta(都是用第一个)+ 本任务封面状态
|
|
4625
4659
|
const bmTask = ((S.state || {}).tasks || []).find((x) => x.id === key);
|
|
4660
|
+
const bmRoot = bmRootTask(bmTask || {});
|
|
4626
4661
|
// 自动续跑退避相位入签名:预定启动时刻过了之后 chip 翻为「正在启动」
|
|
4627
4662
|
const lr0 = runs[0] || {};
|
|
4628
4663
|
const resumePending = (lr0.resume_enqueue_at &&
|
|
@@ -4632,7 +4667,8 @@ function drawTaskDetail(key, runs, appendFrom) {
|
|
|
4632
4667
|
(r.messages || []).length, (r.messages || []).filter((m) => !m.consumed).length,
|
|
4633
4668
|
r.message_count, r.pending_message_count,
|
|
4634
4669
|
r.summary, r.error, r.cost_usd, r.tokens, JSON.stringify(r.verdict || {})])
|
|
4635
|
-
.concat([JSON.stringify((
|
|
4670
|
+
.concat([JSON.stringify([(bmRoot || {}).book_meta || null,
|
|
4671
|
+
(bmTask || {}).cover_gen || null]), resumePending]));
|
|
4636
4672
|
if (sig === S.taskSig) return;
|
|
4637
4673
|
S.taskSig = sig;
|
|
4638
4674
|
const latest = runs[0]; // runs 新→旧
|
|
@@ -5022,12 +5058,14 @@ function renderChatNav() {
|
|
|
5022
5058
|
window.rdChatNavGo = function (tab) {
|
|
5023
5059
|
S.rdTab = tab;
|
|
5024
5060
|
S.rdTabPin = true; // 用户主动去看其它分区:别被自动选卡拽回对话
|
|
5061
|
+
S.rdHiveFirst = false;
|
|
5025
5062
|
applyRdTabs();
|
|
5026
5063
|
};
|
|
5027
5064
|
window.rdChatNavBack = function () {
|
|
5028
5065
|
S.rdTab = "chat";
|
|
5029
5066
|
S.rdTabPin = false;
|
|
5030
5067
|
S.rdTabSig = "";
|
|
5068
|
+
S.rdHiveFirst = false;
|
|
5031
5069
|
applyRdTabs();
|
|
5032
5070
|
};
|
|
5033
5071
|
|
|
@@ -5061,20 +5099,26 @@ function rdTabsSync(ctx) {
|
|
|
5061
5099
|
S.rdTabSig = sig;
|
|
5062
5100
|
const avail = rdTabAvail();
|
|
5063
5101
|
const direct = chatAvail && chatEngineIsDirect(S.lastRun);
|
|
5064
|
-
// 所有任务的主问题都是「继续聊什么、各智能体输出了什么」——默认把
|
|
5065
|
-
// 对话放在第一视线;蜂巢/步骤/成果保留为侧边工作台入口。
|
|
5066
|
-
// 没跑到终态时成果分区是空的,避免打开详情先看到白板。
|
|
5067
5102
|
const finishing = !c.running && ["done", "failed", "cancelled", "timeout"].includes(c.status);
|
|
5068
|
-
|
|
5103
|
+
// 侧栏点开任务详情默认落「蜂巢」工作台(用户 2026-09-29):一次性偏好,
|
|
5104
|
+
// sideOpenTask 置位、蜂巢可用即兑现并钉住(后续状态翻转不再拽走);
|
|
5105
|
+
// ctx 明确 steps=0(纯聊天无蜂巢可看)或手选页签/rdTabReset 时放弃。
|
|
5106
|
+
// 兑现前先留在手上的页签,不让对话抢跑——其余选卡优先级不变。
|
|
5107
|
+
const hivePending = S.rdHiveFirst;
|
|
5108
|
+
const hiveReady = hivePending && avail.hive;
|
|
5109
|
+
if (hiveReady || (hivePending && c.steps === 0)) S.rdHiveFirst = false;
|
|
5110
|
+
S.rdTab = (hiveReady ? "hive" : null)
|
|
5111
|
+
|| (hivePending && S.rdTab && avail[S.rdTab] ? S.rdTab : null)
|
|
5112
|
+
|| (direct && avail.chat ? "chat" : null)
|
|
5069
5113
|
// 跑出了能打开的网页成品:第一视线给「预览」——这正是用户做完一个
|
|
5070
5114
|
// 前端任务最想看到的(借鉴对话式编程产品:跑完先看东西跑起来)。
|
|
5071
|
-
// 对话与待裁决的版本仍排在它前面:那两个是"要用户说话/动手"。
|
|
5072
5115
|
|| (finishing && avail.preview ? "preview" : null)
|
|
5073
5116
|
|| (avail.hive ? "hive" : null)
|
|
5074
5117
|
|| (avail.chat ? "chat" : null)
|
|
5075
|
-
|| (c.running ?
|
|
5118
|
+
|| (c.running ? "steps"
|
|
5076
5119
|
: (c.gitState === "isolated" && avail.git ? "git"
|
|
5077
5120
|
: (c.hasResult && finishing ? "result" : "steps")));
|
|
5121
|
+
if (hiveReady) S.rdTabPin = true;
|
|
5078
5122
|
}
|
|
5079
5123
|
}
|
|
5080
5124
|
applyRdTabs();
|
|
@@ -5098,6 +5142,7 @@ function rdTabReset(pin) {
|
|
|
5098
5142
|
S.rdTab = pin || null;
|
|
5099
5143
|
S.rdTabSig = "";
|
|
5100
5144
|
S.rdTabPin = !!pin;
|
|
5145
|
+
S.rdHiveFirst = false;
|
|
5101
5146
|
S._rdCtx = {};
|
|
5102
5147
|
S._rdHeavy = { runId: "", report: false, artifacts: false, preview: false };
|
|
5103
5148
|
S._rdDetailToken = (S._rdDetailToken || 0) + 1;
|
|
@@ -5420,39 +5465,67 @@ function bmStatusChip(st) {
|
|
|
5420
5465
|
return m ? '<span class="bm-st ' + m[0] + '">' + (m[1] === "生成中" ? "● " : "") + t(m[1]) + "</span>" : "";
|
|
5421
5466
|
}
|
|
5422
5467
|
|
|
5423
|
-
/* 作品信息 TAB
|
|
5424
|
-
* 续写批次(serial.
|
|
5425
|
-
*
|
|
5468
|
+
/* 作品信息 TAB:所有连载任务都展示(一本书一份开书资料,都是用第一个)。
|
|
5469
|
+
* 续写批次(serial.continues 指向首批)只读沿用根任务的资料,生成/重生成回
|
|
5470
|
+
* 首批任务做——面板上给指路入口。非连载任务不渲染。 */
|
|
5426
5471
|
function bmNeedsPanel(task) {
|
|
5427
|
-
|
|
5428
|
-
if (!s) return false;
|
|
5429
|
-
try { return intOf(s.start_chapter, 1) <= 1; } catch (e) { return true; }
|
|
5472
|
+
return !!((task || {}).serial);
|
|
5430
5473
|
}
|
|
5431
5474
|
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5475
|
+
/* 连载链根任务:沿 serial.continues 上溯(环/断链停在当前)。状态里找不到
|
|
5476
|
+
* 父任务(已删除等)返回当前任务,面板退化为自身数据。 */
|
|
5477
|
+
function bmRootTask(task) {
|
|
5478
|
+
let cur = task || null;
|
|
5479
|
+
const seen = {};
|
|
5480
|
+
while (cur && ((cur.serial || {}).continues) && !seen[cur.id]) {
|
|
5481
|
+
seen[cur.id] = 1;
|
|
5482
|
+
const parent = (((S.state || {}).tasks || [])
|
|
5483
|
+
.find((x) => x.id === cur.serial.continues)) || null;
|
|
5484
|
+
if (!parent) break;
|
|
5485
|
+
cur = parent;
|
|
5486
|
+
}
|
|
5487
|
+
return cur || task;
|
|
5435
5488
|
}
|
|
5436
5489
|
|
|
5490
|
+
window.bmGoRoot = function (rootId) {
|
|
5491
|
+
const t = (((S.state || {}).tasks || []).find((x) => x.id === rootId)) || null;
|
|
5492
|
+
if (!t) { toast(t("找不到首批任务(可能已删除)"), true); return; }
|
|
5493
|
+
browserOpenTaskRun(t, "bookmeta");
|
|
5494
|
+
};
|
|
5495
|
+
|
|
5437
5496
|
function renderBookMetaPanel(task) {
|
|
5438
5497
|
const box = $("rd-bookmeta");
|
|
5439
5498
|
if (!box || !(task || {}).id || !bmNeedsPanel(task)) {
|
|
5440
5499
|
if (box) box.classList.add("hidden");
|
|
5441
5500
|
return;
|
|
5442
5501
|
}
|
|
5443
|
-
const
|
|
5502
|
+
const src = bmRootTask(task); // 都是用第一个:开书资料读链上根任务
|
|
5503
|
+
const inherited = src.id !== task.id;
|
|
5504
|
+
const bm = src.book_meta || {};
|
|
5444
5505
|
const taskBusy = task.status === "running" || task.status === "queued";
|
|
5445
5506
|
let html = '<div class="bm-head"><svg class="ico" aria-hidden="true"><use href="#i-idcard"/></svg>' +
|
|
5446
5507
|
'<span class="sec-title">' + t("作品信息") + "</span>" +
|
|
5508
|
+
(inherited ? '<span class="bm-inherit" title="' + esc(src.title || src.id) + '">' +
|
|
5509
|
+
esc(t("继承自首批") + "《" + (src.title || src.id) + "》") + "</span>" : "") +
|
|
5447
5510
|
'<span class="hint">' + esc(t("按发布平台生成建书表单资料,逐字段复制过去")) + "</span>" +
|
|
5448
5511
|
'<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>' +
|
|
5449
5512
|
'<span class="flex1"></span>';
|
|
5450
5513
|
if (taskBusy) html += '<span class="bm-warn">' + esc(t("任务运行中,生成将在本轮结束后可用")) + "</span>";
|
|
5514
|
+
// 续写批次只读沿用:生成/重生成回首批任务做,这里给指路入口(一本书一份资料)
|
|
5515
|
+
if (inherited) html += '<button class="ghost" onclick="bmGoRoot(\'' + esc(src.id) + '\')" title="' +
|
|
5516
|
+
esc(t("开书资料在首批任务上生成与维护")) + '">' +
|
|
5517
|
+
'<svg class="ico" aria-hidden="true"><use href="#i-arrow-right"/></svg>' + t("回首批任务") + "</button>";
|
|
5451
5518
|
html += "</div>";
|
|
5452
5519
|
if (!bm.fanqie && !bm.qimao && !taskBusy) {
|
|
5453
|
-
|
|
5454
|
-
'<div><
|
|
5455
|
-
|
|
5520
|
+
if (inherited) {
|
|
5521
|
+
html += '<div class="bm-guide"><svg class="ico" aria-hidden="true"><use href="#i-idcard"/></svg>' +
|
|
5522
|
+
'<div><b>' + esc(t("首批任务还没生成开书资料")) + "</b>" +
|
|
5523
|
+
"<p>" + esc(t("开书资料属于这本书,在首批任务上生成后整条连载链都能看到。")) + "</p></div></div>";
|
|
5524
|
+
} else {
|
|
5525
|
+
html += '<div class="bm-guide"><svg class="ico" aria-hidden="true"><use href="#i-idcard"/></svg>' +
|
|
5526
|
+
'<div><b>' + esc(t("章节已就绪,创建作品还差开书资料")) + "</b>" +
|
|
5527
|
+
"<p>" + esc(t("点平台卡片上的「生成」按钮,一键产出书名/简介/标签/主角名等建书资料,生成后逐字段复制进建书表单。")) + "</p></div></div>";
|
|
5528
|
+
}
|
|
5456
5529
|
}
|
|
5457
5530
|
html += '<div class="bm-cards">' + BOOKMETA_PLATFORMS.map((p) => {
|
|
5458
5531
|
const entry = bm[p.id] || {};
|
|
@@ -5482,7 +5555,9 @@ function renderBookMetaPanel(task) {
|
|
|
5482
5555
|
body = '<div class="bm-empty">' + esc(t("尚未生成")) + "</div>";
|
|
5483
5556
|
}
|
|
5484
5557
|
let action;
|
|
5485
|
-
if (
|
|
5558
|
+
if (inherited) {
|
|
5559
|
+
action = ""; // 续写批次只读:生成/重生成集中在头部「回首批任务」入口
|
|
5560
|
+
} else if (st === "running") {
|
|
5486
5561
|
action = '<button class="ghost" disabled><svg class="ico spin" aria-hidden="true"><use href="#i-refresh"/></svg>' + t("生成中…") + "</button>";
|
|
5487
5562
|
} else if (st === "done") {
|
|
5488
5563
|
action = '<button class="ghost" onclick="bmGen(\'' + esc(task.id) + "', '" + p.id + '\')" title="' + esc(t("重新生成会覆盖现有内容")) + '">' +
|
|
@@ -5502,8 +5577,11 @@ function renderBookMetaPanel(task) {
|
|
|
5502
5577
|
"</div>";
|
|
5503
5578
|
}).join("") + "</div>";
|
|
5504
5579
|
// 封面卡(covergen,借鉴 oh-story 封面图环节):curl 落盘运行目录,
|
|
5505
|
-
// done 后卡片内直接嵌缩略图(/api/runs/<id>/cover
|
|
5506
|
-
|
|
5580
|
+
// done 后卡片内直接嵌缩略图(/api/runs/<id>/cover 专用通道),点开新页看大图。
|
|
5581
|
+
// 封面也是「这本书」的:本任务没生成过就沿用根任务的封面;点生成则落本任务自己的
|
|
5582
|
+
const cgSrc = (task.cover_gen && (task.cover_gen.status || task.cover_gen.run_id)) ? task : src;
|
|
5583
|
+
const cg = cgSrc.cover_gen || {};
|
|
5584
|
+
const cgInherited = cgSrc !== task;
|
|
5507
5585
|
const cgSt = cg.status || "";
|
|
5508
5586
|
let cgBody = "";
|
|
5509
5587
|
if (cgSt === "running") {
|
|
@@ -5516,7 +5594,8 @@ function renderBookMetaPanel(task) {
|
|
|
5516
5594
|
'<img class="bm-cover-img" src="' + esc(urlAuth(covUrl)) + '" alt="cover.png" ' +
|
|
5517
5595
|
'onerror="this.closest(".bm-cover-wrap").classList.add("noimg")"></a>' +
|
|
5518
5596
|
'<span class="bm-cover-fallback">' + esc(t("cover.png 已不在运行目录")) + "</span>" +
|
|
5519
|
-
'<div class="bm-cover-meta">' + esc([cg.model, cg.provider, cg.size
|
|
5597
|
+
'<div class="bm-cover-meta">' + esc([cg.model, cg.provider, cg.size,
|
|
5598
|
+
cgInherited ? t("继承自首批") : ""].filter(Boolean).join(" · ")) + "</div></div>";
|
|
5520
5599
|
} else if (cgSt === "done") {
|
|
5521
5600
|
cgBody = '<div class="bm-empty">' + esc(t("封面已生成,但缺少运行记录归属")) + "</div>";
|
|
5522
5601
|
} else if (cgSt === "failed") {
|
|
@@ -5585,6 +5664,9 @@ function pbBlock(task, platform) {
|
|
|
5585
5664
|
const [label, cls] = PB_ST[st] || PB_ST.none;
|
|
5586
5665
|
const books = (S.pubTaskInfo && S.pubTaskInfo.books) || {};
|
|
5587
5666
|
const book = books[platform];
|
|
5667
|
+
// A create-book flow can finish without a remote id. Keep that binding
|
|
5668
|
+
// retryable instead of presenting it as a ready-to-publish book.
|
|
5669
|
+
const bookReady = !!(book && (book.book_id || book.source === "manual"));
|
|
5588
5670
|
const hist = (S.pubTaskInfo && S.pubTaskInfo.history) || [];
|
|
5589
5671
|
const pubCnt = (S.pubTaskInfo && S.pubTaskInfo.published) || {};
|
|
5590
5672
|
// 已发口径:平台实况(点过校准)优先;否则本地台账去重章号数
|
|
@@ -5621,7 +5703,7 @@ function pbBlock(task, platform) {
|
|
|
5621
5703
|
}
|
|
5622
5704
|
btns += '<button class="ghost pb-tool" onclick="pbProbe(\'' + platform + '\')" title="' +
|
|
5623
5705
|
esc(t("dump 平台表单结构(校准自动填表用)")) + '">' + t("探测") + "</button>";
|
|
5624
|
-
if (
|
|
5706
|
+
if (bookReady) {
|
|
5625
5707
|
btns += '<span class="pb-book" title="' + esc(t("已在此平台创建的作品")) + '">' +
|
|
5626
5708
|
esc(t("已建书:") + (book.title || "")) + "</span>";
|
|
5627
5709
|
btns += ' <button class="primary" ' + (busy ? "disabled" : "") +
|
|
@@ -5651,6 +5733,11 @@ function pbBlock(task, platform) {
|
|
|
5651
5733
|
// 人工模式一轮只填一章:等用户在浏览器提交后再发起(连发会导航离开
|
|
5652
5734
|
// 未提交的编辑器丢稿)。message 是下一步指引,不是错误。
|
|
5653
5735
|
btns += '<div class="pb-hint">' + esc(run.message || t("已填好一章,请在浏览器确认提交")) + "</div>";
|
|
5736
|
+
if (run.last_chapter) {
|
|
5737
|
+
btns += '<button class="ghost pb-tool" onclick="pbConfirmChapter(\'' +
|
|
5738
|
+
esc(task.id) + "', '" + platform + "', " + Number(run.last_chapter) +
|
|
5739
|
+
')">' + t("我已在平台提交") + "</button>";
|
|
5740
|
+
}
|
|
5654
5741
|
} else if (run && run.status !== "running") {
|
|
5655
5742
|
const doneLine = run.status === "done"
|
|
5656
5743
|
? t("自动发布完成:") + run.done + "/" + run.total + (run.message ? t("。") + run.message : "")
|
|
@@ -5659,16 +5746,19 @@ function pbBlock(task, platform) {
|
|
|
5659
5746
|
esc(doneLine + t("(") + (run.at || "") + t(")")) + "</div>";
|
|
5660
5747
|
}
|
|
5661
5748
|
} else {
|
|
5749
|
+
if (book && book.title) {
|
|
5750
|
+
btns += '<span class="pb-book pb-err">' + esc(t("建书未确认:") + book.title) + "</span>";
|
|
5751
|
+
}
|
|
5662
5752
|
btns += '<button class="primary" ' + (busy ? "disabled" : "") +
|
|
5663
5753
|
' onclick="pbCreateBook(\'' + esc(task.id) + "', '" + platform + '\')">' + t("创建作品") + "</button>";
|
|
5664
5754
|
btns += '<button class="ghost" onclick="pbRegToggle(\'' + platform + '\')" title="' +
|
|
5665
5755
|
esc(t("作品已在平台建好(建书流程没走通或手工建的)?补登记后直接发章,不会在平台新建")) + '">' + t("登记已有作品") + "</button>";
|
|
5666
5756
|
}
|
|
5667
5757
|
let extra = "";
|
|
5668
|
-
if (st === "error" && !
|
|
5758
|
+
if (st === "error" && !bookReady && ps.last_action === "create_book") {
|
|
5669
5759
|
extra += '<div class="pb-hint">' + esc(t("若作品其实已在平台建好,点「登记已有作品」补登记,勿重复创建")) + "</div>";
|
|
5670
5760
|
}
|
|
5671
|
-
if (!
|
|
5761
|
+
if (!bookReady) {
|
|
5672
5762
|
extra += '<div class="pb-reg hidden" id="pb-reg-' + platform + '">' +
|
|
5673
5763
|
'<input id="pb-reg-title-' + platform + '" placeholder="' + esc(t("平台上的作品名(必填)")) + '">' +
|
|
5674
5764
|
'<input id="pb-reg-id-' + platform + '" placeholder="' + esc(t("作品ID(选填,管理页URL里有)")) + '">' +
|
|
@@ -5768,8 +5858,9 @@ window.pbProbe = async function (platform) {
|
|
|
5768
5858
|
window.pbCreateBook = async function (taskId, platform) {
|
|
5769
5859
|
if (!confirm(t("将用生成的作品信息在平台自动填建书表单。填好后会停在最后一步,由你在浏览器里人工点提交。继续?"))) return;
|
|
5770
5860
|
try {
|
|
5771
|
-
await
|
|
5772
|
-
|
|
5861
|
+
await pbApiWithQualityOverride(
|
|
5862
|
+
"/api/publish/task/" + encodeURIComponent(taskId) + "/create-book",
|
|
5863
|
+
{ platform }, "创建作品前的质量门禁未通过");
|
|
5773
5864
|
toast(t("正在自动填写建书表单…(完成后请在浏览器里确认提交)"));
|
|
5774
5865
|
} catch (e) { toast(t("建书失败:") + e.message, true); }
|
|
5775
5866
|
S._pbSig = ""; pbKick();
|
|
@@ -5849,24 +5940,42 @@ window.pbUploadChapter = async function (taskId, platform) {
|
|
|
5849
5940
|
window.pbSendChapter = async function (taskId, platform, relName) {
|
|
5850
5941
|
if (!confirm(t("将把《{0}》填进平台章节编辑器,填好后由你人工提交。继续?", relName))) return;
|
|
5851
5942
|
try {
|
|
5852
|
-
await
|
|
5853
|
-
|
|
5943
|
+
await pbApiWithQualityOverride(
|
|
5944
|
+
"/api/publish/task/" + encodeURIComponent(taskId) + "/chapter",
|
|
5945
|
+
{ platform, file: relName }, "发章前的质量门禁未通过");
|
|
5854
5946
|
toast(t("正在填写章节…(完成后请在浏览器里确认提交)"));
|
|
5855
5947
|
const box = $("pb-ch-" + platform);
|
|
5856
|
-
if (box)
|
|
5948
|
+
if (box) {
|
|
5949
|
+
const n = chapterNumOf(relName);
|
|
5950
|
+
box.classList.remove("hidden");
|
|
5951
|
+
box.innerHTML = '<div class="pb-hint">' + esc(t("填稿已完成,请先在浏览器里提交,再点击确认")) +
|
|
5952
|
+
'</div><button class="ghost pb-tool" onclick="pbConfirmChapter(\'' +
|
|
5953
|
+
esc(taskId) + "', '" + platform + "', " + Number(n || 0) +
|
|
5954
|
+
')">' + t("我已在平台提交") + "</button>";
|
|
5955
|
+
}
|
|
5857
5956
|
} catch (e) { toast(t("发章失败:") + e.message, true); }
|
|
5858
5957
|
S._pbSig = ""; pbKick();
|
|
5859
5958
|
};
|
|
5860
5959
|
|
|
5960
|
+
window.pbConfirmChapter = async function (taskId, platform, chapterNo) {
|
|
5961
|
+
try {
|
|
5962
|
+
await api("/api/publish/task/" + encodeURIComponent(taskId) + "/confirm-chapter",
|
|
5963
|
+
{ method: "POST", body: JSON.stringify({ platform, chapter_no: chapterNo }) });
|
|
5964
|
+
toast(t("已确认提交,台账已更新"));
|
|
5965
|
+
} catch (e) { toast(t("确认提交失败:") + e.message, true); }
|
|
5966
|
+
S._pbSig = ""; pbKick();
|
|
5967
|
+
};
|
|
5968
|
+
|
|
5861
5969
|
window.pbPublishAll = async function (taskId, platform) {
|
|
5862
5970
|
const au = (((S.pubAuto && S.pubAuto.books) || [])
|
|
5863
5971
|
.find((b) => b.platform === platform)) || {};
|
|
5864
5972
|
if (!au.pending) return;
|
|
5865
|
-
if (!confirm(t("将从最靠前的待发章节开始填稿(共 {0}
|
|
5973
|
+
if (!confirm(t("将从最靠前的待发章节开始填稿(共 {0} 章待发)。本轮只填一章并停在表单页,由你在浏览器里确认提交;提交后点击“我已在平台提交”,再选择下一章。护栏(每日上限/连续失败暂停)生效。继续?", au.pending))) return;
|
|
5866
5974
|
try {
|
|
5867
|
-
await
|
|
5868
|
-
|
|
5869
|
-
|
|
5975
|
+
await pbApiWithQualityOverride(
|
|
5976
|
+
"/api/publish/task/" + encodeURIComponent(taskId) + "/publish-all",
|
|
5977
|
+
{ platform }, "批量发布前的质量门禁未通过");
|
|
5978
|
+
toast(t("正在填第一章稿——填好后请在浏览器窗口里确认提交,再重新校准"));
|
|
5870
5979
|
} catch (e) { toast(t("自动发布失败:") + e.message, true); }
|
|
5871
5980
|
S._pbSig = ""; pbKick();
|
|
5872
5981
|
};
|
|
@@ -10498,14 +10607,30 @@ function renderSkills() {
|
|
|
10498
10607
|
const box = $("skill-packs");
|
|
10499
10608
|
if (!box || !S.skills) return;
|
|
10500
10609
|
const packs = S.skills.packs || [], lessons = S.skills.lessons || [];
|
|
10501
|
-
box.innerHTML = packs.map((p) =>
|
|
10502
|
-
|
|
10503
|
-
|
|
10504
|
-
|
|
10505
|
-
|
|
10506
|
-
|
|
10507
|
-
|
|
10508
|
-
|
|
10610
|
+
box.innerHTML = packs.map((p) => {
|
|
10611
|
+
const card = p.card;
|
|
10612
|
+
const cardHtml = card
|
|
10613
|
+
? '<div class="facts" style="margin-top:4px">' +
|
|
10614
|
+
'<span class="tag" title="' + esc(t("来源:") + (card.source && card.source.market || "-")) + '">' +
|
|
10615
|
+
t("来源 ") + esc((card.source && card.source.market) || "-") + "</span>" +
|
|
10616
|
+
(card.digest ? '<span class="tag" title="' + t("内容指纹(重装突变对账用)") + '">' +
|
|
10617
|
+
t("指纹 ") + esc(card.digest) + "</span>" : "") +
|
|
10618
|
+
(card.scan ? (card.scan.indexOf("高风险") >= 0
|
|
10619
|
+
? '<span class="tag" style="color:var(--err,#d33)">' + esc(card.scan.split(":")[0]) + "</span>"
|
|
10620
|
+
: '<span class="tag">' + esc(card.scan.split(":")[0]) + "</span>") : "") +
|
|
10621
|
+
"</div>" +
|
|
10622
|
+
(card.declares ? '<div class="note" style="margin-top:2px">📝 ' + t("自述:") +
|
|
10623
|
+
esc(card.declares.slice(0, 120)) + (card.declares.length > 120 ? "…" : "") + "</div>" : "")
|
|
10624
|
+
: "";
|
|
10625
|
+
return '<div class="card"><div class="head"><span class="name">📚 ' + esc(t(p.name)) + "</span>" +
|
|
10626
|
+
'<span class="tag">' + esc((p.scopes || []).map((s) => t(s)).join(" / ")) + "</span>" +
|
|
10627
|
+
(p.enabled ? '<span class="tag ok">' + t("启用中") + "</span>" : '<span class="tag">' + t("已停用") + "</span>") + "</div>" +
|
|
10628
|
+
'<div class="note">' + esc(t(p.note || "")) + "</div>" +
|
|
10629
|
+
'<div class="facts">' + t("正文 ") + "<b>" + p.chars + "</b>" + t(" 字(自动注入该类任务的规划与评审提示词)") + "</div>" +
|
|
10630
|
+
cardHtml +
|
|
10631
|
+
'<div class="ops"><button class="ghost small" onclick="skillPackOp(\'' + esc(p.id) + '\', \'' +
|
|
10632
|
+
(p.enabled ? "disable" : "enable") + '\')">' + (p.enabled ? t("停用") : t("启用")) + "</button></div></div>";
|
|
10633
|
+
}).join("")
|
|
10509
10634
|
|| '<div class="empty">' + t("暂无经验包") + "</div>";
|
|
10510
10635
|
// 分类 chip 过滤:药丸按钮 + 彩色圆点 + 计数,选中态高亮。值 = 分类名,空串 = 全部。
|
|
10511
10636
|
const cats = S.skills.categories || [];
|
|
@@ -11463,6 +11588,7 @@ bindHooksPanel();
|
|
|
11463
11588
|
function zentaoStateTag(st) {
|
|
11464
11589
|
const MAP = {
|
|
11465
11590
|
fixing: ["修复中", "zt-tag-fix"],
|
|
11591
|
+
retry: ["待重试", "zt-tag-warn"],
|
|
11466
11592
|
resolved: ["已解决", "zt-tag-ok"],
|
|
11467
11593
|
transferred: ["已转派", "zt-tag-fix"],
|
|
11468
11594
|
escalated: ["已升级转派", "zt-tag-warn"],
|
|
@@ -11471,6 +11597,7 @@ function zentaoStateTag(st) {
|
|
|
11471
11597
|
resolve_failed: ["回写失败", "zt-tag-warn"],
|
|
11472
11598
|
done_manual: ["待人工确认", "zt-tag-warn"],
|
|
11473
11599
|
need_manual: ["需人工", "zt-tag-warn"],
|
|
11600
|
+
failed: ["修复失败", "zt-tag-err"],
|
|
11474
11601
|
lost: ["记录丢失", "zt-tag-err"],
|
|
11475
11602
|
};
|
|
11476
11603
|
const m = MAP[st] || [st || "?", ""];
|
|
@@ -11537,6 +11664,20 @@ async function zentaoArchiveClaim(bugId, archived) {
|
|
|
11537
11664
|
} catch (e) { toast((e && e.message) || String(e), true); }
|
|
11538
11665
|
}
|
|
11539
11666
|
|
|
11667
|
+
/* 立即处理:不等下轮扫描,手工重触发失败/留人工的修复(后端按状态分流) */
|
|
11668
|
+
async function zentaoRetryClaim(bugId) {
|
|
11669
|
+
try {
|
|
11670
|
+
const r = await api("/api/zentao/claims/retry", { method: "POST",
|
|
11671
|
+
body: JSON.stringify({ bug_id: bugId }) });
|
|
11672
|
+
toast((r && r.message) || t("已重新触发处理"));
|
|
11673
|
+
await loadZentao();
|
|
11674
|
+
} catch (e) { toast((e && e.message) || String(e), true); }
|
|
11675
|
+
}
|
|
11676
|
+
|
|
11677
|
+
/* 这些状态的记录可以「立即处理」(fixing/resolved/transferred/done_manual 不行) */
|
|
11678
|
+
const ZT_RETRYABLE = { retry: 1, failed: 1, escalated: 1, commented: 1,
|
|
11679
|
+
need_manual: 1, merge_failed: 1, resolve_failed: 1, lost: 1 };
|
|
11680
|
+
|
|
11540
11681
|
function zentaoClaimCard(c, archived) {
|
|
11541
11682
|
const tasks = (c.tasks || []).map((tk) =>
|
|
11542
11683
|
"<span>" + t("【") + (tk.side === "frontend" ? t("前端") : t("后端")) + t("】") +
|
|
@@ -11547,11 +11688,15 @@ function zentaoClaimCard(c, archived) {
|
|
|
11547
11688
|
? '<a class="zt-bug-link" href="' + esc(bugUrl) + '" target="_blank" rel="noopener" title="' +
|
|
11548
11689
|
esc(t("在禅道中打开 Bug 详情")) + '">' + name + "</a>"
|
|
11549
11690
|
: name;
|
|
11691
|
+
const retryBtn = (!archived && ZT_RETRYABLE[c.state])
|
|
11692
|
+
? '<button class="ghost small" title="' + esc(t("不等下轮扫描,立即重新触发修复")) +
|
|
11693
|
+
'" onclick="zentaoRetryClaim(\'' + esc(c.bug_id) + '\')">' + t("立即处理") + "</button>"
|
|
11694
|
+
: "";
|
|
11550
11695
|
const act = archived
|
|
11551
11696
|
? '<button class="ghost small" onclick="zentaoArchiveClaim(\'' + esc(c.bug_id) + '\',false)">' + t("取消归档") + "</button>"
|
|
11552
11697
|
: '<button class="ghost small" title="' + esc(t("移入已归档区,不再出现在进行中列表")) + '" onclick="zentaoArchiveClaim(\'' + esc(c.bug_id) + '\',true)">' + t("归档") + "</button>";
|
|
11553
11698
|
return '<div class="card' + (archived ? " zt-archived" : "") + '"><div class="head"><span class="name">' + headName + "</span>" +
|
|
11554
|
-
zentaoStateTag(c.state) + act + "</div>" +
|
|
11699
|
+
zentaoStateTag(c.state) + retryBtn + act + "</div>" +
|
|
11555
11700
|
'<div class="auto-meta">' +
|
|
11556
11701
|
(zentaoTriText(c) ? "<span>" + esc(zentaoTriText(c)) + "</span>" : "") +
|
|
11557
11702
|
(tasks || "") +
|
|
@@ -11857,7 +12002,11 @@ function renderMarket() {
|
|
|
11857
12002
|
async function mkInstall(id) {
|
|
11858
12003
|
try {
|
|
11859
12004
|
const r = await api("/api/market/" + encodeURIComponent(id) + "/install", { method: "POST", body: "{}" });
|
|
11860
|
-
|
|
12005
|
+
if (r && r.already) toast(t("该插件已安装过"));
|
|
12006
|
+
else {
|
|
12007
|
+
toast(t("安装成功,运行任务时自动注入提示词;可到「经验库」启停或查看"));
|
|
12008
|
+
(r && r.warnings || []).forEach((w) => toast(w, true)); // 供应链/内容警示逐条红牌
|
|
12009
|
+
}
|
|
11861
12010
|
} catch (e) { toast(e.message, true); return; }
|
|
11862
12011
|
loadMarket();
|
|
11863
12012
|
}
|
|
@@ -11943,13 +12092,22 @@ function mkrCardHtml(p) {
|
|
|
11943
12092
|
esc(p.block_reason || t("该插件无法从公开生态下载")) + '">' + t("不适配") + "</button>";
|
|
11944
12093
|
else ops = '<button class="primary small" data-mk="' + esc(p.id) + '" onclick="mkrInstall(\'' + esc(p.id) + '\')">' + t("安装") + "</button>";
|
|
11945
12094
|
const meta = [p.author, p.version ? "v" + p.version : ""].filter(Boolean).join(" · ");
|
|
12095
|
+
// 信任卡(ClawTrust 思路):热度(来源自报下载量,仅参考)+ 装前安检结论
|
|
12096
|
+
const trust = [
|
|
12097
|
+
p.downloads ? '<span class="tag" title="' + t("来源自报下载量,仅作热度参考") + '">▼ ' +
|
|
12098
|
+
esc(p.downloads) + "</span>" : "",
|
|
12099
|
+
p.scan_note ? (p.scan_note.indexOf("高风险") >= 0
|
|
12100
|
+
? '<span class="tag" style="color:var(--err,#d33)" title="' + esc(p.scan_note) + '">' +
|
|
12101
|
+
t("⚠ 安检高风险") + "</span>"
|
|
12102
|
+
: '<span class="tag" title="' + esc(p.scan_note) + '">' + t("安检:") + esc(p.scan_note.split(":")[0]) + "</span>")
|
|
12103
|
+
: ""].filter(Boolean).join("");
|
|
11946
12104
|
return '<div class="card mk-card' + (p.compat === "blocked" ? " blocked" : "") + '"><div class="head">' +
|
|
11947
12105
|
'<span class="name">' + esc(t(p.title || p.name)) + "</span>" +
|
|
11948
12106
|
'<span class="tag mk-src">' + esc(t(p.source_name || "")) + "</span>" +
|
|
11949
12107
|
(p.category ? '<span class="tag">' + esc(p.category) + "</span>" : "") +
|
|
11950
12108
|
"</div>" +
|
|
11951
12109
|
'<div class="note">' + esc(t(p.desc || "")) + "</div>" +
|
|
11952
|
-
'<div class="mk-meta"><span>' + esc(meta) + "</span
|
|
12110
|
+
'<div class="mk-meta"><span>' + esc(meta) + "</span>" + trust + "</div>" +
|
|
11953
12111
|
'<div class="ops">' + ops + "</div></div>";
|
|
11954
12112
|
}
|
|
11955
12113
|
|
|
@@ -12096,6 +12254,13 @@ function mkrShowInstallPreview(p) {
|
|
|
12096
12254
|
: '<div class="mkpv-sec">' + esc(t(p.risk_label)) + ":" + t("部分内容值得留意识别") +
|
|
12097
12255
|
'<ul class="mkpv-strip">' + riskFindings + "</ul></div>")
|
|
12098
12256
|
: "";
|
|
12257
|
+
// 供应链对账(AST10 #2):内容突变 / 近似名仿冒特征
|
|
12258
|
+
const chainHtml =
|
|
12259
|
+
(p.changed ? '<div class="mkpv-sec" style="color:var(--err,#d33)">⚠ ' +
|
|
12260
|
+
t("内容较上次安装发生变化(指纹 ") + esc(p.digest || "") + t("),建议核对后再启用") + "</div>" : "") +
|
|
12261
|
+
((p.near_names || []).map((n) =>
|
|
12262
|
+
'<div class="mkpv-sec">⚠ ' + t("与已装技能「") + esc(n[0]) + t("」名称过近(相似度 ") +
|
|
12263
|
+
Math.round(n[1] * 100) + t("%),注意甄别仿冒") + "</div>").join(""));
|
|
12099
12264
|
openModal(t("安装前确认"),
|
|
12100
12265
|
'<div class="mkpv">' +
|
|
12101
12266
|
'<div class="mkpv-name">' + esc(p.title || p.id || "") + "</div>" +
|
|
@@ -12103,7 +12268,7 @@ function mkrShowInstallPreview(p) {
|
|
|
12103
12268
|
(p.version ? "<span>v" + esc(p.version) + "</span>" : "") +
|
|
12104
12269
|
"<span>" + esc(_mkrIntegrityText(p.integrity)) + "</span></div>" +
|
|
12105
12270
|
(skills ? '<div class="mkpv-skills">' + t("包含技能:") + skills + "</div>" : "") +
|
|
12106
|
-
riskHtml +
|
|
12271
|
+
riskHtml + chainHtml +
|
|
12107
12272
|
'<div class="mkpv-sum">' + t("共 ") + (p.files_total || 0) + t(" 个文件 · ") + esc(mkCharsText(p.total_chars || 0)) + "</div>" +
|
|
12108
12273
|
(rows ? '<table class="mkpv-files"><thead><tr><th>' + t("文件") + "</th><th>" + t("体量") +
|
|
12109
12274
|
"</th></tr></thead><tbody>" + rows + moreF + "</tbody></table>" : "") +
|
|
@@ -12543,6 +12708,7 @@ function renderEvalBench() {
|
|
|
12543
12708
|
samplesBox.innerHTML = eb.samples.map((s) =>
|
|
12544
12709
|
'<div class="item"><div class="t"><span class="name">' + esc(t(s.name)) + "</span>" +
|
|
12545
12710
|
(s.verify ? '<span class="tag ok">' + t("带客观验证") + "</span>" : "") +
|
|
12711
|
+
(s.redteam ? '<span class="tag" style="color:var(--err,#d33)">' + t("安全测试") + "</span>" : "") +
|
|
12546
12712
|
(s.builtin ? "" : '<span class="tag">' + t("自定义") + "</span>" +
|
|
12547
12713
|
'<button class="ghost small" onclick="sampleExport(\'' + esc(s.id) + '\')">' + t("导出") + "</button>" +
|
|
12548
12714
|
'<button class="danger small" onclick="sampleDelete(\'' + esc(s.id) + '\')">' + t("删除") + "</button>") +
|
|
@@ -14878,6 +15044,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
14878
15044
|
if (!b || b.classList.contains("hidden")) return;
|
|
14879
15045
|
S.rdTab = b.dataset.tab;
|
|
14880
15046
|
S.rdTabPin = true;
|
|
15047
|
+
S.rdHiveFirst = false;
|
|
14881
15048
|
applyRdTabs();
|
|
14882
15049
|
rdEnsureActiveTabData();
|
|
14883
15050
|
});
|
|
@@ -15041,6 +15208,43 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
15041
15208
|
document.querySelector("main").addEventListener("scroll", apnSyncActive, { passive: true });
|
|
15042
15209
|
syncLangMode();
|
|
15043
15210
|
$("btn-menu").addEventListener("click", () => document.body.classList.toggle("side-collapsed"));
|
|
15211
|
+
/* 侧栏宽度拖拽:grid 列宽走 --side-w,拖动实时跟手,松手落 localStorage,双击复原。
|
|
15212
|
+
* 仅桌面网格布局生效(窄屏侧栏是固定宽抽屉,手柄已 display:none) */
|
|
15213
|
+
(() => {
|
|
15214
|
+
const KEY = "orch.sideW", MIN = 190, MAX = 460; // 复原默认值=CSS 回退 216
|
|
15215
|
+
const root = document.documentElement;
|
|
15216
|
+
const apply = (w) => root.style.setProperty("--side-w", w + "px");
|
|
15217
|
+
const saved = parseInt(localStorage.getItem(KEY), 10);
|
|
15218
|
+
if (saved >= MIN && saved <= MAX && window.innerWidth >= 900) apply(saved);
|
|
15219
|
+
const el = $("side-resizer");
|
|
15220
|
+
let active = false, startX = 0, startW = 0;
|
|
15221
|
+
const onMove = (e) => {
|
|
15222
|
+
if (!active) return;
|
|
15223
|
+
apply(Math.round(Math.min(MAX, Math.max(MIN, startW + e.clientX - startX))));
|
|
15224
|
+
};
|
|
15225
|
+
const onFinish = () => {
|
|
15226
|
+
if (!active) return;
|
|
15227
|
+
active = false;
|
|
15228
|
+
document.body.classList.remove("side-resizing");
|
|
15229
|
+
localStorage.setItem(KEY, String(Math.round($("sidebar").getBoundingClientRect().width)));
|
|
15230
|
+
};
|
|
15231
|
+
el.addEventListener("pointerdown", (e) => {
|
|
15232
|
+
if (window.innerWidth < 900) return;
|
|
15233
|
+
active = true; startX = e.clientX;
|
|
15234
|
+
startW = $("sidebar").getBoundingClientRect().width;
|
|
15235
|
+
document.body.classList.add("side-resizing");
|
|
15236
|
+
e.preventDefault();
|
|
15237
|
+
});
|
|
15238
|
+
/* move/up 挂 window:setPointerCapture 在部分环境(headless 真实输入实测)不生效,
|
|
15239
|
+
move 会按命中测试派发到主区元素,只有 window 级监听保证收到 */
|
|
15240
|
+
window.addEventListener("pointermove", onMove);
|
|
15241
|
+
window.addEventListener("pointerup", onFinish);
|
|
15242
|
+
window.addEventListener("pointercancel", onFinish);
|
|
15243
|
+
el.addEventListener("dblclick", () => {
|
|
15244
|
+
localStorage.removeItem(KEY);
|
|
15245
|
+
root.style.removeProperty("--side-w");
|
|
15246
|
+
});
|
|
15247
|
+
})();
|
|
15044
15248
|
// 手机抽屉:遮罩点击 / 侧栏内任何可点项(导航、任务树、设置入口)点击后都收回
|
|
15045
15249
|
$("drawer-mask").addEventListener("click", () => document.body.classList.add("side-collapsed"));
|
|
15046
15250
|
$("sidebar").addEventListener("click", (e) => {
|