dsh-novel-write-engine 0.2.5 → 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 +22 -0
- package/README.md +21 -27
- package/briefs/P3.md +2 -0
- package/briefs/P6.md +1 -1
- package/briefs/P8.md +1 -1
- package/lib/client.js +134 -17
- package/lib/index.d.ts +176 -2
- package/lib/index.js +872 -252
- package/lib/types/client/dto.d.ts +13 -0
- package/lib/types/client/viewmodel.d.ts +24 -1
- package/lib/types/constants.d.ts +1 -1
- package/lib/types/panel-contract.d.ts +125 -0
- package/lib/types/types.d.ts +15 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.6
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **语义证据配对**:`novel_check` 失败项新增 `conflicts`(`duplicate`/`same_sentence`/`interval_overlap`,带 `other_id`、重叠原文/字数/比例、段落、句子),submission 错误的 message 汇总最多五条配对;aborted journal 写紧凑 blocking,失败后仍可审计。句边界统一由 `domain/text.ts` 的 `splitSentenceSpans` 提供,支持 `…` 与英文句点。
|
|
8
|
+
- **world 实体锚点**:`summary.new_element_anchors` 支持 `name + anchors`;PG-05 与新世界实体归档共用 `entity-anchors.ts`,新 ledger 持久化 `anchors`,旧数据无 anchors 继续按 name 回退。
|
|
9
|
+
- **panel v6**:`summary.bookData` 增加身份/蓝图、当前章卡与 metrics、世界/人物/关系/伏笔/时间线、15 维质量、节奏、逐章 effort、outline drift、state_errors/pending/damaged 区分;`quality` 明确区分全书 `average` 与 `last_ten_average`。
|
|
10
|
+
- **pipeline 恢复**:`novel_restore { paths: ["state/pipeline.yaml"], txid }` 可从有效备份恢复损坏 pipeline;status/doctor 报 `PIPELINE_UNREADABLE`,普通写操作在恢复前 fail-closed。
|
|
11
|
+
- **回归**:summary-only 的 PG-01 旁路只对紧接的 P6 check 生效;outline restore 复用归档卷守卫;PG-05 对 null world row 不再崩溃;PG-09 重复句匹配所有段落。
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- `resources:null` 不再被当成空对象;summary-only 不再静默吞掉 `issues`/`proof`;rulepack 递归 unknown-key 覆盖 `reader_intrusion`/`god_view`、`genres.*`、`water_words.scene_thresholds`。
|
|
16
|
+
- 面板 `pending` 不再丢失,活跃章详情改读 `pipeline.draft.summary`,`revisionLabel` 用本章 revise 轮次,samples/attempts 不再倒挂,章节行补齐 dialogue ratio 与 archived_at,教程版本升至 0.2.6。
|
|
17
|
+
- 测试与 docs 移除真实项目正文,新增覆盖 docs 的隐私扫描;工具数固定 28 并断言全集;R7 识别 `skipIf`;panel v6 从真实 engine 快照集成验证。
|
|
18
|
+
|
|
19
|
+
### Migration
|
|
20
|
+
|
|
21
|
+
- check/world/summary 新字段均为 additive;旧文档可读,不自动迁移 anchors。
|
|
22
|
+
- 面板 v6 与 v5 不混用;旧标签页显示刷新。
|
|
23
|
+
- 工具 28、操作 32、语义项 5、门控 13、导出 6 均不变。
|
|
24
|
+
|
|
3
25
|
## 0.2.5
|
|
4
26
|
|
|
5
27
|
### Fixed
|
package/README.md
CHANGED
|
@@ -4,17 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
它不是一个提示词合集,也不是独立的写作软件。它把「这一章处于什么阶段、下一步只能做什么、正文是否达标、归档要核销哪些实体」变成可执行、可回滚、可审计的引擎;模型负责写,引擎负责裁决和记账。
|
|
6
6
|
|
|
7
|
-
0.2.
|
|
7
|
+
0.2.6 把“证据、台账、书籍数据”从能跑推进到可解释:语义证据改为配对冲突并统一句边界;world 新实体支持 `name + anchors`,不再逼正文写台账名;网文工坊 v6 增加书脊/当前章/人物关系/世界/伏笔/质量节奏/逐章 effort/健康恢复;同时补上 pipeline 备份恢复、summary-only 一次性 PG-01、outline restore 卷守卫与 PG-05 null 行保护。详见 [0.2.6 方案](docs/0.2.6-plan.md)。
|
|
8
8
|
|
|
9
|
-
0.2.
|
|
10
|
-
|
|
11
|
-
0.2.3 把“账本说真话”补到旧项目:`report` 从 journal 回填旧章 effort,quality 每章只算最终评审并区分 `samples`/`attempts`;briefs 按当前章隔离;归档失败可直接修订;advisory 按章去重并带证据;面板 v5 改为书桌式工作台,项目选择器分离写作/待修复/失效;`check:host` 不再污染真实注册表。
|
|
12
|
-
|
|
13
|
-
0.2.4 根治 `novel_outline` 的“省略即删除”:`chapters` 按 `num`、`volumes` 按 `title` 做 upsert,省略的卡保持不动;删除必须显式 `remove` / `remove_volumes`;新增 `dry_run`、`added`/`updated`/`removed` 回显、`outline.deprecated.yaml` 与 `novel_restore` 的 outline 恢复路径。这是有意的 BREAKING 语义修正:从“提交集合就是全集”变成“提交的是要改的对象”。
|
|
14
|
-
|
|
15
|
-
0.2.5 根治“证据/账本/恢复”三条真实链路:语义引文支持跨句/跨段并逐条解释;check 的 submission 错误不再消耗轮次;world delta 以 ledger 存在性仲裁并给 warning;pending 只读诊断与 init 崩溃恢复;归档卷/active 卡在 blueprint/import/amend/outline 全入口一致;新增 summary-only 修订(不改正文、不重跑 P4/P6);resources 深合并非负整数。P1–P3 及 S1–S8 硬化见 [0.2.5 验收](docs/0.2.5-acceptance.md)。
|
|
16
|
-
|
|
17
|
-
> **当前版本:0.2.5** · 中文界面 · MIT
|
|
9
|
+
> **当前版本:0.2.6** · 中文界面 · MIT
|
|
18
10
|
>
|
|
19
11
|
> **宿主范围**:DSH `>=0.2.1-rc.2 <0.3.0-0`;Node `>=24.9.0`。
|
|
20
12
|
|
|
@@ -101,7 +93,7 @@
|
|
|
101
93
|
| 规则包外置 | 词表、格式、标点、语义、跨章阈值都在 `rulepacks/default.yaml`,代码只执行 |
|
|
102
94
|
| 六种导出 | Markdown、纯文本、EPUB 3、大纲、世界观、书级提案;正文导出附 `cover.svg` 与 `blurb.txt` |
|
|
103
95
|
| 整书规划层 | 书级蓝图、卷结构、章卡(严格模式)、世界观设定集;支持 Markdown/YAML/JSON 导入 |
|
|
104
|
-
| 可观测性 | `novel_status` 书级摘要、`novel_report`、`novel_doctor`、`novel_search`、panel
|
|
96
|
+
| 可观测性 | `novel_status` 书级摘要、`novel_report`、`novel_doctor`、`novel_search`、panel v6、journal 活动 |
|
|
105
97
|
| 多项目注册表 | `stateDir/projects.yaml` 记录已知项目与面板默认 active;不影响项目目录 |
|
|
106
98
|
| 运行期依赖极少 | 只有 `yaml` 一个运行时依赖;Host 侧不访问外部网络、不调用外部 API、不读环境变量里的密钥 |
|
|
107
99
|
|
|
@@ -284,7 +276,7 @@ dsh plugin --profile web add dsh-novel-write-engine
|
|
|
284
276
|
固定版本:
|
|
285
277
|
|
|
286
278
|
```bash
|
|
287
|
-
dsh plugin --profile web add dsh-novel-write-engine@0.2.
|
|
279
|
+
dsh plugin --profile web add dsh-novel-write-engine@0.2.6
|
|
288
280
|
```
|
|
289
281
|
|
|
290
282
|
`dsh` 不在 PATH 时,用 `npx @deepseek-ai/dsh` 代替命令开头的 `dsh`。
|
|
@@ -339,13 +331,13 @@ corepack pnpm pack
|
|
|
339
331
|
会得到类似:
|
|
340
332
|
|
|
341
333
|
```text
|
|
342
|
-
dsh-novel-write-engine-0.2.
|
|
334
|
+
dsh-novel-write-engine-0.2.6.tgz
|
|
343
335
|
```
|
|
344
336
|
|
|
345
337
|
把这个 tarball 复制到目标机器,放在一个**稳定、不会随手删除**的路径,然后:
|
|
346
338
|
|
|
347
339
|
```bash
|
|
348
|
-
dsh plugin --profile web add /absolute/path/to/dsh-novel-write-engine-0.2.
|
|
340
|
+
dsh plugin --profile web add /absolute/path/to/dsh-novel-write-engine-0.2.6.tgz
|
|
349
341
|
```
|
|
350
342
|
|
|
351
343
|
tarball 安装不会运行源码构建,也不会要求 `allowBuilds`;包内已经包含 `lib/`、`rulepacks/`、`briefs/`、`skills/`、`presets/` 和两层 patch。它是最接近「发布产物」的安装方式。
|
|
@@ -360,9 +352,9 @@ tarball 安装不会运行源码构建,也不会要求 `allowBuilds`;包内
|
|
|
360
352
|
2. 点 **添加插件**(或旁边的箭头 → **安装第三方插件**)。
|
|
361
353
|
3. 在包名字段填入以下任一形式:
|
|
362
354
|
- npm 包名:`dsh-novel-write-engine`
|
|
363
|
-
- 精确版本:`dsh-novel-write-engine@0.2.
|
|
355
|
+
- 精确版本:`dsh-novel-write-engine@0.2.6`
|
|
364
356
|
- 源码目录:`/absolute/path/to/dsh-novel-write-engine`
|
|
365
|
-
- tarball:`/absolute/path/to/dsh-novel-write-engine-0.2.
|
|
357
|
+
- tarball:`/absolute/path/to/dsh-novel-write-engine-0.2.6.tgz`
|
|
366
358
|
4. npm 包名从 npm 官方源安装;本地目录和 tarball 直接使用宿主路径。
|
|
367
359
|
5. 点 **安装**,等待完成。
|
|
368
360
|
6. 如果出现 **立即启用**,点它;否则安装后到「已安装插件」里把 **dsh-novel-write-engine** 打开。
|
|
@@ -434,7 +426,7 @@ dsh --profile web --dump-config | grep -A8 '# == dsh-novel-write-engine'
|
|
|
434
426
|
先做全书蓝图、第一卷大纲和世界观设定,再写第一章的世界观约束和章节计划。
|
|
435
427
|
```
|
|
436
428
|
|
|
437
|
-
模型会调用 `novel_init` 建项目,然后按 `novel_guide` 的提示做书级规划;也可以跳过蓝图直接写第一章,严格大纲是可选开关。0.2.
|
|
429
|
+
模型会调用 `novel_init` 建项目,然后按 `novel_guide` 的提示做书级规划;也可以跳过蓝图直接写第一章,严格大纲是可选开关。0.2.6 推荐顺序:
|
|
438
430
|
|
|
439
431
|
```text
|
|
440
432
|
novel_init
|
|
@@ -947,7 +939,7 @@ corepack pnpm test
|
|
|
947
939
|
|
|
948
940
|
0.2.3 不迁移项目数据,只改变读模型、面板 wire 版本与项目卫生:
|
|
949
941
|
|
|
950
|
-
1. **面板 payload
|
|
942
|
+
1. **面板 payload v6**:旧标签页显示刷新;刷新后是书桌式布局、分组项目选择器和独立的提示/旧数据区。
|
|
951
943
|
2. **report 口径**:旧章 effort 从 journal 回填,quality 每章只算最终评审;`samples` 是章数,`attempts` 是 committed 评审轮次(含历史),样本不足时 `trend` 为 `null`。
|
|
952
944
|
3. **briefs 按章**:`pipeline.briefs` 只显示当前章;`novel_state {kind:"briefs"}` 仍保留全书收据。
|
|
953
945
|
4. **归档失败**:`ready_to_archive` 可以直接 `novel_revise` 回 P4,不需要先 rollback。
|
|
@@ -1024,7 +1016,7 @@ dsh plugin --profile web add dsh-novel-write-engine@latest
|
|
|
1024
1016
|
也可以固定版本:
|
|
1025
1017
|
|
|
1026
1018
|
```bash
|
|
1027
|
-
dsh plugin --profile web add dsh-novel-write-engine@0.2.
|
|
1019
|
+
dsh plugin --profile web add dsh-novel-write-engine@0.2.6
|
|
1028
1020
|
```
|
|
1029
1021
|
|
|
1030
1022
|
升级后核对:
|
|
@@ -1066,7 +1058,7 @@ corepack pnpm pack
|
|
|
1066
1058
|
|
|
1067
1059
|
```bash
|
|
1068
1060
|
dsh plugin --profile web remove dsh-novel-write-engine
|
|
1069
|
-
dsh plugin --profile web add /absolute/path/to/dsh-novel-write-engine-0.2.
|
|
1061
|
+
dsh plugin --profile web add /absolute/path/to/dsh-novel-write-engine-0.2.6.tgz
|
|
1070
1062
|
```
|
|
1071
1063
|
|
|
1072
1064
|
### 11.5 在 Web / Desktop 里升级
|
|
@@ -1334,13 +1326,13 @@ tests/ # 单测、集成、falsify、e2e
|
|
|
1334
1326
|
|
|
1335
1327
|
### 16.5 当前验证基线
|
|
1336
1328
|
|
|
1337
|
-
0.2.
|
|
1329
|
+
0.2.6 在 Node 24.19 / DSH 0.2.1-rc.3 上完成:
|
|
1338
1330
|
|
|
1339
|
-
- `npm test` 全绿:**
|
|
1340
|
-
- 覆盖率:**语句 93.51% / 分支 89.
|
|
1341
|
-
- `typecheck`、`lint`、`build` 通过;`check:hygiene`(
|
|
1342
|
-
- M1–M7、S1–S8 与 P2/P3 硬化见 [0.2.5 验收](docs/0.2.5-acceptance.md);
|
|
1343
|
-
-
|
|
1331
|
+
- `npm test` 全绿:**90 个测试文件 / 906 项**;
|
|
1332
|
+
- 覆盖率:**语句 93.51% / 分支 89.14% / 函数 98.12% / 行 93.51%**;`test:coverage` 与逐文件 domain/book/insight/viewmodel 门禁全过;
|
|
1333
|
+
- `typecheck`、`lint`、`build` 通过;`check:hygiene`(155 files,0 v8 ignore)、`test:isolated`(62 源文件)、`validate:rulepack`、`check:pack`(26 files)、`check:artifacts`、`check:install`(26 packages)、`check:preset`(13 packages)、`check:host`、`check:crash` 全绿;
|
|
1334
|
+
- M1–M7、S1–S8 与 P2/P3 硬化见 [0.2.5 验收](docs/0.2.5-acceptance.md);0.2.6 的 E1–E6 见 [0.2.6 验收](docs/0.2.6-acceptance.md);
|
|
1335
|
+
- 真实模型:0.2.6 临时项目中 `agnes-2.5-flash` 实时完成 P1/P2/P3,公开引擎 P4/P6/P8(确定性夹具)归档成功,`stage=done`、`score=145`;`agnes-3.0-flash` 连续 HTTP 429,未产出第二份实时结果。0.2.5 的两篇真实正文也在 0.2.6 引擎离线重放通过;爸爸已确认接受 3.0 的离线重放作为唯一 3.0 证据,详见 [0.2.6 验收](docs/0.2.6-acceptance.md) 4.1 节。
|
|
1344
1336
|
|
|
1345
1337
|
0.2.4 在 Node 24.19 / DSH 0.2.1-rc.3 上完成:
|
|
1346
1338
|
|
|
@@ -1372,7 +1364,9 @@ tests/ # 单测、集成、falsify、e2e
|
|
|
1372
1364
|
| [PLAN.md](https://github.com/ThzxxArt/dsh-novel-write-engine/blob/main/PLAN.md) | 0.1.0 实施计划(历史) |
|
|
1373
1365
|
| [docs/0.2.0-plan.md](https://github.com/ThzxxArt/dsh-novel-write-engine/blob/main/docs/0.2.0-plan.md) | 0.2.0 整书生命周期:蓝图/大纲/世界观/章卡/观测 |
|
|
1374
1366
|
| [docs/0.2.4-plan.md](https://github.com/ThzxxArt/dsh-novel-write-engine/blob/main/docs/0.2.4-plan.md) | 0.2.4 outline upsert、显式删除、dry_run 与恢复路径的根治方案 |
|
|
1375
|
-
| [docs/0.2.
|
|
1367
|
+
| [docs/0.2.6-plan.md](https://github.com/ThzxxArt/dsh-novel-write-engine/blob/main/docs/0.2.6-plan.md) | 0.2.6 语义配对、实体锚点、面板 v6、恢复与证据方案 |
|
|
1368
|
+
| [docs/0.2.6-acceptance.md](https://github.com/ThzxxArt/dsh-novel-write-engine/blob/main/docs/0.2.6-acceptance.md) | 0.2.6 验收矩阵、门禁与真实项目探针 |
|
|
1369
|
+
| [docs/0.2.7-backlog.md](https://github.com/ThzxxArt/dsh-novel-write-engine/blob/main/docs/0.2.7-backlog.md) | 0.2.6 审计保留到 0.2.7 的 worldbook replace 等项 |
|
|
1376
1370
|
| [docs/0.2.5-acceptance.md](https://github.com/ThzxxArt/dsh-novel-write-engine/blob/main/docs/0.2.5-acceptance.md) | 0.2.5 验收矩阵、真实项目回归与门禁证据 |
|
|
1377
1371
|
| [docs/0.2.5-real-model-evidence.md](https://github.com/ThzxxArt/dsh-novel-write-engine/blob/main/docs/0.2.5-real-model-evidence.md) | 0.2.5 agnes 2.5/3.0 正文经引擎 P3→P8/归档回执 |
|
|
1378
1372
|
| [docs/0.2.4-acceptance.md](https://github.com/ThzxxArt/dsh-novel-write-engine/blob/main/docs/0.2.4-acceptance.md) | 0.2.4 验收矩阵、质量门与边界 |
|
package/briefs/P3.md
CHANGED
|
@@ -35,6 +35,7 @@ P3 brief 的 `context.world_plan` 是 P1 的本章约束;`context.characters`
|
|
|
35
35
|
"resolved": ["FS-002"]
|
|
36
36
|
},
|
|
37
37
|
"new_elements": { "locations": ["旧办公室"], "abilities": [], "artifacts": [] },
|
|
38
|
+
"new_element_anchors": { "locations": { "旧办公室": ["三楼尾房"] } },
|
|
38
39
|
"resource_changes": [
|
|
39
40
|
{ "character": "他", "resource": "铜钱", "delta": -200, "reason": "买信封" }
|
|
40
41
|
],
|
|
@@ -48,6 +49,7 @@ P3 brief 的 `context.world_plan` 是 P1 的本章约束;`context.characters`
|
|
|
48
49
|
|
|
49
50
|
- `foreshadows.planted` **不是字符串数组**,每项是 `{key,title,description}`。
|
|
50
51
|
- `new_elements` 是**对象**,不是数组;三个键都必须是字符串数组(没有就 `[]`)。
|
|
52
|
+
- `new_element_anchors` 可选:只用于新地点/能力/物品;键必须同时出现在对应 `new_elements` 数组;每个实体 1–8 个正文锚点,每个 2–120 字。有锚点时任一锚点逐字命中即可,不再要求正文出现可读登记名。
|
|
51
53
|
- `resource_changes` 每项是 `{character,resource,delta,reason}`,`delta` 是非零整数;没有变化就 `[]`。
|
|
52
54
|
- `opening` 必须逐字出现在正文前两段,不能写剧情概括。
|
|
53
55
|
- `continuity` 是 `{opens_from,ends_with}` 对象。
|
package/briefs/P6.md
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
]
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
五个 id 必须各出现一次;`verdict` 只能是 `pass`/`fail`;`evidence` 至少 8
|
|
25
|
+
五个 id 必须各出现一次;`verdict` 只能是 `pass`/`fail`;`evidence` 至少 8 字、必须逐字出现在正文里,可以跨句/跨段。五条引用必须互不相同、不得复用同一句;同一句不同子串、完全重复、真正包含或达到 `max(8, 较短引文一半)` 的区间重叠会被按 `same_sentence`/`duplicate`/`interval_overlap` 拒绝,并回传配对 `conflicts`。不同段落只是共享常用词不算重叠。引文不存在/太短/形状不合法属于 **submission error**(`ENGINE_CHECK_SUBMISSION_INVALID`):不写 check、不消耗轮次,修正 `semantic` 后仍调用 `novel_check`。
|
|
26
26
|
|
|
27
27
|
| id | 检查 |
|
|
28
28
|
|---|---|
|
package/briefs/P8.md
CHANGED
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
`novel_archive` **不接受 summary**;它校验的是 P3/P5 已经固定的 draft summary。delta 必须与这份 summary 对齐:
|
|
89
89
|
|
|
90
90
|
- `characters` 里每个 `action:"created"` 的人物名,必须出现在 draft `summary.characters_appeared`。
|
|
91
|
-
- `world` 以 `state/world.yaml` 的存在性为唯一真源:已有实体写 `created` 会按 `updated` 归一化并给 warning;新实体写 `updated` 且正文出现、kind 可确定时按 `created` 归一化;kind 冲突仍拒绝。`summary.new_elements`
|
|
91
|
+
- `world` 以 `state/world.yaml` 的存在性为唯一真源:已有实体写 `created` 会按 `updated` 归一化并给 warning;新实体写 `updated` 且正文出现、kind 可确定时按 `created` 归一化;kind 冲突仍拒绝。`summary.new_elements` 只是意图声明;新实体可用 `summary.new_element_anchors` 提供正文锚点(1–8 个、每个 2–120 字),有锚点时任一命中即可,无锚点才要求 name 逐字出现。已有实体不再强求 `created`。
|
|
92
92
|
- `timeline` 的键必须是 draft `summary.events` 的下标(字符串)。
|
|
93
93
|
- `relationships` 的两端必须已在 `characters` 里声明(本章 created 或台账已有),否则先声明人物。
|
|
94
94
|
- `timeline.<key>.characters` 里的每个名字必须已在 `characters` 台账里声明。
|
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,13 +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: [],
|
|
169
176
|
pending: null,
|
|
177
|
+
bookData: null,
|
|
170
178
|
nextTitle: "",
|
|
171
179
|
nextDetail: "",
|
|
172
180
|
nextTool: "",
|
|
@@ -308,7 +316,16 @@ window.__ModuleLoader__.load({
|
|
|
308
316
|
badge: row.status === "passed" ? "已归档" : row.status === "blocked" ? "阻塞" : "草稿",
|
|
309
317
|
tone: row.status === "passed" ? "ok" : row.status === "blocked" ? "bad" : "muted",
|
|
310
318
|
details: row.details ?? "",
|
|
311
|
-
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
|
|
312
329
|
};
|
|
313
330
|
});
|
|
314
331
|
const scored = rows.filter((row) => row.tone === "ok");
|
|
@@ -329,7 +346,7 @@ window.__ModuleLoader__.load({
|
|
|
329
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)}`,
|
|
330
347
|
stageLabel: doneLabel,
|
|
331
348
|
finished,
|
|
332
|
-
revisionLabel: `第 ${summary.revision} 次修订`,
|
|
349
|
+
revisionLabel: `第 ${summary.bookData?.current.rounds.revise ?? summary.revision} 次修订`,
|
|
333
350
|
phases: summary.phases.map((phase) => ({
|
|
334
351
|
...phase,
|
|
335
352
|
label: phaseLabel(phase.id)
|
|
@@ -347,6 +364,7 @@ window.__ModuleLoader__.load({
|
|
|
347
364
|
present: summary.book?.outline.present === true,
|
|
348
365
|
strict: summary.book?.strict === true,
|
|
349
366
|
premise: summary.book?.premise ?? "",
|
|
367
|
+
logline: summary.book?.logline ?? "",
|
|
350
368
|
target: summary.book?.target ?? null,
|
|
351
369
|
planned: summary.book?.outline.planned ?? 0,
|
|
352
370
|
outlineTotal: summary.book?.outline.total ?? 0,
|
|
@@ -373,17 +391,23 @@ window.__ModuleLoader__.load({
|
|
|
373
391
|
}
|
|
374
392
|
},
|
|
375
393
|
quality: {
|
|
376
|
-
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 ?? [],
|
|
377
400
|
scores: summary.quality?.scores ?? [],
|
|
378
|
-
trend: summary.quality?.trend ?? null,
|
|
379
|
-
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}`,
|
|
380
|
-
samples: summary.quality
|
|
381
|
-
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)
|
|
382
405
|
},
|
|
383
406
|
health: {
|
|
384
407
|
ok: summary.health?.ok ?? blocking.length === 0,
|
|
385
408
|
blocking: summary.health?.blocking ?? blocking.length,
|
|
386
|
-
stateErrors: summary.health?.stateErrors ?? 0
|
|
409
|
+
stateErrors: summary.health?.stateErrors ?? 0,
|
|
410
|
+
pending: summary.health?.pending === true
|
|
387
411
|
},
|
|
388
412
|
activity: summary.activity ?? [],
|
|
389
413
|
advisories: (summary.advisories ?? []).map((item) => ({
|
|
@@ -393,7 +417,8 @@ window.__ModuleLoader__.load({
|
|
|
393
417
|
})),
|
|
394
418
|
acknowledgedAdvisories: summary.acknowledgedAdvisories ?? 0,
|
|
395
419
|
legacy: summary.legacy ?? [],
|
|
396
|
-
pending: summary.pending ?? null,
|
|
420
|
+
pending: summary.bookData?.health.pending ?? summary.pending ?? null,
|
|
421
|
+
bookData: summary.bookData ?? null,
|
|
397
422
|
nextTitle: finished ? "" : next === void 0 ? "状态已同步" : toolLabel(next.tool),
|
|
398
423
|
nextDetail: finished ? "" : next === void 0 ? "没有待执行的下一步,刷新查看最新状态。" : NEXT_DETAILS[next.tool] ?? next.reason,
|
|
399
424
|
nextTool: next?.tool ?? "",
|
|
@@ -1012,6 +1037,24 @@ window.__ModuleLoader__.load({
|
|
|
1012
1037
|
.nwe-pending-error { border-left-color: #b3261e; }
|
|
1013
1038
|
.nwe-pending-detail { margin: 8px 0 0; color: var(--nwe-ink); font-size: 11px; line-height: 1.6; }
|
|
1014
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; } }
|
|
1015
1058
|
.nwe-legacy {
|
|
1016
1059
|
display: flex;
|
|
1017
1060
|
gap: 8px;
|
|
@@ -1198,7 +1241,7 @@ brief P6 → check → brief P8 → archive 的顺序推进。
|
|
|
1198
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", {
|
|
1199
1242
|
className: "nwe-chevron",
|
|
1200
1243
|
"aria-hidden": "true"
|
|
1201
|
-
}, "›")), 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 ?? "—"}`)));
|
|
1202
1245
|
const phases = (view) => (0, react.createElement)("ol", {
|
|
1203
1246
|
className: "nwe-phases",
|
|
1204
1247
|
"aria-label": "创作阶段"
|
|
@@ -1228,7 +1271,7 @@ brief P6 → check → brief P8 → archive 的顺序推进。
|
|
|
1228
1271
|
return (0, react.createElement)("section", {
|
|
1229
1272
|
className: "nwe-section",
|
|
1230
1273
|
"aria-label": "全书规划与质量"
|
|
1231
|
-
}, (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", {
|
|
1232
1275
|
className: "nwe-progress",
|
|
1233
1276
|
role: "progressbar",
|
|
1234
1277
|
"aria-valuemin": 0,
|
|
@@ -1247,7 +1290,7 @@ brief P6 → check → brief P8 → archive 的顺序推进。
|
|
|
1247
1290
|
}, `${label} ${value}`))), (0, react.createElement)("div", {
|
|
1248
1291
|
className: "nwe-book-head",
|
|
1249
1292
|
style: { marginTop: 12 }
|
|
1250
|
-
}, (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", {
|
|
1251
1294
|
className: "nwe-bars",
|
|
1252
1295
|
role: "img",
|
|
1253
1296
|
"aria-label": "最近十章评分"
|
|
@@ -1256,7 +1299,7 @@ brief P6 → check → brief P8 → archive 的顺序推进。
|
|
|
1256
1299
|
className: "nwe-bar",
|
|
1257
1300
|
style: { height: `${Math.max(4, Math.round(score / 150 * 100))}%` },
|
|
1258
1301
|
title: `${score}/150`
|
|
1259
|
-
}))), (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 事务" : ""}`)))));
|
|
1260
1303
|
};
|
|
1261
1304
|
const activitySection = (view) => {
|
|
1262
1305
|
if (view.activity.length === 0) return null;
|
|
@@ -1472,7 +1515,7 @@ brief P6 → check → brief P8 → archive 的顺序推进。
|
|
|
1472
1515
|
ref: closeRef,
|
|
1473
1516
|
onClick: onClose,
|
|
1474
1517
|
"aria-label": "关闭使用教程"
|
|
1475
|
-
}, (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 · 具体字段以当前任务书和工具返回为准 · 面板只读")));
|
|
1476
1519
|
const NovelHelp = () => {
|
|
1477
1520
|
const [open, setOpen] = (0, react.useState)(false);
|
|
1478
1521
|
const triggerRef = (0, react.useRef)(null);
|
|
@@ -1540,6 +1583,80 @@ brief P6 → check → brief P8 → archive 的顺序推进。
|
|
|
1540
1583
|
minHeight: 170
|
|
1541
1584
|
}
|
|
1542
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
|
+
};
|
|
1543
1660
|
/**
|
|
1544
1661
|
* Pure view: given a view model, return the element tree. Keeping the hooks in
|
|
1545
1662
|
* `NovelPanel` makes every visual state renderable without a browser.
|
|
@@ -1568,7 +1685,7 @@ brief P6 → check → brief P8 → archive 的顺序推进。
|
|
|
1568
1685
|
className: "nwe-stats",
|
|
1569
1686
|
role: "group",
|
|
1570
1687
|
"aria-label": "项目统计"
|
|
1571
|
-
}, 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), pendingSection(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", {
|
|
1572
1689
|
className: "nwe-desk-side",
|
|
1573
1690
|
"aria-label": "全书与活动"
|
|
1574
1691
|
}, bookSection(view), activitySection(view))) : null, footer());
|