@waterwx/dsh-novel-forge 1.2.1-alpha → 1.3.0-alpha

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 (52) hide show
  1. package/README.md +4 -5
  2. package/lib/client.js +2470 -858
  3. package/lib/index.js +2155 -388
  4. package/lib/types/action-guard.d.ts +1 -1
  5. package/lib/types/assets.d.ts +7 -7
  6. package/lib/types/client/api.d.ts +31 -1
  7. package/lib/types/client/llmLive.d.ts +12 -14
  8. package/lib/types/client/panel/LiveFeedLog.d.ts +5 -0
  9. package/lib/types/client/panel/ModelManager.d.ts +2 -2
  10. package/lib/types/client/panel/PromptSlotsView.d.ts +11 -0
  11. package/lib/types/client/panel/TensionView.d.ts +14 -0
  12. package/lib/types/client/panel/TimelineView.d.ts +14 -0
  13. package/lib/types/engine.d.ts +40 -2
  14. package/lib/types/index.d.ts +4 -0
  15. package/lib/types/llm-live.d.ts +65 -34
  16. package/lib/types/llm-retry.d.ts +38 -0
  17. package/lib/types/market-radar-sources.d.ts +1 -1
  18. package/lib/types/prompt-slots.d.ts +57 -0
  19. package/lib/types/prompting.d.ts +2 -2
  20. package/lib/types/protocol.d.ts +283 -3
  21. package/lib/types/snapshots.d.ts +46 -0
  22. package/lib/types/stage-contract.d.ts +1 -1
  23. package/lib/types/tension.d.ts +65 -0
  24. package/lib/types/timeline.d.ts +37 -0
  25. package/package.json +3 -2
  26. package/src/action-guard.ts +1 -1
  27. package/src/assets.ts +300 -268
  28. package/src/assistant.ts +45 -29
  29. package/src/client/api.ts +76 -2
  30. package/src/client/llmLive.ts +26 -16
  31. package/src/client/panel/AssetsTab.tsx +2 -2
  32. package/src/client/panel/LiveFeedLog.tsx +204 -67
  33. package/src/client/panel/ModelManager.tsx +2 -2
  34. package/src/client/panel/NovelPanel.tsx +127 -13
  35. package/src/client/panel/PromptSlotsView.tsx +97 -0
  36. package/src/client/panel/SettingsView.tsx +35 -0
  37. package/src/client/panel/TensionView.tsx +196 -0
  38. package/src/client/panel/TimelineView.tsx +192 -0
  39. package/src/engine.ts +549 -108
  40. package/src/index.ts +12 -0
  41. package/src/llm-live.ts +260 -68
  42. package/src/llm-retry.ts +77 -0
  43. package/src/market-radar-sources.ts +173 -173
  44. package/src/prompt-slots.ts +125 -0
  45. package/src/prompting.ts +8 -8
  46. package/src/protocol.ts +314 -3
  47. package/src/routes.ts +325 -6
  48. package/src/run.ts +4 -0
  49. package/src/snapshots.ts +174 -0
  50. package/src/stage-contract.ts +189 -189
  51. package/src/tension.ts +240 -0
  52. package/src/timeline.ts +159 -0
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * 动作失败分级(fail-stop 规则)——不依赖 LLM/宿主服务的纯逻辑模块,可离线单测。
3
3
  *
4
- * 规则来源(借鉴 dsh-ai-novel-writer):
4
+ * 规则来源(借鉴同类 DSH 小说插件的规则设计):
5
5
  * - 参数/契约类失败**不得重复同一调用**,解释一次即停;
6
6
  * - 瞬时故障允许有限重试,同样禁止无限重试。
7
7
  * 规则必须可测试,否则只是文档。
@@ -1,17 +1,17 @@
1
1
  /**
2
2
  * Writing assets — 题材基底库 / 推进模式库 / 反 AI 规则 / 写法引擎.
3
3
  *
4
- * Ported from AI-Novel-Writing-Assistant (Apache-2.0) built-in seed data:
5
- * - 8 preset style templates (DEFAULT_STYLE_TEMPLATES)
6
- * - 12 anti-AI rules with concrete detect patterns (DEFAULT_ANTI_AI_RULES)
7
- * - genre tree + progression mode seeds
4
+ * 内置种子数据(本项目自研表述;结构参考了同类工具的功能划分):
5
+ * - 8 套预置叙事风格模板
6
+ * - 12 条带命中模式的反 AI 规则
7
+ * - 题材树 + 推进模式种子
8
8
  * Assets persist with the project (novel-project.json) and are injected into
9
9
  * generation / planning / review prompts, and available to the AI assistant.
10
10
  */
