dsh-novel-write-engine 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +51 -0
- package/README.md +25 -18
- package/briefs/P3.md +2 -0
- package/briefs/P5.md +2 -0
- package/briefs/P6.md +3 -2
- package/briefs/P8.md +5 -4
- package/lib/client.js +148 -16
- package/lib/index.d.ts +244 -4
- package/lib/index.js +1608 -311
- package/lib/types/client/dto.d.ts +19 -0
- package/lib/types/client/viewmodel.d.ts +30 -1
- package/lib/types/constants.d.ts +1 -1
- package/lib/types/panel-contract.d.ts +125 -0
- package/lib/types/types.d.ts +29 -1
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -42,7 +42,7 @@ window.__ModuleLoader__.load({
|
|
|
42
42
|
novel_word_count: "按引擎字数口径核对正文长度。"
|
|
43
43
|
};
|
|
44
44
|
// src/client/viewmodel.ts
|
|
45
|
-
const PANEL_SCHEMA_VERSION =
|
|
45
|
+
const PANEL_SCHEMA_VERSION = 6;
|
|
46
46
|
const PHASE_LABELS = {
|
|
47
47
|
P1: "世界观",
|
|
48
48
|
P2: "章节计划",
|
|
@@ -130,6 +130,7 @@ window.__ModuleLoader__.load({
|
|
|
130
130
|
present: false,
|
|
131
131
|
strict: false,
|
|
132
132
|
premise: "",
|
|
133
|
+
logline: "",
|
|
133
134
|
target: null,
|
|
134
135
|
planned: 0,
|
|
135
136
|
outlineTotal: 0,
|
|
@@ -151,6 +152,11 @@ window.__ModuleLoader__.load({
|
|
|
151
152
|
},
|
|
152
153
|
quality: {
|
|
153
154
|
averageScore: 0,
|
|
155
|
+
lastTenAverage: 0,
|
|
156
|
+
bookMedian: 0,
|
|
157
|
+
bookMin: 0,
|
|
158
|
+
bookMax: 0,
|
|
159
|
+
dimensions: [],
|
|
154
160
|
scores: [],
|
|
155
161
|
trend: null,
|
|
156
162
|
trendText: "—",
|
|
@@ -160,12 +166,15 @@ window.__ModuleLoader__.load({
|
|
|
160
166
|
health: {
|
|
161
167
|
ok: true,
|
|
162
168
|
blocking: 0,
|
|
163
|
-
stateErrors: 0
|
|
169
|
+
stateErrors: 0,
|
|
170
|
+
pending: false
|
|
164
171
|
},
|
|
165
172
|
activity: [],
|
|
166
173
|
advisories: [],
|
|
167
174
|
acknowledgedAdvisories: 0,
|
|
168
175
|
legacy: [],
|
|
176
|
+
pending: null,
|
|
177
|
+
bookData: null,
|
|
169
178
|
nextTitle: "",
|
|
170
179
|
nextDetail: "",
|
|
171
180
|
nextTool: "",
|
|
@@ -307,7 +316,16 @@ window.__ModuleLoader__.load({
|
|
|
307
316
|
badge: row.status === "passed" ? "已归档" : row.status === "blocked" ? "阻塞" : "草稿",
|
|
308
317
|
tone: row.status === "passed" ? "ok" : row.status === "blocked" ? "bad" : "muted",
|
|
309
318
|
details: row.details ?? "",
|
|
310
|
-
stageLabel: stageLabel(row.stage)
|
|
319
|
+
stageLabel: stageLabel(row.stage),
|
|
320
|
+
sceneType: row.sceneType ?? "",
|
|
321
|
+
dialogueRatio: row.dialogueRatio ?? null,
|
|
322
|
+
hookStrength: row.hookStrength ?? null,
|
|
323
|
+
finalCheck: row.finalCheck ?? null,
|
|
324
|
+
reviews: row.reviews ?? 0,
|
|
325
|
+
revisions: row.revisions ?? 0,
|
|
326
|
+
checks: row.checks ?? 0,
|
|
327
|
+
rollbacks: row.rollbacks ?? 0,
|
|
328
|
+
archivedAt: row.archivedAt ?? null
|
|
311
329
|
};
|
|
312
330
|
});
|
|
313
331
|
const scored = rows.filter((row) => row.tone === "ok");
|
|
@@ -328,7 +346,7 @@ window.__ModuleLoader__.load({
|
|
|
328
346
|
subtitle: summary.stage === "done" ? `${summary.genre} · ${platformLabel(summary.platform)} · ${finished ? `共 ${summary.totalChapters} 章` : `下一章 第 ${summary.chapter} 章`}${volumeLabel(summary)}` : `${summary.genre} · ${platformLabel(summary.platform)} · 第 ${summary.chapter} 章${volumeLabel(summary)}`,
|
|
329
347
|
stageLabel: doneLabel,
|
|
330
348
|
finished,
|
|
331
|
-
revisionLabel: `第 ${summary.revision} 次修订`,
|
|
349
|
+
revisionLabel: `第 ${summary.bookData?.current.rounds.revise ?? summary.revision} 次修订`,
|
|
332
350
|
phases: summary.phases.map((phase) => ({
|
|
333
351
|
...phase,
|
|
334
352
|
label: phaseLabel(phase.id)
|
|
@@ -346,6 +364,7 @@ window.__ModuleLoader__.load({
|
|
|
346
364
|
present: summary.book?.outline.present === true,
|
|
347
365
|
strict: summary.book?.strict === true,
|
|
348
366
|
premise: summary.book?.premise ?? "",
|
|
367
|
+
logline: summary.book?.logline ?? "",
|
|
349
368
|
target: summary.book?.target ?? null,
|
|
350
369
|
planned: summary.book?.outline.planned ?? 0,
|
|
351
370
|
outlineTotal: summary.book?.outline.total ?? 0,
|
|
@@ -372,17 +391,23 @@ window.__ModuleLoader__.load({
|
|
|
372
391
|
}
|
|
373
392
|
},
|
|
374
393
|
quality: {
|
|
375
|
-
averageScore: summary.quality
|
|
394
|
+
averageScore: summary.bookData?.quality.average ?? summary.averageScore ?? average,
|
|
395
|
+
lastTenAverage: summary.bookData?.quality.last_ten_average ?? summary.quality?.averageScore ?? average,
|
|
396
|
+
bookMedian: summary.bookData?.quality.median ?? 0,
|
|
397
|
+
bookMin: summary.bookData?.quality.min ?? 0,
|
|
398
|
+
bookMax: summary.bookData?.quality.max ?? 0,
|
|
399
|
+
dimensions: summary.bookData?.quality.dimensions ?? [],
|
|
376
400
|
scores: summary.quality?.scores ?? [],
|
|
377
|
-
trend: summary.quality?.trend ?? null,
|
|
378
|
-
trendText: summary.quality?.trend === null || summary.quality?.trend === void 0 ? summary.quality?.trend_basis === "insufficient_samples" ? "样本不足" : summary.quality?.trend_basis === "insufficient_history" ? "历史不足" : "—" : `${summary.quality.trend >= 0 ? "+" : ""}${summary.quality.trend}`,
|
|
379
|
-
samples: summary.quality
|
|
380
|
-
attempts: summary.quality?.attempts ?? summary.quality?.samples ?? 0
|
|
401
|
+
trend: summary.bookData?.quality.trend ?? summary.quality?.trend ?? null,
|
|
402
|
+
trendText: (summary.bookData?.quality.trend ?? summary.quality?.trend) === null || (summary.bookData?.quality.trend ?? summary.quality?.trend) === void 0 ? summary.quality?.trend_basis === "insufficient_samples" || summary.bookData?.quality.trend_basis === "insufficient_samples" ? "样本不足" : summary.quality?.trend_basis === "insufficient_history" || summary.bookData?.quality.trend_basis === "insufficient_history" ? "历史不足" : "—" : `${(summary.bookData?.quality.trend ?? summary.quality.trend) >= 0 ? "+" : ""}${summary.bookData?.quality.trend ?? summary.quality.trend}`,
|
|
403
|
+
samples: summary.bookData?.quality.samples ?? summary.quality?.samples ?? 0,
|
|
404
|
+
attempts: Math.max(summary.bookData?.quality.attempts ?? summary.quality?.attempts ?? 0, summary.bookData?.quality.samples ?? summary.quality?.samples ?? 0)
|
|
381
405
|
},
|
|
382
406
|
health: {
|
|
383
407
|
ok: summary.health?.ok ?? blocking.length === 0,
|
|
384
408
|
blocking: summary.health?.blocking ?? blocking.length,
|
|
385
|
-
stateErrors: summary.health?.stateErrors ?? 0
|
|
409
|
+
stateErrors: summary.health?.stateErrors ?? 0,
|
|
410
|
+
pending: summary.health?.pending === true
|
|
386
411
|
},
|
|
387
412
|
activity: summary.activity ?? [],
|
|
388
413
|
advisories: (summary.advisories ?? []).map((item) => ({
|
|
@@ -392,6 +417,8 @@ window.__ModuleLoader__.load({
|
|
|
392
417
|
})),
|
|
393
418
|
acknowledgedAdvisories: summary.acknowledgedAdvisories ?? 0,
|
|
394
419
|
legacy: summary.legacy ?? [],
|
|
420
|
+
pending: summary.bookData?.health.pending ?? summary.pending ?? null,
|
|
421
|
+
bookData: summary.bookData ?? null,
|
|
395
422
|
nextTitle: finished ? "" : next === void 0 ? "状态已同步" : toolLabel(next.tool),
|
|
396
423
|
nextDetail: finished ? "" : next === void 0 ? "没有待执行的下一步,刷新查看最新状态。" : NEXT_DETAILS[next.tool] ?? next.reason,
|
|
397
424
|
nextTool: next?.tool ?? "",
|
|
@@ -1005,6 +1032,29 @@ window.__ModuleLoader__.load({
|
|
|
1005
1032
|
color: var(--nwe-muted);
|
|
1006
1033
|
font-size: 11px;
|
|
1007
1034
|
}
|
|
1035
|
+
|
|
1036
|
+
.nwe-pending-section { border-left: 3px solid #c9a227; padding-left: 10px; }
|
|
1037
|
+
.nwe-pending-error { border-left-color: #b3261e; }
|
|
1038
|
+
.nwe-pending-detail { margin: 8px 0 0; color: var(--nwe-ink); font-size: 11px; line-height: 1.6; }
|
|
1039
|
+
.nwe-pending-hint { margin: 4px 0 0; color: var(--nwe-muted); font-size: 10px; line-height: 1.5; }
|
|
1040
|
+
|
|
1041
|
+
.nwe-v6-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
|
|
1042
|
+
.nwe-v6-card { border: 1px solid var(--nwe-border); border-radius: 8px; padding: 10px; background: var(--nwe-surface); }
|
|
1043
|
+
.nwe-v6-card[open] { grid-column: span 2; }
|
|
1044
|
+
.nwe-v6-card > summary { cursor: pointer; font: 600 12px/1.4 var(--nwe-font); color: var(--nwe-ink); }
|
|
1045
|
+
.nwe-v6-card > summary:focus-visible { outline: 2px solid var(--nwe-accent); outline-offset: 2px; }
|
|
1046
|
+
.nwe-v6-list { margin: 8px 0 0; padding: 0; list-style: none; display: grid; gap: 4px; }
|
|
1047
|
+
.nwe-v6-list li { display: grid; grid-template-columns: 7.5em minmax(0, 1fr); gap: 8px; font-size: 10px; line-height: 1.55; color: var(--nwe-muted); }
|
|
1048
|
+
.nwe-v6-list b { color: var(--nwe-ink); font-weight: 600; overflow-wrap: anywhere; }
|
|
1049
|
+
.nwe-v6-bars { display: grid; gap: 4px; margin-top: 8px; }
|
|
1050
|
+
.nwe-v6-bar { display: grid; grid-template-columns: 7em minmax(0, 1fr) 3em; gap: 6px; align-items: center; font-size: 10px; color: var(--nwe-muted); }
|
|
1051
|
+
.nwe-v6-bar span { height: 4px; border-radius: 2px; background: var(--nwe-border); overflow: hidden; }
|
|
1052
|
+
.nwe-v6-bar span i { display: block; height: 100%; background: var(--nwe-accent); }
|
|
1053
|
+
.nwe-v6-note { margin: 8px 0 0; font-size: 10px; line-height: 1.5; color: var(--nwe-muted); }
|
|
1054
|
+
.nwe-v6-tags { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 6px; }
|
|
1055
|
+
.nwe-v6-tags span { border: 1px solid var(--nwe-border); border-radius: 999px; padding: 2px 7px; font-size: 9px; color: var(--nwe-muted); }
|
|
1056
|
+
.nwe-chapter-extra { margin-top: 6px; font-size: 10px; color: var(--nwe-muted); line-height: 1.5; overflow-wrap: anywhere; }
|
|
1057
|
+
@media (max-width: 640px) { .nwe-v6-grid { grid-template-columns: 1fr; } .nwe-v6-card[open] { grid-column: auto; } }
|
|
1008
1058
|
.nwe-legacy {
|
|
1009
1059
|
display: flex;
|
|
1010
1060
|
gap: 8px;
|
|
@@ -1191,7 +1241,7 @@ brief P6 → check → brief P8 → archive 的顺序推进。
|
|
|
1191
1241
|
}, (0, react.createElement)("span", { style: { width: `${row.scorePercent}%` } }))), (0, react.createElement)("span", { className: `nwe-badge is-${row.tone}` }, row.badge)), (0, react.createElement)("span", {
|
|
1192
1242
|
className: "nwe-chevron",
|
|
1193
1243
|
"aria-hidden": "true"
|
|
1194
|
-
}, "›")), row.details === "" ? null : (0, react.createElement)("div", { className: "nwe-chapter-detail" }, row.details)));
|
|
1244
|
+
}, "›")), row.details === "" ? null : (0, react.createElement)("div", { className: "nwe-chapter-detail" }, row.details), (0, react.createElement)("div", { className: "nwe-chapter-extra" }, `${row.sceneType || "—"} · 对话 ${row.dialogueRatio === null ? "—" : `${Math.round(row.dialogueRatio * 100)}%`} · 钩子 ${row.hookStrength ?? "—"} · 终检 ${row.finalCheck ?? "—"} · 评 ${row.reviews} / 修 ${row.revisions} / 检 ${row.checks} / 回滚 ${row.rollbacks} · 归档 ${row.archivedAt ?? "—"}`)));
|
|
1195
1245
|
const phases = (view) => (0, react.createElement)("ol", {
|
|
1196
1246
|
className: "nwe-phases",
|
|
1197
1247
|
"aria-label": "创作阶段"
|
|
@@ -1221,7 +1271,7 @@ brief P6 → check → brief P8 → archive 的顺序推进。
|
|
|
1221
1271
|
return (0, react.createElement)("section", {
|
|
1222
1272
|
className: "nwe-section",
|
|
1223
1273
|
"aria-label": "全书规划与质量"
|
|
1224
|
-
}, (0, react.createElement)("div", { className: "nwe-section-head" }, (0, react.createElement)("h2", { className: "nwe-section-title" }, "全书规划"), (0, react.createElement)("span", { className: "nwe-section-note" }, view.book.present ? `${view.book.strict ? "严格大纲" : "宽松大纲"} · 规划 ${view.book.plannedCoverage === null ? "—" : `${Math.round(view.book.plannedCoverage * 1e3) / 10}%`} · 归档 ${view.book.archivedCoverage === null ? "—" : `${Math.round(view.book.archivedCoverage * 1e3) / 10}%`}${view.book.missingCards > 0 ? ` · 缺卡 ${view.book.missingCards}` : ""}` : "还没有章卡")), (0, react.createElement)("div", { className: "nwe-book-grid" }, (0, react.createElement)("div", { className: "nwe-book-card" }, (0, react.createElement)("div", { className: "nwe-book-head" }, (0, react.createElement)("span", { className: "nwe-book-title" }, view.book.premise === "" ? "全书进度" : view.book.premise), (0, react.createElement)("span", { className: "nwe-book-note" }, `${view.book.planned} 计划 / ${view.book.archivedCards} 归档`)), (0, react.createElement)("div", {
|
|
1274
|
+
}, (0, react.createElement)("div", { className: "nwe-section-head" }, (0, react.createElement)("h2", { className: "nwe-section-title" }, "全书规划"), (0, react.createElement)("span", { className: "nwe-section-note" }, view.book.present ? `${view.book.strict ? "严格大纲" : "宽松大纲"} · 规划 ${view.book.plannedCoverage === null ? "—" : `${Math.round(view.book.plannedCoverage * 1e3) / 10}%`} · 归档 ${view.book.archivedCoverage === null ? "—" : `${Math.round(view.book.archivedCoverage * 1e3) / 10}%`}${view.book.missingCards > 0 ? ` · 缺卡 ${view.book.missingCards}` : ""}` : "还没有章卡")), (0, react.createElement)("div", { className: "nwe-book-grid" }, (0, react.createElement)("div", { className: "nwe-book-card" }, (0, react.createElement)("div", { className: "nwe-book-head" }, (0, react.createElement)("span", { className: "nwe-book-title" }, view.book.premise === "" ? "全书进度" : view.book.premise), (0, react.createElement)("span", { className: "nwe-book-note" }, `${view.book.planned} 计划 / ${view.book.archivedCards} 归档`)), (0, react.createElement)("p", { className: "nwe-v6-note" }, view.book.logline || "还没有 logline"), (0, react.createElement)("div", {
|
|
1225
1275
|
className: "nwe-progress",
|
|
1226
1276
|
role: "progressbar",
|
|
1227
1277
|
"aria-valuemin": 0,
|
|
@@ -1240,7 +1290,7 @@ brief P6 → check → brief P8 → archive 的顺序推进。
|
|
|
1240
1290
|
}, `${label} ${value}`))), (0, react.createElement)("div", {
|
|
1241
1291
|
className: "nwe-book-head",
|
|
1242
1292
|
style: { marginTop: 12 }
|
|
1243
|
-
}, (0, react.createElement)("span", { className: "nwe-book-title" }, "质量趋势"), (0, react.createElement)("span", { className: "nwe-book-note" }, bars.length === 0 ? "—" : `${view.quality.
|
|
1293
|
+
}, (0, react.createElement)("span", { className: "nwe-book-title" }, "质量趋势"), (0, react.createElement)("span", { className: "nwe-book-note" }, bars.length === 0 ? "—" : `${view.quality.lastTenAverage.toFixed(1)} 近十均分 · 趋势 ${view.quality.trendText} · ${view.quality.samples} 章 / ${view.quality.attempts} 评`)), (0, react.createElement)("div", {
|
|
1244
1294
|
className: "nwe-bars",
|
|
1245
1295
|
role: "img",
|
|
1246
1296
|
"aria-label": "最近十章评分"
|
|
@@ -1249,7 +1299,7 @@ brief P6 → check → brief P8 → archive 的顺序推进。
|
|
|
1249
1299
|
className: "nwe-bar",
|
|
1250
1300
|
style: { height: `${Math.max(4, Math.round(score / 150 * 100))}%` },
|
|
1251
1301
|
title: `${score}/150`
|
|
1252
|
-
}))), (0, react.createElement)("div", { className: `nwe-health${view.health.ok ? "" : " is-bad"}` }, (0, react.createElement)("span", { "aria-hidden": "true" }, view.health.ok ? "✓" : "!"), (0, react.createElement)("span", null, view.health.ok ? "项目健康:没有阻塞或损坏状态" : `需要处理:${view.health.blocking} 条门控 · ${view.health.stateErrors}
|
|
1302
|
+
}))), (0, react.createElement)("div", { className: `nwe-health${view.health.ok ? "" : " is-bad"}` }, (0, react.createElement)("span", { "aria-hidden": "true" }, view.health.ok ? "✓" : "!"), (0, react.createElement)("span", null, view.health.ok ? "项目健康:没有阻塞或损坏状态" : `需要处理:${view.health.blocking} 条门控 · ${view.health.stateErrors} 个损坏文档${view.health.pending ? " · 有 pending 事务" : ""}`)))));
|
|
1253
1303
|
};
|
|
1254
1304
|
const activitySection = (view) => {
|
|
1255
1305
|
if (view.activity.length === 0) return null;
|
|
@@ -1266,6 +1316,14 @@ brief P6 → check → brief P8 → archive 的顺序推进。
|
|
|
1266
1316
|
key: `${item.code}-${item.detail}`
|
|
1267
1317
|
}, (0, react.createElement)("code", { className: "nwe-advisory-code" }, item.code), (0, react.createElement)("span", { className: "nwe-advisory-detail" }, item.detail), item.hint === "" ? null : (0, react.createElement)("span", { className: "nwe-advisory-hint" }, item.hint)))));
|
|
1268
1318
|
};
|
|
1319
|
+
const pendingSection = (view) => {
|
|
1320
|
+
if (view.pending === null) return null;
|
|
1321
|
+
const partial = view.pending.state === "partial";
|
|
1322
|
+
return (0, react.createElement)("section", {
|
|
1323
|
+
className: `nwe-section nwe-pending-section${partial ? " nwe-pending-error" : ""}`,
|
|
1324
|
+
"aria-label": "待恢复事务"
|
|
1325
|
+
}, (0, react.createElement)("div", { className: "nwe-section-head" }, (0, react.createElement)("h2", { className: "nwe-section-title" }, partial ? "事务部分完成" : "待恢复事务"), (0, react.createElement)("span", { className: "nwe-section-note" }, view.pending.state)), (0, react.createElement)("p", { className: "nwe-pending-detail" }, view.pending.detail), (0, react.createElement)("p", { className: "nwe-pending-hint" }, partial ? `检查 backups 与 .staging/${view.pending.txid}/before/,不要整项目恢复。` : "下一次写操作会自动收尾;只读面不会改动事务。"));
|
|
1326
|
+
};
|
|
1269
1327
|
const legacySection = (view) => {
|
|
1270
1328
|
if (view.legacy.length === 0) return null;
|
|
1271
1329
|
return (0, react.createElement)("section", {
|
|
@@ -1457,7 +1515,7 @@ brief P6 → check → brief P8 → archive 的顺序推进。
|
|
|
1457
1515
|
ref: closeRef,
|
|
1458
1516
|
onClick: onClose,
|
|
1459
1517
|
"aria-label": "关闭使用教程"
|
|
1460
|
-
}, (0, react.createElement)("span", { "aria-hidden": "true" }, "×"))), (0, react.createElement)("div", { className: "nwe-modal-body" }, (0, react.createElement)("div", { className: "nwe-tutorial-intro" }, (0, react.createElement)("p", null, "网文工坊不是一个“帮你想剧情”的按钮,而是一台有状态的写作引擎:你提供内容,引擎负责阶段、字数、门控、评审、归档和文件。"), (0, react.createElement)("p", null, "每一步都先领取当前阶段的任务书,再提交;提交后看引擎返回的 blocking 与下一步。面板只读,真正的写操作都在对话里的工具调用中。")), (0, react.createElement)("section", { className: "nwe-tutorial-block" }, (0, react.createElement)("h3", null, "开始前:记住三件事"), (0, react.createElement)("div", { className: "nwe-tutorial-cards" }, (0, react.createElement)("div", { className: "nwe-tutorial-card" }, (0, react.createElement)("strong", null, "只用引擎入口写"), (0, react.createElement)("span", null, "正文只走 novel_write_draft / novel_revise;状态只走工具。手改 chapters/、state/、outline/ 会破坏事务与校验。")), (0, react.createElement)("div", { className: "nwe-tutorial-card" }, (0, react.createElement)("strong", null, "每一步先领 brief"), (0, react.createElement)("span", null, "任务书携带当前上下文和 digest;跳过它会撞 PG-01。任务书过期就重新领取,不要复用旧上下文。")), (0, react.createElement)("div", { className: "nwe-tutorial-card" }, (0, react.createElement)("strong", null, "失败先读 code"), (0, react.createElement)("span", null, "参数错改参数,门控没过改正文,系统故障查目录/稍后重试。blocking 会一次列全,一次改完再交。"))), (0, react.createElement)("p", { className: "nwe-tutorial-note" }, (0, react.createElement)("strong", null, "用哪个会话:"), " 想获得完整的引擎保证,请新开一个「网文工坊」预设会话;普通会话里模型可能还挂着文件工具,能绕过引擎直接改稿。")), (0, react.createElement)("section", { className: "nwe-tutorial-block" }, (0, react.createElement)("h3", null, "一本书的完整流程"), (0, react.createElement)("p", null, "前四步是开书准备,后面是每章循环。第一次先按顺序走完一章,熟悉后再批量推进整本。"), (0, react.createElement)("ol", { className: "nwe-steps" }, ...TUTORIAL_STEPS.map((step, index) => tutorialStep(step, index)))), (0, react.createElement)("section", { className: "nwe-tutorial-block" }, (0, react.createElement)("h3", null, "面板怎么看"), (0, react.createElement)("div", { className: "nwe-tutorial-cards" }, (0, react.createElement)("div", { className: "nwe-tutorial-card" }, (0, react.createElement)("strong", null, "顶部统计"), (0, react.createElement)("span", null, "已归档章、总字数、平均分、待处理门控。数字来自引擎,不是正文估算。")), (0, react.createElement)("div", { className: "nwe-tutorial-card" }, (0, react.createElement)("strong", null, "九阶段轨与下一步"), (0, react.createElement)("span", null, "亮起的阶段是当前进度,“下一步”卡会直接给出该调用的工具名。")), (0, react.createElement)("div", { className: "nwe-tutorial-card" }, (0, react.createElement)("strong", null, "章节与门控"), (0, react.createElement)("span", null, "章节卡展开可看字数、评分与状态;门控区按 code/detail/hint 列出所有阻塞。"))), (0, react.createElement)("p", { className: "nwe-tutorial-note" }, (0, react.createElement)("strong", null, "刷新:"), " 面板每 15 秒静默同步一次,也可以点右上角刷新。它是只读投影;不会替你改稿、归档或修状态。"), (0, react.createElement)("p", { className: "nwe-tutorial-note" }, (0, react.createElement)("strong", null, "项目状态:"), "选择器里 choose 表示发现多本书、还没选;项目不在了表示目录缺失,绑定仍会保留;不是项目表示目录还在但没有 novel.yaml 或项目状态;项目不可读表示权限或 IO 错误,先修权限或换一本。面板选择只影响查看,不改变任何会话的写作绑定。")), (0, react.createElement)("section", { className: "nwe-tutorial-block" }, (0, react.createElement)("h3", null, "卡住时先看这里"), (0, react.createElement)("ul", { className: "nwe-faq" }, (0, react.createElement)("li", null, (0, react.createElement)("strong", null, "为什么一直让我先领 brief?"), (0, react.createElement)("p", null, "这是正常门控。任务书决定当前阶段可用的字段与上下文 digest;先按返回值调用 novel_brief,再继续。")), (0, react.createElement)("li", null, (0, react.createElement)("strong", null, "门控失败,改一条还是全改?"), (0, react.createElement)("p", null, "一次看完所有 blocking,再一起改。逐条试会反复烧掉阶段轮次;错误里的 code 会告诉你属于参数、门控还是系统故障。")), (0, react.createElement)("li", null, (0, react.createElement)("strong", null, "字数和评分为什么对不上?"), (0, react.createElement)("p", null, "字数以 engine 返回为准,可用 novel_word_count 核对;review 的 total 必须等于 150 减全部扣分,不能另算一套。")), (0, react.createElement)("li", null, (0, react.createElement)("strong", null, "P4/P6 过不去怎么办?"), (0, react.createElement)("p", null, "P4 有 blocker 就领 P5 任务书修订,再回 P4 重评;P6 失败同样回修订。不要 waive blocker,也不要只跑半个 scope 就归档。")), (0, react.createElement)("li", null, (0, react.createElement)("strong", null, "想改已经归档的章节?"), (0, react.createElement)("p", null, "先 novel_reopen,带非空 reason。它会从归档备份恢复,重开后重新走 P3→P8;不要直接编辑归档卡或正文文件。")), (0, react.createElement)("li", null, (0, react.createElement)("strong", null, "状态文档坏了能手工修吗?"), (0, react.createElement)("p", null, "先用 novel_state 看 raw 和 next;需要修复时用 novel_amend,并在 reason 写清原因。手改 YAML 会丢失事务保护。")), (0, react.createElement)("li", null, (0, react.createElement)("strong", null, "有多个项目,面板看的是哪本?"), (0, react.createElement)("p", null, "面板用顶部项目选择器切换查看对象;选择只改 ?root= 和浏览器 localStorage,不调用 activate、不写会话绑定。写作项目请用 /novel use 或显式 project_root。")), (0, react.createElement)("li", null, (0, react.createElement)("strong", null, "选错书、想清掉绑定怎么办?"), (0, react.createElement)("p", null, "先用 /novel current 看当前会话绑定;要换书用 /novel use <路径|书名|.>,要清掉只影响当前会话的绑定用 /novel unbind。不要靠 active 猜,也不要手改 state/session-bindings.yaml。")), (0, react.createElement)("li", null, (0, react.createElement)("strong", null, "扫描到了,为什么还没切过去?"), (0, react.createElement)("p", null, "scan 只读发现;scan --register 只登记项目,不选择、不改 active、不绑会话。真正切换写作项目要用 /novel use,或第一次调用显式传 project_root。")))), (0, react.createElement)("section", { className: "nwe-tutorial-block" }, (0, react.createElement)("h3", null, "可以直接复制给模型的起手话术"), (0, react.createElement)("pre", { className: "nwe-prompt" }, TUTORIAL_PROMPT)), (0, react.createElement)("section", { className: "nwe-tutorial-block" }, (0, react.createElement)("h3", null, "续写下一章时,直接复制这段给 Agent"), (0, react.createElement)("p", null, "这段会先确认状态,再处理缺章卡和目标章数已满,最后按 P1→P8 推进;归档前会要求你确认 state_delta。"), (0, react.createElement)("pre", { className: "nwe-prompt" }, TUTORIAL_NEXT_PROMPT)), (0, react.createElement)("section", { className: "nwe-tutorial-block" }, (0, react.createElement)("h3", null, "一章完成检查清单"), (0, react.createElement)("ul", { className: "nwe-checklist" }, (0, react.createElement)("li", null, "本章 P1/P2 计划已提交,P3 正文已通过格式与字数门控。"), (0, react.createElement)("li", null, "P4 评审 total 对账无误,没有未修 blocker。"), (0, react.createElement)("li", null, "P6 五个语义项都有不同句子的正文引用,完整 scope 通过。"), (0, react.createElement)("li", null, "P8 任务书当前,novel_archive 带完整 state_delta 成功。"), (0, react.createElement)("li", null, "面板出现已归档章节,门控区没有新的阻塞。")))), (0, react.createElement)("div", { className: "nwe-modal-foot" }, "教程基于 0.2.
|
|
1518
|
+
}, (0, react.createElement)("span", { "aria-hidden": "true" }, "×"))), (0, react.createElement)("div", { className: "nwe-modal-body" }, (0, react.createElement)("div", { className: "nwe-tutorial-intro" }, (0, react.createElement)("p", null, "网文工坊不是一个“帮你想剧情”的按钮,而是一台有状态的写作引擎:你提供内容,引擎负责阶段、字数、门控、评审、归档和文件。"), (0, react.createElement)("p", null, "每一步都先领取当前阶段的任务书,再提交;提交后看引擎返回的 blocking 与下一步。面板只读,真正的写操作都在对话里的工具调用中。")), (0, react.createElement)("section", { className: "nwe-tutorial-block" }, (0, react.createElement)("h3", null, "开始前:记住三件事"), (0, react.createElement)("div", { className: "nwe-tutorial-cards" }, (0, react.createElement)("div", { className: "nwe-tutorial-card" }, (0, react.createElement)("strong", null, "只用引擎入口写"), (0, react.createElement)("span", null, "正文只走 novel_write_draft / novel_revise;状态只走工具。手改 chapters/、state/、outline/ 会破坏事务与校验。")), (0, react.createElement)("div", { className: "nwe-tutorial-card" }, (0, react.createElement)("strong", null, "每一步先领 brief"), (0, react.createElement)("span", null, "任务书携带当前上下文和 digest;跳过它会撞 PG-01。任务书过期就重新领取,不要复用旧上下文。")), (0, react.createElement)("div", { className: "nwe-tutorial-card" }, (0, react.createElement)("strong", null, "失败先读 code"), (0, react.createElement)("span", null, "参数错改参数,门控没过改正文,系统故障查目录/稍后重试。blocking 会一次列全,一次改完再交。"))), (0, react.createElement)("p", { className: "nwe-tutorial-note" }, (0, react.createElement)("strong", null, "用哪个会话:"), " 想获得完整的引擎保证,请新开一个「网文工坊」预设会话;普通会话里模型可能还挂着文件工具,能绕过引擎直接改稿。")), (0, react.createElement)("section", { className: "nwe-tutorial-block" }, (0, react.createElement)("h3", null, "一本书的完整流程"), (0, react.createElement)("p", null, "前四步是开书准备,后面是每章循环。第一次先按顺序走完一章,熟悉后再批量推进整本。"), (0, react.createElement)("ol", { className: "nwe-steps" }, ...TUTORIAL_STEPS.map((step, index) => tutorialStep(step, index)))), (0, react.createElement)("section", { className: "nwe-tutorial-block" }, (0, react.createElement)("h3", null, "面板怎么看"), (0, react.createElement)("div", { className: "nwe-tutorial-cards" }, (0, react.createElement)("div", { className: "nwe-tutorial-card" }, (0, react.createElement)("strong", null, "顶部统计"), (0, react.createElement)("span", null, "已归档章、总字数、平均分、待处理门控。数字来自引擎,不是正文估算。")), (0, react.createElement)("div", { className: "nwe-tutorial-card" }, (0, react.createElement)("strong", null, "九阶段轨与下一步"), (0, react.createElement)("span", null, "亮起的阶段是当前进度,“下一步”卡会直接给出该调用的工具名。")), (0, react.createElement)("div", { className: "nwe-tutorial-card" }, (0, react.createElement)("strong", null, "章节与门控"), (0, react.createElement)("span", null, "章节卡展开可看字数、评分与状态;门控区按 code/detail/hint 列出所有阻塞。"))), (0, react.createElement)("p", { className: "nwe-tutorial-note" }, (0, react.createElement)("strong", null, "刷新:"), " 面板每 15 秒静默同步一次,也可以点右上角刷新。它是只读投影;不会替你改稿、归档或修状态。"), (0, react.createElement)("p", { className: "nwe-tutorial-note" }, (0, react.createElement)("strong", null, "项目状态:"), "选择器里 choose 表示发现多本书、还没选;项目不在了表示目录缺失,绑定仍会保留;不是项目表示目录还在但没有 novel.yaml 或项目状态;项目不可读表示权限或 IO 错误,先修权限或换一本。面板选择只影响查看,不改变任何会话的写作绑定。")), (0, react.createElement)("section", { className: "nwe-tutorial-block" }, (0, react.createElement)("h3", null, "卡住时先看这里"), (0, react.createElement)("ul", { className: "nwe-faq" }, (0, react.createElement)("li", null, (0, react.createElement)("strong", null, "为什么一直让我先领 brief?"), (0, react.createElement)("p", null, "这是正常门控。任务书决定当前阶段可用的字段与上下文 digest;先按返回值调用 novel_brief,再继续。")), (0, react.createElement)("li", null, (0, react.createElement)("strong", null, "门控失败,改一条还是全改?"), (0, react.createElement)("p", null, "一次看完所有 blocking,再一起改。逐条试会反复烧掉阶段轮次;错误里的 code 会告诉你属于参数、门控还是系统故障。")), (0, react.createElement)("li", null, (0, react.createElement)("strong", null, "字数和评分为什么对不上?"), (0, react.createElement)("p", null, "字数以 engine 返回为准,可用 novel_word_count 核对;review 的 total 必须等于 150 减全部扣分,不能另算一套。")), (0, react.createElement)("li", null, (0, react.createElement)("strong", null, "P4/P6 过不去怎么办?"), (0, react.createElement)("p", null, "P4 有 blocker 就领 P5 任务书修订,再回 P4 重评;P6 失败同样回修订。不要 waive blocker,也不要只跑半个 scope 就归档。")), (0, react.createElement)("li", null, (0, react.createElement)("strong", null, "想改已经归档的章节?"), (0, react.createElement)("p", null, "先 novel_reopen,带非空 reason。它会从归档备份恢复,重开后重新走 P3→P8;不要直接编辑归档卡或正文文件。")), (0, react.createElement)("li", null, (0, react.createElement)("strong", null, "状态文档坏了能手工修吗?"), (0, react.createElement)("p", null, "先用 novel_state 看 raw 和 next;需要修复时用 novel_amend,并在 reason 写清原因。手改 YAML 会丢失事务保护。")), (0, react.createElement)("li", null, (0, react.createElement)("strong", null, "有多个项目,面板看的是哪本?"), (0, react.createElement)("p", null, "面板用顶部项目选择器切换查看对象;选择只改 ?root= 和浏览器 localStorage,不调用 activate、不写会话绑定。写作项目请用 /novel use 或显式 project_root。")), (0, react.createElement)("li", null, (0, react.createElement)("strong", null, "选错书、想清掉绑定怎么办?"), (0, react.createElement)("p", null, "先用 /novel current 看当前会话绑定;要换书用 /novel use <路径|书名|.>,要清掉只影响当前会话的绑定用 /novel unbind。不要靠 active 猜,也不要手改 state/session-bindings.yaml。")), (0, react.createElement)("li", null, (0, react.createElement)("strong", null, "扫描到了,为什么还没切过去?"), (0, react.createElement)("p", null, "scan 只读发现;scan --register 只登记项目,不选择、不改 active、不绑会话。真正切换写作项目要用 /novel use,或第一次调用显式传 project_root。")))), (0, react.createElement)("section", { className: "nwe-tutorial-block" }, (0, react.createElement)("h3", null, "可以直接复制给模型的起手话术"), (0, react.createElement)("pre", { className: "nwe-prompt" }, TUTORIAL_PROMPT)), (0, react.createElement)("section", { className: "nwe-tutorial-block" }, (0, react.createElement)("h3", null, "续写下一章时,直接复制这段给 Agent"), (0, react.createElement)("p", null, "这段会先确认状态,再处理缺章卡和目标章数已满,最后按 P1→P8 推进;归档前会要求你确认 state_delta。"), (0, react.createElement)("pre", { className: "nwe-prompt" }, TUTORIAL_NEXT_PROMPT)), (0, react.createElement)("section", { className: "nwe-tutorial-block" }, (0, react.createElement)("h3", null, "一章完成检查清单"), (0, react.createElement)("ul", { className: "nwe-checklist" }, (0, react.createElement)("li", null, "本章 P1/P2 计划已提交,P3 正文已通过格式与字数门控。"), (0, react.createElement)("li", null, "P4 评审 total 对账无误,没有未修 blocker。"), (0, react.createElement)("li", null, "P6 五个语义项都有不同句子的正文引用,完整 scope 通过。"), (0, react.createElement)("li", null, "P8 任务书当前,novel_archive 带完整 state_delta 成功。"), (0, react.createElement)("li", null, "面板出现已归档章节,门控区没有新的阻塞。")))), (0, react.createElement)("div", { className: "nwe-modal-foot" }, "教程基于 0.2.6 · 具体字段以当前任务书和工具返回为准 · 面板只读")));
|
|
1461
1519
|
const NovelHelp = () => {
|
|
1462
1520
|
const [open, setOpen] = (0, react.useState)(false);
|
|
1463
1521
|
const triggerRef = (0, react.useRef)(null);
|
|
@@ -1525,6 +1583,80 @@ brief P6 → check → brief P8 → archive 的顺序推进。
|
|
|
1525
1583
|
minHeight: 170
|
|
1526
1584
|
}
|
|
1527
1585
|
}, (0, react.createElement)("div", { className: "nwe-state-icon" }, stateIcon()), (0, react.createElement)("div", { className: "nwe-state-title" }, "书架还是空的"), (0, react.createElement)("div", { className: "nwe-state-text" }, "完成第一次归档后,章节会出现在这里。")) : (0, react.createElement)("ul", { className: "nwe-chapters" }, ...view.rows.slice(-30).map(chapterCard)));
|
|
1586
|
+
const v6Kv = (entries) => (0, react.createElement)("ul", { className: "nwe-v6-list" }, ...entries.map(([key, value]) => (0, react.createElement)("li", { key }, (0, react.createElement)("b", null, key), (0, react.createElement)("span", null, value))));
|
|
1587
|
+
const v6Card = (key, title, children, open = false) => (0, react.createElement)("details", {
|
|
1588
|
+
className: "nwe-v6-card",
|
|
1589
|
+
key,
|
|
1590
|
+
...open ? { open: true } : {}
|
|
1591
|
+
}, (0, react.createElement)("summary", null, title), ...children);
|
|
1592
|
+
const bookDataSection = (view) => {
|
|
1593
|
+
const data = view.bookData;
|
|
1594
|
+
if (data === null) return null;
|
|
1595
|
+
const dims = data.quality.dimensions.map((dimension) => (0, react.createElement)("div", {
|
|
1596
|
+
className: "nwe-v6-bar",
|
|
1597
|
+
key: dimension.id
|
|
1598
|
+
}, (0, react.createElement)("span", null, dimension.id), (0, react.createElement)("span", null, (0, react.createElement)("i", { style: { width: `${Math.min(100, Math.max(0, dimension.average / 10 * 100))}%` } })), (0, react.createElement)("span", null, dimension.samples === 0 ? "—" : dimension.average.toFixed(1))));
|
|
1599
|
+
const worldEntries = (entries) => entries.map((entry) => (0, react.createElement)("li", { key: entry.name }, (0, react.createElement)("b", null, entry.name), (0, react.createElement)("span", null, entry.description), entry.anchors === void 0 || entry.anchors.length === 0 ? null : (0, react.createElement)("span", { className: "nwe-v6-note" }, `锚点:${entry.anchors.join(" / ")}`)));
|
|
1600
|
+
return (0, react.createElement)("section", {
|
|
1601
|
+
className: "nwe-section",
|
|
1602
|
+
"aria-label": "书籍数据"
|
|
1603
|
+
}, (0, react.createElement)("div", { className: "nwe-section-head" }, (0, react.createElement)("h2", { className: "nwe-section-title" }, "这本书"), (0, react.createElement)("span", { className: "nwe-section-note" }, "只读投影 · 不含正文")), (0, react.createElement)("div", { className: "nwe-v6-grid" }, v6Card("bp", "书脊与蓝图", [v6Kv([
|
|
1604
|
+
["作者", data.identity.author || "未填写"],
|
|
1605
|
+
["简介", data.identity.description || "未填写"],
|
|
1606
|
+
["创建", data.identity.created_at || "—"],
|
|
1607
|
+
["目标", `${data.identity.targets.words_per_chapter[0]}–${data.identity.targets.words_per_chapter[1]} 字 / 章 · ${data.identity.targets.total_chapters ?? "不限"} 章`],
|
|
1608
|
+
["主题", data.blueprint.themes.join(" / ") || "—"],
|
|
1609
|
+
["基调 / POV", `${data.blueprint.tone || "—"} / ${data.blueprint.pov || "—"}`],
|
|
1610
|
+
["读者 / 卖点", `${data.blueprint.audience || "—"} / ${data.blueprint.selling_points.join(" / ") || "—"}`],
|
|
1611
|
+
["结局", data.blueprint.ending || "—"]
|
|
1612
|
+
]), (0, react.createElement)("p", { className: "nwe-v6-note" }, data.blueprint.logline || data.blueprint.premise || "还没有蓝图摘要")], true), v6Card("progress", "进度与当前章", [v6Kv([
|
|
1613
|
+
["已归档", `${data.progress.archived} / ${data.progress.target ?? "不限"} 章`],
|
|
1614
|
+
["字数", `归档 ${data.progress.words_archived} · 含草稿 ${data.progress.words_with_draft}`],
|
|
1615
|
+
["剩余", data.progress.remaining_words === null ? "—" : `${data.progress.remaining_words} 字(${data.progress.remaining_words_basis})`],
|
|
1616
|
+
["当前章", `${data.current.chapter} · ${data.current.stage} · ${data.current.card?.title ?? "无章卡"}`],
|
|
1617
|
+
["轮次", `评 ${data.current.rounds.review} / 修 ${data.current.rounds.revise} / 检 ${data.current.rounds.check}`],
|
|
1618
|
+
["正文 hash", data.current.manuscript_hash ?? "—"],
|
|
1619
|
+
["当前字数", data.current.metrics === null ? "—" : String(data.current.metrics.total_word_count)]
|
|
1620
|
+
]), data.current.card === null ? null : (0, react.createElement)("div", { className: "nwe-v6-tags" }, ...data.current.card.cast.map((name) => (0, react.createElement)("span", { key: name }, name)))]), v6Card("cast", "人物与关系", [v6Kv([["角色总数", String(data.cast.total)], ["关系", `${data.relationships.total} 条 · ${Object.entries(data.relationships.types).map(([type, count]) => `${type} ${count}`).join(" / ") || "—"}`]]), (0, react.createElement)("ul", { className: "nwe-v6-list" }, ...data.cast.top.map((person) => (0, react.createElement)("li", { key: person.name }, (0, react.createElement)("b", null, person.name), (0, react.createElement)("span", null, `${person.role} · 出场 ${person.appearances} · 最近第 ${person.last_seen} 章`))))]), v6Card("world", "世界与设定", [
|
|
1621
|
+
v6Kv([
|
|
1622
|
+
["地点 / 势力", `${data.worldbook.counts.locations} / ${data.worldbook.counts.factions}`],
|
|
1623
|
+
["规则 / 术语", `${data.worldbook.counts.rules} / ${data.worldbook.counts.glossary}`],
|
|
1624
|
+
["前史 / 数值事实", `${data.worldbook.counts.backstory} / ${data.worldbook.counts.facts ?? 0}`]
|
|
1625
|
+
]),
|
|
1626
|
+
(0, react.createElement)("ul", { className: "nwe-v6-list" }, ...worldEntries(data.worldbook.locations)),
|
|
1627
|
+
(0, react.createElement)("ul", { className: "nwe-v6-list" }, ...worldEntries(data.worldbook.factions)),
|
|
1628
|
+
(0, react.createElement)("ul", { className: "nwe-v6-list" }, ...worldEntries(data.worldbook.rules)),
|
|
1629
|
+
...data.worldbook.facts.map((fact) => (0, react.createElement)("p", {
|
|
1630
|
+
className: "nwe-v6-note",
|
|
1631
|
+
key: fact.alias
|
|
1632
|
+
}, `${fact.alias} = ${fact.value} ${fact.unit}`))
|
|
1633
|
+
]), v6Card("foreshadow", "伏笔与时间线", [v6Kv([["伏笔", `未解 ${data.foreshadows.open} · 休眠 ${data.foreshadows.dormant} · 逾期 ${data.foreshadows.overdue} · 已解 ${data.foreshadows.resolved}`], ["时间线", `${data.timeline.events} 个事件 · 第 ${data.timeline.first_chapter ?? "—"}–${data.timeline.last_chapter ?? "—"} 章`]]), (0, react.createElement)("ul", { className: "nwe-v6-list" }, ...data.foreshadows.oldest.map((item) => (0, react.createElement)("li", { key: item.id }, (0, react.createElement)("b", null, item.id), (0, react.createElement)("span", null, `${item.title} · 已 ${item.age} 章`))))]), v6Card("quality", "质量与节奏", [
|
|
1634
|
+
v6Kv([
|
|
1635
|
+
["全书均分", `${data.quality.average.toFixed(1)} · 中位 ${data.quality.median.toFixed(1)} · ${data.quality.min}–${data.quality.max}`],
|
|
1636
|
+
["近十均分", data.quality.last_ten_average.toFixed(1)],
|
|
1637
|
+
["样本 / 评审", `${data.quality.samples} / ${data.quality.attempts}`],
|
|
1638
|
+
["趋势", data.quality.trend === null ? data.quality.trend_basis : `${data.quality.trend >= 0 ? "+" : ""}${data.quality.trend}`],
|
|
1639
|
+
["节奏", JSON.stringify(data.pacing.scene_types)],
|
|
1640
|
+
["钩子 / 推进", `${JSON.stringify(data.pacing.hook_strength)} / ${JSON.stringify(data.pacing.plot_advancement)}`],
|
|
1641
|
+
["最长 / 最短", data.pacing.longest === null ? "—" : `第 ${data.pacing.longest.chapter} 章 ${data.pacing.longest.words} 字 / 第 ${data.pacing.shortest?.chapter ?? "—"} 章 ${data.pacing.shortest?.words ?? "—"} 字`],
|
|
1642
|
+
["返工", `评 ${data.effort.reviews} · 修 ${data.effort.revisions} · 检 ${data.effort.checks} · 回滚 ${data.effort.rollbacks}`]
|
|
1643
|
+
]),
|
|
1644
|
+
(0, react.createElement)("div", {
|
|
1645
|
+
className: "nwe-v6-bars",
|
|
1646
|
+
"aria-label": "十五维质量"
|
|
1647
|
+
}, ...dims),
|
|
1648
|
+
(0, react.createElement)("details", null, (0, react.createElement)("summary", null, "逐章 effort"), (0, react.createElement)("ul", { className: "nwe-v6-list" }, ...data.effort.chapters.map((chapter) => (0, react.createElement)("li", { key: chapter.num }, (0, react.createElement)("b", null, `第 ${chapter.num} 章`), (0, react.createElement)("span", null, `评 ${chapter.reviews} / 修 ${chapter.revisions} / 检 ${chapter.checks} / 回滚 ${chapter.rollbacks} · ${chapter.final_check ?? "—"}`)))))
|
|
1649
|
+
]), v6Card("health", "健康与恢复", [
|
|
1650
|
+
v6Kv([
|
|
1651
|
+
["大纲", data.health.outline.present ? "可读" : data.health.outline.error === null ? "还没有大纲" : "损坏"],
|
|
1652
|
+
["世界观", data.health.worldbook.present ? "可读" : data.health.worldbook.error === null ? "还没有设定" : "损坏"],
|
|
1653
|
+
["pending", data.health.pending === null ? "无" : `${data.health.pending.state} · ${data.health.pending.detail}`]
|
|
1654
|
+
]),
|
|
1655
|
+
data.health.outline.error === null ? null : (0, react.createElement)("p", { className: "nwe-v6-note" }, data.health.outline.error),
|
|
1656
|
+
data.health.worldbook.error === null ? null : (0, react.createElement)("p", { className: "nwe-v6-note" }, data.health.worldbook.error),
|
|
1657
|
+
(0, react.createElement)("ul", { className: "nwe-v6-list" }, ...data.health.state_errors.map((error) => (0, react.createElement)("li", { key: `${error.path}:${error.code}` }, (0, react.createElement)("b", null, error.code), (0, react.createElement)("span", null, `${error.path}: ${error.message}`))))
|
|
1658
|
+
])));
|
|
1659
|
+
};
|
|
1528
1660
|
/**
|
|
1529
1661
|
* Pure view: given a view model, return the element tree. Keeping the hooks in
|
|
1530
1662
|
* `NovelPanel` makes every visual state renderable without a browser.
|
|
@@ -1553,7 +1685,7 @@ brief P6 → check → brief P8 → archive 的顺序推进。
|
|
|
1553
1685
|
className: "nwe-stats",
|
|
1554
1686
|
role: "group",
|
|
1555
1687
|
"aria-label": "项目统计"
|
|
1556
|
-
}, stat("已归档", String(view.stats.chapters), "章"), stat("总字数", view.stats.words.toLocaleString("zh-CN"), "字"), stat("全书均分", view.stats.chapters === 0 ? "—" : String(view.stats.averageScore), "/150"), stat("待处理", String(view.stats.blocking), "项")) : null, view.state === "loading" ? loadingState() : null, view.state === "choose" ? choosePanel(view, onSelectProject) : null, view.state === "missing" ? missingPanel(view) : null, view.state === "error" || view.state === "empty" || view.state === "outdated" ? statePanel(view.state, view, loading, onRefresh) : null, ready ? (0, react.createElement)("div", { className: "nwe-desk" }, (0, react.createElement)("div", { className: "nwe-desk-main" }, (0, react.createElement)("div", { className: "nwe-desk-heading" }, (0, react.createElement)("span", { className: "nwe-desk-kicker" }, "当前一章"), (0, react.createElement)("span", { className: "nwe-desk-note" }, view.stageLabel)), phases(view), blockingSection(view), advisoriesSection(view), legacySection(view), chaptersSection(view)), (0, react.createElement)("aside", {
|
|
1688
|
+
}, stat("已归档", String(view.stats.chapters), "章"), stat("总字数", view.stats.words.toLocaleString("zh-CN"), "字"), stat("全书均分", view.stats.chapters === 0 ? "—" : String(view.stats.averageScore), "/150"), stat("待处理", String(view.stats.blocking), "项")) : null, ready ? bookDataSection(view) : null, view.state === "loading" ? loadingState() : null, view.state === "choose" ? choosePanel(view, onSelectProject) : null, view.state === "missing" ? missingPanel(view) : null, view.state === "error" || view.state === "empty" || view.state === "outdated" ? statePanel(view.state, view, loading, onRefresh) : null, ready ? (0, react.createElement)("div", { className: "nwe-desk" }, (0, react.createElement)("div", { className: "nwe-desk-main" }, (0, react.createElement)("div", { className: "nwe-desk-heading" }, (0, react.createElement)("span", { className: "nwe-desk-kicker" }, "当前一章"), (0, react.createElement)("span", { className: "nwe-desk-note" }, view.stageLabel)), phases(view), blockingSection(view), advisoriesSection(view), pendingSection(view), legacySection(view), chaptersSection(view)), (0, react.createElement)("aside", {
|
|
1557
1689
|
className: "nwe-desk-side",
|
|
1558
1690
|
"aria-label": "全书与活动"
|
|
1559
1691
|
}, bookSection(view), activitySection(view))) : null, footer());
|