@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
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 结构化输出预算与字段长度合同。
|
|
3
|
+
*
|
|
4
|
+
* 背景:本插件此前给每个 LLM 调用写死一个 `Math.max(config.maxTokens, N)`。短任务
|
|
5
|
+
* 与长任务拿到同一个额度,长任务(分卷、拆章、逐章规划)**额度不随规模增长**,
|
|
6
|
+
* 截断后只能抛「请增大 maxTokens」。这里把额度改成按规模推算,并给结构化字段
|
|
7
|
+
* 定长度上限,让预算花在结构上而不是被自由发挥吃光。
|
|
8
|
+
*
|
|
9
|
+
* 设计取舍:结构化输出的额度应当**随本次要产出的条目数增长**,并由硬上限兜住,
|
|
10
|
+
* 否则要么短任务浪费额度,要么长任务写到一半被截断。字段长度上限同时写进提示词
|
|
11
|
+
* 与解析端,两处共用一张表,避免漂移。
|
|
12
|
+
*
|
|
13
|
+
* 一条硬纪律:**不要用 JSON repair 去补被截断的结构**。repair 只能修已有结构,
|
|
14
|
+
* 补不出尚未输出的条目;在服务层手工补齐缺失项,等于把不完整的规划伪装成可用
|
|
15
|
+
* 资产。预算给够 + 输出够紧凑,才是正解。
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** 一次调用的输出规模描述:给出能决定输出体量的那个量。 */
|
|
19
|
+
export interface OutputScope {
|
|
20
|
+
/** 预期卷数(分卷/卷战略类)。 */
|
|
21
|
+
volumes?: number
|
|
22
|
+
/** 预期章节数(章节列表/批量拆章类)。 */
|
|
23
|
+
chapters?: number
|
|
24
|
+
/** 预期角色数(角色阵容/资产提炼类)。 */
|
|
25
|
+
characters?: number
|
|
26
|
+
/** 预期条目数(伏笔、题材、资产等清单类)。 */
|
|
27
|
+
items?: number
|
|
28
|
+
/** 需要覆盖的正文规模(字),用于拆书/反推这类按输入体量走的任务。 */
|
|
29
|
+
sourceChars?: number
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** 一类结构化任务的预算规格。 */
|
|
33
|
+
export interface OutputBudgetSpec {
|
|
34
|
+
/** 无视规模也要给到的下限。 */
|
|
35
|
+
floor: number
|
|
36
|
+
/** 规模为 0 时的起步额度。 */
|
|
37
|
+
base: number
|
|
38
|
+
/** 每单位规模追加的额度。 */
|
|
39
|
+
perUnit: number
|
|
40
|
+
/** 硬上限:超过它只会让模型啰嗦,不会让结构更完整。 */
|
|
41
|
+
ceiling: number
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** 结构化任务分组——按"输出里每个单位要写多少字段"分档,而不是按业务名。 */
|
|
45
|
+
export type OutputBudgetKind =
|
|
46
|
+
| 'volume-strategy'
|
|
47
|
+
| 'chapter-list'
|
|
48
|
+
| 'character-cast'
|
|
49
|
+
| 'item-list'
|
|
50
|
+
| 'analysis'
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 数值口径:以**本插件真实产出字段的体积**折算,不是照搬参考项目的数字
|
|
54
|
+
* (它的卷战略字段与本插件的章节字段结构不同,直接套用会造成新的截断)。
|
|
55
|
+
*
|
|
56
|
+
* 折算假设:中文约 1.5-2 token/字;账本式 JSON 的键名与括号再占约 15%。
|
|
57
|
+
* 例如单章条目 = 标题(≤40) + 摘要(≤300) + 本章目标(≤200) + beats + 最多 4 条
|
|
58
|
+
* payoff 指令 → 约 250-400 字 → **约 350 token/章**。若字段合同调整,这里必须同步。
|
|
59
|
+
*/
|
|
60
|
+
const SPECS: Record<OutputBudgetKind, OutputBudgetSpec> = {
|
|
61
|
+
// 每卷:roleLabel(≤32) + coreReward(≤64) + escalationFocus(≤64) + uncertainty,
|
|
62
|
+
// 再加书级阶梯字段(readerRewardLadder/escalationLadder/midpointShift/notes)。
|
|
63
|
+
'volume-strategy': { floor: 1_800, base: 1_200, perUnit: 220, ceiling: 6_000 },
|
|
64
|
+
// 每章 ≈350 token(见上方折算说明),另留写作余量;上限对齐单次调用能力。
|
|
65
|
+
'chapter-list': { floor: 2_500, base: 2_000, perUnit: 350, ceiling: 20_000 },
|
|
66
|
+
// 每角色:名字 + roleLabel + 定位 + 关系 + 弧线 ≈150 token。
|
|
67
|
+
'character-cast': { floor: 1_500, base: 1_000, perUnit: 150, ceiling: 8_000 },
|
|
68
|
+
// 清单类,每条几个短字段。
|
|
69
|
+
'item-list': { floor: 1_000, base: 800, perUnit: 60, ceiling: 4_000 },
|
|
70
|
+
// 拆书/反推:按输入正文体量走,每千字约 120 token。
|
|
71
|
+
analysis: { floor: 3_000, base: 2_000, perUnit: 12, ceiling: 16_000 },
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 取出与任务类型相关的那一个规模量。
|
|
76
|
+
* @param kind - 任务类型。
|
|
77
|
+
* @param scope - 规模描述。
|
|
78
|
+
* @returns 单位数;分析类返回千字单位(向上取整)。
|
|
79
|
+
*/
|
|
80
|
+
export function scopeUnits(kind: OutputBudgetKind, scope: OutputScope): number {
|
|
81
|
+
const positive = (value: number | undefined): number =>
|
|
82
|
+
typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : 0
|
|
83
|
+
switch (kind) {
|
|
84
|
+
case 'volume-strategy':
|
|
85
|
+
return positive(scope.volumes)
|
|
86
|
+
case 'chapter-list':
|
|
87
|
+
return positive(scope.chapters)
|
|
88
|
+
case 'character-cast':
|
|
89
|
+
return positive(scope.characters)
|
|
90
|
+
case 'item-list':
|
|
91
|
+
return positive(scope.items)
|
|
92
|
+
case 'analysis':
|
|
93
|
+
// 输入正文每 1000 字算一个单位。
|
|
94
|
+
return Math.ceil(positive(scope.sourceChars) / 1_000)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* 按规模推算输出额度。
|
|
100
|
+
* @param kind - 结构化任务类型。
|
|
101
|
+
* @param scope - 本次任务的规模(缺省时按最小规模处理)。
|
|
102
|
+
* @returns 建议的 `maxTokens`,落在该类型的 [floor, ceiling] 内。
|
|
103
|
+
*/
|
|
104
|
+
export function resolveOutputBudget(kind: OutputBudgetKind, scope: OutputScope = {}): number {
|
|
105
|
+
const spec = SPECS[kind]
|
|
106
|
+
const raw = spec.base + scopeUnits(kind, scope) * spec.perUnit
|
|
107
|
+
return Math.min(spec.ceiling, Math.max(spec.floor, Math.round(raw)))
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** 读取某类任务的规格(供诊断与测试断言)。 */
|
|
111
|
+
export function outputBudgetSpec(kind: OutputBudgetKind): OutputBudgetSpec {
|
|
112
|
+
return { ...SPECS[kind] }
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* 结构化字段长度合同 —— 与 prompt 里的长度声明必须逐项一致。
|
|
117
|
+
*
|
|
118
|
+
* 两边同步的理由:只写进 schema,模型不知道就会超长被拒;只写进 prompt,模型
|
|
119
|
+
* 偶尔超长没人拦。本表是单一数据源。
|
|
120
|
+
*/
|
|
121
|
+
export const STRUCTURED_FIELD_LIMITS = {
|
|
122
|
+
/** 分卷战略:书级阶梯类字段。 */
|
|
123
|
+
volumeLadder: 160,
|
|
124
|
+
/** 分卷战略:每卷定位标签。 */
|
|
125
|
+
volumeRoleLabel: 32,
|
|
126
|
+
/** 分卷战略:每卷核心回报 / 升级焦点。 */
|
|
127
|
+
volumeReward: 64,
|
|
128
|
+
/** 分卷战略:不确定项原因。 */
|
|
129
|
+
volumeUncertaintyReason: 64,
|
|
130
|
+
/** 章节计划:单章标题。 */
|
|
131
|
+
chapterTitle: 40,
|
|
132
|
+
/** 章节计划:单章摘要。 */
|
|
133
|
+
chapterSummary: 300,
|
|
134
|
+
/** 章节计划:本章目标。 */
|
|
135
|
+
chapterGoal: 200,
|
|
136
|
+
} as const
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* 把长度合同渲染成可直接塞进 system prompt 的一句话,避免 prompt 与表漂移。
|
|
140
|
+
* @param entries - 要声明的字段(描述 + 上限),按声明顺序渲染。
|
|
141
|
+
* @returns 形如「为保证输出完整:A 不超过 160 字;B 不超过 64 字。」的中文说明。
|
|
142
|
+
*/
|
|
143
|
+
export function renderLengthContract(entries: ReadonlyArray<readonly [string, number]>): string {
|
|
144
|
+
if (entries.length === 0) return ''
|
|
145
|
+
const parts = entries.map(([label, limit]) => `${label}不超过 ${limit} 字`)
|
|
146
|
+
return `为保证输出完整:${parts.join(';')}。每个字段只保留完成本阶段决策所需的信息,写完后立即结束输出,不要展开细纲或重复说明。`
|
|
147
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 提示词槽位:给作者留几个**安全可改**的位置,用来微调本书的写作偏好。
|
|
3
|
+
*
|
|
4
|
+
* 设计边界(很重要):
|
|
5
|
+
* * 槽位只追加"作者偏好",**不允许覆盖**规则工程与合规红线——道藏、写作红线、
|
|
6
|
+
* 九条内容合规红线、反 AI 规则、阶段契约都由宿主锁定,槽位碰不到;
|
|
7
|
+
* * 槽位内容随项目保存(`novel-project.json`),换书即换槽位;
|
|
8
|
+
* * 每个槽位有长度上限,超长直接截断并标记(沿用「截断显式化」的规则)。
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { ProjectState, PromptSlotId } from './protocol.ts'
|
|
12
|
+
|
|
13
|
+
/** 单个槽位的元信息。 */
|
|
14
|
+
export interface PromptSlotSpec {
|
|
15
|
+
id: PromptSlotId
|
|
16
|
+
/** 面板显示名。 */
|
|
17
|
+
label: string
|
|
18
|
+
/** 一句话说明它影响什么。 */
|
|
19
|
+
hint: string
|
|
20
|
+
/** 示例(面板占位符)。 */
|
|
21
|
+
placeholder: string
|
|
22
|
+
/** 字符上限。 */
|
|
23
|
+
maxChars: number
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** 允许作者编辑的槽位(白名单;不在此列表的提示词一律不可改)。 */
|
|
27
|
+
export const PROMPT_SLOTS: readonly PromptSlotSpec[] = [
|
|
28
|
+
{
|
|
29
|
+
id: 'styleExtra',
|
|
30
|
+
label: '文风补充',
|
|
31
|
+
hint: '追加到写作提示词末尾的风格偏好(句式、叙述习惯、用词倾向)。',
|
|
32
|
+
placeholder: '例如:多用短句和对话推进,少用成语;环境描写只写能影响动作的部分。',
|
|
33
|
+
maxChars: 1200,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: 'antiAiExtra',
|
|
37
|
+
label: '反 AI 补充',
|
|
38
|
+
hint: '本书额外的"别这么写"清单,与内置反 AI 规则叠加(不能覆盖内置规则)。',
|
|
39
|
+
placeholder: '例如:不要每段都用比喻;不要用"空气中弥漫着"这类句式。',
|
|
40
|
+
maxChars: 1200,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: 'dialogueExtra',
|
|
44
|
+
label: '对话补充',
|
|
45
|
+
hint: '角色说话方式的额外约束,写入时会与角色卡一起注入。',
|
|
46
|
+
placeholder: '例如:长辈说话带方言词;主角对熟人用绰号称呼。',
|
|
47
|
+
maxChars: 800,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 'endingHookExtra',
|
|
51
|
+
label: '章末钩子偏好',
|
|
52
|
+
hint: '对结尾钩子的额外要求(与章节计划里的 endingHook 叠加)。',
|
|
53
|
+
placeholder: '例如:结尾尽量落在一个未闭合的选择上,而不是新出现的敌人。',
|
|
54
|
+
maxChars: 600,
|
|
55
|
+
},
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
/** 槽位 id → 规格。 */
|
|
59
|
+
const SLOT_BY_ID = new Map(PROMPT_SLOTS.map(s => [s.id, s] as const))
|
|
60
|
+
|
|
61
|
+
/** 单个槽位的渲染结果。 */
|
|
62
|
+
export interface RenderedSlot {
|
|
63
|
+
id: PromptSlotId
|
|
64
|
+
label: string
|
|
65
|
+
text: string
|
|
66
|
+
/** 是否因超长被截断。 */
|
|
67
|
+
truncated: boolean
|
|
68
|
+
/** 原文长度(截断前)。 */
|
|
69
|
+
chars: number
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 读取并规整项目里的槽位内容(去空白、按上限截断)。
|
|
74
|
+
*
|
|
75
|
+
* @param project 项目状态。
|
|
76
|
+
* @returns 非空槽位列表(保持 PROMPT_SLOTS 的声明顺序)。
|
|
77
|
+
*/
|
|
78
|
+
export function readPromptSlots(project: ProjectState): RenderedSlot[] {
|
|
79
|
+
const stored = project.promptSlots ?? {}
|
|
80
|
+
const out: RenderedSlot[] = []
|
|
81
|
+
for (const spec of PROMPT_SLOTS) {
|
|
82
|
+
const raw = (stored[spec.id] ?? '').trim()
|
|
83
|
+
if (raw === '') continue
|
|
84
|
+
const truncated = raw.length > spec.maxChars
|
|
85
|
+
out.push({
|
|
86
|
+
id: spec.id,
|
|
87
|
+
label: spec.label,
|
|
88
|
+
text: truncated ? raw.slice(0, spec.maxChars) : raw,
|
|
89
|
+
truncated,
|
|
90
|
+
chars: raw.length,
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
return out
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 把槽位渲染成注入提示词的段落。
|
|
98
|
+
*
|
|
99
|
+
* @param project 项目状态。
|
|
100
|
+
* @returns 提示词片段;无槽位时返回空串。
|
|
101
|
+
*/
|
|
102
|
+
export function renderPromptSlots(project: ProjectState): string {
|
|
103
|
+
const slots = readPromptSlots(project)
|
|
104
|
+
if (slots.length === 0) return ''
|
|
105
|
+
const lines = ['==================== 作者偏好(本书自定义,优先级低于道藏/红线/合规) ====================']
|
|
106
|
+
for (const slot of slots) {
|
|
107
|
+
lines.push(`【${slot.label}】${slot.text}${slot.truncated ? `(已按 ${SLOT_BY_ID.get(slot.id)?.maxChars ?? 0} 字上限截断,原 ${slot.chars} 字)` : ''}`)
|
|
108
|
+
}
|
|
109
|
+
lines.push('以上偏好只影响表达方式;与道藏、写作红线、内容合规红线冲突时,一律以后者为准。')
|
|
110
|
+
return lines.join('\n')
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 校验并规整一次槽位写入。
|
|
115
|
+
*
|
|
116
|
+
* @param id 槽位 id。
|
|
117
|
+
* @param value 内容(空串表示清空该槽位)。
|
|
118
|
+
* @returns 归一化后的内容。
|
|
119
|
+
* @throws 槽位 id 不在白名单时抛错。
|
|
120
|
+
*/
|
|
121
|
+
export function normalizeSlotValue(id: string, value: string): string {
|
|
122
|
+
const spec = SLOT_BY_ID.get(id as PromptSlotId)
|
|
123
|
+
if (spec === undefined) throw new Error(`未知提示词槽位:${id}`)
|
|
124
|
+
return value.replace(/\r\n/g, '\n').trim()
|
|
125
|
+
}
|
package/src/prompting.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** 章节写作约束:把写作骨架渲染成可附加到系统提示词末尾的必达/禁止/自查块。 */
|
|
2
|
+
export function renderChapterWriterSkeleton(meta: {
|
|
3
|
+
targetChars: number
|
|
4
|
+
minChars: number
|
|
5
|
+
maxChars: number
|
|
6
|
+
pov?: string
|
|
7
|
+
tonePreference?: string
|
|
8
|
+
endingHookPreference?: string
|
|
9
|
+
antiAiRules?: string
|
|
10
|
+
}): string {
|
|
11
|
+
const pov = meta.pov ?? '第三人称有限视角,严格跟随主角所见所知。'
|
|
12
|
+
const tone = meta.tonePreference ?? '文风贴合本书设定,用具体细节与动作推进。'
|
|
13
|
+
const hook = meta.endingHookPreference ?? '章末留一个明确的钩子(新信息、新风险或未闭合的选择)。'
|
|
14
|
+
const antiAi = meta.antiAiRules ?? ''
|
|
15
|
+
return [
|
|
16
|
+
'==================== 章节生成骨架(必达 / 禁止 / 输出前自查) ====================',
|
|
17
|
+
'【叙事视角】' + pov,
|
|
18
|
+
'【本章必达】本章必须发生实质变化——局面、关系、信息、风险、决策至少动一项;开篇直接进入情境,不复述上一章。',
|
|
19
|
+
'【禁止事项】不写以总结、复盘、解释为主的段落;不新增核心角色,不写入与上下文冲突的设定;不为凑字数注水。',
|
|
20
|
+
'【篇幅】目标 ' + meta.targetChars + ' 字,区间 ' + meta.minChars + '-' + meta.maxChars + ' 字;不够就继续推进有效情节,不许草草收尾。',
|
|
21
|
+
'【结尾】' + hook,
|
|
22
|
+
'【表达】用具体动作、对话与可感知细节往前推;' + tone,
|
|
23
|
+
'【反 AI】' + antiAi,
|
|
24
|
+
'【输出前自查】逐项确认:读者的回报、关键转折、章末净变化是否看得见?上一章留下的钩子有没有交代?人物的硬事实有没有被写反?确认无误再输出,正文里不要出现核查过程。',
|
|
25
|
+
].join('\n')
|
|
26
|
+
}
|