11
11
  import type { AntiAiRule, GenreNode, PlotBeatTemplate, ProgressionMode, ProjectAssets, StarterStyleProfile, StyleAsset, StyleTemplate } from './protocol.ts';
12
- /** 预置写法模板(来自 AI-Novel-Writing-Assistant 内置 DEFAULT_STYLE_TEMPLATES)。 */
12
+ /** 预置叙事风格模板(8 套,一键绑定无需样本文本)。 */
13
13
  export declare const BUILTIN_STYLE_TEMPLATES: StyleTemplate[];
14
- /** 内置全局反 AI 规则(来自 AI-Novel-Writing-Assistant 内置 DEFAULT_ANTI_AI_RULES)。 */
14
+ /** 内置全局反 AI 规则(12 条,随插件发布;项目可叠加自定义规则)。 */
15
15
  export declare const BUILTIN_ANTI_AI_RULES: AntiAiRule[];
16
16
  /** 内置题材基底库(常用网文题材树,跨书复用)。 */
17
17
  export declare const BUILTIN_GENRE_LIBRARY: GenreNode[];
@@ -38,7 +38,7 @@ export declare function renderStyleAssets(assets: ProjectAssets | undefined): st
38
38
  export declare function renderAllAssets(assets: ProjectAssets | undefined): string;
39
39
  /** 预置写法模板 → 可直接绑定的 StyleAsset。 */
40
40
  export declare function styleTemplateToAsset(template: StyleTemplate): StyleAsset;
41
- /** 起始风格画像库:无样本文本也能快速绑定一套写法(对齐上游 DEFAULT_STARTER_STYLE_PROFILES)。 */
41
+ /** 起始风格画像库:无样本文本也能快速绑定一套写法(4 档起步写法)。 */
42
42
  export declare const BUILTIN_STARTER_STYLE_PROFILES: StarterStyleProfile[];
43
43
  /** 起始风格画像 → 可直接绑定的 StyleAsset(绑定对应模板,名称用画像名)。 */
44
44
  export declare function starterProfileToAsset(profile: StarterStyleProfile): StyleAsset | null;
@@ -33,7 +33,7 @@ export declare class NovelApi {
33
33
  summary: string;
34
34
  }>;
35
35
  foreshadow(req: ForeshadowRequest): Promise<ForeshadowResponse>;
36
- exportBook(format: 'txt' | 'md'): Promise<ExportResponse>;
36
+ exportBook(format: 'txt' | 'md' | 'json', scope?: import('../protocol.ts').ExportScope): Promise<ExportResponse>;
37
37
  chapter(no: number): Promise<ChapterResponse>;
38
38
  /** 审查手动编辑的正文(不落盘)。previousReport 传入时走「验证模式」(核对原意见解决 + 只挑新增 high)。 */
