@waterwx/dsh-novel-forge 2.0.0-alpha.1 → 2.0.0
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/README.md +286 -285
- package/lib/client.js +19561 -19439
- package/lib/index.js +8159 -4456
- package/lib/types/action-guard.d.ts +61 -0
- package/lib/types/ai-scan.d.ts +0 -6
- package/lib/types/assets.d.ts +20 -9
- package/lib/types/assistant.d.ts +29 -0
- package/lib/types/author-assets.d.ts +1 -3
- package/lib/types/bookshelf.d.ts +13 -3
- package/lib/types/client/api.d.ts +136 -27
- package/lib/types/client/chip-prefs.d.ts +36 -0
- package/lib/types/client/docx.d.ts +2 -2
- package/lib/types/client/llmLive.d.ts +31 -0
- package/lib/types/client/locales.d.ts +15 -70
- package/lib/types/client/mount.d.ts +0 -2
- package/lib/types/client/panel/AiTag.d.ts +1 -0
- package/lib/types/client/panel/AssistantTab.d.ts +4 -1
- package/lib/types/client/panel/AuthorHome.d.ts +13 -11
- package/lib/types/client/panel/BookAnalysisView.d.ts +4 -0
- package/lib/types/client/panel/CmdPalette.d.ts +12 -0
- package/lib/types/client/panel/CreateBookView.d.ts +5 -1
- package/lib/types/client/panel/DirectorView.d.ts +7 -0
- package/lib/types/client/panel/IdeaInspirationView.d.ts +6 -0
- package/lib/types/client/panel/KnowledgeBaseView.d.ts +4 -0
- package/lib/types/client/panel/LiveFeedLog.d.ts +13 -0
- package/lib/types/client/panel/MarketRadarView.d.ts +5 -0
- package/lib/types/client/panel/ModelManager.d.ts +2 -2
- package/lib/types/client/panel/NovelPanel.d.ts +6 -1
- package/lib/types/client/panel/PageHeader.d.ts +18 -0
- package/lib/types/client/panel/PromptSlotsView.d.ts +11 -0
- package/lib/types/client/panel/SettingsView.d.ts +12 -9
- package/lib/types/client/panel/ShelfView.d.ts +10 -1
- package/lib/types/client/panel/SubPage.d.ts +27 -0
- package/lib/types/client/panel/TensionView.d.ts +18 -0
- package/lib/types/client/panel/TimelineView.d.ts +18 -0
- package/lib/types/client/panel/helpers.d.ts +5 -3
- package/lib/types/client/panel/market-radar-state.d.ts +56 -0
- package/lib/types/client/panel/toast.d.ts +2 -0
- package/lib/types/client/panel/views.d.ts +21 -35
- package/lib/types/client/run-status.d.ts +21 -0
- package/lib/types/client/sidebar-entry.d.ts +0 -2
- package/lib/types/docx-core.d.ts +16 -0
- package/lib/types/docx.d.ts +2 -3
- package/lib/types/engine.d.ts +78 -103
- package/lib/types/global-assets.d.ts +6 -0
- package/lib/types/home.d.ts +11 -0
- package/lib/types/index.d.ts +26 -12
- package/lib/types/llm-live.d.ts +70 -0
- package/lib/types/llm-retry.d.ts +38 -0
- package/lib/types/market-radar-scan.d.ts +18 -0
- package/lib/types/market-radar-sources.d.ts +31 -0
- package/lib/types/migrate-output.d.ts +25 -0
- package/lib/types/model-capability.d.ts +93 -0
- package/lib/types/output-budget.d.ts +86 -0
- package/lib/types/prompt-slots.d.ts +57 -0
- package/lib/types/prompting.d.ts +10 -0
- package/lib/types/protocol.d.ts +816 -601
- package/lib/types/revision.d.ts +76 -0
- package/lib/types/routes.d.ts +9 -3
- package/lib/types/run.d.ts +23 -1
- package/lib/types/settings-persist.d.ts +59 -0
- package/lib/types/snapshots.d.ts +46 -0
- package/lib/types/stage-contract.d.ts +58 -0
- package/lib/types/tension.d.ts +53 -0
- package/lib/types/timeline.d.ts +37 -0
- package/package.json +50 -25
- package/src/action-guard.ts +127 -0
- package/src/ai-scan.ts +0 -24
- package/src/assets.ts +1801 -879
- package/src/assistant.ts +1098 -770
- package/src/author-assets.ts +131 -147
- package/src/bookshelf.ts +193 -167
- package/src/client/api.ts +767 -617
- package/src/client/chip-prefs.ts +121 -0
- package/src/client/docx.ts +32 -69
- package/src/client/index.ts +2 -0
- package/src/client/llmLive.ts +111 -0
- package/src/client/locales.ts +15 -274
- package/src/client/mount.tsx +3 -2
- package/src/client/panel/AdaptModeView.tsx +535 -520
- package/src/client/panel/AiTag.tsx +7 -0
- package/src/client/panel/AssetsTab.tsx +448 -458
- package/src/client/panel/AssistantTab.tsx +355 -301
- package/src/client/panel/AuthorAssetsView.tsx +205 -207
- package/src/client/panel/AuthorHome.tsx +309 -204
- package/src/client/panel/BookAnalysisView.tsx +63 -0
- package/src/client/panel/CmdPalette.tsx +102 -0
- package/src/client/panel/CreateBookView.tsx +366 -352
- package/src/client/panel/DirectorView.tsx +200 -0
- package/src/client/panel/GlobalAssetLibraryView.tsx +168 -172
- package/src/client/panel/IdeaInspirationView.tsx +76 -0
- package/src/client/panel/ImportModal.tsx +302 -302
- package/src/client/panel/KnowledgeBaseView.tsx +125 -0
- package/src/client/panel/LiveFeedLog.tsx +204 -0
- package/src/client/panel/MarketRadarView.tsx +415 -0
- package/src/client/panel/ModelManager.tsx +205 -205
- package/src/client/panel/NovelPanel.tsx +3144 -2205
- package/src/client/panel/PageHeader.tsx +37 -0
- package/src/client/panel/ProgressConsole.tsx +63 -64
- package/src/client/panel/PromptSlotsView.tsx +97 -0
- package/src/client/panel/ReaderView.tsx +306 -306
- package/src/client/panel/RunPanel.tsx +56 -24
- package/src/client/panel/SettingsView.tsx +175 -106
- package/src/client/panel/ShelfView.tsx +63 -39
- package/src/client/panel/SubPage.tsx +42 -0
- package/src/client/panel/TensionView.tsx +212 -0
- package/src/client/panel/TimelineView.tsx +208 -0
- package/src/client/panel/WorldTab.tsx +202 -202
- package/src/client/panel/helpers.ts +7 -5
- package/src/client/panel/market-radar-state.ts +79 -0
- package/src/client/panel/panel.module.css +5767 -4360
- package/src/client/panel/toast.ts +51 -0
- package/src/client/panel/views.tsx +316 -314
- package/src/client/run-status.ts +171 -0
- package/src/client/sidebar-entry.ts +3 -3
- package/src/docx-core.ts +53 -0
- package/src/docx.ts +44 -83
- package/src/engine.ts +1197 -1638
- package/src/global-assets.ts +58 -0
- package/src/home.ts +33 -0
- package/src/index.ts +181 -101
- package/src/llm-live.ts +245 -0
- package/src/llm-retry.ts +77 -0
- package/src/market-radar-scan.ts +35 -0
- package/src/market-radar-sources.ts +173 -0
- package/src/migrate-output.ts +76 -0
- package/src/model-capability.ts +215 -0
- package/src/novel-context.ts +286 -287
- package/src/output-budget.ts +147 -0
- package/src/prompt-slots.ts +125 -0
- package/src/prompting.ts +26 -0
- package/src/protocol.ts +835 -614
- package/src/revision.ts +283 -0
- package/src/routes.ts +1286 -910
- package/src/run.ts +113 -17
- package/src/settings-persist.ts +114 -0
- package/src/snapshots.ts +174 -0
- package/src/stage-contract.ts +186 -0
- package/src/tension.ts +251 -0
- package/src/timeline.ts +159 -0
- package/assets/styles/arcane-thick-3d.webp +0 -0
- package/assets/styles/chinese-ink.webp +0 -0
- package/assets/styles/clay-stopmotion-3d.webp +0 -0
- package/assets/styles/dark-ghibli.webp +0 -0
- package/assets/styles/dark-gothic-3d.webp +0 -0
- package/assets/styles/film-photo.webp +0 -0
- package/assets/styles/guofeng-heavy.webp +0 -0
- package/assets/styles/guoman-2d-gufeng.webp +0 -0
- package/assets/styles/guoman-3d-tianyuan.webp +0 -0
- package/assets/styles/guoman-3d-xuanhuan.webp +0 -0
- package/assets/styles/horror-game-3d.webp +0 -0
- package/assets/styles/hyperreal-3d.webp +0 -0
- package/assets/styles/japan-cel-dark.webp +0 -0
- package/assets/styles/korean-webtoon.webp +0 -0
- package/assets/styles/low-cyberpunk-3d.webp +0 -0
- package/assets/styles/nextgen-cg.webp +0 -0
- package/assets/styles/noir-film.webp +0 -0
- package/assets/styles/openworld-survival.webp +0 -0
- package/assets/styles/paper-cut-3d.webp +0 -0
- package/assets/styles/pixar-adult-3d.webp +0 -0
- package/assets/styles/pop-marvel-2d.webp +0 -0
- package/assets/styles/sandiao-qban.webp +0 -0
- package/assets/styles/shinkai-anime.webp +0 -0
- package/assets/styles/tactical-stealth.webp +0 -0
- package/assets/styles/urban-lightreal-3d.webp +0 -0
- package/assets/styles/vaporwave-3d.webp +0 -0
- package/assets/styles/vhs90.webp +0 -0
- package/lib/types/client/panel/BookshelfBar.d.ts +0 -11
- package/lib/types/client/panel/FlowGuide.d.ts +0 -22
- package/lib/types/client/panel/MangaRoleLibrary.d.ts +0 -20
- package/lib/types/client/panel/MangaWorkspace.d.ts +0 -12
- package/lib/types/client/panel/ModelPicker.d.ts +0 -11
- package/lib/types/client/panel/ProgressHomeView.d.ts +0 -5
- package/lib/types/client/panel/PropLibrary.d.ts +0 -8
- package/lib/types/client/panel/SceneLibrary.d.ts +0 -16
- package/lib/types/client/panel/StoryboardTab.d.ts +0 -19
- package/lib/types/client/panel/StyleCard.d.ts +0 -8
- package/lib/types/client/panel/StyleGallery.d.ts +0 -1
- package/lib/types/comic-presets.d.ts +0 -12
- package/lib/types/manga-genre-rules.d.ts +0 -22
- package/lib/types/prompt-utils.d.ts +0 -35
- package/lib/types/review-policy.d.ts +0 -97
- package/lib/types/shot-language.d.ts +0 -46
- package/lib/types/story-beat-language.d.ts +0 -35
- package/lib/types/style-library.d.ts +0 -33
- package/skills/chapter-batch/SKILL.md +0 -81
- package/src/client/mount.tsx._new +0 -97
- package/src/client/mount.tsx._nf +0 -97
- package/src/client/mount.tsx._tmp +0 -97
- package/src/client/panel/BookshelfBar.tsx +0 -170
- package/src/client/panel/FlowGuide.tsx +0 -84
- package/src/client/panel/MangaRoleLibrary.tsx +0 -1002
- package/src/client/panel/MangaWorkspace.tsx +0 -715
- package/src/client/panel/ProgressHomeView.tsx +0 -87
- package/src/client/panel/PropLibrary.tsx +0 -175
- package/src/client/panel/SceneLibrary.tsx +0 -453
- package/src/client/panel/StoryboardTab.tsx +0 -472
- package/src/client/panel/StyleCard.tsx +0 -90
- package/src/client/panel/StyleGallery.tsx +0 -58
- package/src/comic-presets.ts +0 -67
- package/src/manga-genre-rules.ts +0 -87
- package/src/prompt-utils.ts +0 -73
- package/src/review-policy.ts +0 -161
- package/src/shot-language.ts +0 -181
- package/src/story-beat-language.ts +0 -130
- package/src/style-library.ts +0 -84
package/src/revision.ts
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 修订指令合并层(arbitration)。
|
|
3
|
+
*
|
|
4
|
+
* 问题:审稿、时间线、张力三条通道各自给建议,如果各自改一轮,会出现「改完 A 又违反 B」
|
|
5
|
+
* 的互相打脸:同一章可能被改三遍,最后一轮的改动把第一轮的修正覆盖掉。
|
|
6
|
+
*
|
|
7
|
+
* 规则(与面板/生产单共享同一份实现,避免两头各写一套):
|
|
8
|
+
* 1. 同一章只交付**一份**修订指令(本模块负责汇总,调用方不得自行拼装)。
|
|
9
|
+
* 2. 固定优先级,不做权重打分(可解释、可复现):
|
|
10
|
+
* 0 审稿 high(必改,是硬门)
|
|
11
|
+
* 1 时间线 high(硬矛盾:时间倒流、地点瞬移)
|
|
12
|
+
* 2 张力 medium(曲线偏差,建议性最强)
|
|
13
|
+
* 3 其余(审稿 medium / 时间线 medium)
|
|
14
|
+
* 4 低优先(审稿 low)
|
|
15
|
+
* 3. 一次修订最多吃 REVISION_ITEM_CAP 条,超出按优先级截断;截断数量显式回传,不静默丢弃。
|
|
16
|
+
* 4. 每章修订轮次上限 MAX_REVISION_ROUNDS,超出转人工待办。
|
|
17
|
+
* 5. 验证基准 = 本轮实际下发的全部条目(审稿 + 时间线 + 张力),
|
|
18
|
+
* 这样「复核」核对的就是「我们要求改的东西」,而不是只核对审稿那部分。
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type {
|
|
22
|
+
ProjectState,
|
|
23
|
+
ReviewIssue,
|
|
24
|
+
ReviewReport,
|
|
25
|
+
RevisionItem,
|
|
26
|
+
RevisionSource,
|
|
27
|
+
Severity,
|
|
28
|
+
TimelineIssue,
|
|
29
|
+
TensionIssue,
|
|
30
|
+
} from './protocol.js'
|
|
31
|
+
import { detectTimelineIssues } from './timeline.js'
|
|
32
|
+
import { detectTensionIssues } from './tension.js'
|
|
33
|
+
|
|
34
|
+
/** 每章修订轮次上限:两轮仍不过即转人工,不做「无限自动修改」。 */
|
|
35
|
+
export const MAX_REVISION_ROUNDS = 2
|
|
36
|
+
/** 单次修订指令最多携带的条目数(防止指令过长把正文挤出上下文)。 */
|
|
37
|
+
export const REVISION_ITEM_CAP = 8
|
|
38
|
+
|
|
39
|
+
export const REVISION_SOURCE_LABELS: Record<RevisionSource, string> = {
|
|
40
|
+
review: '审稿',
|
|
41
|
+
timeline: '时间线',
|
|
42
|
+
tension: '张力',
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** 固定优先级:数值越小越先改。 */
|
|
46
|
+
export function revisionPriority(source: RevisionSource, severity: Severity): number {
|
|
47
|
+
if (source === 'review') return severity === 'high' ? 0 : severity === 'medium' ? 3 : 4
|
|
48
|
+
if (source === 'timeline') return severity === 'high' ? 1 : 3
|
|
49
|
+
// 张力只产出 medium(曲线偏差是建议,不是违规)。
|
|
50
|
+
return severity === 'high' ? 2 : 2
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** 同优先级的来源次序:审稿 > 时间线 > 张力(同级时先改让本章"判不过"的那一类)。 */
|
|
54
|
+
const SOURCE_RANK: Record<RevisionSource, number> = { review: 0, timeline: 1, tension: 2 }
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 条目在**来源内部**的原始序号(id 末段)。
|
|
58
|
+
* 必须按数值比较:字符串比较会得到 0,1,10,11,2… 的顺序,
|
|
59
|
+
* 于是截断时先丢「第 3 条意见」却留下「第 11 条」——等于把靠前的意见丢了。
|
|
60
|
+
*/
|
|
61
|
+
function sequenceOf(item: RevisionItem): number {
|
|
62
|
+
const tail = item.id.slice(item.id.lastIndexOf(':') + 1)
|
|
63
|
+
const n = Number.parseInt(tail, 10)
|
|
64
|
+
return Number.isFinite(n) ? n : 0
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** 去重键:同源 + 归一化问题描述(忽略空白与标点差异)。 */
|
|
68
|
+
function dedupeKey(item: RevisionItem): string {
|
|
69
|
+
return `${item.source}|${item.item.replace(/[\s,。、;:,.!?!?"'"'()()【】\[\]]/g, '').slice(0, 60)}`
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** 排序 + 去重 + 截断。去重时保留优先级更高的那条,并累加 count。 */
|
|
73
|
+
export function mergeRevisionItems(
|
|
74
|
+
items: RevisionItem[],
|
|
75
|
+
cap = REVISION_ITEM_CAP,
|
|
76
|
+
): { items: RevisionItem[]; omitted: number } {
|
|
77
|
+
const sorted = [...items].sort((a, b) => {
|
|
78
|
+
const byPriority = revisionPriority(a.source, a.severity) - revisionPriority(b.source, b.severity)
|
|
79
|
+
if (byPriority !== 0) return byPriority
|
|
80
|
+
if (a.chapterNo !== b.chapterNo) return a.chapterNo - b.chapterNo
|
|
81
|
+
if (a.source !== b.source) return SOURCE_RANK[a.source] - SOURCE_RANK[b.source]
|
|
82
|
+
const bySequence = sequenceOf(a) - sequenceOf(b)
|
|
83
|
+
if (bySequence !== 0) return bySequence
|
|
84
|
+
return a.id.localeCompare(b.id)
|
|
85
|
+
})
|
|
86
|
+
const seen = new Map<string, RevisionItem>()
|
|
87
|
+
const unique: RevisionItem[] = []
|
|
88
|
+
for (const item of sorted) {
|
|
89
|
+
const key = dedupeKey(item)
|
|
90
|
+
const dup = seen.get(key)
|
|
91
|
+
if (dup !== undefined) {
|
|
92
|
+
dup.count = (dup.count ?? 1) + 1
|
|
93
|
+
continue
|
|
94
|
+
}
|
|
95
|
+
seen.set(key, item)
|
|
96
|
+
unique.push(item)
|
|
97
|
+
}
|
|
98
|
+
const kept = unique.slice(0, cap)
|
|
99
|
+
return { items: kept, omitted: Math.max(0, unique.length - kept.length) }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** 时间线/张力问题是否落在目标章上(chapters 为空视为全书级,跟随目标章)。 */
|
|
103
|
+
function matchesChapter(chapters: number[] | undefined, target: number | undefined): boolean {
|
|
104
|
+
if (target === undefined) return true
|
|
105
|
+
const list = chapters ?? []
|
|
106
|
+
if (list.length === 0) return true
|
|
107
|
+
return list.includes(target)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function pickChapterNo(chapters: number[] | undefined, target: number | undefined, fallback: number): number {
|
|
111
|
+
if (target !== undefined && matchesChapter(chapters, target)) return target
|
|
112
|
+
return chapters?.[0] ?? fallback
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function timelineToItems(issues: TimelineIssue[], target: number | undefined): RevisionItem[] {
|
|
116
|
+
const out: RevisionItem[] = []
|
|
117
|
+
issues.forEach((issue, i) => {
|
|
118
|
+
if (!matchesChapter(issue.chapters, target)) return
|
|
119
|
+
out.push({
|
|
120
|
+
id: `timeline:${i}:${pickChapterNo(issue.chapters, target, 0)}`,
|
|
121
|
+
source: 'timeline',
|
|
122
|
+
severity: issue.severity,
|
|
123
|
+
chapterNo: pickChapterNo(issue.chapters, target, 0),
|
|
124
|
+
item: issue.item,
|
|
125
|
+
suggestion: issue.suggestion,
|
|
126
|
+
})
|
|
127
|
+
})
|
|
128
|
+
return out
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function tensionToItems(issues: TensionIssue[], target: number | undefined): RevisionItem[] {
|
|
132
|
+
const out: RevisionItem[] = []
|
|
133
|
+
issues.forEach((issue, i) => {
|
|
134
|
+
if (!matchesChapter(issue.chapters, target)) return
|
|
135
|
+
out.push({
|
|
136
|
+
id: `tension:${i}:${pickChapterNo(issue.chapters, target, 0)}`,
|
|
137
|
+
source: 'tension',
|
|
138
|
+
severity: issue.severity,
|
|
139
|
+
chapterNo: pickChapterNo(issue.chapters, target, 0),
|
|
140
|
+
item: issue.item,
|
|
141
|
+
suggestion: issue.suggestion,
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
return out
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface RevisionCollectOptions {
|
|
148
|
+
/** 目标章;缺省表示全书(用于整本批量修订)。 */
|
|
149
|
+
chapterNo?: number
|
|
150
|
+
/** 显式指定的审稿意见;缺省取该章已落盘的审稿报告。 */
|
|
151
|
+
reviewIssues?: ReviewIssue[]
|
|
152
|
+
/** 是否纳入时间线矛盾(默认纳入)。 */
|
|
153
|
+
includeTimeline?: boolean
|
|
154
|
+
/** 是否纳入张力偏差(默认纳入)。 */
|
|
155
|
+
includeTension?: boolean
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** 汇总一章(或全书)的三类建议。纯函数,不改项目状态。 */
|
|
159
|
+
export function collectRevisionItems(project: ProjectState, options: RevisionCollectOptions = {}): RevisionItem[] {
|
|
160
|
+
const target = options.chapterNo
|
|
161
|
+
const items: RevisionItem[] = []
|
|
162
|
+
const chapters = (project.chapters ?? []).filter(c => target === undefined || c.no === target)
|
|
163
|
+
|
|
164
|
+
const explicit = options.reviewIssues
|
|
165
|
+
if (explicit !== undefined) {
|
|
166
|
+
explicit.forEach((issue, i) => {
|
|
167
|
+
items.push({
|
|
168
|
+
id: `review:${target ?? 0}:${i}`,
|
|
169
|
+
source: 'review',
|
|
170
|
+
severity: issue.severity,
|
|
171
|
+
chapterNo: target ?? 0,
|
|
172
|
+
item: issue.item,
|
|
173
|
+
suggestion: issue.suggestion,
|
|
174
|
+
})
|
|
175
|
+
})
|
|
176
|
+
} else {
|
|
177
|
+
for (const chapter of chapters) {
|
|
178
|
+
for (const [i, issue] of (chapter.review?.issues ?? []).entries()) {
|
|
179
|
+
items.push({
|
|
180
|
+
id: `review:${chapter.no}:${i}`,
|
|
181
|
+
source: 'review',
|
|
182
|
+
severity: issue.severity,
|
|
183
|
+
chapterNo: chapter.no,
|
|
184
|
+
item: issue.item,
|
|
185
|
+
suggestion: issue.suggestion,
|
|
186
|
+
})
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (options.includeTimeline ?? true) items.push(...timelineToItems(detectTimelineIssues(project), target))
|
|
192
|
+
if (options.includeTension ?? true) items.push(...tensionToItems(detectTensionIssues(project), target))
|
|
193
|
+
return items
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export interface RevisionPlan {
|
|
197
|
+
/** 已排序、去重、截断后的条目。 */
|
|
198
|
+
items: RevisionItem[]
|
|
199
|
+
/** 下发给模型的修订指令(唯一入口,调用方不要自行拼装)。 */
|
|
200
|
+
instruction: string
|
|
201
|
+
counts: Record<RevisionSource, number>
|
|
202
|
+
/** 因超出上限被截断的条数(显式回传,便于提示作者)。 */
|
|
203
|
+
omitted: number
|
|
204
|
+
/** 验证基准:把本轮下发的全部条目当作「上一轮意见」,复核时逐条核对。 */
|
|
205
|
+
baseline: ReviewReport
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export interface BuildRevisionPlanOptions extends RevisionCollectOptions {
|
|
209
|
+
/** 审稿基线报告(提供后 baseline 会保留其分数与时间)。 */
|
|
210
|
+
baseReport?: ReviewReport
|
|
211
|
+
/** 指令抬头(默认「按合并建议修订」)。 */
|
|
212
|
+
title?: string
|
|
213
|
+
cap?: number
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** 渲染修订指令。格式与历史「按审稿意见修订」保持同族,模型无需重新学习。 */
|
|
217
|
+
export function renderRevisionInstruction(items: RevisionItem[], title = '按合并建议修订'): string {
|
|
218
|
+
const counts: Record<RevisionSource, number> = { review: 0, timeline: 0, tension: 0 }
|
|
219
|
+
for (const item of items) counts[item.source] += item.count ?? 1
|
|
220
|
+
const header = `(审稿 ${counts.review} · 时间线 ${counts.timeline} · 张力 ${counts.tension})`
|
|
221
|
+
const lines = items.map(item => {
|
|
222
|
+
const tag = `【${REVISION_SOURCE_LABELS[item.source]}·${item.severity}】`
|
|
223
|
+
const where = item.chapterNo > 0 ? `第${item.chapterNo}章 ` : ''
|
|
224
|
+
const repeat = (item.count ?? 1) > 1 ? `(合并同类 ${item.count} 条)` : ''
|
|
225
|
+
const suggestion = item.suggestion.trim() === '' ? '' : `\n建议:${item.suggestion.trim()}`
|
|
226
|
+
return `${tag}${where}${item.item}${repeat}${suggestion}`
|
|
227
|
+
})
|
|
228
|
+
return `${title}${header}:\n${lines.join('\n')}`
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* 构造验证基准:把时间线/张力条目转成「上一轮意见」的编号项,
|
|
233
|
+
* 与审稿意见合并成一份报告。复核模型据此逐条核对,避免
|
|
234
|
+
* 「审稿说已解决、时间线说没改」这类互相打架的判定。
|
|
235
|
+
*/
|
|
236
|
+
export function buildRevisionBaseline(items: RevisionItem[], base?: ReviewReport): ReviewReport {
|
|
237
|
+
const issues: ReviewIssue[] = items.map(item => ({
|
|
238
|
+
severity: item.severity,
|
|
239
|
+
item: item.source === 'review' ? item.item : `[${REVISION_SOURCE_LABELS[item.source]}] ${item.item}`,
|
|
240
|
+
suggestion: item.suggestion,
|
|
241
|
+
}))
|
|
242
|
+
return {
|
|
243
|
+
score: base?.score ?? 0,
|
|
244
|
+
passed: false,
|
|
245
|
+
verdict: base?.verdict ?? '本轮修订基准',
|
|
246
|
+
issues,
|
|
247
|
+
riskScore: base?.riskScore,
|
|
248
|
+
aiFlavor: base?.aiFlavor,
|
|
249
|
+
aiPhrases: base?.aiPhrases,
|
|
250
|
+
reviewedAt: base?.reviewedAt ?? new Date().toISOString(),
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** 汇总 → 排序 → 指令 → 基准。面板与生产单都走这里。 */
|
|
255
|
+
export function buildRevisionPlan(project: ProjectState, options: BuildRevisionPlanOptions = {}): RevisionPlan {
|
|
256
|
+
const collected = collectRevisionItems(project, options)
|
|
257
|
+
const merged = mergeRevisionItems(collected, options.cap ?? REVISION_ITEM_CAP)
|
|
258
|
+
const counts: Record<RevisionSource, number> = { review: 0, timeline: 0, tension: 0 }
|
|
259
|
+
for (const item of merged.items) counts[item.source] += item.count ?? 1
|
|
260
|
+
return {
|
|
261
|
+
items: merged.items,
|
|
262
|
+
instruction: renderRevisionInstruction(merged.items, options.title),
|
|
263
|
+
counts,
|
|
264
|
+
omitted: merged.omitted,
|
|
265
|
+
baseline: buildRevisionBaseline(merged.items, options.baseReport),
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** 一行摘要,用于日志与面板提示("审稿 3 · 时间线 1 · 张力 1,另截断 2 条")。 */
|
|
270
|
+
export function describeRevisionPlan(plan: RevisionPlan): string {
|
|
271
|
+
const parts: string[] = []
|
|
272
|
+
if (plan.counts.review > 0) parts.push(`审稿 ${plan.counts.review}`)
|
|
273
|
+
if (plan.counts.timeline > 0) parts.push(`时间线 ${plan.counts.timeline}`)
|
|
274
|
+
if (plan.counts.tension > 0) parts.push(`张力 ${plan.counts.tension}`)
|
|
275
|
+
const head = parts.length === 0 ? '无可修订条目' : parts.join(' · ')
|
|
276
|
+
return plan.omitted > 0 ? `${head}(另截断 ${plan.omitted} 条)` : head
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** 待办文本(生产单只提示不阻塞时写入项目待办,供作者人工决定)。 */
|
|
280
|
+
export function revisionTodoText(item: RevisionItem): string {
|
|
281
|
+
const where = item.chapterNo > 0 ? `第${item.chapterNo}章` : '全书'
|
|
282
|
+
return `[${REVISION_SOURCE_LABELS[item.source]}] ${where}:${item.item}`
|
|
283
|
+
}
|