gwe-engine 3.2.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.
Files changed (118) hide show
  1. package/README.md +265 -0
  2. package/README_EN.md +126 -0
  3. package/dist/cli.js +10444 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/index.d.ts +847 -0
  6. package/dist/index.js +10569 -0
  7. package/dist/index.js.map +1 -0
  8. package/package.json +1 -0
  9. package/src/kb/base-fillers.json +35 -0
  10. package/src/kb/base-prompt.ts +151 -0
  11. package/src/kb/base-vocab.json +87 -0
  12. package/src/kb/nodes/node_ai_continue_length/opt_ai_long.kb.json +36 -0
  13. package/src/kb/nodes/node_ai_continue_length/opt_ai_medium.kb.json +30 -0
  14. package/src/kb/nodes/node_ai_continue_length/opt_ai_short.kb.json +34 -0
  15. package/src/kb/nodes/node_ai_continue_length/opt_ai_xtra_long.kb.json +37 -0
  16. package/src/kb/nodes/node_ai_creativity/opt_ai_adventurous.kb.json +31 -0
  17. package/src/kb/nodes/node_ai_creativity/opt_ai_balanced.kb.json +31 -0
  18. package/src/kb/nodes/node_ai_creativity/opt_ai_conservative.kb.json +31 -0
  19. package/src/kb/nodes/node_anchor_density/opt_anchor_dense.kb.json +36 -0
  20. package/src/kb/nodes/node_anchor_density/opt_anchor_extreme.kb.json +37 -0
  21. package/src/kb/nodes/node_anchor_density/opt_anchor_sparse.kb.json +34 -0
  22. package/src/kb/nodes/node_anchor_density/opt_anchor_standard.kb.json +34 -0
  23. package/src/kb/nodes/node_battle_style/opt_battle_atmosphere.kb.json +32 -0
  24. package/src/kb/nodes/node_battle_style/opt_battle_detail.kb.json +36 -0
  25. package/src/kb/nodes/node_battle_style/opt_battle_psychological.kb.json +36 -0
  26. package/src/kb/nodes/node_battle_style/opt_battle_result.kb.json +37 -0
  27. package/src/kb/nodes/node_description_style/opt_desc_clinical.kb.json +36 -0
  28. package/src/kb/nodes/node_description_style/opt_desc_minimal.kb.json +37 -0
  29. package/src/kb/nodes/node_description_style/opt_desc_poetic.kb.json +35 -0
  30. package/src/kb/nodes/node_description_style/opt_desc_sensory.kb.json +39 -0
  31. package/src/kb/nodes/node_dialogue_style/opt_dialogue_concise.kb.json +34 -0
  32. package/src/kb/nodes/node_dialogue_style/opt_dialogue_natural.kb.json +32 -0
  33. package/src/kb/nodes/node_dialogue_style/opt_dialogue_stylized.kb.json +33 -0
  34. package/src/kb/nodes/node_dialogue_style/opt_dialogue_verbose.kb.json +34 -0
  35. package/src/kb/nodes/node_emotion_style/opt_emotion_both.kb.json +40 -0
  36. package/src/kb/nodes/node_emotion_style/opt_emotion_show.kb.json +41 -0
  37. package/src/kb/nodes/node_emotion_style/opt_emotion_tell.kb.json +40 -0
  38. package/src/kb/nodes/node_ending_hook/opt_hook_break.kb.json +54 -0
  39. package/src/kb/nodes/node_ending_hook/opt_hook_soft.kb.json +29 -0
  40. package/src/kb/nodes/node_ending_hook/opt_hook_suspense.kb.json +30 -0
  41. package/src/kb/nodes/node_info_density/opt_info_balanced.kb.json +37 -0
  42. package/src/kb/nodes/node_info_density/opt_info_fast.kb.json +37 -0
  43. package/src/kb/nodes/node_info_density/opt_info_leisurely.kb.json +41 -0
  44. package/src/kb/nodes/node_opening_impact/opt_opening_medium.kb.json +30 -0
  45. package/src/kb/nodes/node_opening_impact/opt_opening_strong.kb.json +46 -0
  46. package/src/kb/nodes/node_opening_impact/opt_opening_weak.kb.json +29 -0
  47. package/src/kb/nodes/node_paragraph_density/opt_dense_high.kb.json +36 -0
  48. package/src/kb/nodes/node_paragraph_density/opt_dense_low.kb.json +37 -0
  49. package/src/kb/nodes/node_paragraph_density/opt_dense_medium.kb.json +28 -0
  50. package/src/kb/nodes/node_payoff_frequency/opt_payoff_every_3.kb.json +31 -0
  51. package/src/kb/nodes/node_payoff_frequency/opt_payoff_every_chapter.kb.json +35 -0
  52. package/src/kb/nodes/node_payoff_frequency/opt_payoff_sparse.kb.json +37 -0
  53. package/src/kb/nodes/node_platform/opt_platform_feilu.kb.json +44 -0
  54. package/src/kb/nodes/node_platform/opt_platform_generic.kb.json +31 -0
  55. package/src/kb/nodes/node_platform/opt_platform_jinjiang.kb.json +42 -0
  56. package/src/kb/nodes/node_platform/opt_platform_newmedia.kb.json +43 -0
  57. package/src/kb/nodes/node_platform/opt_platform_qidian.kb.json +39 -0
  58. package/src/kb/nodes/node_platform/opt_platform_qimao.kb.json +43 -0
  59. package/src/kb/nodes/node_platform/opt_platform_tomato.kb.json +51 -0
  60. package/src/kb/nodes/node_pov/opt_pov_first_past.kb.json +34 -0
  61. package/src/kb/nodes/node_pov/opt_pov_first_present.kb.json +37 -0
  62. package/src/kb/nodes/node_pov/opt_pov_limited_third_past.kb.json +31 -0
  63. package/src/kb/nodes/node_pov/opt_pov_limited_third_present.kb.json +35 -0
  64. package/src/kb/nodes/node_pov/opt_pov_omniscient_third_past.kb.json +37 -0
  65. package/src/kb/nodes/node_pov/opt_pov_omniscient_third_present.kb.json +36 -0
  66. package/src/kb/nodes/node_rhetoric/opt_rhetoric_metaphor.kb.json +35 -0
  67. package/src/kb/nodes/node_rhetoric/opt_rhetoric_ornate.kb.json +37 -0
  68. package/src/kb/nodes/node_rhetoric/opt_rhetoric_plain.kb.json +25 -0
  69. package/src/kb/nodes/node_rhetoric/opt_rhetoric_sketch.kb.json +37 -0
  70. package/src/kb/nodes/node_sentence_rhythm/opt_rhythm_long.kb.json +42 -0
  71. package/src/kb/nodes/node_sentence_rhythm/opt_rhythm_medium.kb.json +40 -0
  72. package/src/kb/nodes/node_sentence_rhythm/opt_rhythm_mixed.kb.json +40 -0
  73. package/src/kb/nodes/node_sentence_rhythm/opt_rhythm_short.kb.json +41 -0
  74. package/src/kb/nodes/node_strictness/opt_strict_lenient.kb.json +50 -0
  75. package/src/kb/nodes/node_strictness/opt_strict_standard.kb.json +30 -0
  76. package/src/kb/nodes/node_strictness/opt_strict_strict.kb.json +52 -0
  77. package/src/kb/nodes/node_target_length/opt_length_1500.kb.json +39 -0
  78. package/src/kb/nodes/node_target_length/opt_length_2200.kb.json +39 -0
  79. package/src/kb/nodes/node_target_length/opt_length_3000.kb.json +39 -0
  80. package/src/kb/nodes/node_target_length/opt_length_4000.kb.json +39 -0
  81. package/src/kb/nodes/node_target_length/opt_length_custom.kb.json +31 -0
  82. package/src/kb/nodes/node_tone/opt_tone_badass.kb.json +45 -0
  83. package/src/kb/nodes/node_tone/opt_tone_classical.kb.json +41 -0
  84. package/src/kb/nodes/node_tone/opt_tone_cold.kb.json +42 -0
  85. package/src/kb/nodes/node_tone/opt_tone_colloquial.kb.json +33 -0
  86. package/src/kb/nodes/node_tone/opt_tone_folksy.kb.json +40 -0
  87. package/src/kb/nodes/node_tone/opt_tone_playful.kb.json +42 -0
  88. package/src/kb/nodes/node_tone/opt_tone_serious.kb.json +42 -0
  89. package/src/kb/nodes/node_twist_frequency/opt_twist_dense.kb.json +36 -0
  90. package/src/kb/nodes/node_twist_frequency/opt_twist_sparse.kb.json +26 -0
  91. package/src/kb/nodes/node_twist_frequency/opt_twist_standard.kb.json +32 -0
  92. package/src/kb/nodes/node_vocab_filler/opt_filler_relaxed.kb.json +45 -0
  93. package/src/kb/nodes/node_vocab_filler/opt_filler_standard.kb.json +46 -0
  94. package/src/kb/nodes/node_vocab_filler/opt_filler_strict.kb.json +56 -0
  95. package/src/kb/nodes/node_vocab_filler/opt_filler_webnovel.kb.json +56 -0
  96. package/src/kb/nodes/node_vocab_sensory/opt_vocab_daily.kb.json +46 -0
  97. package/src/kb/nodes/node_vocab_sensory/opt_vocab_esports.kb.json +45 -0
  98. package/src/kb/nodes/node_vocab_sensory/opt_vocab_fantasy.kb.json +40 -0
  99. package/src/kb/nodes/node_vocab_sensory/opt_vocab_generic.kb.json +32 -0
  100. package/src/kb/nodes/node_vocab_sensory/opt_vocab_historical.kb.json +43 -0
  101. package/src/kb/nodes/node_vocab_sensory/opt_vocab_horror.kb.json +48 -0
  102. package/src/kb/nodes/node_vocab_sensory/opt_vocab_military.kb.json +46 -0
  103. package/src/kb/nodes/node_vocab_sensory/opt_vocab_mystery.kb.json +43 -0
  104. package/src/kb/nodes/node_vocab_sensory/opt_vocab_scifi.kb.json +40 -0
  105. package/src/kb/nodes/node_vocab_sensory/opt_vocab_urban.kb.json +40 -0
  106. package/src/kb/nodes/node_vocab_sensory/opt_vocab_xianxia.kb.json +40 -0
  107. package/src/kb/presets/preset_daily.json +42 -0
  108. package/src/kb/presets/preset_esports.json +42 -0
  109. package/src/kb/presets/preset_fantasy.json +40 -0
  110. package/src/kb/presets/preset_historical.json +41 -0
  111. package/src/kb/presets/preset_horror.json +67 -0
  112. package/src/kb/presets/preset_miner.json +123 -0
  113. package/src/kb/presets/preset_mystery.json +55 -0
  114. package/src/kb/presets/preset_scifi_hardcore.json +55 -0
  115. package/src/kb/presets/preset_urban_jinjiang.json +42 -0
  116. package/src/kb/presets/preset_urban_tomato.json +66 -0
  117. package/src/kb/presets/preset_xianxia_qidian.json +50 -0
  118. package/src/kb/presets/preset_xianxia_tomato.json +66 -0