39
39
  chapterCheck(no: number, text: string, previousReport?: ReviewReport): Promise<{
@@ -269,6 +269,36 @@ export declare class NovelApi {
269
269
  }>;
270
270
  /** Run one assistant turn (NDJSON stream). */
271
271
  assistant(message: string, onFrame: (frame: import('../protocol.ts').AssistantFrame) => void): Promise<void>;
272
+ /** 张力曲线:数据 + 规则核对。 */
273
+ tension(): Promise<import('../protocol.ts').TensionResponse>;
274
+ /** 设置张力曲线预设。 */
275
+ tensionPreset(preset: import('../protocol.ts').TensionCurvePreset): Promise<void>;
276
+ /** 设置/清除某一章的目标张力。 */
277
+ tensionSet(chapterNo: number, tension?: number): Promise<void>;
278
+ /** 提示词槽位:列表。 */
279
+ promptSlots(): Promise<import('../protocol.ts').PromptSlotsResponse>;
280
+ /** 写入一个提示词槽位(空串 = 清空)。 */
281
+ promptSlotSet(id: import('../protocol.ts').PromptSlotId, value: string): Promise<void>;
282
+ /** 故事时间线:列表(含规则初筛问题)。 */
283
+ timeline(): Promise<import('../protocol.ts').TimelineResponse>;
284
+ /** 抽取某一章的时间线事件。 */
285
+ timelineExtract(chapterNo: number): Promise<{
286
+ events: import('../protocol.ts').TimelineEvent[];
287
+ }>;
288
+ /** 检查时间线矛盾(规则初筛 + AI 复核)。 */
289
+ timelineCheck(): Promise<import('../protocol.ts').TimelineResponse>;
290
+ /** 手工修正一条时间线事件。 */
291
+ timelineUpdate(event: import('../protocol.ts').TimelineEvent): Promise<void>;
292
+ /** 删除一条时间线事件。 */
293
+ timelineRemove(id: string): Promise<void>;
294
+ /** 章节历史版本列表。 */
295
+ snapshots(): Promise<import('../protocol.ts').SnapshotResponse>;
296
+ /** 回滚到某个历史版本。 */
297
+ snapshotRestore(id: string): Promise<void>;
298
+ /** 给某章手工存档。 */
299
+ snapshotCreate(chapterNo: number): Promise<void>;
300
+ /** 删除一个历史版本。 */
301
+ snapshotRemove(id: string): Promise<void>;
272
302
  /** Load the persisted assistant conversation. */
273
303
  assistantHistory(): Promise<import('../protocol.ts').AssistantMessage[]>;
274
304
  /** 清空助手对话记录。 */
@@ -2,20 +2,8 @@
2
2
  * 浏览器端「AI 创作实况」钩子:连接 /llm-live/stream (SSE),按 LLM 调用分组会话。
3
3
  * 轻量实现,不持久化缓存(对齐上游 useLlmLiveFeed 的核心行为)。
4
4
  */
5
- export interface LlmLiveFrame {
6
- type: 'session_started' | 'output_delta' | 'reasoning_delta' | 'phase_changed' | 'session_completed';
7
- sessionId: string;
8
- label?: string;
9
- model?: string;
10
- at: string;
11
- content?: string;
12
- totalChars?: number;
13
- totalReasoningChars?: number;
14
- phase?: 'requesting' | 'streaming' | 'completed' | 'failed';
15
- phaseMessage?: string;
16
- preview?: string;
17
- error?: string;
18
- }
5
+ import { type LlmLiveFrame, type LlmTokenUsage } from '../protocol.ts';
6
+ export type { LlmLiveFrame };
19
7
  export interface LlmLiveSession {
20
8
  sessionId: string;
21
9
  label: string;
@@ -26,6 +14,16 @@ export interface LlmLiveSession {
26
14
  totalChars: number;
27
15
  startedAt: string;
28
16
  updatedAt: string;
17
+ /** 本次调用的 token 用量(供应商未上报时缺省)。 */
18
+ usage?: LlmTokenUsage;
19
+ /** 本次调用耗时(毫秒)。 */
20
+ elapsedMs?: number;
21
+ /** 首字耗时(毫秒)。 */
22
+ firstTokenMs?: number;
23
+ /** 是否保存了可查看的 Prompt。 */
24
+ hasPrompt?: boolean;
25
+ /** Prompt 字符数(system + user)。 */
26
+ promptChars?: number;
29
27
  }
30
28
  export declare function useLlmLiveFeed(enabled?: boolean): {
31
29
  connected: boolean;
@@ -2,6 +2,11 @@
2
2
  * 「实时调用 / LIVE」—— 内嵌到 AI 进度窗的下半区(详情层),不再独立浮窗。
3
3
  * 连接 /llm-live/stream,按 LLM 调用分组展示模型实时输出。
4
4
  *
5
+ * 除实时输出外,这里还承担两件与「花了多少」有关的事:
6
+ * 1) 每次调用显示 token 用量(输入↑ / 输出↓ / 思考)与耗时、首字耗时;
7
+ * 2) 「查看 Prompt」按 sessionId 拉取这次调用**实际发送**的 system / user。
8
+ * 顶部另有一条「本次运行」累计条(进程内,重启归零)。
9
+ *
5
10
  * 状态色全部走 panel.module.css 的 .lfPill / .lfDot(主题令牌 + --nf-streaming),
6
11
  * 不再内联硬编码色;布局性 inline style 保留(均为令牌引用或动态值)。
7
12
  */
@@ -1,7 +1,7 @@
1
1
  import type { NovelApi } from '../api.ts';
2
2
  import type { SavedModel } from '../../protocol.ts';
3
- /** 目录不可用时回退的内置预设(历史行为)。 */
4
- export declare const FALLBACK_MODEL_PRESETS: readonly ["deepseek-v4-flash", "deepseek-v4-pro", "deepseek-chat", "deepseek-reasoner"];
3
+ /** 目录不可用时回退的内置预设(对齐当前模型目录)。 */
4
+ export declare const FALLBACK_MODEL_PRESETS: readonly ["deepseek-flash", "deepseek-v4-pro", "deepseek-chat", "deepseek-reasoner"];
5
5
  export interface ModelManagerProps {
6
6
  api: NovelApi;
7
7
  provider: string;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * 提示词槽位:给作者留几个**安全可改**的位置来微调本书表达偏好。
3
+ *
4
+ * 边界写在界面上:槽位只**追加偏好**,不能覆盖道藏、写作红线、九条内容合规红线、
5
+ * 反 AI 规则与阶段契约 —— 这些由宿主锁定。清空即恢复默认。
6
+ */
7
+ import type { NovelApi } from '../api.ts';
8
+ export interface PromptSlotsViewProps {
9
+ api: NovelApi;
10
+ }
11
+ export default function PromptSlotsView({ api }: PromptSlotsViewProps): JSX.Element;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * 张力曲线:把「每章该有多紧」画出来并可逐章调整。
3
+ *
4
+ * 图上是三条线:
5
+ * 参考(虚线,形状预设) / 目标(实线,作者或规划设定) / 实际(散点,审稿打分)。
6
+ * 下方列出规则核对发现的曲线级问题(连续同值、高位不回落、长期低位、实际偏离目标)。
7
+ *
8
+ * 张力只影响写作提示词里的"松紧"指引,不改变任何校验规则。
9
+ */
10
+ import type { NovelApi } from '../api.ts';
11
+ export interface TensionViewProps {
12
+ api: NovelApi;
13
+ }
14
+ export default function TensionView({ api }: TensionViewProps): JSX.Element;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * 故事时间线:把逐章抽取的「时间点 / 地点 / 在场角色 / 事件」按章节顺序排开,
3
+ * 带规则初筛 + AI 复核的矛盾清单,并支持就地修正(手改后标记为 manual)。
4
+ *
5
+ * 与「编年录」的分工:编年录记事实状态,这里记**顺序**——专门抓时间倒流、
6
+ * 地点瞬移、跨度不合理这类长篇最容易崩的地方。
7
+ */
8
+ import type { NovelApi } from '../api.ts';
9
+ export interface TimelineViewProps {
10
+ api: NovelApi;
11
+ /** 章节号列表(用于「抽取本章」下拉)。 */
12
+ chapters: number[];
13
+ }
14
+ export default function TimelineView({ api, chapters }: TimelineViewProps): JSX.Element;
@@ -12,7 +12,7 @@
12
12
  */
13
13
  export declare const COMPLIANCE_REDLINES: ReadonlyArray<string>;
14
14
  import type { Context } from '@deepseek-ai/cordis';
15
- import type { AdaptationDimension, AdaptAnalyzeResponse, AdaptationMapping, AdaptationRules, AdaptProposeResponse, AuditIssue, AuthorReview, BreakdownResponse, ChapterPlan, Foreshadow, NovelConfig, OutlineCandidate, Plotline, PlotlineHealthReport, PlotlinePlan, ProjectState, ReviewReport, RoleRecord, RoleStatusCard, StoryBible, AddModelRequest, AddModelResponse, LlmModelsResponse, LlmVendorsResponse, LlmProvidersResponse, RemoveProviderRequest, RemoveProviderResponse, LlmTestResponse, Volume, WorldState, AdaptMaterializeRequest, AdaptMaterializeResponse, AdaptMaterializeSaveRequest, AdaptMaterializeSaveResponse, MarketRadarRequest, MarketRadarResult, MarketRadarSignal, MarketRadarBriefRequest, ProductionFoundation, MarketCreativeBrief, BookAnalysisRequest, BookAnalysisResult, IdeaInspirationRequest, IdeaInspirationResult, DirectorRequest, DirectorAdvice } from './protocol.ts';
15
+ import type { AdaptationDimension, AdaptAnalyzeResponse, AdaptationMapping, AdaptationRules, AdaptProposeResponse, AuditIssue, AuthorReview, ExportScope, TimelineEvent, TimelineIssue, BreakdownResponse, ChapterPlan, Foreshadow, NovelConfig, OutlineCandidate, Plotline, PlotlineHealthReport, PlotlinePlan, ProjectState, ReviewReport, RoleRecord, RoleStatusCard, StoryBible, AddModelRequest, AddModelResponse, LlmModelsResponse, LlmVendorsResponse, LlmProvidersResponse, RemoveProviderRequest, RemoveProviderResponse, LlmTestResponse, Volume, WorldState, AdaptMaterializeRequest, AdaptMaterializeResponse, AdaptMaterializeSaveRequest, AdaptMaterializeSaveResponse, MarketRadarRequest, MarketRadarResult, MarketRadarSignal, MarketRadarBriefRequest, ProductionFoundation, MarketCreativeBrief, BookAnalysisRequest, BookAnalysisResult, IdeaInspirationRequest, IdeaInspirationResult, DirectorRequest, DirectorAdvice } from './protocol.ts';
16
16
  /** Project state file name inside the output dir. */
17
17
  export declare const PROJECT_FILE = "novel-project.json";
18
18
  /** Chapter output file name, e.g. 第001章_开篇.md */
@@ -99,7 +99,7 @@ export declare function importBookText(filePath: string, outputDir: string): {
99
99
  /** 保证风格词块已内嵌(LLM 偶发漏嵌时兜底):zh 段首前缀,en 末尾追加。 */
100
100
  export declare function suggestOutlines(ctx: Context, config: NovelConfig, idea: string, count?: number, exclude?: string[]): Promise<OutlineCandidate[]>;
101
101
  /** 拆书分析:对已写章节做结构/人物/文风/卖点四维体检。
102
- * 两阶段管道(借鉴 AI-Novel-Writing-Assistant):
102
+ * 两阶段管道(先抽取候选、再落库):
103
103
  * ① 源片段笔记:每章抽取结构化笔记(剧情/人物/设定/写法/卖点/短板信号)
104
104
  * ② 分节分析:按维度各跑一次 LLM,输出可读分析稿 + 结构化数据 + 证据链。
105
105
  * @param scope 'recent'(默认最近20章) | 'volume:N' | 'all'
@@ -331,9 +331,47 @@ export declare function runMarketIdeaInspiration(ctx: Context, config: NovelConf
331
331
  }): Promise<IdeaInspirationResult>;
332
332
  /** 自动导演编排建议:基于全书上下文,给出下一卷/阶段编排 + 修复再平衡。 */
333
333
  export declare function runDirectorAdvice(ctx: Context, config: NovelConfig, project: ProjectState, req: DirectorRequest): Promise<DirectorAdvice>;
334
+ /**
335
+ * 从一章正文抽取时间线事件(时间点 / 地点 / 参与角色 / 事件)。
336
+ *
337
+ * 会**替换**该章已有事件(重复抽取幂等);抽取结果按 order 递增排序后写回项目。
338
+ * 失败时抛错,由调用方决定是否降级(自动流程用 try/catch 包住,不阻断出章)。
339
+ *
340
+ * @param ctx 宿主上下文。
341
+ * @param config 运行时配置。
342
+ * @param project 项目状态(就地更新 `project.timeline`)。
343
+ * @param outputDir 书目录。
344
+ * @param chapterNo 章节号。
345
+ * @returns 本次抽取到的事件(已写入 project)。
346
+ */
347
+ export declare function extractTimelineForChapter(ctx: Context, config: NovelConfig, project: ProjectState, outputDir: string, chapterNo: number): Promise<TimelineEvent[]>;
348
+ /**
349
+ * AI 复核时间线:在规则初筛之外,让模型找规则抓不到的时序问题
350
+ * (年龄/季节错位、回忆与当前时间混淆、跨章跨度不合理等)。
351
+ *
352
+ * @param ctx 宿主上下文。
353
+ * @param config 运行时配置。
354
+ * @param project 项目状态。
355
+ * @returns 模型给出的问题清单(与规则初筛结果合并由调用方决定)。
356
+ */
357
+ export declare function reviewTimeline(ctx: Context, config: NovelConfig, project: ProjectState): Promise<TimelineIssue[]>;
334
358
  /** Export the whole book as one txt/md file. */
335
359
  export declare function exportBook(outputDir: string, project: ProjectState, format: 'txt' | 'md'): {
336
360
  file: string;
337
361
  chars: number;
338
362
  chapters: number;
339
363
  };
364
+ /**
365
+ * 按范围导出:整本正文 / 设定 / 规划 / 角色 / 质检记录 / 项目 JSON。
366
+ *
367
+ * @param outputDir 书目录。
368
+ * @param project 项目状态。
369
+ * @param scope 导出范围。
370
+ * @param format txt / md / json(json 只对 `project` 范围有独立含义,其余按 md 落盘)。
371
+ * @returns 文件名、字符数与章节数。
372
+ */
373
+ export declare function exportScope(outputDir: string, project: ProjectState, scope: ExportScope, format: 'txt' | 'md' | 'json'): {
374
+ file: string;
375
+ chars: number;
376
+ chapters: number;
377
+ };
@@ -39,6 +39,10 @@ export interface Config {
39
39
  reviewModel?: string;
40
40
  /** 任务级模型路由:质检模型(留空则跟随 model)。 */
41
41
  auditModel?: string;
42
+ /** 备用模型:主模型失败时自动切换重试一次(留空 = 不切换)。 */
43
+ fallbackModel?: string;
44
+ /** 出章后自动抽取故事时间线(默认开)。 */
45
+ autoTimeline?: boolean;
42
46
  /** LLM reasoning effort (off/low/high/max). */
43
47
  reasoningEffort?: 'off' | 'low' | 'high' | 'max';
44
48
  /** 分析类任务(提炼/拆书/反推大纲等)的推理档位;默认 low。 */
@@ -1,44 +1,75 @@
1
1
  /**
2
- * LLM 实况 feed(对齐上游「AI 创作实况 / LIVE LOG」的轻量实现)。
2
+ * LLM 实况 feed:以「每次 LLM 调用」为粒度,把生命周期事件(开始/流式正文/阶段变化/完成)
3
+ * 推给订阅者,并提供环形缓冲供新连上的客户端重放。
3
4
  *
4
- * 以「每次 LLM 调用」为粒度,把生命周期事件(开始/流式正文/阶段变化/完成)推给订阅者,
5
- * 并提供环形缓冲供新连上的客户端重放。不持久化(与上游缓存不同,如需成 cache 再扩展)。
5
+ * 同时承担三件与用量有关的事(进程内,不落盘):
6
+ * 1) **用量账本**:每次调用结束后按用途累计 token / 耗时,供 /status 展示;
7
+ * 2) **Prompt 记录**:保存每次调用实际发送的 system + user(环形,按需查询);
8
+ * 3) **首字耗时**:调用方在拿到第一个文本增量时打点,用于观察模型响应速度。
6
9
  */
10
+ import type { BlockAssembler, TokenUsage } from '@deepseek-ai/dsh-llm';
11
+ import type { LlmLiveFrame, LlmPromptRecord, LlmTokenUsage, LlmUsageSummary } from './protocol.ts';
12
+ export type { LlmLiveFrame };
7
13
  export type LlmLivePhase = 'requesting' | 'streaming' | 'completed' | 'failed';
8
- export interface LlmLiveFrame {
9
- type: 'session_started' | 'output_delta' | 'reasoning_delta' | 'phase_changed' | 'session_completed';
10
- /** 由服务端生成,形如 ll-<n>。 */
11
- sessionId: string;
12
- /** 本次调用用途(如 正文生成/审稿/章节规划)。 */
13
- label?: string;
14
- /** 使用模型(用于展示)。 */
15
- model?: string;
16
- /** 事件时间(ISO)。 */
17
- at: string;
18
- /** session_started / 部分帧:交互上下文。 */
19
- context?: {
20
- taskId?: string;
21
- interactionId: string;
22
- };
23
- /** output_delta / reasoning_delta:本次增量文本。 */
24
- content?: string;
25
- /** session 累计字符数。 */
26
- totalChars?: number;
27
- /** reasoning 累计字符数。 */
28
- totalReasoningChars?: number;
29
- /** phase_changed:新阶段。 */
30
- phase?: LlmLivePhase;
31
- /** phase_changed:阶段描述。 */
32
- phaseMessage?: string;
33
- /** session_completed:最终预览(截断)。 */
34
- preview?: string;
35
- /** session_completed:失败信息。 */
36
- error?: string;
37
- }
38
14
  type Listener = (frame: LlmLiveFrame) => void;
39
15
  export declare function nextSessionId(): string;
40
16
  export declare function subscribeLiveFeed(listener: Listener): () => void;
41
17
  export declare function emitLive(frame: LlmLiveFrame): void;
42
18
  export declare function liveLatest(): LlmLiveFrame[];
43
19
  export declare function clearLiveFeed(): void;
44
- export {};
20
+ /** 把 dsh-llm 的 usage 转成线上形状。 */
21
+ export declare function toLiveUsage(usage: TokenUsage | undefined): LlmTokenUsage | undefined;
22
+ /** 本次运行的用量汇总(按耗时降序排列用途,便于看谁最贵)。 */
23
+ export declare function liveUsage(): LlmUsageSummary;
24
+ /** 清零用量账本(面板「重新计数」用)。 */
25
+ export declare function resetLiveUsage(): void;
26
+ /** 记一条 Prompt(超长截断并标记)。 */
27
+ export declare function recordPrompt(input: {
28
+ sessionId: string;
29
+ label?: string;
30
+ model?: string;
31
+ system?: string;
32
+ user?: string;
33
+ }): LlmPromptRecord;
34
+ /** 取一条 Prompt 记录(不存在返回 undefined)。 */
35
+ export declare function livePrompt(sessionId: string): LlmPromptRecord | undefined;
36
+ /** 清空 Prompt 记录。 */
37
+ export declare function clearLivePrompts(): void;
38
+ /** 一次调用的打点句柄。 */
39
+ export interface LiveCallHandle {
40
+ sessionId: string;
41
+ startedAt: number;
42
+ /** 用途(用于 session_completed 的用量分组)。 */
43
+ label: string;
44
+ /** 实际使用的模型。 */
45
+ model: string;
46
+ /** 首字耗时(毫秒),由 markFirstToken 写入。 */
47
+ firstTokenMs?: number;
48
+ }
49
+ /**
50
+ * 开始一次 LLM 调用:发 session_started、记 Prompt、起计时。
51
+ *
52
+ * @param input 用途/模型/实际发送的 system 与 user。
53
+ * @returns 打点句柄(结束时交给 endLiveCall)。
54
+ */
55
+ export declare function beginLiveCall(input: {
56
+ label: string;
57
+ model: string;
58
+ system?: string;
59
+ user?: string;
60
+ }): LiveCallHandle;
61
+ /** 首字打点(调用方在收到第一个文本增量时调用,只记第一次)。 */
62
+ export declare function markFirstToken(handle: LiveCallHandle): void;
63
+ /**
64
+ * 结束一次 LLM 调用:从 assembler 取用量,发 session_completed(含 token/耗时/首字)。
65
+ *
66
+ * @param handle beginLiveCall 的句柄。
67
+ * @param assembler 本次调用的 BlockAssembler。
68
+ * @param extra 字符数/预览/失败信息等。
69
+ */
70
+ export declare function endLiveCall(handle: LiveCallHandle, assembler: BlockAssembler, extra?: {
71
+ chars?: number;
72
+ preview?: string;
73
+ phase?: 'completed' | 'failed';
74
+ error?: string;
75
+ }): void;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * 模型失败恢复规则(纯逻辑,可离线单测)。
3
+ *
4
+ * 解决的问题:批量连写时,主模型偶发限流/超时/额度耗尽会让**整批中断**,
5
+ * 而换一个模型重试一次往往就能过去。规则:
6
+ * 1) 只对「可重试」错误切换模型——用户主动取消、参数/契约类错误不重试;
7
+ * 2) 备用模型为空或与主模型相同则不切换;
8
+ * 3) 备用模型也失败时,抛**原始错误**(保留主因,不掩盖现场);
9
+ * 4) 每次调用最多切换一次。
10
+ */
11
+ /**
12
+ * 判断一次 LLM 失败是否值得换模型重试。
13
+ *
14
+ * @param error 捕获到的异常。
15
+ * @returns 可重试时为 true。
16
+ */
17
+ export declare function isRetryableLlmError(error: unknown): boolean;
18
+ /**
19
+ * 判断是否应当切换到备用模型。
20
+ *
21
+ * @param fallbackModel 配置的备用模型(空串 = 未配置)。
22
+ * @param primaryModel 本次实际使用的主模型。
23
+ * @param error 捕获到的异常。
24
+ * @returns 应当切换时为 true。
25
+ */
26
+ export declare function shouldSwitchModel(fallbackModel: string | undefined, primaryModel: string, error: unknown): boolean;
27
+ /**
28
+ * 用主模型执行;失败且满足切换条件时,用备用模型再执行一次。
29
+ *
30
+ * @param input 主模型 / 备用模型 / 实际执行函数(接收本次要用的模型 id)。
31
+ * @returns 执行结果(备用模型成功时返回其结果)。
32
+ * @throws 主模型失败且未切换时抛原错误;切换后仍失败时同样抛**主模型的原始错误**。
33
+ */
34
+ export declare function withModelFallback<T>(input: {
35
+ model: string;
36
+ fallbackModel?: string;
37
+ onSwitch?: (from: string, to: string, error: unknown) => void;
38
+ }, run: (model: string) => Promise<T>): Promise<T>;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * 「热门题材雷达」真实榜单抓取源(移植自 AI-Novel-Writing-Assistant marketRadarSources)。
2
+ * 「热门题材雷达」真实榜单抓取源(公开榜单页面的地址与选择器,属事实性配置)。
3
3
  * 用全局 fetch 抓取公开榜单移动版页面,正则抽取元数据;无第三方依赖。
