@sendevia/pi-ask-author 1.0.6 → 1.1.1
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 +17 -14
- package/dist/component.d.ts +76 -94
- package/dist/expansion.d.ts +52 -0
- package/dist/format.d.ts +5 -5
- package/dist/i18n.d.ts +52 -0
- package/dist/index.d.ts +18 -40
- package/dist/index.js +1 -1
- package/dist/layout.d.ts +78 -0
- package/dist/llm-texts.d.ts +230 -0
- package/dist/model.d.ts +219 -16
- package/dist/novel-markdown.d.ts +2 -2
- package/dist/novel-syntax.d.ts +38 -0
- package/dist/sanitize.d.ts +17 -2
- package/dist/schema.d.ts +161 -4
- package/dist/texts.d.ts +224 -425
- package/dist/tool-card.d.ts +64 -0
- package/dist/tool-result.d.ts +69 -0
- package/dist/view-editor.d.ts +24 -0
- package/dist/view-question.d.ts +129 -0
- package/dist/view-rows.d.ts +21 -3
- package/dist/view-summary.d.ts +42 -8
- package/lang/en_US.json +181 -0
- package/lang/ja_JP.json +156 -0
- package/lang/ko_KR.json +156 -0
- package/lang/zh_CN.json +156 -0
- package/package.json +10 -10
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file llm-texts.ts
|
|
3
|
+
* @description 面向大语言模型的协议文案:工具元数据、TypeBox 字段描述、实时推演系统提示词与返回封套模板
|
|
4
|
+
*
|
|
5
|
+
* 核心架构特性:
|
|
6
|
+
* - 全部文案恒为英文,不参与语言切换,保证封套与工具声明的前缀缓存稳定
|
|
7
|
+
* - 唯一非英文片段为封套选项标签定界符 `【】`;作者文本在 `model.ts` 编译前已经消毒,无法击穿令牌边界
|
|
8
|
+
* - 新增字段描述或错误路径时只需扩充本文件,作者界面文案仍由 `lang/*.json` 提供
|
|
9
|
+
*
|
|
10
|
+
* 依赖方向:llm-texts.ts → layout.ts(`MAX_OPTIONS` / `MAX_QUESTIONS` 插值);被 texts.ts 组装进 `TEXTS`
|
|
11
|
+
*/
|
|
12
|
+
/** 面向大模型的提示词、字段描述与返回封套:作为协议常量固定为英文,提升前缀缓存命中率 */
|
|
13
|
+
export declare const LLM_TEXTS: {
|
|
14
|
+
/** 工具注册元数据(`label` 面向作者,取自语言文件,故在此缺席) */
|
|
15
|
+
readonly tool: {
|
|
16
|
+
/** 工具调用名(LLM 协议标识,注册为 `ask_author`;不出现在作者界面) */
|
|
17
|
+
readonly name: "ask_author";
|
|
18
|
+
/** 工具说明(LLM 端英文;一句话能力概要与适用场景) */
|
|
19
|
+
readonly description: "Ask the author to decide between concrete narrative options in an interactive terminal form. Handles a single question or a batch, optional draft previews, author notes, conditional questions, and live follow-up questions generated while the author answers.";
|
|
20
|
+
/** `Available tools` 区块单行条目(LLM 端英文;动词引导,无句末标点,缺省则工具不进入该区块) */
|
|
21
|
+
readonly promptSnippet: "Ask the author to pick narrative directions in a terminal form";
|
|
22
|
+
/**
|
|
23
|
+
* `Guidelines` 区块附加条目(LLM 端英文;分类标签前缀 + 行为要求)
|
|
24
|
+
* 约定:宿主把条目扁平追加进 `Guidelines` 且不加工具名前缀,故每条首词必须显式点名 `ask_author`
|
|
25
|
+
*/
|
|
26
|
+
readonly promptGuidelines: readonly ["ask_author: Use it when the author must choose a plot branch, character action, scene direction, or setting; do not ask the same question in chat first.", "ask_author: Prefer one call over several: flat mode (formTitle + question + options) for a single decision, the questions array for related decisions.", "ask_author: Write option labels as concrete actions, put consequences in description, and add preview only when a Markdown draft or storyboard helps the choice.", "ask_author: Give a short formTitle and a tabLabel for every long question title; omit fields that use defaults.", "ask_author: Add a question id only when a showIf or expansion.insert.after refers to it; showIf may only point at an earlier question's option label or answered.", 'ask_author: Pass expansion when follow-up decisions depend on the answers: declare only the first question and let the same model add questions and options on each confirmation; expansion.insert picks "follow" (default), "tail", or {"after":"question_id"}.', "ask_author: The author can multi-select, add notes, and edit drafts; confirmed drafts return as [Draft directive: ...] lines. Treat [Author Decision Cancelled] as no decision, and [Author Consultation Error] as a request to fix the arguments once."];
|
|
27
|
+
};
|
|
28
|
+
/** LLM 端 TypeBox 字段描述字典(单句浓缩英文,仅声明语义、必要性与省略规则;数量约束来自 {@link MAX_OPTIONS} / {@link MAX_QUESTIONS} 插值) */
|
|
29
|
+
readonly schema: {
|
|
30
|
+
/** 选项标签(必须填写;要求简明动作导向,禁止占位式命名) */
|
|
31
|
+
readonly optionLabel: "Concrete action or choice label";
|
|
32
|
+
/** 选项情节推演(可选;作者端渲染为「情节设定推演」预览面板) */
|
|
33
|
+
readonly optionDesc: "Consequences or rationale (optional)";
|
|
34
|
+
/** 选项草稿预览(可选;作者端渲染为「正文草稿/分镜头试撰写」预览面板) */
|
|
35
|
+
readonly optionPreview: "Markdown draft or storyboard for this option (optional)";
|
|
36
|
+
/** 选项批注占位符(可选;作者端作为选项补充说明编辑器的输入框占位文本) */
|
|
37
|
+
readonly optionCustomPlaceholder: "Placeholder for the author's note on this option (optional)";
|
|
38
|
+
/** 选项显示条件(可选;条件不成立时该选项不展示) */
|
|
39
|
+
readonly optionShowIf: "Show only if an earlier answer matches; array means all must hold (optional)";
|
|
40
|
+
/** 题目 ID(可选;仅在后续 showIf 或 `expansion.insert.after` 引用时需要) */
|
|
41
|
+
readonly questionId: "Id referenced by showIf and expansion.insert.after (optional)";
|
|
42
|
+
/** 题目标题(必须填写;作者端呈现为题头与总览页条目) */
|
|
43
|
+
readonly questionTitle: "Question text";
|
|
44
|
+
/** Tab 栏短标签(可选;缺省时 Tab 栏显示 `Q<序号>`,显式给出可让作者在一屏内区分多题) */
|
|
45
|
+
readonly questionTabLabel: "Short tab label (default: Q<index>)";
|
|
46
|
+
/** 题目背景说明(可选;作者端呈现为题头下方的上下文行) */
|
|
47
|
+
readonly questionDesc: "Context shown under the question (optional)";
|
|
48
|
+
/** 多选开关(可选;默认 false,开启后作者端展示多选模式标签与方框勾选指示符) */
|
|
49
|
+
readonly questionMultiSelect: "Allow multiple selections (default false)";
|
|
50
|
+
/** 最小勾选数(可选;默认 1,仅多选生效,作者端据此标注强制下限) */
|
|
51
|
+
readonly questionMinSelect: "Minimum selections, multi-select only (default 1)";
|
|
52
|
+
/** 最大勾选数(可选;默认全部选项,仅多选生效,小于选项总数时作者端追加上限标注) */
|
|
53
|
+
readonly questionMaxSelect: "Maximum selections, multi-select only (default: all options)";
|
|
54
|
+
/** 零勾选提交开关(可选;默认 false,单选与多选均生效) */
|
|
55
|
+
readonly questionAllowEmpty: "Allow submitting without a selection (default false)";
|
|
56
|
+
/** 候选选项列表(必须填写;1~{@link MAX_OPTIONS} 项;建议 2~10 项) */
|
|
57
|
+
readonly questionOptions: "Options (1-30, 2-10 recommended)";
|
|
58
|
+
/** 自由批注开关(可选;默认 true,开启后作者端可额外提交非选项文本) */
|
|
59
|
+
readonly questionAllowCustom: "Allow author notes (default true)";
|
|
60
|
+
/** 作者批注占位符(可选;作者端作为题目补充说明编辑器的输入框占位文本) */
|
|
61
|
+
readonly questionCustomPlaceholder: "Placeholder for the author's note (optional)";
|
|
62
|
+
/** 题目显示条件(可选;条件不成立时该题不展示,也不计入答卷) */
|
|
63
|
+
readonly questionShowIf: "Show only if an earlier answer matches; array means all must hold (optional)";
|
|
64
|
+
/** 显示条件引用的题目 ID(必须为本次表单中更早出现的题目) */
|
|
65
|
+
readonly conditionQuestionId: "Earlier question id";
|
|
66
|
+
/** 显示条件匹配的选项(引用题目的选项标签;作者选中该选项时条件成立) */
|
|
67
|
+
readonly conditionOption: "Option label of that question that activates the condition";
|
|
68
|
+
/** 显示条件的作答存在性(缺省等价于 true;与 option 同时给出时忽略) */
|
|
69
|
+
readonly conditionAnswered: "Any answer (default true) or no answer (false); ignored when option is set";
|
|
70
|
+
/** 问卷标题(必须填写;作者端呈现为卡片标题与总览页标题) */
|
|
71
|
+
readonly formTitle: "Short title for the consultation";
|
|
72
|
+
/** 问卷背景(可选;作者端呈现为问卷级上下文) */
|
|
73
|
+
readonly formDesc: "Overall context (optional)";
|
|
74
|
+
/** 批量模式题目数组(可选;1~{@link MAX_QUESTIONS} 题;与平铺单题模式互斥) */
|
|
75
|
+
readonly formQuestions: "Questions (1-20); batch related decisions in one call";
|
|
76
|
+
/** 平铺模式的题目标题(可选;与批量模式的 `questionTitle` 同义) */
|
|
77
|
+
readonly formSingleQuestion: "Question for flat mode";
|
|
78
|
+
/** 平铺单题模式字段描述的统一区分前缀(LLM 端英文,`schema.ts` 逐字段拼接) */
|
|
79
|
+
readonly flatPrefix: "(Flat single-question mode only) ";
|
|
80
|
+
/** 平铺模式候选选项列表(可选;1~{@link MAX_OPTIONS} 项;建议 2~10 项) */
|
|
81
|
+
readonly flatQuestionOptions: "Options for flat mode (1-30, 2-10 recommended)";
|
|
82
|
+
/** 实时推演开关(可选;开启后作者确认答案时由同一模型追加题目与选项) */
|
|
83
|
+
readonly expansion: "Generate follow-up questions and options while the author answers; insert controls where they land (optional)";
|
|
84
|
+
/** 推演题目总量上限(可选;缺省为全部可用题目数) */
|
|
85
|
+
readonly expansionMaxQuestions: "Question cap including declared ones (default 20)";
|
|
86
|
+
/** 推演方向指示(可选;作为长期要求传给推演模型) */
|
|
87
|
+
readonly expansionGuidance: "Standing hint for what the expansion should probe (optional)";
|
|
88
|
+
/** 推演题目插入位置(三选一预设) */
|
|
89
|
+
readonly expansionInsert: '"follow" (default, after the answered question), "tail" (end of the form), or {"after":"question_id"} (optional)';
|
|
90
|
+
/** 自定义插入位置的锚点题目 ID */
|
|
91
|
+
readonly expansionInsertAfter: "Declared question id to insert after";
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* 实时推演协议(作者确认答案后由同一模型追加题目与选项;全英文,输出约束为纯 JSON)
|
|
95
|
+
*/
|
|
96
|
+
readonly expansion: {
|
|
97
|
+
/** 推演系统提示词(约束输出结构与新增范围,禁止复述既有内容) */
|
|
98
|
+
readonly systemPrompt: `You extend an interactive author decision form while the author answers it.
|
|
99
|
+
|
|
100
|
+
Input JSON: formTitle, formDescription, guidance, insert, maxAdditionalQuestions, questions (with ids), answers (selected labels and notes).
|
|
101
|
+
|
|
102
|
+
Return JSON only:
|
|
103
|
+
{"questions":[{"id":"unique_id","title":"...","description":"...","options":[{"label":"...","description":"..."}]}],"options":[{"questionId":"existing_id","options":[{"label":"..."}]}]}
|
|
104
|
+
|
|
105
|
+
Rules:
|
|
106
|
+
- Add at most maxAdditionalQuestions questions; fewer is better.
|
|
107
|
+
- New questions may also set multiSelect, minSelect, maxSelect, allowEmpty; every option needs a label.
|
|
108
|
+
- Add options to a declared question only when the answers reveal a choice it cannot express; use its id and unused labels.
|
|
109
|
+
- Give new questions unique ids and 2-6 concrete options; put consequences in description.
|
|
110
|
+
- Never repeat a declared question or a declared label.
|
|
111
|
+
- showIf takes {"questionId":"id","option":"label"} or {"questionId":"id","answered":true} and may only reference a question above the insertion point or one of your earlier new questions.
|
|
112
|
+
- Reply {"questions":[],"options":[]} when the answers already determine the next step.
|
|
113
|
+
- No prose, no code fences, no trailing commas.`;
|
|
114
|
+
/** 推演用户消息前缀(英文单行,后接 JSON 载荷) */
|
|
115
|
+
readonly userPreamble: "Extend this author decision form. Input JSON:";
|
|
116
|
+
/** 推演失败诊断(英文短句;作者界面经 `status.expansionFailed` 呈现,不直接展示原文) */
|
|
117
|
+
readonly errors: {
|
|
118
|
+
/** 当前会话没有可用模型 */
|
|
119
|
+
readonly noModel: "no model is available for live expansion";
|
|
120
|
+
/** 推演请求被中止 */
|
|
121
|
+
readonly aborted: "live expansion was aborted";
|
|
122
|
+
/** 供应商返回错误终止 */
|
|
123
|
+
readonly providerFailed: "the expansion request failed";
|
|
124
|
+
/** 回复不是可解析的 JSON 对象 */
|
|
125
|
+
readonly invalidReply: "the expansion reply was not a JSON object";
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
/**
|
|
129
|
+
* LLM 端返回封套与纠正指令模板(全英文确定性结构,提升 Prompt Cache 命中率)
|
|
130
|
+
*
|
|
131
|
+
* 约定:首行方括号封套令牌 `[Author Decision Finalized]` / `[Author Decision Cancelled]` / `[Author Consultation Error]`;行内令牌 `[Author note: …]` / `[Draft directive: …]`;选项标签定界符 `【】`
|
|
132
|
+
* 语言归属:`【】` 是本区段唯一的非英文片段,`model.ts` 的 `sanitizeEnvelopeText` 剥离作者文本中的 `【】` 并把 ASCII `;` 归一为全角 `;`,插值文本无法击穿上述定界符
|
|
133
|
+
*/
|
|
134
|
+
readonly markdown: {
|
|
135
|
+
/** 完成封套首行标识(固定英文令牌,恒为返回正文第一行) */
|
|
136
|
+
readonly envelopePrefix: "[Author Decision Finalized]";
|
|
137
|
+
/** 完成封套收尾指令(固定英文单行,驱动后续写作) */
|
|
138
|
+
readonly envelopeSuffix: "Proceed with the author decisions and draft directives above without conversational acknowledgment.";
|
|
139
|
+
/** 取消封套(固定两行:标识行 + 禁止沿用被丢弃决策的英文处置指令) */
|
|
140
|
+
readonly envelopeCancelled: "[Author Decision Cancelled]\nThe author cancelled. Do not proceed with the discarded decision; propose next steps or ask for clarification.";
|
|
141
|
+
/**
|
|
142
|
+
* 构建错误封套(固定三行:标识行 + 原因行 + 指引行)
|
|
143
|
+
* @param reason - 面向 LLM 的英文失败原因(取自 `errorReasons`)
|
|
144
|
+
* @param guidance - 面向 LLM 的英文纠正指引(取自 `errorGuidances`)
|
|
145
|
+
*/
|
|
146
|
+
readonly envelopeError: (reason: string, guidance: string) => string;
|
|
147
|
+
/** 错误封套原因行字典(英文单句,按失败路径枚举) */
|
|
148
|
+
readonly errorReasons: {
|
|
149
|
+
/** 非交互式终端环境无法启动 TUI(对应作者端 `status.errorNonTui`) */
|
|
150
|
+
readonly nonTui: "Interactive TUI is unavailable in the current mode.";
|
|
151
|
+
/** 入参无有效题目或选项(对应作者端 `status.errorNoQuestions`) */
|
|
152
|
+
readonly noQuestions: "No questions or options were provided.";
|
|
153
|
+
/** 构建执行期异常的原因行(`details` 为宿主异常摘要,对应作者端 `status.errorExecutionFailed`) */
|
|
154
|
+
readonly runtimeError: (details: string) => string;
|
|
155
|
+
/** 自适应条件引用无效(对应作者端 `status.errorInvalidCondition`) */
|
|
156
|
+
readonly invalidCondition: (details: string) => string;
|
|
157
|
+
/** 请求了实时推演但当前会话没有可用模型 */
|
|
158
|
+
readonly expansionUnavailable: "Live expansion was requested but no model is available in this session.";
|
|
159
|
+
};
|
|
160
|
+
/** 错误封套纠正指引行字典(英文单句,与 `errorReasons` 一一对应) */
|
|
161
|
+
readonly errorGuidances: {
|
|
162
|
+
/** `nonTui` 的纠正指引 */
|
|
163
|
+
readonly nonTui: "Use defaults, or ask the user in chat if the decision is crucial.";
|
|
164
|
+
/** `noQuestions` 的纠正指引 */
|
|
165
|
+
readonly noQuestions: "Provide at least one question with concrete options.";
|
|
166
|
+
/** `runtimeError` 的纠正指引 */
|
|
167
|
+
readonly runtimeError: "Check the arguments and retry with valid questions and options.";
|
|
168
|
+
/** `invalidCondition` 的纠正指引 */
|
|
169
|
+
readonly invalidCondition: "Reference an earlier question id plus one of its option labels, use answered, or point expansion.insert.after at a declared question id.";
|
|
170
|
+
/** `expansionUnavailable` 的纠正指引 */
|
|
171
|
+
readonly expansionUnavailable: "Drop the expansion field and declare the questions directly, or retry in a session with a model selected.";
|
|
172
|
+
};
|
|
173
|
+
/** 自适应条件引用校验的英文诊断消息(拼入错误封套原因行) */
|
|
174
|
+
readonly adaptiveErrors: {
|
|
175
|
+
/** 条件引用的题目 ID 不存在 */
|
|
176
|
+
readonly unknownQuestion: (questionId: string) => string;
|
|
177
|
+
/** 条件引用的题目不在该条件所属题目之前 */
|
|
178
|
+
readonly forwardQuestion: (questionId: string) => string;
|
|
179
|
+
/** 条件引用的选项标签在目标题目中不存在 */
|
|
180
|
+
readonly unknownOption: (questionId: string, option: string) => string;
|
|
181
|
+
/** 推演插入锚点引用的题目 ID 不存在 */
|
|
182
|
+
readonly unknownInsertAnchor: (questionId: string) => string;
|
|
183
|
+
};
|
|
184
|
+
/** 空答卷封套正文(作者未作答时的英文处置指令,位于首尾标识之间) */
|
|
185
|
+
readonly envelopeEmpty: "The author submitted no answers. Use your defaults, or ask a follow-up if the decision is crucial.";
|
|
186
|
+
/** 单题无任何作答(未勾选且无批注)时的英文占位正文 */
|
|
187
|
+
readonly noSelection: "(No selection; the author skipped this question)";
|
|
188
|
+
/** 行内批注令牌左定界符(尾随 1 空格,与 `noteSuffix` 成对) */
|
|
189
|
+
readonly notePrefix: "[Author note: ";
|
|
190
|
+
/** 行内批注令牌右定界符 */
|
|
191
|
+
readonly noteSuffix: "]";
|
|
192
|
+
/** 分镜头草稿指令令牌左定界符(尾随 1 空格,与 `draftDirectiveSuffix` 成对) */
|
|
193
|
+
readonly draftDirectivePrefix: "[Draft directive: ";
|
|
194
|
+
/** 分镜头草稿指令令牌右定界符 */
|
|
195
|
+
readonly draftDirectiveSuffix: "]";
|
|
196
|
+
/** 构建封套单题作答行(`index` 为 1-based 题号,`title` 为题目标题,`body` 为单行正文) */
|
|
197
|
+
readonly answerLine: (index: number, title: string, body: string) => string;
|
|
198
|
+
/** 同一作答行内多个片段(选项推演 / 批注令牌 / 草稿指令)的分隔符(纯 ASCII;作者文本中的 `;` 已在 `sanitizeEnvelopeText` 归一为全角 `;` 消除歧义) */
|
|
199
|
+
readonly partSeparator: "; ";
|
|
200
|
+
/** 正文草稿 / 分镜头草稿压缩为单行指令时的分镜头片段分隔符(LLM 端纯 ASCII) */
|
|
201
|
+
readonly draftSegmentSeparator: " / ";
|
|
202
|
+
/**
|
|
203
|
+
* 构建带情节推演的选项片段:`【标签】推演 [Author note: …] [Draft directive: …]`
|
|
204
|
+
* @param label - 选项标签原文(调用方已过 `sanitizeEnvelopeText`)
|
|
205
|
+
* @param desc - 选项情节推演 / 设定阐述(调用方已过 `sanitizeEnvelopeText`)
|
|
206
|
+
* @param draftDirective - 已包装的 `[Draft directive: …]` 令牌(可选,非原文)
|
|
207
|
+
* @param note - 已包装的 `[Author note: …]` 令牌(可选,非原文)
|
|
208
|
+
* @returns 输出顺序恒为「标签 → 推演 → 批注 → 草稿指令」,与形参顺序不同处在于批注先于草稿
|
|
209
|
+
*/
|
|
210
|
+
readonly optionWithDesc: (label: string, desc: string, draftDirective?: string, note?: string) => string;
|
|
211
|
+
/**
|
|
212
|
+
* 构建无情节推演的选项片段:`【标签】[Author note: …] [Draft directive: …]`
|
|
213
|
+
* @param label - 选项标签原文(调用方已过 `sanitizeEnvelopeText`)
|
|
214
|
+
* @param draftDirective - 已包装的 `[Draft directive: …]` 令牌(可选,非原文)
|
|
215
|
+
* @param note - 已包装的 `[Author note: …]` 令牌(可选,非原文)
|
|
216
|
+
* @returns 输出顺序恒为「标签 → 批注 → 草稿指令」,与形参顺序不同处在于批注先于草稿
|
|
217
|
+
*/
|
|
218
|
+
readonly optionWithoutDesc: (label: string, draftDirective?: string, note?: string) => string;
|
|
219
|
+
/**
|
|
220
|
+
* 构建未选中选项上的孤立批注片段(英文前缀 + 标签 + 批注令牌)
|
|
221
|
+
* @param label - 选项标签原文(调用方已过 `sanitizeEnvelopeText`)
|
|
222
|
+
* @param note - 已包装的 `[Author note: …]` 令牌(非原文)
|
|
223
|
+
*/
|
|
224
|
+
readonly unselectedOptionNote: (label: string, note: string) => string;
|
|
225
|
+
/** 生成题目标题的英文占位(`index` 为 1-based 题号;仅封套编译期替换 `fallbacks.autoQuestionTitle` 与 `fallbacks.defaultQuestionTitle`,作者界面保留语言文件中的占位) */
|
|
226
|
+
readonly fallbackQuestionTitle: (index: number) => string;
|
|
227
|
+
/** 生成选项标签的英文占位(`index` 为 1-based 选项序号;仅封套编译期替换 `fallbacks.autoOptionLabel` / `fallbacks.unnamedOptionLabel` / `fallbacks.defaultOptionLabel`,作者界面保留语言文件中的占位) */
|
|
228
|
+
readonly fallbackOptionLabel: (index: number) => string;
|
|
229
|
+
};
|
|
230
|
+
};
|
package/dist/model.d.ts
CHANGED
|
@@ -7,17 +7,48 @@
|
|
|
7
7
|
* - 两层清洗:`sanitize.ts` 边界归一化 + 本层对文本字段幂等兜底(绕过 sanitize 直接构造模型时同样安全)
|
|
8
8
|
* - 封套防止击穿:插值文本经由 `sanitizeEnvelopeText` 归一化,定界符不可被作者文本击穿
|
|
9
9
|
* - 封套语言单一:自动生成的占位题目/标签在封套编译期替换为英文占位(`Question N` / `Option N`),作者界面保留中文占位
|
|
10
|
-
* -
|
|
10
|
+
* - 答卷完整性:显示中的题目即使显式跳过或仅批注提交也产出条目,多选题目恒产出条目,不存在静默消失的作答
|
|
11
|
+
* - 自适应显隐:题目与选项可声明 `showIf` 条件且仅允许引用更早题目;显隐快照按 `revision` 缓存,隐藏项的选择不计入作答与答卷
|
|
12
|
+
* - 实时推演:`pushExpansion` 按 `expansion.insert` 把新题目插到跟随位、队尾或自定义锚点之后,把新选项追加到目标题目末尾,插入与撤回都会平移后续题目的条件、作答状态与推演记录索引
|
|
11
13
|
*
|
|
12
|
-
* 依赖方向:model.ts → format.ts / schema.ts / texts.ts
|
|
14
|
+
* 依赖方向:model.ts → format.ts / layout.ts / schema.ts / texts.ts(纯函数工具、上限常量、Schema 单一类型源与文案字典,无回边);不引用 `Theme` 与 TUI 组件,渲染细节全部上移组件层
|
|
13
15
|
*/
|
|
14
|
-
import type { QuestionOption, RawParams } from "./schema.js";
|
|
16
|
+
import type { ExpansionPayload, InsertPosition, QuestionOption, RawParams } from "./schema.js";
|
|
17
|
+
/**
|
|
18
|
+
* 显示条件的归一化形态:引用的题目索引与选项索引均已解析
|
|
19
|
+
* - `option`:指定题目中某个选项当前处于选中状态
|
|
20
|
+
* - `answered`:指定题目存在(或不存在)任何作答痕迹
|
|
21
|
+
*/
|
|
22
|
+
export type VisibilityCondition = {
|
|
23
|
+
readonly kind: "option";
|
|
24
|
+
readonly questionIndex: number;
|
|
25
|
+
readonly optionIndex: number;
|
|
26
|
+
} | {
|
|
27
|
+
readonly kind: "answered";
|
|
28
|
+
readonly questionIndex: number;
|
|
29
|
+
readonly answered: boolean;
|
|
30
|
+
};
|
|
31
|
+
/** 归一化后的候选选项(在 Schema 选项字段之外附带显示条件) */
|
|
32
|
+
export interface NormalizedOption {
|
|
33
|
+
/** 选项标签 */
|
|
34
|
+
label: string;
|
|
35
|
+
/** 情节推演 / 设定阐述(可选) */
|
|
36
|
+
description?: string;
|
|
37
|
+
/** 正文草稿或分镜头预览(可选) */
|
|
38
|
+
preview?: string;
|
|
39
|
+
/** 选项补充说明占位词(可选) */
|
|
40
|
+
customPlaceholder?: string;
|
|
41
|
+
/** 该选项的显示条件(空数组表示恒显示) */
|
|
42
|
+
showIf: VisibilityCondition[];
|
|
43
|
+
}
|
|
15
44
|
/** 归一化后的问题对象(字段已兜底,区间已收敛) */
|
|
16
45
|
export interface NormalizedQuestion {
|
|
17
46
|
/** 题目唯一标识符 */
|
|
18
47
|
id: string;
|
|
19
48
|
/** 题目标题 */
|
|
20
49
|
title: string;
|
|
50
|
+
/** Tab 栏短标签(可选;缺省时 Tab 栏显示题号) */
|
|
51
|
+
tabLabel?: string;
|
|
21
52
|
/** 题目背景描述(可选) */
|
|
22
53
|
description?: string;
|
|
23
54
|
/** 是否多选(默认 false,即单选) */
|
|
@@ -31,11 +62,21 @@ export interface NormalizedQuestion {
|
|
|
31
62
|
/** 是否允许 0 勾选提交(默认 false;单选与多选均生效) */
|
|
32
63
|
allowEmpty: boolean;
|
|
33
64
|
/** 归一化后的候选选项列表(至少一项) */
|
|
34
|
-
options:
|
|
65
|
+
options: NormalizedOption[];
|
|
35
66
|
/** 是否允许自由补充说明(默认 true) */
|
|
36
67
|
allowCustom: boolean;
|
|
37
68
|
/** 补充说明输入框占位提示词 */
|
|
38
69
|
customPlaceholder: string;
|
|
70
|
+
/** 该题目的显示条件(空数组表示恒显示) */
|
|
71
|
+
showIf: VisibilityCondition[];
|
|
72
|
+
}
|
|
73
|
+
/** 自适应条件非法(引用未知题目、后置题目或不存在的选项):携带面向 LLM 的英文诊断消息,由入口层映射为结构化错误结果 */
|
|
74
|
+
export declare class AdaptiveConditionError extends Error {
|
|
75
|
+
/**
|
|
76
|
+
* 构造自适应条件异常
|
|
77
|
+
* @param message - 英文诊断消息(取自 `TEXTS.markdown.adaptiveErrors`)
|
|
78
|
+
*/
|
|
79
|
+
constructor(message: string);
|
|
39
80
|
}
|
|
40
81
|
/** 单题作答状态(内部数据结构,选项索引 0-based) */
|
|
41
82
|
export interface QuestionAnswerState {
|
|
@@ -110,6 +151,22 @@ export interface MenuItem {
|
|
|
110
151
|
}
|
|
111
152
|
/** 勾选 toggle 操作语义(三个操作语义类型均不导出,由组件层消费并映射为状态栏提示) */
|
|
112
153
|
type ToggleResult = "selected" | "deselected" | "limit";
|
|
154
|
+
/** 一次推演追加的记录,作为撤回的最小单元 */
|
|
155
|
+
export interface ExpansionRecord {
|
|
156
|
+
/** 触发推演的题目索引(0-based) */
|
|
157
|
+
readonly questionIndex: number;
|
|
158
|
+
/** 触发时该题的答案签名(选中项标签按序拼接) */
|
|
159
|
+
readonly signature: string;
|
|
160
|
+
/** 追加题目在题目列表中的起始位置(即触发题目的下一题) */
|
|
161
|
+
readonly insertAt: number;
|
|
162
|
+
/** 本次插入的题目数 */
|
|
163
|
+
readonly addedQuestions: number;
|
|
164
|
+
/** 本次向既有题目追加的选项 */
|
|
165
|
+
readonly addedOptions: readonly {
|
|
166
|
+
readonly questionIndex: number;
|
|
167
|
+
readonly count: number;
|
|
168
|
+
}[];
|
|
169
|
+
}
|
|
113
170
|
/** 全选/清空操作语义 */
|
|
114
171
|
type SelectAllResult = "cleared" | "filled";
|
|
115
172
|
/** 批注保存操作语义 */
|
|
@@ -122,24 +179,36 @@ type NoteSaveResult = "saved" | "cleared" | "savedUnselected" | "savedAtLimit";
|
|
|
122
179
|
export declare function hasQuestionNote(st: QuestionAnswerState): boolean;
|
|
123
180
|
/**
|
|
124
181
|
* 问卷数据模型:题目归一化、作答状态流转与答卷编译;全部状态突变经本类 mutation 方法完成并自动推进 `revision`,组件层只读
|
|
125
|
-
* 只读访问面:`revision` / `questionCount` / `getQuestion` / `getState` / `getSelectedIndices`(0-based 升序,排序不变量唯一实现)/ `getMenuItems` / `isQuestionAnswered` / `areAllQuestionsAnswered` / `compileAnswers` / `formatAnswers`
|
|
182
|
+
* 只读访问面:`revision` / `questionCount` / `getQuestion` / `getState` / `getSelectedIndices`(0-based 升序,排序不变量唯一实现)/ `getSelectedIndex` / `isQuestionVisible` / `isOptionVisible` / `getVisibleQuestionIndices` / `hasAnyResponse` / `getMenuItems` / `isQuestionAnswered` / `areAllQuestionsAnswered` / `compileAnswers` / `formatAnswers`
|
|
126
183
|
*/
|
|
127
184
|
export declare class AskAuthorFormModel {
|
|
128
185
|
/** 问卷总标题 */
|
|
129
186
|
readonly formTitle: string;
|
|
130
187
|
/** 问卷全局背景阐述(可选) */
|
|
131
188
|
readonly formDescription?: string;
|
|
132
|
-
/**
|
|
133
|
-
readonly
|
|
189
|
+
/** 推演允许的题目总量上限(含初始题目) */
|
|
190
|
+
readonly expansionMaxQuestions: number;
|
|
191
|
+
/** 推演题目的插入位置预设(缺省为跟随触发题目) */
|
|
192
|
+
readonly expansionInsert: InsertPosition;
|
|
193
|
+
/** 归一化后的题目列表(推演会向末尾追加,外部只读) */
|
|
194
|
+
private readonly questionList;
|
|
195
|
+
/** 推演追加记录栈(按追加顺序;撤回时从栈顶开始) */
|
|
196
|
+
private readonly expansions;
|
|
134
197
|
/** 作答状态版本号:单调递增,作为组件层派生缓存的失效键 */
|
|
135
198
|
private revisionValue;
|
|
136
199
|
/** 各题作答状态映射(题目索引 0-based) */
|
|
137
200
|
private readonly states;
|
|
201
|
+
/** 显隐快照缓存(按 `revision` 失效;包含题目可见性、选项可见性与可见选中项) */
|
|
202
|
+
private visibilityCache?;
|
|
138
203
|
/**
|
|
139
204
|
* 构造问卷数据模型实例(单题平铺与批量问卷两模式归一化)
|
|
140
205
|
* @param rawParams - 经 prepareArguments 规整的入参;`questions` 缺省或为空数组时回退单题平铺模式,`options` 亦为空则题目列表为空
|
|
141
206
|
*/
|
|
142
207
|
constructor(rawParams: RawParams);
|
|
208
|
+
/** 归一化后的题目列表(推演会插入新题目,调用方只读) */
|
|
209
|
+
get questions(): NormalizedQuestion[];
|
|
210
|
+
/** 推演题目插入位置的可读描述(供推演模型判定哪些题目位于插入点之前) */
|
|
211
|
+
get expansionInsertLabel(): string;
|
|
143
212
|
/** 当前作答状态版本号(组件层派生缓存失效键) */
|
|
144
213
|
get revision(): number;
|
|
145
214
|
/** 问卷题目总数 */
|
|
@@ -158,31 +227,164 @@ export declare class AskAuthorFormModel {
|
|
|
158
227
|
*/
|
|
159
228
|
getState(index: number): QuestionAnswerState;
|
|
160
229
|
/**
|
|
161
|
-
*
|
|
230
|
+
* 获取当前显隐快照(按 `revision` 缓存,题目顺序一次正向计算)
|
|
231
|
+
* 条件只允许引用更早题目,故单次正向遍历即可得出全部可见性;被引用题目自身不可见时,引用它的条件恒不成立
|
|
232
|
+
* @returns 题目可见性、各题选项可见性与各题可见选中项索引(升序)
|
|
233
|
+
*/
|
|
234
|
+
private snapshot;
|
|
235
|
+
/**
|
|
236
|
+
* 判定单个显示条件是否成立
|
|
237
|
+
* @param condition - 已解析的条件
|
|
238
|
+
* @param questions - 已完成计算的题目可见性(下标小于当前题)
|
|
239
|
+
* @param selected - 已完成计算的可见选中项(下标小于当前题)
|
|
240
|
+
* @returns 被引用题目不可见时为 false;否则按选项选中态或作答存在性判定
|
|
241
|
+
*/
|
|
242
|
+
private conditionHolds;
|
|
243
|
+
/**
|
|
244
|
+
* 判定指定题目是否留下过任何作答痕迹(已选选项或非空批注,不区分选项当前是否可见)
|
|
245
|
+
* @param qIndex - 题目顺序索引(0-based)
|
|
246
|
+
* @returns 存在任意痕迹时为 true;状态未创建时为 false
|
|
247
|
+
*/
|
|
248
|
+
hasAnyResponse(qIndex: number): boolean;
|
|
249
|
+
/**
|
|
250
|
+
* 判定指定题目是否可见
|
|
251
|
+
* @param index - 题目顺序索引(0-based)
|
|
252
|
+
* @returns 无显示条件或全部条件成立时为 true;索引越界为 false
|
|
253
|
+
*/
|
|
254
|
+
isQuestionVisible(index: number): boolean;
|
|
255
|
+
/**
|
|
256
|
+
* 判定指定选项是否可见
|
|
257
|
+
* @param qIndex - 题目顺序索引(0-based)
|
|
258
|
+
* @param optionIndex - 选项索引(0-based)
|
|
259
|
+
* @returns 无显示条件或全部条件成立时为 true;索引越界为 false
|
|
260
|
+
*/
|
|
261
|
+
isOptionVisible(qIndex: number, optionIndex: number): boolean;
|
|
262
|
+
/**
|
|
263
|
+
* 获取当前可见题目索引
|
|
264
|
+
* @returns 升序索引数组;第一题无前置题目可引用,因此结果恒非空(题目列表非空时)
|
|
265
|
+
*/
|
|
266
|
+
getVisibleQuestionIndices(): number[];
|
|
267
|
+
/**
|
|
268
|
+
* 获取已勾选且当前可见的选项索引(0-based 升序,未作答为空数组;排序不变量唯一实现在此)
|
|
269
|
+
* 选项因条件失效而隐藏时,其既有勾选不计入作答与答卷;条件恢复后重新生效
|
|
162
270
|
* @param qIndex - 题目顺序索引(0-based)
|
|
163
271
|
* @returns 全新数组(调用方可安全持有而不影响内部状态);题目越界或状态未创建时为空数组
|
|
164
272
|
*/
|
|
165
273
|
getSelectedIndices(qIndex: number): number[];
|
|
274
|
+
/**
|
|
275
|
+
* 获取单选选中项索引(可见选中项为空时 undefined)
|
|
276
|
+
* @param qIndex - 题目顺序索引(0-based)
|
|
277
|
+
* @returns 首个可见选中项索引;未选中、选中项已隐藏或索引越界时为 undefined
|
|
278
|
+
*/
|
|
279
|
+
getSelectedIndex(qIndex: number): number | undefined;
|
|
166
280
|
/** 推进作答状态版本号(全部 mutation 方法的统一出口) */
|
|
167
281
|
private bumpRevision;
|
|
282
|
+
/**
|
|
283
|
+
* 获取推演追加记录快照
|
|
284
|
+
* @returns 按追加顺序排列的新数组(调用方可安全持有)
|
|
285
|
+
*/
|
|
286
|
+
getExpansions(): readonly ExpansionRecord[];
|
|
287
|
+
/**
|
|
288
|
+
* 计算指定题目的答案签名(判断该题推演结果是否仍然有效)
|
|
289
|
+
* @param qIndex - 题目顺序索引(0-based)
|
|
290
|
+
* @returns 可见选中项标签按索引升序以 `|` 拼接的签名;未作答或题目越界时为 undefined
|
|
291
|
+
*/
|
|
292
|
+
answerSignature(qIndex: number): string | undefined;
|
|
293
|
+
/**
|
|
294
|
+
* 计算推演题目的插入位置
|
|
295
|
+
* @param insert - 插入位置预设(跟随触发题目 / 队尾 / 指定题目之后)
|
|
296
|
+
* @param triggerIndex - 触发推演的题目索引(0-based,仅 `follow` 使用)
|
|
297
|
+
* @returns 插入位置(0-based;`questionList.length` 表示队尾)
|
|
298
|
+
* @throws AdaptiveConditionError 自定义锚点引用的题目不存在时抛出
|
|
299
|
+
*/
|
|
300
|
+
private resolveInsertIndex;
|
|
301
|
+
/**
|
|
302
|
+
* 写入一次推演结果(新题目按 {@link expansionInsert} 指定的位置插入,新选项追加到目标题目的选项末尾)
|
|
303
|
+
* @param input - 记录元数据与推演载荷;超出 {@link expansionMaxQuestions} 的新题目被截断
|
|
304
|
+
* @returns 本次追加记录(无实际追加时计数均为 0,记录仍入栈以标记该答案已推演)
|
|
305
|
+
* @throws AdaptiveConditionError 追加内容的条件引用未知题目、后置题目或不存在的选项时抛出(本次追加整体不生效)
|
|
306
|
+
*/
|
|
307
|
+
pushExpansion(input: {
|
|
308
|
+
questionIndex: number;
|
|
309
|
+
signature: string;
|
|
310
|
+
payload: ExpansionPayload;
|
|
311
|
+
}): ExpansionRecord;
|
|
312
|
+
/**
|
|
313
|
+
* 撤回指定题目及其后续题目的推演结果(作者改动答案后调用)
|
|
314
|
+
* @param questionIndex - 起始题目索引(0-based);触发题目不早于该索引的记录全部撤回
|
|
315
|
+
* @returns 撤回的记录数;无记录被撤回时为 0(状态与 `revision` 均不变)
|
|
316
|
+
*/
|
|
317
|
+
retractFrom(questionIndex: number): number;
|
|
318
|
+
/**
|
|
319
|
+
* 判定指定题目在确认时是否会发起实时推演
|
|
320
|
+
* 条件:表单开启推演、该题可见、题目总数未达上限,且该题尚未用当前答案推演过
|
|
321
|
+
* @param qIndex - 题目顺序索引(0-based)
|
|
322
|
+
* @param enabled - 本表单是否开启推演(由组件层持有)
|
|
323
|
+
* @returns 确认本题将发起新的推演请求时为 true
|
|
324
|
+
*/
|
|
325
|
+
canExpandOnConfirm(qIndex: number, enabled: boolean): boolean;
|
|
326
|
+
/**
|
|
327
|
+
* 归一化并在指定位置插入新题目(全部条件先校验,任一非法项抛出时整个批次不生效)
|
|
328
|
+
* @param at - 插入位置(原有索引不小于该值的题目整体后移)
|
|
329
|
+
* @param raws - 推演给出的原始题目
|
|
330
|
+
* @returns 实际插入的题目数
|
|
331
|
+
*/
|
|
332
|
+
private insertQuestions;
|
|
333
|
+
/**
|
|
334
|
+
* 把索引不小于指定值的题目引用整体平移(插入或移除题目后维持既有条件、作答状态与推演记录的指向)
|
|
335
|
+
* @param from - 起始索引(0-based)
|
|
336
|
+
* @param delta - 位移量(插入为正、移除为负)
|
|
337
|
+
*/
|
|
338
|
+
private remapQuestionIndices;
|
|
339
|
+
/**
|
|
340
|
+
* 向既有题目追加选项(全部条件先校验,任一非法项抛出时整个批次不生效)
|
|
341
|
+
* @param additions - 按题目 ID 归集的追加选项
|
|
342
|
+
* @returns 每题实际追加的选项数
|
|
343
|
+
*/
|
|
344
|
+
private appendOptions;
|
|
345
|
+
/**
|
|
346
|
+
* 回滚单条推演记录(先回滚选项追加,再移除插入的题目块)
|
|
347
|
+
* @param record - 待回滚的推演记录
|
|
348
|
+
*/
|
|
349
|
+
private rollback;
|
|
350
|
+
/**
|
|
351
|
+
* 剪除被移除选项上的选中与批注状态
|
|
352
|
+
* @param questionIndex - 题目顺序索引(0-based)
|
|
353
|
+
* @param keptCount - 保留的选项数
|
|
354
|
+
*/
|
|
355
|
+
private dropOptionsFromState;
|
|
356
|
+
/**
|
|
357
|
+
* 生成不与既有题目重复的题目 ID
|
|
358
|
+
* @param id - 推演给出的原始 ID(可为自动占位)
|
|
359
|
+
* @param index - 题目顺序索引(0-based)
|
|
360
|
+
* @param used - 已占用的 ID 集合(就地更新)
|
|
361
|
+
* @returns 未占用的题目 ID
|
|
362
|
+
*/
|
|
363
|
+
private uniqueQuestionId;
|
|
168
364
|
/**
|
|
169
365
|
* 派生指定题目的左侧菜单项列表(预设选项 + 自由补充入口 + 确认推进按钮)
|
|
170
366
|
* 菜单项标签与确认按钮明细随当前作答状态派生:多选展示已选数/下限,单选展示已选项标签、自定义方案名或「未选」
|
|
171
367
|
* @param qIndex - 题目顺序索引(0-based)
|
|
172
|
-
* @returns 顺序固定的菜单项数组(选项按声明顺序在前、补充入口居中、确认按钮殿后);题目越界为空数组;`allowCustom` 为 false
|
|
368
|
+
* @returns 顺序固定的菜单项数组(选项按声明顺序在前、补充入口居中、确认按钮殿后);题目越界为空数组;`allowCustom` 为 false 时省略补充入口;不可见选项不产出条目
|
|
173
369
|
*/
|
|
174
370
|
getMenuItems(qIndex: number): MenuItem[];
|
|
371
|
+
/**
|
|
372
|
+
* 判定指定题目是否存在会被提交的批注(题目级批注或可见选项上的批注)
|
|
373
|
+
* @param qIndex - 题目顺序索引(0-based)
|
|
374
|
+
* @returns 存在非空批注时为 true;状态未创建时为 false
|
|
375
|
+
*/
|
|
376
|
+
private hasSubmittableNote;
|
|
175
377
|
/**
|
|
176
378
|
* 判定指定题目是否满足作答下限提交要求
|
|
177
379
|
* 单选:已选一项、`allowEmpty`,或 `allowCustom` 开启且存在任意非空批注;多选:勾选数达 `minSelect`、`allowEmpty` 下 0 选,
|
|
178
380
|
* 或缺省下限未勾选(`minSelect` 未显式给出时收敛为 `min(1, 选项数)`,故此时 `count < minSelect` 等价于 `count === 0`)且 `allowCustom` 开启并存在任意非空批注
|
|
179
381
|
* @param qIndex - 题目顺序索引(0-based)
|
|
180
|
-
* @returns
|
|
382
|
+
* @returns 题目越界或该题当前不可见时恒为 false;作答状态尚未懒加载时按空作答判定,不写入任何状态
|
|
181
383
|
*/
|
|
182
384
|
isQuestionAnswered(qIndex: number): boolean;
|
|
183
385
|
/**
|
|
184
|
-
*
|
|
185
|
-
* @returns
|
|
386
|
+
* 判定问卷中当前可见的题目是否均已作答完毕(隐藏题目不参与判定)
|
|
387
|
+
* @returns 所有可见题目均满足 {@link isQuestionAnswered} 时为 true;无可见题目时恒为 true(`every` 的空集语义)
|
|
186
388
|
*/
|
|
187
389
|
areAllQuestionsAnswered(): boolean;
|
|
188
390
|
/**
|
|
@@ -234,17 +436,18 @@ export declare class AskAuthorFormModel {
|
|
|
234
436
|
*/
|
|
235
437
|
clearQuestionNote(qIndex: number): boolean;
|
|
236
438
|
/**
|
|
237
|
-
*
|
|
439
|
+
* 收集全部非空选项批注(含未选中选项上的孤立批注,跳过当前不可见的选项),键转换为 1-based;与总览页展示保持一致
|
|
440
|
+
* @param qIndex - 题目顺序索引(0-based)
|
|
238
441
|
* @param q - 归一化题目对象
|
|
239
442
|
* @param st - 题目作答状态(可选)
|
|
240
443
|
* @returns 1-based 选项索引 → 批注内容的映射表
|
|
241
444
|
*/
|
|
242
445
|
private collectOptionNotes;
|
|
243
446
|
/**
|
|
244
|
-
*
|
|
447
|
+
* 编译当前可见题目的作答状态为结果答案条目列表(隐藏题目不产出条目)
|
|
245
448
|
* 产出规则:多选题目恒产出条目(未作答时 `selectedOptions` / `selectedIndices` 为空);单选题目仅在已作答时才产出条目(已选一项、`allowEmpty` 主动跳过或仅批注提交)
|
|
246
|
-
*
|
|
247
|
-
* @returns
|
|
449
|
+
* 两种模式均杜绝可见题目在结果封套中静默消失;0-based 内部索引统一转换为 1-based
|
|
450
|
+
* @returns 与题目顺序一致的答卷条目列表(未产出的单选题目不占位);无已作答题目时为空数组
|
|
248
451
|
*/
|
|
249
452
|
compileAnswers(): AuthorAnswerItem[];
|
|
250
453
|
/**
|
package/dist/novel-markdown.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @description 小说语法高亮引擎:把语义定界符(`「」` / `()`)标注为携带哨兵的原生行内代码,折行与配色交还 Pi 原生 Markdown 引擎
|
|
4
4
|
*
|
|
5
5
|
* 核心架构特性:
|
|
6
|
-
* -
|
|
6
|
+
* - 声明式语法模板表(定义于 `novel-syntax.ts` 的 `NOVEL_TEMPLATES` 与派生索引)驱动「定界符 → 语义哨兵 → 行内代码载体 → 主题样式」映射
|
|
7
7
|
* - 对话台词「...」与旁白心理(...)由 `MarkdownOptions.transform` 在 marked 解析前完成标注
|
|
8
8
|
* - `theme.code` 回调兼作哨兵分类器,未命中哨兵即完整交还原生行内代码样式
|
|
9
9
|
* - 折行、嵌套与配色全部由原生 Markdown 引擎回填,插件不触碰任何 ANSI 转义流
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* 索引约定:全文「扁平索引 / 行内列索引」一律指 UTF-16 码元下标,含义与 `String.prototype.length` / `charAt` / `slice` 一致
|
|
13
13
|
*
|
|
14
|
-
* 依赖方向:novel-markdown.ts → format.ts / texts.ts / theme.ts(另行引入 model.ts 的 `MenuItem` 类型,无回环依赖);外部依赖 `@earendil-works/pi-tui` 的 `Markdown` / `MarkdownTheme` 与 `@earendil-works/pi-coding-agent` 的 `getMarkdownTheme`
|
|
14
|
+
* 依赖方向:novel-markdown.ts → format.ts / novel-syntax.ts / texts.ts / theme.ts(另行引入 model.ts 的 `MenuItem` 类型,无回环依赖);外部依赖 `@earendil-works/pi-tui` 的 `Markdown` / `MarkdownTheme` 与 `@earendil-works/pi-coding-agent` 的 `getMarkdownTheme`
|
|
15
15
|
* 宿主主题耦合:围栏代码块由 `getMarkdownTheme()` 的 `highlightCode` 着色,提取颜色读取宿主全局活动主题(须宿主已 `initTheme`,本模块不自行持有主题);行内代码仅在未命中哨兵时走同一全局 `code` 提取颜色
|
|
16
16
|
*/
|
|
17
17
|
import type { MenuItem } from "./model.js";
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file novel-syntax.ts
|
|
3
|
+
* @description 小说语法资产:定界符 → 语义哨兵 → 原生 Markdown 载体 → 主题样式的声明式模板表
|
|
4
|
+
*
|
|
5
|
+
* 核心架构特性:
|
|
6
|
+
* - 新增小说语法只需在 `NOVEL_TEMPLATES` 表尾追加一项,两个派生索引自动生成
|
|
7
|
+
* - 仅依赖 `theme.ts` 的类型别名,不装载语言资源与任何视图模块
|
|
8
|
+
*
|
|
9
|
+
* 依赖方向:novel-syntax.ts → theme.ts(仅类型);被 novel-markdown.ts 消费
|
|
10
|
+
*/
|
|
11
|
+
import type { Theme } from "./theme.js";
|
|
12
|
+
/**
|
|
13
|
+
* 语义哨兵前缀:不可见 NUL 控制字符,仅存在于 transform 产出与 `theme.code` 消费之间的中间态
|
|
14
|
+
* 约定:输入侧已在 `format.ts` 边界剥离,分类时立即剥离,绝不进入终端输出
|
|
15
|
+
*/
|
|
16
|
+
export declare const NOVEL_MARKER_PREFIX = "\0";
|
|
17
|
+
/** 小说语法模板的字段规范(定界符 → 语义哨兵 → 原生 Markdown 载体 → 主题样式) */
|
|
18
|
+
export interface NovelSyntaxTemplate {
|
|
19
|
+
/** 开始定界符 */
|
|
20
|
+
readonly opener: string;
|
|
21
|
+
/** 结束定界符 */
|
|
22
|
+
readonly closer: string;
|
|
23
|
+
/** 语义哨兵字符(紧随 {@link NOVEL_MARKER_PREFIX} 之后,`theme.code` 分类依据;`d` = dialogue、`m` = monologue) */
|
|
24
|
+
readonly marker: string;
|
|
25
|
+
/** 单个语义跨距最大字符数(含定界符;超出视为残缺定界符并放弃高亮) */
|
|
26
|
+
readonly maxSpanLength: number;
|
|
27
|
+
/**
|
|
28
|
+
* 依据当前主题绘制语义文本
|
|
29
|
+
* @param theme - 当前 TUI 主题实例(类型定义于叶层 `theme.ts`)
|
|
30
|
+
* @param text - 已剥离哨兵的语义原文(含定界符)
|
|
31
|
+
* @returns 应用语义样式后的富文本
|
|
32
|
+
*/
|
|
33
|
+
readonly paint: (theme: Theme, text: string) => string;
|
|
34
|
+
}
|
|
35
|
+
/** 开定界符 → 语法模板 的 `O(1)` 索引(同开定界符按声明顺序后者覆盖前者) */
|
|
36
|
+
export declare const NOVEL_TEMPLATE_BY_OPENER: ReadonlyMap<string, NovelSyntaxTemplate>;
|
|
37
|
+
/** 语义哨兵字符 → 语法模板 的 `O(1)` 索引(`theme.code` 分类依据) */
|
|
38
|
+
export declare const NOVEL_TEMPLATE_BY_MARKER: ReadonlyMap<string, NovelSyntaxTemplate>;
|