@@ -0,0 +1,847 @@
1
+ type NodeCategory = 'style' | 'vocab' | 'rule' | 'ai';
2
+ interface NodeDefinition {
3
+ readonly id: NodeId;
4
+ readonly name: string;
5
+ readonly category: NodeCategory;
6
+ readonly description: string;
7
+ readonly required: boolean;
8
+ readonly defaultOptionId: string;
9
+ readonly options: OptionId[];
10
+ }
11
+ type NodeId = 'node_sentence_rhythm' | 'node_paragraph_density' | 'node_dialogue_style' | 'node_description_style' | 'node_battle_style' | 'node_emotion_style' | 'node_tone' | 'node_platform' | 'node_info_density' | 'node_payoff_frequency' | 'node_pov' | 'node_rhetoric' | 'node_vocab_sensory' | 'node_vocab_filler' | 'node_target_length' | 'node_anchor_density' | 'node_strictness' | 'node_opening_impact' | 'node_twist_frequency' | 'node_ending_hook' | 'node_ai_creativity' | 'node_ai_continue_length';
12
+ type OptionId = string;
13
+ /** 节点选项 .kb.json 文件格式 */
14
+ interface NodeOptionKB {
15
+ readonly kb_version: '2.0' | '3.0';
16
+ readonly node_id: NodeId;
17
+ readonly option_id: OptionId;
18
+ readonly option_name: string;
19
+ readonly option_description: string;
20
+ /** 词汇表:词库类节点使用,其他节点可为空 */
21
+ readonly vocabulary?: {
22
+ readonly add?: {
23
+ readonly bodyParts?: string[];
24
+ readonly sensoryVerbs?: string[];
25
+ readonly environmentSignals?: string[];
26
+ readonly actionVerbs?: string[];
27
+ readonly fillerPatterns?: string[];
28
+ readonly dialogueTags?: string[];
29
+ readonly worldTerms?: string[];
30
+ readonly [category: string]: string[] | undefined;
31
+ };
32
+ readonly remove?: string[];
33
+ };
34
+ /** 检测阈值覆盖 */
35
+ readonly threshold_overrides?: Partial<Thresholds>;
36
+ /** 雷达权重调整(乘系数) */
37
+ readonly radar_weights?: Partial<RadarWeights>;
38
+ /** 启用/禁用检测项 */
39
+ readonly checks?: {
40
+ readonly disable?: string[];
41
+ readonly enable?: string[];
42
+ };
43
+ /** AI系统提示词(注入到system prompt) */
44
+ readonly system_prompt?: string;
45
+ /** 示例段落(给用户预览,也作为AI的few-shot) */
46
+ readonly examples?: Array<{
47
+ readonly text: string;
48
+ readonly note?: string;
49
+ }>;
50
+ /** 约束:AI必须遵守的硬规则 */
51
+ readonly constraints?: string[];
52
+ /** 依赖:必须同时选中这些选项才能生效 */
53
+ readonly requires?: OptionId[];
54
+ /** 冲突:不能和这些选项同时选中 */
55
+ readonly conflicts?: OptionId[];
56
+ }
57
+ interface Thresholds {
58
+ minAnchors: number;
59
+ anchorsPerWords: number;
60
+ maxAnchorGap: number;
61
+ maxFillerSentences: number;
62
+ maxFillerWords: number;
63
+ maxDashCount: number;
64
+ maxEllipsisCount: number;
65
+ maxParagraphLength: number;
66
+ targetParagraphLength: number;
67
+ minParagraphCount: number;
68
+ maxDialogueContinuous: number;
69
+ maxDialogueTagRepeat: number;
70
+ maxSentenceLength: number;
71
+ targetSentenceLength: number;
72
+ shortSentenceRatio: number;
73
+ maxExpositionContinuous: number;
74
+ infoPointsPerThousand: number;
75
+ minSensoryTypes: number;
76
+ sightRatio: number;
77
+ }
78
+ declare const DEFAULT_THRESHOLDS: Thresholds;
79
+ interface RadarWeights {
80
+ bodyReaction: number;
81
+ sensorySignal: number;
82
+ action: number;
83
+ emotion: number;
84
+ infoAdvance: number;
85
+ twistFrequency: number;
86
+ hookStrength: number;
87
+ }
88
+ declare const DEFAULT_RADAR_WEIGHTS: RadarWeights;
89
+ interface Preset {
90
+ readonly preset_id: string;
91
+ readonly preset_name: string;
92
+ readonly preset_description: string;
93
+ readonly based_on: string | null;
94
+ /** 节点选项选择 */
95
+ readonly selections: Record<NodeId, OptionId>;
96
+ /** 预设追加词库 */
97
+ readonly extra_vocab?: NodeOptionKB['vocabulary'];
98
+ /** 预设追加提示词 */
99
+ readonly extra_prompt?: string;
100
+ /** 预设追加约束 */
101
+ readonly extra_constraints?: string[];
102
+ /** 预设专属规则(正则/自定义检查) */
103
+ readonly extra_rules?: CustomRule[];
104
+ /** 预设阈值覆盖 */
105
+ readonly threshold_overrides?: Partial<Thresholds>;
106
+ /** 封面/标签 */
107
+ readonly tags?: string[];
108
+ }
109
+ interface CustomRule {
110
+ readonly id: string;
111
+ readonly name: string;
112
+ readonly description: string;
113
+ readonly severity: 'error' | 'warning' | 'info';
114
+ readonly pattern?: string;
115
+ readonly check?: (text: string) => RuleViolation | null;
116
+ readonly injectToPrompt?: boolean;
117
+ }
118
+ interface EngineConfig {
119
+ presetId: string | null;
120
+ selections: Record<NodeId, OptionId>;
121
+ userOverrides: UserOverrides;
122
+ }
123
+ interface UserOverrides {
124
+ thresholds?: Partial<Thresholds>;
125
+ vocab?: NodeOptionKB['vocabulary'];
126
+ prompt?: string;
127
+ rules?: CustomRule[];
128
+ targetLength?: number;
129
+ }
130
+ interface WritingContext {
131
+ book: BookMeta;
132
+ characters: Character[];
133
+ settings: Setting[];
134
+ volumes?: Volume[];
135
+ chapters: Chapter[];
136
+ subplots?: Subplot[];
137
+ currentChapterId: string;
138
+ cursorPosition: number;
139
+ selection: TextSelection | null;
140
+ }
141
+ interface BookMeta {
142
+ id: string;
143
+ title: string;
144
+ author?: string;
145
+ synopsis?: string;
146
+ tags?: string[];
147
+ }
148
+ interface Character {
149
+ id: string;
150
+ name: string;
151
+ aliases?: string[];
152
+ role: 'protagonist' | 'antagonist' | 'supporting' | 'minor';
153
+ tags?: string[];
154
+ description?: string;
155
+ background?: string;
156
+ relationships?: CharacterRelationship[];
157
+ speechStyle?: string;
158
+ attributes?: Record<string, unknown>;
159
+ }
160
+ interface CharacterRelationship {
161
+ targetId: string;
162
+ type: string;
163
+ description?: string;
164
+ }
165
+ interface Setting {
166
+ id: string;
167
+ name: string;
168
+ type: 'world' | 'location' | 'faction' | 'system' | 'item' | 'other';
169
+ description: string;
170
+ attributes?: Record<string, unknown>;
171
+ }
172
+ interface Volume {
173
+ id: string;
174
+ title: string;
175
+ number: number;
176
+ summary?: string;
177
+ }
178
+ interface Chapter {
179
+ id: string;
180
+ title: string;
181
+ number: number;
182
+ volumeId?: string;
183
+ content: string;
184
+ wordCount: number;
185
+ summary?: string;
186
+ status: 'draft' | 'writing' | 'done';
187
+ }
188
+ interface Subplot {
189
+ id: string;
190
+ name: string;
191
+ description: string;
192
+ status: 'active' | 'foreshadowing' | 'resolved';
193
+ relatedCharacterIds?: string[];
194
+ }
195
+ interface TextSelection {
196
+ from: number;
197
+ to: number;
198
+ text: string;
199
+ }
200
+ interface CheckResult {
201
+ score: number;
202
+ radarScores: RadarScores;
203
+ violations: RuleViolation[];
204
+ stats: TextStats;
205
+ passed: boolean;
206
+ }
207
+ interface RadarScores {
208
+ bodyReaction: number;
209
+ sensorySignal: number;
210
+ action: number;
211
+ emotion: number;
212
+ infoAdvance: number;
213
+ twistFrequency: number;
214
+ hookStrength: number;
215
+ }
216
+ interface RuleViolation {
217
+ ruleId: string;
218
+ ruleName: string;
219
+ message: string;
220
+ severity: 'error' | 'warning' | 'info';
221
+ position?: {
222
+ from: number;
223
+ to: number;
224
+ };
225
+ suggestion?: string;
226
+ }
227
+ interface TextStats {
228
+ totalChars: number;
229
+ totalWords: number;
230
+ paragraphCount: number;
231
+ avgParagraphLength: number;
232
+ sentenceCount: number;
233
+ avgSentenceLength: number;
234
+ shortSentenceRatio: number;
235
+ anchorCount: number;
236
+ fillerCount: number;
237
+ dialogueRatio: number;
238
+ sensoryMentions: Record<string, number>;
239
+ }
240
+ type ChatRole = 'system' | 'user' | 'assistant';
241
+ interface ChatMessage {
242
+ role: ChatRole;
243
+ content: string;
244
+ }
245
+ interface LLMRequest {
246
+ messages: ChatMessage[];
247
+ temperature?: number;
248
+ maxTokens?: number;
249
+ stream?: boolean;
250
+ }
251
+ interface LLMUsage {
252
+ promptTokens: number;
253
+ completionTokens: number;
254
+ totalTokens: number;
255
+ }
256
+ interface StreamCallbacks {
257
+ onToken: (delta: string) => void;
258
+ onDone: (fullContent: string, usage?: LLMUsage) => void;
259
+ onError: (error: Error) => void;
260
+ }
261
+ interface LLMProvider {
262
+ readonly name: string;
263
+ chat(request: LLMRequest): Promise<{
264
+ content: string;
265
+ usage?: LLMUsage;
266
+ }>;
267
+ stream(request: LLMRequest, callbacks: StreamCallbacks): Promise<void>;
268
+ }
269
+ type EngineEventType = 'config:changed' | 'config:validated' | 'preset:loaded' | 'preset:saved' | 'preset:deleted' | 'check:done' | 'ai:token' | 'ai:done' | 'ai:error' | 'rule:violation';
270
+ interface EngineEvent {
271
+ type: EngineEventType;
272
+ payload?: unknown;
273
+ }
274
+ type EngineEventListener = (event: EngineEvent) => void;
275
+ interface ValidationResult {
276
+ valid: boolean;
277
+ conflicts: ConflictInfo[];
278
+ missingDeps: DependencyInfo[];
279
+ }
280
+ interface ConflictInfo {
281
+ optionA: OptionId;
282
+ optionB: OptionId;
283
+ nodeA: NodeId;
284
+ nodeB: NodeId;
285
+ message: string;
286
+ }
287
+ interface DependencyInfo {
288
+ optionId: OptionId;
289
+ nodeId: NodeId;
290
+ requiredOption: OptionId;
291
+ message: string;
292
+ }
293
+ interface MergedConfig {
294
+ thresholds: Thresholds;
295
+ radarWeights: RadarWeights;
296
+ vocabulary: MergedVocabulary;
297
+ systemPrompts: string[];
298
+ constraints: string[];
299
+ examples: Array<{
300
+ text: string;
301
+ note?: string;
302
+ }>;
303
+ extraRules: CustomRule[];
304
+ disabledChecks: Set<string>;
305
+ enabledChecks: Set<string>;
306
+ }
307
+ interface MergedVocabulary {
308
+ bodyParts: Set<string>;
309
+ sensoryVerbs: Set<string>;
310
+ environmentSignals: Set<string>;
311
+ actionVerbs: Set<string>;
312
+ fillerPatterns: Set<string>;
313
+ dialogueTags: Set<string>;
314
+ worldTerms: Set<string>;
315
+ [category: string]: Set<string>;
316
+ }
317
+
318
+ type TaskType = 'continue' | 'rewrite' | 'review' | 'polish' | 'expand' | 'compress' | 'outline' | 'dialog' | 'generate';
319
+ interface NodeCatalog {
320
+ nodes: NodeDefinition[];
321
+ presets: Preset[];
322
+ }
323
+ declare class GWEEngine {
324
+ private config;
325
+ private merged;
326
+ private llmProvider;
327
+ private loadedOptions;
328
+ private presets;
329
+ private listeners;
330
+ private context;
331
+ private customNodes;
332
+ private basePrompt;
333
+ private baseVocab;
334
+ private userPresets;
335
+ constructor(provider?: LLMProvider);
336
+ /**
337
+ * 从KB数据加载节点选项(.kb.json文件内容)
338
+ */
339
+ loadNodeOption(kb: NodeOptionKB): void;
340
+ /**
341
+ * 批量加载节点选项
342
+ */
343
+ loadNodeOptions(kbs: NodeOptionKB[]): void;
344
+ /**
345
+ * 注册预设包
346
+ */
347
+ registerPreset(preset: Preset): void;
348
+ /**
349
+ * 设置基础提示词(base-prompt.md内容)
350
+ */
351
+ setBasePrompt(prompt: string): void;
352
+ /**
353
+ * 设置基础词库(base-vocab.json内容)
354
+ */
355
+ setBaseVocab(vocab: NonNullable<NodeOptionKB['vocabulary']>['add']): void;
356
+ /**
357
+ * 设置LLM Provider
358
+ */
359
+ setLLMProvider(provider: LLMProvider): void;
360
+ /**
361
+ * 注册自定义节点
362
+ */
363
+ registerCustomNode(nodeDef: NodeDefinition): void;
364
+ /**
365
+ * 注册自定义规则
366
+ */
367
+ addCustomRule(rule: CustomRule): void;
368
+ /**
369
+ * 加载预设包(一键配置所有节点)
370
+ */
371
+ loadPreset(presetId: string): void;
372
+ /**
373
+ * 设置单个节点的选项
374
+ */
375
+ setNodeOption(nodeId: NodeId, optionId: OptionId): void;
376
+ /**
377
+ * 批量设置节点选项
378
+ */
379
+ setNodeOptions(selections: Partial<Record<NodeId, OptionId>>): void;
380
+ /**
381
+ * 应用用户自定义覆盖
382
+ */
383
+ applyUserOverrides(overrides: UserOverrides): void;
384
+ /**
385
+ * 清除所有用户自定义覆盖(恢复到纯节点+预设状态)
386
+ */
387
+ clearUserOverrides(): void;
388
+ /**
389
+ * 重置用户自定义词库(用于面板重新输入词库时替换而非追加)
390
+ */
391
+ setUserVocab(vocab: NonNullable<UserOverrides['vocab']>): void;
392
+ /**
393
+ * 重置用户自定义阈值(替换而非合并)
394
+ */
395
+ setUserThresholds(thresholds: Partial<Thresholds>): void;
396
+ /**
397
+ * 清除用户自定义阈值(恢复预设/节点默认)
398
+ */
399
+ clearUserThresholds(): void;
400
+ /**
401
+ * 设置用户自由文本自定义规则(直接注入system prompt)
402
+ */
403
+ setCustomPromptText(text: string): void;
404
+ /**
405
+ * 获取用户自由文本自定义规则
406
+ */
407
+ getCustomPromptText(): string;
408
+ /**
409
+ * 获取当前引擎配置(可序列化)
410
+ */
411
+ getConfig(): EngineConfig;
412
+ /**
413
+ * 加载引擎配置(从保存的文件恢复)
414
+ */
415
+ loadConfig(config: EngineConfig): void;
416
+ /**
417
+ * 验证当前配置(冲突/依赖检测)
418
+ */
419
+ validateConfig(): ValidationResult;
420
+ /**
421
+ * 设置写作上下文(书籍/角色/章节等数据)
422
+ */
423
+ setContext(context: WritingContext): void;
424
+ /**
425
+ * 运行文本检测(审稿核心)
426
+ */
427
+ check(text: string, context?: WritingContext): CheckResult;
428
+ /**
429
+ * 构建AI系统提示词
430
+ */
431
+ getSystemPrompt(task: TaskType, context?: WritingContext): string;
432
+ /**
433
+ * 构建AI用户消息
434
+ */
435
+ getUserMessage(task: TaskType, context?: WritingContext, params?: Record<string, unknown>): string;
436
+ /**
437
+ * 非流式AI调用(审稿/大纲等不需要实时输出的场景)
438
+ */
439
+ callAI(task: TaskType, params?: Record<string, unknown>): Promise<{
440
+ content: string;
441
+ usage?: LLMUsage;
442
+ checkResult?: CheckResult;
443
+ }>;
444
+ /**
445
+ * 流式AI调用(续写/润色等实时输出场景)
446
+ */
447
+ streamAI(task: TaskType, callbacks: StreamCallbacks, params?: Record<string, unknown>): Promise<void>;
448
+ /** 流式续写 */
449
+ continueWrite(callbacks: StreamCallbacks): Promise<void>;
450
+ /** 流式改写选中文本 */
451
+ rewriteSelection(callbacks: StreamCallbacks, selectedText: string): Promise<void>;
452
+ /** 审稿(非流式,返回检测结果+AI建议) */
453
+ review(): Promise<CheckResult>;
454
+ /** 润色(流式) */
455
+ polishSelection(callbacks: StreamCallbacks, selectedText: string): Promise<void>;
456
+ /** 扩写(流式) */
457
+ expandSelection(callbacks: StreamCallbacks, selectedText: string): Promise<void>;
458
+ /** 缩写(非流式) */
459
+ compressSelection(selectedText: string): Promise<string>;
460
+ /**
461
+ * 获取所有可用节点和预设列表
462
+ */
463
+ getNodeCatalog(): NodeCatalog;
464
+ /**
465
+ * 保存当前配置为用户自定义预设
466
+ */
467
+ saveUserPreset(name: string, description?: string): string;
468
+ /**
469
+ * 删除用户自定义预设
470
+ */
471
+ deleteUserPreset(presetId: string): boolean;
472
+ /**
473
+ * 从localStorage加载用户预设
474
+ */
475
+ private loadUserPresetsFromStorage;
476
+ /**
477
+ * 保存用户预设到localStorage
478
+ */
479
+ private saveUserPresetsToStorage;
480
+ /**
481
+ * 获取某个节点的已加载选项
482
+ */
483
+ getNodeOptions(nodeId: NodeId): NodeOptionKB[];
484
+ /**
485
+ * 获取单个选项的完整KB数据(包含描述、示例、约束等)
486
+ */
487
+ getOption(optionId: OptionId): NodeOptionKB | undefined;
488
+ /**
489
+ * 获取当前合并后的运行时配置(用于调试/UI显示)
490
+ */
491
+ getMergedConfig(): MergedConfig | null;
492
+ /**
493
+ * 同步获取当前合并后的运行时配置(别名,UI用)
494
+ */
495
+ getMergedConfigSync(): MergedConfig | null;
496
+ /**
497
+ * 获取所有预设(内置+用户自定义)
498
+ */
499
+ getPresets(): Preset[];
500
+ /**
501
+ * 保存当前配置为预设(UI友好的别名,对应saveUserPreset)
502
+ */
503
+ saveAsPreset(opts: {
504
+ preset_name: string;
505
+ description?: string;
506
+ tags?: string[];
507
+ }): string;
508
+ /**
509
+ * 获取当前配置的简短文字摘要(用于注入到对话AI的system prompt中,轻量不占token)
510
+ */
511
+ getConfigSummary(): string;
512
+ on(event: EngineEventType, listener: EngineEventListener): () => void;
513
+ off(event: EngineEventType, listener: EngineEventListener): void;
514
+ private emit;
515
+ private ensureMerged;
516
+ private getTemperature;
517
+ }
518
+
519
+ interface OpenAICompatibleConfig {
520
+ apiKey: string;
521
+ baseURL: string;
522
+ model: string;
523
+ temperature?: number;
524
+ maxTokens?: number;
525
+ timeoutMs?: number;
526
+ }
527
+ declare class OpenAICompatibleProvider implements LLMProvider {
528
+ readonly name = "openai-compatible";
529
+ private config;
530
+ constructor(config: OpenAICompatibleConfig);
531
+ chat(request: LLMRequest): Promise<{
532
+ content: string;
533
+ usage?: LLMUsage;
534
+ }>;
535
+ stream(request: LLMRequest, callbacks: StreamCallbacks): Promise<void>;
536
+ }
537
+ declare class MockProvider implements LLMProvider {
538
+ readonly name = "mock";
539
+ private mockContinue;
540
+ private mockRewrite;
541
+ chat(request: LLMRequest): Promise<{
542
+ content: string;
543
+ usage?: LLMUsage;
544
+ }>;
545
+ stream(request: LLMRequest, callbacks: StreamCallbacks): Promise<void>;
546
+ }
547
+ interface PresetProvider {
548
+ id: string;
549
+ name: string;
550
+ baseURL: string;
551
+ defaultModel: string;
552
+ docs?: string;
553
+ }
554
+ declare const PRESET_PROVIDERS: PresetProvider[];
555
+ declare function createProvider(presetId: string, apiKey: string, model?: string): LLMProvider;
556
+
557
+ interface LoadResult {
558
+ optionsLoaded: number;
559
+ presetsLoaded: number;
560
+ errors: string[];
561
+ }
562
+ /**
563
+ * 从 JSON 对象加载单个节点选项 KB
564
+ */
565
+ declare function loadKBFromJSON(engine: GWEEngine, kb: NodeOptionKB): void;
566
+ /**
567
+ * 加载所有内置 KB 数据到引擎
568
+ */
569
+ declare function loadAllKB(engine: GWEEngine): LoadResult;
570
+ /**
571
+ * 快速创建一个已加载所有内置知识库的引擎实例
572
+ */
573
+ declare function createEngineWithKB(provider?: ConstructorParameters<typeof GWEEngine>[0]): {
574
+ engine: GWEEngine;
575
+ result: LoadResult;
576
+ };
577
+
578
+ /**
579
+ * 对文本执行全面质量检查
580
+ *
581
+ * @param text 待检查文本
582
+ * @param context 写作上下文(书籍/章节/角色等数据)
583
+ * @param mergedConfig 合并后的运行时配置
584
+ * @returns CheckResult 包含综合得分、雷达分、违规项、统计信息
585
+ */
586
+ declare function check(text: string, _context: WritingContext | null, mergedConfig: MergedConfig): CheckResult;
587
+
588
+ interface AnchorDetectionResult {
589
+ /** 检测到的锚点总数 */
590
+ count: number;
591
+ /** 质量加权后的有效锚点数(套路反应0.3倍,生理反应1.5倍) */
592
+ weightedCount: number;
593
+ /** 锚点在文本中的起始位置列表 */
594
+ positions: number[];
595
+ /** 按锚点类型分类的统计 */
596
+ types: Record<string, number>;
597
+ /** 按质量分级的统计:套路/普通/高质量 */
598
+ quality: Record<string, number>;
599
+ /** 检测到的锚点文本片段列表 */
600
+ matches: string[];
601
+ }
602
+ /**
603
+ * 检测文本中的身体锚点
604
+ * 锚点定义:身体部位词 + (0~MAX_ANCHOR_GAP个字符内) 感官动词或动作动词
605
+ *
606
+ * @param text 待检测文本
607
+ * @param vocab 合并后的词库(含bodyParts, sensoryVerbs, actionVerbs)
608
+ * @returns AnchorDetectionResult
609
+ */
610
+ declare function detectAnchors(text: string, vocab: MergedVocabulary): AnchorDetectionResult;
611
+ /**
612
+ * 计算锚点密度(每千字锚点数)
613
+ */
614
+ declare function calculateAnchorDensity(count: number, totalChars: number): number;
615
+ /**
616
+ * 计算锚点间最大间隔(字数)
617
+ */
618
+ declare function calculateMaxAnchorGap(positions: number[], totalChars: number): number;
619
+
620
+ interface FillerDetectionResult {
621
+ /** 检测到的填充词总数 */
622
+ count: number;
623
+ /** 包含填充词的句子列表 */
624
+ sentences: string[];
625
+ /** 填充词在文本中的位置列表 */
626
+ positions: number[];
627
+ /** 破折号"——"出现次数 */
628
+ dashCount: number;
629
+ /** 省略号"……"出现次数 */
630
+ ellipsisCount: number;
631
+ /** 重复对话标签信息 */
632
+ repeatedTags: Array<{
633
+ tag: string;
634
+ count: number;
635
+ positions: number[];
636
+ }>;
637
+ /** 检测到的填充词详情(词+位置) */
638
+ fillerDetails: Array<{
639
+ word: string;
640
+ position: number;
641
+ }>;
642
+ }
643
+ /**
644
+ * 检测文本中的填充词和相关问题
645
+ *
646
+ * @param text 待检测文本
647
+ * @param vocab 合并后的词库
648
+ * @param thresholds 检测阈值(用于对话标签重复判断)
649
+ * @returns FillerDetectionResult
650
+ */
651
+ declare function detectFillers(text: string, vocab: MergedVocabulary, thresholds: Thresholds): FillerDetectionResult;
652
+ /**
653
+ * 计算每千字填充词密度
654
+ */
655
+ declare function calculateFillerDensity(count: number, totalChars: number): number;
656
+
657
+ /** 雷达评分计算所需的统计信息 */
658
+ interface RadarInput {
659
+ text: string;
660
+ stats: TextStats;
661
+ mergedConfig: MergedConfig;
662
+ }
663
+ /**
664
+ * 计算7维雷达评分(V3追读力增强版)
665
+ *
666
+ * 维度说明:
667
+ * - bodyReaction: 基于锚点密度(身体反应描写丰富度)
668
+ * - sensorySignal: 基于五感词汇出现次数和覆盖面
669
+ * - action: 基于动作动词密度和对话穿插
670
+ * - emotion: 基于情感词和身体反应表达
671
+ * - infoAdvance: 基于新信息点(名词/专有名词首次出现)
672
+ * - twistFrequency: V3新增:转折/反咬词密度,衡量信息反转频率
673
+ * - hookStrength: V3新增:章末钩子强度,衡量结尾追读力
674
+ *
675
+ * 每个维度0-100分,使用radar_weights做权重调整(乘法系数)
676
+ */
677
+ declare function calculateRadar(input: RadarInput): RadarScores;
678
+ /**
679
+ * 计算雷达综合得分(加权平均)
680
+ */
681
+ declare function calculateScore(radar: RadarScores): number;
682
+ /**
683
+ * 计算带自定义权重的综合得分
684
+ */
685
+ declare function calculateWeightedScore(radar: RadarScores, weights: Partial<RadarWeights>): number;
686
+ /** 统计五感词汇出现次数 */
687
+ declare function countSensoryMentions(text: string): Record<string, number>;
688
+
689
+ /**
690
+ * 验证当前选项选择是否合法
691
+ * @param selections 用户选择的节点->选项映射
692
+ * @param loadedOptions 已加载的选项KB数据(如果不传则从全局注册表取)
693
+ * @returns ValidationResult 包含conflicts和missingDeps列表
694
+ */
695
+ declare function validate(selections: Record<NodeId, OptionId>, loadedOptions?: Map<OptionId, NodeOptionKB>): ValidationResult;
696
+ /**
697
+ * 格式化验证结果为人类可读字符串(用于UI提示)
698
+ */
699
+ declare function formatValidationResult(result: ValidationResult): string;
700
+ /**
701
+ * 快速检查:某个optionId是否可以安全选中
702
+ * 返回true表示无冲突且依赖满足
703
+ */
704
+ declare function canSelectOption(optionId: OptionId, selections: Record<NodeId, OptionId>, loadedOptions?: Map<OptionId, NodeOptionKB>): {
705
+ canSelect: boolean;
706
+ reason?: string;
707
+ };
708
+
709
+ interface MergeOptions {
710
+ /** 节点选择集 */
711
+ selections: Record<NodeId, OptionId>;
712
+ /** 已加载的选项KB数据 */
713
+ loadedOptions: Map<OptionId, NodeOptionKB>;
714
+ /** 预设包(可选) */
715
+ preset?: Preset | null;
716
+ /** 用户自定义覆盖 */
717
+ userOverrides?: UserOverrides;
718
+ /** 从文件加载的基础词库(覆盖硬编码BASE_VOCAB) */
719
+ baseVocabOverride?: Required<NonNullable<NodeOptionKB['vocabulary']>>['add'] | null;
720
+ }
721
+ /**
722
+ * 合并所有配置层,生成最终运行时配置
723
+ * 优先级(从低到高):
724
+ * 1. 引擎内置默认值
725
+ * 2. 基础词库
726
+ * 3. 词库类节点选项(vocab.add追加,vocab.remove删除)
727
+ * 4. 风格/规则节点选项的threshold_overrides和radar_weights
728
+ * 5. 预设包extra_vocab和extra_prompt
729
+ * 6. 用户自定义词库追加
730
+ * 7. 用户自定义阈值覆盖
731
+ */
732
+ declare function mergeConfig(options: MergeOptions): MergedConfig;
733
+ /**
734
+ * 深拷贝一个MergedConfig(主要用于测试或快照)
735
+ */
736
+ declare function cloneMergedConfig(config: MergedConfig): MergedConfig;
737
+
738
+ type WriteTask = 'continue' | 'rewrite' | 'review' | 'polish' | 'expand' | 'compress' | 'outline' | 'dialog' | 'generate';
739
+ interface BuildPromptOptions {
740
+ /** 当前任务类型 */
741
+ task: WriteTask;
742
+ /** 写作上下文 */
743
+ context: WritingContext | null;
744
+ /** 合并后的配置 */
745
+ mergedConfig: MergedConfig;
746
+ /** 预设包(可选) */
747
+ preset?: Preset | null;
748
+ /** 用户选择的NodeId->OptionId映射 */
749
+ selections?: Record<NodeId, OptionId>;
750
+ /** 自定义基础提示词(覆盖内置BASE_PROMPT) */
751
+ basePrompt?: string;
752
+ /** 用户自由文本自定义规则(直接拼接到system prompt) */
753
+ userCustomPrompt?: string;
754
+ }
755
+ /**
756
+ * 构建System Prompt(7层拼装)
757
+ *
758
+ * 拼装顺序:
759
+ * [1] 通用基础提示词(base-prompt)
760
+ * [2] 所有选中NodeOption的system_prompt(按节点顺序拼接,用---分隔)
761
+ * [3] 预设包的extra_prompt(如果有)
762
+ * [4] 用户自定义提示词(mergedConfig里如果有)
763
+ * [5] 上下文注入(书籍信息+前情摘要+角色卡+关系网+状态追踪)
764
+ * [6] 所有选中NodeOption的constraints(拼成"禁止/要求"列表)
765
+ * [7] 任务指令(根据task参数生成)
766
+ */
767
+ declare function buildSystemPrompt(options: BuildPromptOptions): string;
768
+ interface BuildUserMessageOptions {
769
+ task: WriteTask;
770
+ context: WritingContext | null;
771
+ /** 附加参数(如选中文字、额外指令等) */
772
+ params?: {
773
+ /** 选中的文本(用于rewrite/polish/expand/compress) */
774
+ selectedText?: string;
775
+ /** 光标前的上文内容 */
776
+ precedingText?: string;
777
+ /** 光标的后文内容 */
778
+ followingText?: string;
779
+ /** 用户额外的指令 */
780
+ userInstruction?: string;
781
+ /** 大纲生成的提示/要求 */
782
+ outlinePrompt?: string;
783
+ [key: string]: unknown;
784
+ };
785
+ }
786
+ /**
787
+ * 构建User Message(发送给AI的用户侧消息)
788
+ */
789
+ declare function buildUserMessage(options: BuildUserMessageOptions): string;
790
+
791
+ /**
792
+ * 注册一个新节点(通常用于插件扩展)
793
+ * @param def 节点定义
794
+ * @throws 如果节点ID已存在则抛出错误
795
+ */
796
+ declare function registerNode(def: NodeDefinition): void;
797
+ /**
798
+ * 注册一个节点选项(从.kb.json文件加载)
799
+ * @param kb 选项KB数据
800
+ * @throws 如果选项所属节点不存在则抛出错误
801
+ */
802
+ declare function registerNodeOption(kb: NodeOptionKB): void;
803
+ /**
804
+ * 获取节点定义
805
+ * @param id 节点ID
806
+ */
807
+ declare function getNode(id: NodeId): NodeDefinition | undefined;
808
+ /**
809
+ * 获取已加载的节点选项KB数据
810
+ * @param optionId 选项ID
811
+ */
812
+ declare function getNodeOption(optionId: OptionId): NodeOptionKB | undefined;
813
+ /**
814
+ * 获取所有已注册的节点定义
815
+ * @param category 可选分类过滤
816
+ */
817
+ declare function getAllNodes(category?: NodeCategory): NodeDefinition[];
818
+ /**
819
+ * 获取所有已注册选项的数量
820
+ */
821
+ declare function getRegisteredOptionCount(): number;
822
+ /**
823
+ * 获取默认选项选择集
824
+ * 对每个必选节点使用defaultOptionId,非必选节点不选
825
+ */
826
+ declare function getDefaultSelections(): Partial<Record<NodeId, OptionId>>;
827
+ /**
828
+ * 获取完整的默认选择(包括非必选节点,也使用默认值)
829
+ */
830
+ declare function getDefaultSelectionsFull(): Record<NodeId, OptionId>;
831
+ /**
832
+ * 按分类获取节点
833
+ */
834
+ declare function getNodesByCategory(category: NodeCategory): NodeDefinition[];
835
+ /**
836
+ * 验证一个selections对象是否完整(所有必选节点都有选择)
837
+ */
838
+ declare function validateSelectionsComplete(selections: Partial<Record<NodeId, OptionId>>): {
839
+ complete: boolean;
840
+ missingNodes: NodeId[];
841
+ };
842
+ /**
843
+ * 重置注册表(主要用于测试)
844
+ */
845
+ declare function _resetRegistry(): void;
846
+
847
+ export { type AnchorDetectionResult, type BookMeta, type BuildPromptOptions, type BuildUserMessageOptions, type CharacterRelationship, type ChatMessage, type ChatRole, type CheckResult, type ConflictInfo, type CustomRule, DEFAULT_RADAR_WEIGHTS, DEFAULT_THRESHOLDS, type DependencyInfo, type EngineConfig, type EngineEvent, type EngineEventListener, type EngineEventType, type FillerDetectionResult, type Chapter as GWEChapter, type Character as GWECharacter, GWEEngine, type Setting as GWESetting, type Subplot as GWESubplot, type Volume as GWEVolume, type LLMProvider, type LLMRequest, type LLMUsage, type LoadResult, type MergeOptions, type MergedConfig, type MergedVocabulary, MockProvider, type NodeCatalog, type NodeCategory, type NodeDefinition, type NodeId, type NodeOptionKB, type OpenAICompatibleConfig, OpenAICompatibleProvider, type OptionId, PRESET_PROVIDERS, type Preset, type PresetProvider, type RadarInput, type RadarScores, type RadarWeights, type RuleViolation, type StreamCallbacks, type TaskType, type TextSelection, type TextStats, type Thresholds, type UserOverrides, type ValidationResult, type WriteTask, type WritingContext, _resetRegistry, buildSystemPrompt, buildUserMessage, calculateAnchorDensity, calculateFillerDensity, calculateMaxAnchorGap, calculateRadar, calculateScore, calculateWeightedScore, canSelectOption, check, cloneMergedConfig, countSensoryMentions, createEngineWithKB, createProvider, detectAnchors, detectFillers, formatValidationResult, getAllNodes, getDefaultSelections, getDefaultSelectionsFull, getNode, getNodeOption, getNodesByCategory, getRegisteredOptionCount, loadAllKB, loadKBFromJSON, mergeConfig, registerNode, registerNodeOption, validate, validateSelectionsComplete };