4
4
  */
5
5
  export type MarketRadarPlatform = 'fanqie' | 'qidian' | 'jinjiang';
@@ -0,0 +1,57 @@
1
+ /**
2
+ * 提示词槽位:给作者留几个**安全可改**的位置,用来微调本书的写作偏好。
3
+ *
4
+ * 设计边界(很重要):
5
+ * * 槽位只追加"作者偏好",**不允许覆盖**规则工程与合规红线——道藏、写作红线、
6
+ * 九条内容合规红线、反 AI 规则、阶段契约都由宿主锁定,槽位碰不到;
7
+ * * 槽位内容随项目保存(`novel-project.json`),换书即换槽位;
8
+ * * 每个槽位有长度上限,超长直接截断并标记(沿用「截断显式化」的规则)。
9
+ */
10
+ import type { ProjectState, PromptSlotId } from './protocol.ts';
11
+ /** 单个槽位的元信息。 */
12
+ export interface PromptSlotSpec {
13
+ id: PromptSlotId;
14
+ /** 面板显示名。 */
15
+ label: string;
16
+ /** 一句话说明它影响什么。 */
17
+ hint: string;
18
+ /** 示例(面板占位符)。 */
19
+ placeholder: string;
20
+ /** 字符上限。 */
21
+ maxChars: number;
22
+ }
23
+ /** 允许作者编辑的槽位(白名单;不在此列表的提示词一律不可改)。 */
24
+ export declare const PROMPT_SLOTS: readonly PromptSlotSpec[];
25
+ /** 单个槽位的渲染结果。 */
26
+ export interface RenderedSlot {
27
+ id: PromptSlotId;
28
+ label: string;
29
+ text: string;
30
+ /** 是否因超长被截断。 */
31
+ truncated: boolean;
32
+ /** 原文长度(截断前)。 */
33
+ chars: number;
34
+ }
35
+ /**
36
+ * 读取并规整项目里的槽位内容(去空白、按上限截断)。
37
+ *
38
+ * @param project 项目状态。
39
+ * @returns 非空槽位列表(保持 PROMPT_SLOTS 的声明顺序)。
40
+ */
41
+ export declare function readPromptSlots(project: ProjectState): RenderedSlot[];
42
+ /**
43
+ * 把槽位渲染成注入提示词的段落。
44
+ *
45
+ * @param project 项目状态。
46
+ * @returns 提示词片段;无槽位时返回空串。
47
+ */
48
+ export declare function renderPromptSlots(project: ProjectState): string;
49
+ /**
50
+ * 校验并规整一次槽位写入。
51
+ *
52
+ * @param id 槽位 id。
53
+ * @param value 内容(空串表示清空该槽位)。
54
+ * @returns 归一化后的内容。
55
+ * @throws 槽位 id 不在白名单时抛错。
56
+ */
57
+ export declare function normalizeSlotValue(id: string, value: string): string;
@@ -1,5 +1,5 @@
1
- /** 章节写作约束:官方 writer 骨架渲染成可附加到现有系统提示词末尾的必达/禁止/自查块。 */
2
- export declare function renderOfficialChapterWriterSkeleton(meta: {
1
+ /** 章节写作约束:把写作骨架渲染成可附加到系统提示词末尾的必达/禁止/自查块。 */
2
+ export declare function renderChapterWriterSkeleton(meta: {
3
3
  targetChars: number;
4
4
  minChars: number;
5
5
  maxChars: number;