@sendevia/pi-ask-author 1.0.1 → 1.0.2

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.
@@ -0,0 +1,494 @@
1
+ /**
2
+ * @file texts.ts
3
+ * @description 布局常量、终端图标、小说语法模板表与全局双向文案字典的单一来源,禁止各层直接书写文案字面量
4
+ *
5
+ * 核心架构特性:
6
+ * - 全插件唯一文案来源:任何可见字符串均必须经由本模块获取数值,其余各层只做获取数值、组合与排版
7
+ * - `LAYOUT_CONFIG` / `ICONS` / `MAX_*` 集中声明可调参数与强制上限,便于统一调整参数与验证边界
8
+ * - `NOVEL_TEMPLATES` 以「定界符 → 语义哨兵 → 原生 Markdown 载体 → 主题样式」单表声明小说语法,新增语法只需在表尾追加一项
9
+ * - `TEXTS` 按受众严格分区,LLM 面恒为英文,作者面恒为中文,两侧文案不得互相渗透
10
+ *
11
+ * 分区导航(与文件自上而下的顺序一致):终端呈现常量 → 载荷上限 → 小说语法资产 → 共享排版字面量 → 文案字典 `TEXTS`
12
+ * 受众划分:`tool`(`label` 除外)/ `schema` / `markdown` 面向 LLM,唯一非英文片段为封套定界符 `【】`;`tool.label` / `command` / `common` / `tabs` / `view` / `menu` / `editor` / `status` / `help` / `summary` / `card` / `fallbacks` 面向作者
13
+ *
14
+ * 依赖方向:texts.ts → theme.ts(仅类型);作为叶层被 format / schema / model / novel-markdown / view-rows / view-summary / component / index 消费,禁止任何反向导入
15
+ */
16
+ import type { Theme } from "./theme.js";
17
+ /** TUI 视图几何、分栏与滚动行为配置(数值项均标注单位 ms / cols / lines;分隔线条目为字符文本,不带单位) */
18
+ export declare const LAYOUT_CONFIG: {
19
+ /** 双栏中间分隔符文本 */
20
+ readonly dividerChar: " │ ";
21
+ /** 外层框架分割线字符(双横线) */
22
+ readonly dividerThick: "═";
23
+ /** 内部内容分割线字符(单横线) */
24
+ readonly dividerThin: "─";
25
+ /** 中间分隔符可见宽度 (cols) */
26
+ readonly dividerWidth: 3;
27
+ /** 左栏最大可见宽度 (cols) */
28
+ readonly leftColMax: 46;
29
+ /** 左栏最小可见宽度 (cols) */
30
+ readonly leftColMin: 30;
31
+ /** 宽屏双栏下左栏占视口比例 */
32
+ readonly leftColRatio: 0.42;
33
+ /** 触发宽屏双栏的终端最小列宽 (cols) */
34
+ readonly splitViewMinWidth: 72;
35
+ /** 预览区单次翻页滚动行数 (lines) */
36
+ readonly previewScrollStep: 3;
37
+ /** 窄屏单栏折叠预览区最大可见行数 (lines) */
38
+ readonly singlePreviewHeight: 7;
39
+ /** 宽屏双栏右侧预览区最大可见行数 (lines) */
40
+ readonly splitPreviewHeight: 14;
41
+ /** 总览页单页最大内容行数 (lines) */
42
+ readonly summaryPageSize: 16;
43
+ /** 总览页单次滚动步长 (lines) */
44
+ readonly summaryScrollStep: 3;
45
+ /** 菜单每页最大可见选项数 (项) */
46
+ readonly visibleOptionCount: 7;
47
+ /** 底部状态通知停留时长 (ms) */
48
+ readonly statusDurationMs: 3000;
49
+ };
50
+ /** Nerd Font 状态与操作语义图标字典(按语义分区排列;四个选择指示符自带 1 空格尾随填充以拉开与标签的间距) */
51
+ export declare const ICONS: {
52
+ /** 取消 / 中止 */
53
+ readonly cancel: "󰜺";
54
+ /** 完成与确认(结果卡片与总览页完成态) */
55
+ readonly check: "󰄬";
56
+ /** 题目已完成 */
57
+ readonly checked: "󰐾";
58
+ /** 题目未完成 */
59
+ readonly unchecked: "󰄰";
60
+ /** 失败 */
61
+ readonly error: "󰅚";
62
+ /** 补充说明 */
63
+ readonly note: "󰷬";
64
+ /** 菜单行首聚焦指示 */
65
+ readonly cursor: "";
66
+ /** 列表与总览页向上方向提示 */
67
+ readonly scrollUp: "";
68
+ /** 列表与总览页向下方向提示 */
69
+ readonly scrollDown: "";
70
+ /** 多选已勾选(方括号复选框样式) */
71
+ readonly checkedMulti: "[󰄬] ";
72
+ /** 多选未勾选(方括号复选框样式) */
73
+ readonly uncheckedMulti: "[ ] ";
74
+ /** 单选已选中(圆括号圆钮样式) */
75
+ readonly checkedSingle: "(•) ";
76
+ /** 单选未选中(圆括号圆钮样式) */
77
+ readonly uncheckedSingle: "( ) ";
78
+ };
79
+ /** 单题最大选项数上限(项;同时约束 `schema.ts` 的 `options.maxItems`(批量与平铺两处)与 `sanitize.ts` 的选项截断) */
80
+ export declare const MAX_OPTIONS = 30;
81
+ /** 单份问卷最大题目数上限(题;同时约束 `schema.ts` 的 `questions.maxItems` 与 `sanitize.ts` 的题目截断) */
82
+ export declare const MAX_QUESTIONS = 20;
83
+ /**
84
+ * 语义哨兵前缀:不可见 NUL 控制字符,仅存在于 transform 产出与 `theme.code` 消费之间的中间态
85
+ * 约定:输入侧已在 `format.ts` 边界剥离,分类时立即剥离,绝不进入终端输出
86
+ */
87
+ export declare const NOVEL_MARKER_PREFIX = "\0";
88
+ /** 小说语法模板的字段规范(定界符 → 语义哨兵 → 原生 Markdown 载体 → 主题样式) */
89
+ export interface NovelSyntaxTemplate {
90
+ /** 开始定界符 */
91
+ readonly opener: string;
92
+ /** 结束定界符 */
93
+ readonly closer: string;
94
+ /** 语义哨兵字符(紧随 {@link NOVEL_MARKER_PREFIX} 之后,`theme.code` 分类依据;`d` = dialogue、`m` = monologue) */
95
+ readonly marker: string;
96
+ /** 单个语义跨距最大字符数(含定界符;超出视为残缺定界符并放弃高亮) */
97
+ readonly maxSpanLength: number;
98
+ /**
99
+ * 依据当前主题绘制语义文本
100
+ * @param theme - 当前 TUI 主题实例(类型定义于叶层 `theme.ts`)
101
+ * @param text - 已剥离哨兵的语义原文(含定界符)
102
+ * @returns 应用语义样式后的富文本
103
+ */
104
+ readonly paint: (theme: Theme, text: string) => string;
105
+ }
106
+ /** 开定界符 → 语法模板 的 `O(1)` 索引(同开定界符按声明顺序后者覆盖前者) */
107
+ export declare const NOVEL_TEMPLATE_BY_OPENER: ReadonlyMap<string, NovelSyntaxTemplate>;
108
+ /** 语义哨兵字符 → 语法模板 的 `O(1)` 索引(`theme.code` 分类依据) */
109
+ export declare const NOVEL_TEMPLATE_BY_MARKER: ReadonlyMap<string, NovelSyntaxTemplate>;
110
+ /** 全局双向文案字典:统一定义面向大模型 (LLM) 的提示词规范与面向用户 (TUI) 的界面文案 */
111
+ export declare const TEXTS: {
112
+ /** 工具注册元数据:`name` / `description` / `promptSnippet` / `promptGuidelines` 面向 LLM(英文高信噪比),`label` 面向作者(TUI 工具行中文名) */
113
+ readonly tool: {
114
+ /** 工具调用名(LLM 协议标识,注册为 `ask_author`;不出现在作者界面) */
115
+ readonly name: "ask_author";
116
+ /** 工具展示名(作者端中文,经 `index.ts` 渲染为会话调用卡片标题) */
117
+ readonly label: "向作者请示";
118
+ /** 工具说明(LLM 端英文;业务能力概要与调用场景) */
119
+ readonly description: "Consult the author on novel creative decisions, plot branches, scene drafting, or story settings. Supports single and batched questions with plot deductions and draft previews.";
120
+ /** `Available tools` 区块单行条目(LLM 端英文;动词引导,无句末标点,缺省则工具不进入该区块) */
121
+ readonly promptSnippet: "Consult author on novel creative decisions, plot branches, scene drafting, or story settings";
122
+ /**
123
+ * `Guidelines` 区块附加条目(LLM 端英文;分类标签前缀 + 行为要求)
124
+ * 约定:宿主把条目扁平追加进 `Guidelines` 且不加工具名前缀,故每条首词必须显式点名 `ask_author`
125
+ */
126
+ readonly promptGuidelines: readonly ["Invoke ask_author DIRECTLY without conversational preambles or greetings whenever author confirmation is needed for plot, characters, scenes, branches, or settings.", "ask_author modes: Always provide a concise formTitle. Use flat mode (formTitle + question + options) for single decisions, or the questions array for batch consultations. Batch interrelated decisions into one ask_author call; never make consecutive single-question calls.", "ask_author payload minimization: Strictly omit optional fields (id, description, minSelect, maxSelect, allowEmpty, allowCustom, customPlaceholder) when using default behavior. Never output auto-generated IDs or default boolean flags.", "ask_author option quality: Provide clear, descriptive labels summarizing the core action (never generic placeholders like 'Option A'). Include cause-and-effect plot deductions in description, and draft excerpts or dialogue in preview when helpful.", "ask_author multi-select & notes: Set multiSelect: true only when choices are not mutually exclusive. The author can always submit free-form notes alongside choices (both per-question and per-option).", "ask_author draft directives: Confirmed draft previews are automatically condensed into single-line directives in the ask_author result to guide subsequent story drafting.", "ask_author envelopes: [Author Decision Cancelled] means do not proceed with the discarded decision; [Author Consultation Error] means fix the arguments and retry once, then continue without the confirmation if it fails again."];
127
+ };
128
+ /** LLM 端 TypeBox 字段描述字典(单句浓缩英文,仅声明语义、必要性与省略规则;数量约束来自 {@link MAX_OPTIONS} / {@link MAX_QUESTIONS} 插值) */
129
+ readonly schema: {
130
+ /** 选项标签(必须填写;要求简明动作导向,禁止占位式命名) */
131
+ readonly optionLabel: "Concise, descriptive label summarizing the action or choice";
132
+ /** 选项情节推演(可选;作者端渲染为「情节设定推演」预览面板) */
133
+ readonly optionDesc: "Plot consequences, rationale, or deduction (optional)";
134
+ /** 选项草稿预览(可选;作者端渲染为「正文草稿/分镜头试撰写」预览面板) */
135
+ readonly optionPreview: "Draft prose excerpt or scene storyboard preview (optional, Markdown)";
136
+ /** 选项批注占位符(可选;作者端作为选项补充说明编辑器的输入框占位文本) */
137
+ readonly optionCustomPlaceholder: "Placeholder text for option note input (optional)";
138
+ /** 题目 ID(可选;供批量模式与返回封套稳定引用) */
139
+ readonly questionId: "Question identifier (optional, omit unless needed)";
140
+ /** 题目标题(必须填写;作者端呈现为 Tab 标签与题头) */
141
+ readonly questionTitle: "Question title (e.g., 'Choose the next route')";
142
+ /** 题目背景说明(可选;作者端呈现为题头下方的上下文行) */
143
+ readonly questionDesc: "Context or background for this question (optional)";
144
+ /** 多选开关(可选;默认 false,开启后作者端展示多选模式标签与方框勾选指示符) */
145
+ readonly questionMultiSelect: "Allow multiple selections (default false)";
146
+ /** 最小勾选数(可选;默认 1,仅多选生效,作者端据此标注强制下限) */
147
+ readonly questionMinSelect: "Minimum selections required (default 1)";
148
+ /** 最大勾选数(可选;默认全部选项,仅多选生效,小于选项总数时作者端追加上限标注) */
149
+ readonly questionMaxSelect: "Maximum selections allowed (default: all options)";
150
+ /** 零勾选提交开关(可选;默认 false,仅多选生效,开启后作者端标注可不选) */
151
+ readonly questionAllowEmpty: "Allow submitting with zero selections (default false)";
152
+ /** 候选选项列表(必须填写;1~{@link MAX_OPTIONS} 项;建议 2~10 项) */
153
+ readonly questionOptions: "Candidate options (1-30, 2-10 recommended)";
154
+ /** 自由批注开关(可选;默认 true,开启后作者端可额外提交非选项文本) */
155
+ readonly questionAllowCustom: "Allow author free-form custom notes (default true)";
156
+ /** 作者批注占位符(可选;作者端作为题目补充说明编辑器的输入框占位文本) */
157
+ readonly questionCustomPlaceholder: "Placeholder text for author note input (optional)";
158
+ /** 问卷标题(必须填写;作者端呈现为卡片标题与总览页标题) */
159
+ readonly formTitle: "Concise title describing the overall consultation task";
160
+ /** 问卷背景(可选;作者端呈现为问卷级上下文) */
161
+ readonly formDesc: "Overall context or background for the consultation (optional)";
162
+ /** 批量模式题目数组(可选;1~{@link MAX_QUESTIONS} 题;与平铺单题模式互斥) */
163
+ readonly formQuestions: "Batched related questions (1-20); batch interrelated decisions into one call instead of consecutive single-question calls";
164
+ /** 平铺模式的题目标题(可选;与批量模式的 `questionTitle` 同义) */
165
+ readonly formSingleQuestion: "Question title for flat single-question mode";
166
+ /** 平铺单题模式字段描述的统一区分前缀(LLM 端英文,`schema.ts` 逐字段拼接) */
167
+ readonly flatPrefix: "(Flat single-question mode only) ";
168
+ /** 平铺模式候选选项列表(可选;1~{@link MAX_OPTIONS} 项;建议 2~10 项) */
169
+ readonly flatQuestionOptions: "Candidate options for flat single-question mode (1-30, 2-10 recommended)";
170
+ };
171
+ /**
172
+ * LLM 端返回封套与纠正指令模板(全英文确定性结构,提升 Prompt Cache 命中率)
173
+ *
174
+ * 约定:首行方括号封套令牌 `[Author Decision Finalized]` / `[Author Decision Cancelled]` / `[Author Consultation Error]`;行内令牌 `[Author note: …]` / `[Draft directive: …]`;选项标签定界符 `【】`
175
+ * 语言归属:`【】` 是本区段唯一的非英文片段,`model.ts` 的 `sanitizeEnvelopeText` 剥离作者文本中的 `【】` 并把 ASCII `;` 归一为全角 `;`,插值文本无法击穿上述定界符
176
+ */
177
+ readonly markdown: {
178
+ /** 完成封套首行标识(固定英文令牌,恒为返回正文第一行) */
179
+ readonly envelopePrefix: "[Author Decision Finalized]";
180
+ /** 完成封套收尾指令(固定英文单行,驱动下游正文写作与大纲更新) */
181
+ readonly envelopeSuffix: "Execute workflow and writing strictly according to the author decisions and draft directives above without conversational acknowledgments.";
182
+ /** 取消封套(固定两行:标识行 + 禁止沿用被丢弃决策的英文处置指令) */
183
+ readonly envelopeCancelled: "[Author Decision Cancelled]\nThe author cancelled the consultation. Do not proceed with the discarded decision; propose next steps or ask for clarification in chat.";
184
+ /**
185
+ * 构建错误封套(固定三行:标识行 + 原因行 + 指引行)
186
+ * @param reason - 面向 LLM 的英文失败原因(取自 `errorReasons`)
187
+ * @param guidance - 面向 LLM 的英文纠正指引(取自 `errorGuidances`)
188
+ */
189
+ readonly envelopeError: (reason: string, guidance: string) => string;
190
+ /** 错误封套原因行字典(英文单句,按失败路径枚举) */
191
+ readonly errorReasons: {
192
+ /** 非交互式终端环境无法启动 TUI(对应作者端 {@link TEXTS.status.errorNonTui}) */
193
+ readonly nonTui: "Interactive TUI is unavailable in the current execution mode.";
194
+ /** 入参无有效题目或选项(对应作者端 {@link TEXTS.status.errorNoQuestions}) */
195
+ readonly noQuestions: "No valid questions or options were provided in arguments.";
196
+ /** 构建执行期异常的原因行(`details` 为宿主异常摘要,对应作者端 {@link TEXTS.status.errorExecutionFailed}) */
197
+ readonly runtimeError: (details: string) => string;
198
+ };
199
+ /** 错误封套纠正指引行字典(英文单句,与 `errorReasons` 一一对应) */
200
+ readonly errorGuidances: {
201
+ /** `nonTui` 的纠正指引 */
202
+ readonly nonTui: "Proceed using defaults or ask user via standard chat/message if crucial.";
203
+ /** `noQuestions` 的纠正指引 */
204
+ readonly noQuestions: "Provide at least one question with concrete options.";
205
+ /** `runtimeError` 的纠正指引 */
206
+ readonly runtimeError: "Check arguments format and retry with valid questions and options.";
207
+ };
208
+ /** 空答卷封套正文(作者未作答时的英文处置指令,位于首尾标识之间) */
209
+ readonly envelopeEmpty: "Author submitted the form without any answers. Proceed with your defaults, or ask a follow-up question in chat if the decision is crucial.";
210
+ /** 单题无任何作答(未勾选且无批注)时的英文占位正文 */
211
+ readonly noSelection: "(No selection; author skipped this question)";
212
+ /** 行内批注令牌左定界符(尾随 1 空格,与 `noteSuffix` 成对) */
213
+ readonly notePrefix: "[Author note: ";
214
+ /** 行内批注令牌右定界符 */
215
+ readonly noteSuffix: "]";
216
+ /** 分镜头草稿指令令牌左定界符(尾随 1 空格,与 `draftDirectiveSuffix` 成对) */
217
+ readonly draftDirectivePrefix: "[Draft directive: ";
218
+ /** 分镜头草稿指令令牌右定界符 */
219
+ readonly draftDirectiveSuffix: "]";
220
+ /** 构建封套单题作答行(`index` 为 1-based 题号,`title` 为题目标题,`body` 为单行正文) */
221
+ readonly answerLine: (index: number, title: string, body: string) => string;
222
+ /** 同一作答行内多个片段(选项推演 / 批注令牌 / 草稿指令)的分隔符(纯 ASCII;作者文本中的 `;` 已在 `sanitizeEnvelopeText` 归一为全角 `;` 消除歧义) */
223
+ readonly partSeparator: "; ";
224
+ /** 正文草稿 / 分镜头草稿压缩为单行指令时的分镜头片段分隔符(LLM 端纯 ASCII) */
225
+ readonly draftSegmentSeparator: " / ";
226
+ /**
227
+ * 构建带情节推演的选项片段:`【标签】推演 [Author note: …] [Draft directive: …]`
228
+ * @param label - 选项标签原文(调用方已过 `sanitizeEnvelopeText`)
229
+ * @param desc - 选项情节推演 / 设定阐述(调用方已过 `sanitizeEnvelopeText`)
230
+ * @param draftDirective - 已包装的 `[Draft directive: …]` 令牌(可选,非原文)
231
+ * @param note - 已包装的 `[Author note: …]` 令牌(可选,非原文)
232
+ * @returns 输出顺序恒为「标签 → 推演 → 批注 → 草稿指令」,与形参顺序不同处在于批注先于草稿
233
+ */
234
+ readonly optionWithDesc: (label: string, desc: string, draftDirective?: string, note?: string) => string;
235
+ /**
236
+ * 构建无情节推演的选项片段:`【标签】[Author note: …] [Draft directive: …]`
237
+ * @param label - 选项标签原文(调用方已过 `sanitizeEnvelopeText`)
238
+ * @param draftDirective - 已包装的 `[Draft directive: …]` 令牌(可选,非原文)
239
+ * @param note - 已包装的 `[Author note: …]` 令牌(可选,非原文)
240
+ * @returns 输出顺序恒为「标签 → 批注 → 草稿指令」,与形参顺序不同处在于批注先于草稿
241
+ */
242
+ readonly optionWithoutDesc: (label: string, draftDirective?: string, note?: string) => string;
243
+ /**
244
+ * 构建未选中选项上的孤立批注片段(英文前缀 + 标签 + 批注令牌)
245
+ * @param label - 选项标签原文(调用方已过 `sanitizeEnvelopeText`)
246
+ * @param note - 已包装的 `[Author note: …]` 令牌(非原文)
247
+ */
248
+ readonly unselectedOptionNote: (label: string, note: string) => string;
249
+ /** 生成题目标题的英文占位(`index` 为 1-based 题号;仅封套编译期替换 `fallbacks.autoQuestionTitle` 与 `fallbacks.defaultQuestionTitle`,作者界面保留中文占位) */
250
+ readonly fallbackQuestionTitle: (index: number) => string;
251
+ /** 生成选项标签的英文占位(`index` 为 1-based 选项序号;仅封套编译期替换 `fallbacks.autoOptionLabel` / `fallbacks.unnamedOptionLabel` / `fallbacks.defaultOptionLabel`,作者界面保留中文占位) */
252
+ readonly fallbackOptionLabel: (index: number) => string;
253
+ };
254
+ /** `/ask-author` 辅助命令的注册元数据与作者可见提示文案 */
255
+ readonly command: {
256
+ /** 命令名(注册为 `ask-author`,作者输入 `/ask-author` 触发) */
257
+ readonly name: "ask-author";
258
+ /** 命令说明(命令面板与补全列表展示) */
259
+ readonly description: "查看 ask_author 创作请示工具说明";
260
+ /** 命令执行后的提示通知(引导作者改用自然语言指示 AI 调用工具) */
261
+ readonly notify: "在提示词中指示 AI 调用 ask_author,即可向您请示创作决策";
262
+ };
263
+ /** 跨视图共享的通用排版片段(枚举分隔符、列表条目前缀与按键帮助分隔符) */
264
+ readonly common: {
265
+ /** 选项标签并列展示时的中文枚举分隔符(总览页与结果卡片共用) */
266
+ readonly optionSeparator: "、";
267
+ /** 列表条目统一前缀(作者端圆点标记;卡片行与预览面板提示行的唯一字面量来源) */
268
+ readonly bulletPrefix: "• ";
269
+ /** 底部按键帮助行各片段之间的分隔符 */
270
+ readonly helpSeparator: " • ";
271
+ };
272
+ /** Tab 导航栏标签文案(各题目页与答卷提交页) */
273
+ readonly tabs: {
274
+ /** 渲染题目页标签(`index` 为 1-based 题号) */
275
+ readonly questionTab: (index: number) => string;
276
+ /** 答卷提交页标签(末尾固定页,前面附带完成徽标) */
277
+ readonly submitTab: "󰄬 提交答卷";
278
+ };
279
+ /** 作答视图与预览面板的结构化文案(题目标签、补充横幅、滚动信息与 Markdown 区块构建器,按界面渲染顺序排列) */
280
+ readonly view: {
281
+ /** 单选模式标签 */
282
+ readonly modeSingle: "[单选]";
283
+ /**
284
+ * 按勾选数量上下限渲染多选模式标签(形如 `[多选 可不选 至多3项]`)
285
+ * @param min - 模型收敛后的最小勾选数(区间 `0 ~ optionCount`)
286
+ * @param max - 模型收敛后的最大勾选数(区间 `min ~ optionCount`)
287
+ * @param optionCount - 本题候选选项数
288
+ * @param allowEmpty - 是否允许零勾选提交
289
+ * @returns `min > 1` 时标注强制下限;`min === 0` 或 `allowEmpty` 时标注可不选;`max` 小于选项总数时追加上限标注,等于总数则视为无额外上限而省略
290
+ */
291
+ readonly modeMulti: (min: number, max: number, optionCount: number, allowEmpty: boolean) => string;
292
+ /** 渲染题目级补充说明横幅(`text` 为补充说明原文,位于题头下方) */
293
+ readonly attachedNoteBanner: (text: string) => string;
294
+ /** 渲染选项级补充说明横幅(`label` 为选项标签,位于对应选项行下方) */
295
+ readonly optionAttachedNoteBanner: (label: string, text: string) => string;
296
+ /** 渲染列表上方剩余项数提示(`count` 为剩余项数) */
297
+ readonly listTopMore: (count: number) => string;
298
+ /** 渲染列表下方剩余项数提示(`count` 为剩余项数) */
299
+ readonly listBottomMore: (count: number) => string;
300
+ /** 选项补充说明面板标题 */
301
+ readonly optionNotePanelTitle: "选项补充说明";
302
+ /** 选项情节推演面板标题 */
303
+ readonly storyDescriptionPanelTitle: "情节设定推演";
304
+ /** 选项正文草稿 / 分镜头面板标题 */
305
+ readonly storyDraftPanelTitle: "正文草稿/分镜头试撰写";
306
+ /** 题目补充说明面板标题 */
307
+ readonly customNotePanelTitle: "题目补充说明";
308
+ /** 确认入口的按键说明面板标题 */
309
+ readonly confirmPanelTitle: "操作说明";
310
+ /** 已填写的题目补充说明字段标签(下方接说明原文) */
311
+ readonly customNotePanelCurrent: "当前填写内容:";
312
+ /** 渲染草稿预览的行区间与滚动提示(`start` / `end` / `total` 均为 1-based 行号与总行数) */
313
+ readonly previewScrollInfo: (start: number, end: number, total: number) => string;
314
+ /** 草稿预览缺省占位(无内容可预览时) */
315
+ readonly previewNoDraft: "(无内容预览)";
316
+ /** 题目补充说明面板的按键提示行集合 */
317
+ readonly customNotePanelTips: readonly ["• 随时按「N」或选择此项按「Enter」填写/修改本题补充说明", "• 按「x」清空本题补充说明", "• 补充说明将随所选选项及选项专属补充说明一并提交给 AI 助手"];
318
+ /** 确认面板的按键提示行集合 */
319
+ readonly confirmPanelTips: readonly ["• 按 `Enter` 确认本题并前往下一题", "• 最后一题确认后自动进入总览提交页"];
320
+ /** 构建预览面板 Markdown 三级小节标题(`title` 为小节标题原文) */
321
+ readonly sectionTitle: (title: string) => string;
322
+ /** 构建预览面板 Markdown bold 字段标签(`label` 为字段标签原文) */
323
+ readonly fieldLabel: (label: string) => string;
324
+ /** 构建预览面板 Markdown 斜体提示行(`text` 为提示行原文) */
325
+ readonly italicLine: (text: string) => string;
326
+ };
327
+ /** 题目菜单条目文案(补充说明入口与确认推进按钮) */
328
+ readonly menu: {
329
+ /** 补充说明入口标签(未填写态) */
330
+ readonly customNoteLabel: "󰷬 题目补充说明";
331
+ /** 补充说明入口标签(已填写态) */
332
+ readonly customNoteLabelFilled: "󰷬 题目补充说明 (已填写)";
333
+ /** 渲染多选确认按钮的已选计数明细(`selectedCount` 为已选数,`minSelect` 为下限;未达下限时呈现为 `已选 n/min`) */
334
+ readonly confirmDetailMulti: (selectedCount: number, minSelect: number) => string;
335
+ /** 渲染确认本题按钮标签(`detail` 取自 `confirmDetailMulti`,空串时不显示括号;`hasNote` 为真时追加补充标记) */
336
+ readonly confirmLabel: (detail: string, hasNote: boolean) => string;
337
+ /** 确认按钮的下方说明(声明确认后的跳转目标) */
338
+ readonly confirmDesc: "确认并前往下一题或总览页";
339
+ };
340
+ /** 全屏补充说明编辑器的标题与页脚按键帮助文案 */
341
+ readonly editor: {
342
+ /** 渲染题目补充说明编辑器标题(`questionTitle` 为题目标题) */
343
+ readonly title: (questionTitle: string) => string;
344
+ /** 渲染选项补充说明编辑器标题(`optionLabel` 为选项标签,`questionTitle` 为题目标题) */
345
+ readonly optionTitle: (questionTitle: string, optionLabel: string) => string;
346
+ /** 编辑器页脚按键帮助(保存与取消) */
347
+ readonly footerHelp: " [Enter] 保存并返回 • [Esc/Ctrl+C] 取消";
348
+ };
349
+ /** 底部临时状态栏通知(操作结果、阻断原因与阻断后的纠正操作路径) */
350
+ readonly status: {
351
+ /** 题目补充说明已保存 */
352
+ readonly questionNoteSaved: "已保存本题补充说明";
353
+ /** 题目补充说明已清空 */
354
+ readonly noteCleared: "已清空本题补充说明";
355
+ /** 提示选项批注已保存(`label` 为选项标签;该选项已勾选) */
356
+ readonly optionNoteSaved: (label: string) => string;
357
+ /** 提示选项批注已保存但该选项未勾选(`label` 为选项标签;批注仅作批注提交) */
358
+ readonly optionNoteSavedUnselected: (label: string) => string;
359
+ /** 提示已达批注选项数量上限(`max` 为上限值,批注保留但该选项不再勾选) */
360
+ readonly optionNoteSavedAtLimit: (max: number) => string;
361
+ /** 提示选项批注已清空(`label` 为选项标签) */
362
+ readonly optionNoteCleared: (label: string) => string;
363
+ /** 提示单选已达上限(`limit` 为本题勾选上限;再次勾选被拒) */
364
+ readonly selectedUpToLimit: (limit: number) => string;
365
+ /** 提示多选已达上限(`max` 为本题勾选上限;引导取消已选项后重选) */
366
+ readonly maxSelectExceeded: (max: number) => string;
367
+ /** 提示多选未达下限(`min` 为本题勾选下限;引导继续勾选或改用补充说明) */
368
+ readonly minSelectRequired: (min: number) => string;
369
+ /** 提交时本题仍无任何作答:并列给出两条通过路径 */
370
+ readonly pleaseAnswerBeforeSubmit: "本题未完成:请按「空格/Enter」选定选项,或按「n/N」填写补充说明";
371
+ /** 编辑器放弃修改的二次确认(首次 Esc/Ctrl+C 触发) */
372
+ readonly editorDiscardConfirm: "再次按 Esc/Ctrl+C 放弃修改(未保存内容将丢失),继续输入可撤销";
373
+ /** 非交互式终端环境无法启动交互界面(与 `markdown.errorReasons.nonTui` 对应) */
374
+ readonly errorNonTui: "当前为非交互式终端环境,无法启动交互界面,请在交互式 TUI 会话中重试";
375
+ /** 入参无有效题目或选项(与 `markdown.errorReasons.noQuestions` 对应) */
376
+ readonly errorNoQuestions: "未提供有效的问题或选项,请提供至少一个带选项的问题";
377
+ /** 生成组件执行期异常通知(`reason` 为宿主异常摘要,与 `markdown.errorReasons.runtimeError` 对应) */
378
+ readonly errorExecutionFailed: (reason: string) => string;
379
+ };
380
+ /** 底部按键帮助行片段(标准键名 + 动宾结构,经 {@link TEXTS.common.helpSeparator} 拼接;条目按按键字母序排列) */
381
+ readonly help: {
382
+ /** Esc / Ctrl+C:取消并关闭当前界面 */
383
+ readonly cancel: "Esc/Ctrl+C 取消";
384
+ /** x:清空光标所在位置的补充说明 */
385
+ readonly clearNote: "x 清空补充说明";
386
+ /** Enter:多选模式下确认本题并推进 */
387
+ readonly confirmQuestion: "Enter 确认";
388
+ /** Enter:总览页提交整份答卷 */
389
+ readonly confirmSubmit: "Enter 提交";
390
+ /** n:编辑题目补充说明(光标位于题目补充说明菜单项时) */
391
+ readonly editNote: "n 补充说明";
392
+ /** n:编辑当前选项的专属批注(光标位于选项行时) */
393
+ readonly editOptionNote: "n 选项补充说明";
394
+ /** N:直接编辑题目补充说明(光标位于选项行时的大写入口) */
395
+ readonly editQuestionNote: "N 题目补充说明";
396
+ /** 1~9:按题号直接跳转题目(题目数大于 1 时展示) */
397
+ readonly jumpQuestion: "1~9 跳转题目";
398
+ /** ↑↓ / jk:上下移动光标 */
399
+ readonly moveCursor: "↑↓/jk 移动";
400
+ /** PgUp / PgDn:作答页滚动草稿预览(存在预览内容时展示) */
401
+ readonly scrollDraft: "PgUp/PgDn 滚动草稿";
402
+ /** ↑↓ / PgUp / PgDn:总览页滚动内容 */
403
+ readonly scrollSummary: "↑↓/PgUp/PgDn 滚动";
404
+ /** 空格 / Enter:单选模式下选定当前项 */
405
+ readonly selectOptionSingle: "空格/Enter 选定";
406
+ /** Tab / ←→ / hl:切换题目或总览页 */
407
+ readonly switchQuestion: "Tab/←→/hl 切换题目";
408
+ /** a:多选模式下全选或清空 */
409
+ readonly toggleAll: "a 全选/清空";
410
+ /** 空格:多选模式下勾选或取消当前项 */
411
+ readonly toggleCheckMulti: "空格 勾选";
412
+ };
413
+ /** 答卷总览确认页文案(完成状态徽标、已选项标签、选项批注角标与滚动提示,按页面自上而下的渲染顺序排列) */
414
+ readonly summary: {
415
+ /** 页标题 */
416
+ readonly title: "【答卷总览与确认】";
417
+ /** 单题完成徽标 */
418
+ readonly statusDone: "[已完成]";
419
+ /** 单题未完成徽标 */
420
+ readonly statusPending: "[未完成]";
421
+ /** 渲染题目块标题(`index` 为 1-based 题号,`title` 为题目标题) */
422
+ readonly questionPrefix: (index: number, title: string) => string;
423
+ /** 已选选项行标签 */
424
+ readonly selectedLabel: "已选:";
425
+ /** 已选选项行尾的批注存在角标 */
426
+ readonly optionNoteBadge: "(󰷬 批注)";
427
+ /** 渲染选项级批注行标签(`optLabel` 为所属选项标签) */
428
+ readonly optionNotePrefix: (optLabel: string) => string;
429
+ /** 题目级补充说明行标签 */
430
+ readonly customNoteLabel: "󰷬 题目补充说明:";
431
+ /** 全部完成时的提交引导行 */
432
+ readonly allDonePrompt: " 󰄬 全部完成,按 Enter/Ctrl+S 提交答卷,Tab/hl/1-9 返回修改";
433
+ /** 存在未完成题目时的阻断提示行 */
434
+ readonly pendingWarning: " 󰄰 尚有题目未完成,按 Enter 前往作答,Tab/hl/1-9 切换修改";
435
+ /** 渲染总览页上方剩余行数提示(`count` 为剩余行数) */
436
+ readonly topMore: (count: number) => string;
437
+ /** 渲染总览页下方剩余行数提示(`count` 为剩余行数) */
438
+ readonly bottomMore: (count: number) => string;
439
+ };
440
+ /** 会话回放卡片文案(调用态摘要、结果态徽标与展开态明细) */
441
+ readonly card: {
442
+ /** 渲染调用态多题摘要(`qCount` 为题数,`optCount` 为选项总数;形如 `3 题 • 8 选项`) */
443
+ readonly callQuestionAndOptionCount: (qCount: number, optCount: number) => string;
444
+ /** 渲染调用态平铺单题摘要(`optCount` 为选项数;形如 `4 选项`) */
445
+ readonly callOptionCountOnly: (optCount: number) => string;
446
+ /** 作者取消(warning 配色) */
447
+ readonly resultCancelled: "󰜺 已取消";
448
+ /** 执行失败(error 配色;展开态另附错误原因) */
449
+ readonly resultErrorShort: "󰅚 执行失败";
450
+ /** 正常完成(success 配色) */
451
+ readonly resultCompleted: "󰄬 已完成";
452
+ /** 渲染结果态已作答题目数(`n` 为题目数;形如 `3 项`,经 `common.bulletPrefix` 拼接) */
453
+ readonly resultCount: (n: number) => string;
454
+ /** 折叠态展开提示(键位取自宿主 `app.tools.expand`) */
455
+ readonly expandHint: "展开查看明细";
456
+ /** 渲染展开态单条作答行(`title` 为题目标题,`content` 为明细正文) */
457
+ readonly optionBullet: (title: string, content: string) => string;
458
+ /** 渲染带专属批注的选项标签角标(`label` 为选项标签,`note` 为批注原文;形如 `选项 [note 批注]`) */
459
+ readonly optionWithNote: (label: string, note: string) => string;
460
+ /** 渲染题目补充说明角标(`text` 为补充说明原文,追加于作答行行尾) */
461
+ readonly questionNoteTag: (text: string) => string;
462
+ /** 渲染展开态错误原因行(`err` 为宿主异常摘要) */
463
+ readonly errorBullet: (err: string) => string;
464
+ };
465
+ /**
466
+ * 兜底文案与自动命名派生:任一缺失 / 越界输入的最终回退,保证界面与封套永不空白
467
+ * 语言归属:本组为作者端中文占位,作者界面始终呈现中文
468
+ * 约定:`model.ts` 在封套编译期把 `autoQuestionTitle` / `defaultQuestionTitle` / `autoOptionLabel` / `unnamedOptionLabel` / `defaultOptionLabel` 替换为 `markdown.fallbackQuestionTitle` / `markdown.fallbackOptionLabel` 的英文占位
469
+ */
470
+ readonly fallbacks: {
471
+ /** 问卷标题缺省(模型未提供 `formTitle` 时) */
472
+ readonly defaultFormTitle: "创作决策";
473
+ /** 题目标题缺省(模型未提供标题时的作者端中文占位,封套编译期替换为英文) */
474
+ readonly defaultQuestionTitle: "请选择接下来的创作方案";
475
+ /** 题目补充说明编辑器的缺省占位文本 */
476
+ readonly defaultCustomPlaceholder: "输入本题整体补充要求、伏笔细节或修改意见...";
477
+ /** 选项补充说明编辑器的缺省占位文本 */
478
+ readonly defaultOptionCustomPlaceholder: "输入针对该选项的具体补充要求或修改细节...";
479
+ /** 自由文本作答的选项名(作者填写自定义内容时占位) */
480
+ readonly defaultCustomChoiceName: "自定义方案";
481
+ /** 选项标签缺省(模型未提供标签时的作者端中文占位,封套编译期替换为英文) */
482
+ readonly defaultOptionLabel: "默认方案";
483
+ /** 选项标签缺失且无序号信息时的占位(封套编译期替换为英文) */
484
+ readonly unnamedOptionLabel: "未命名方案";
485
+ /** 未勾选且无补充说明时的展示占位(总览页与结果卡片共用;封套中由 `markdown.noSelection` 承担同义英文) */
486
+ readonly unselectedOption: "未选";
487
+ /** 生成自动题目 ID(`n` 为 1-based 序号) */
488
+ readonly autoQuestionId: (n: number) => string;
489
+ /** 生成自动题目标题(`n` 为 1-based 序号,作者端中文占位,封套编译期替换为英文) */
490
+ readonly autoQuestionTitle: (n: number) => string;
491
+ /** 生成自动选项标签(`n` 为 1-based 序号,作者端中文占位,封套编译期替换为英文) */
492
+ readonly autoOptionLabel: (n: number) => string;
493
+ };
494
+ };
@@ -8,8 +8,6 @@
8
8
  *
9
9
  * 依赖方向:theme.ts → 无插件内依赖;外部仅取自 `@earendil-works/pi-coding-agent` 类型
10
10
  */
11
-
12
11
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
13
-
14
12
  /** TUI 主题样式提供者类型(取自扩展上下文 `ui.theme`,与 `ctx.ui.custom` / `renderCall` / `renderResult` 注入的实例同型) */
15
13
  export type Theme = ExtensionContext["ui"]["theme"];
@@ -0,0 +1,69 @@
1
+ /**
2
+ * @file view-rows.ts
3
+ * @description 纯函数视图排版层:题目菜单行、Tab 导航栏与底部框架 chrome 的统一生成器
4
+ *
5
+ * 核心架构特性:
6
+ * - 全部导出函数为无状态纯函数(无 `this` / 缓存 / IO),输出仅由入参决定,可独立单元测试与跨视图复用
7
+ * - 文案与几何常量一律取自 `texts.ts`,本层不直接书写任何可见字符
8
+ * - 折行与补齐一律经 `visibleWidth` / `wrapTextWithAnsi` / `safeLine` 处理,禁止使用 `string.length` 直接计算
9
+ *
10
+ * 依赖方向:view-rows.ts → format.ts / model.ts / texts.ts / theme.ts;禁止反向导入 `component.ts`
11
+ */
12
+ import { type AskAuthorFormModel, type MenuItem, type NormalizedQuestion, type QuestionAnswerState } from "./model.js";
13
+ import type { Theme } from "./theme.js";
14
+ /**
15
+ * 解析菜单选项的展示标签(作者可见文案兜底入口)
16
+ * @param q - 归一化题目对象
17
+ * @param optionIndex - 0-based 索引(兜底文案按 1-based 展示)
18
+ * @returns 选项自带标签;索引越界获取不到条目时回退 `TEXTS.fallbacks.autoOptionLabel`(形如「选项 3」)
19
+ */
20
+ export declare function optionLabelOf(q: NormalizedQuestion, optionIndex: number): string;
21
+ /**
22
+ * 格式化单个菜单条目行
23
+ *
24
+ * 排版不变量:
25
+ * - 首行携带「光标 + 勾选」前缀,后续折行等宽悬挂缩进、左缘与前缀一致;聚焦项全部折行套用 `selectedBg` 与 `bold` 样式至 `targetWidth`
26
+ * - 非聚焦未选中选项与题目补充说明淡化 `dim`(其余 `text`)且不超限;带专属补充说明的选项追加 `ICONS.note`
27
+ * - 快路径:标签可见宽度未超限时直接复用单行,跳过 `wrapTextWithAnsi` 正则解析
28
+ *
29
+ * @param theme - 主题样式提供者
30
+ * @param item - 目标菜单项
31
+ * @param isFocused - 是否为当前光标聚焦项
32
+ * @param q - 当前题目
33
+ * @param st - 作答状态
34
+ * @param targetWidth - 单行可用列宽(内容区宽度)
35
+ * @returns 终端行数组;标签超长时按悬挂缩进折为多行(至少一行)
36
+ */
37
+ export declare function formatMenuItemRow(theme: Theme, item: MenuItem, isFocused: boolean, q: NormalizedQuestion, st: QuestionAnswerState, targetWidth: number): string[];
38
+ /**
39
+ * 渲染完整 Tab 导航栏区块(各题标签 + 提交页标签 + 末尾单横分隔线)
40
+ *
41
+ * 算法不变量:
42
+ * - 题目标签以 `ICONS.checked` / `ICONS.unchecked` 与 `success` / `muted` 配色表达完成度,激活题页恒为
43
+ * `selectedBg` + `accent` 的 `bold` 反白(激活提交页为 `selectedBg` + `success`)
44
+ * - 提交页标签在全部作答完成时用 `success`,否则 `dim`;激活判定为 `currentTab === model.questionCount`
45
+ * - 分段按 `renderWidth` 贪心折行(段间单空格连接),超宽分段独占一行而不被丢弃;末尾恒追加一条
46
+ * `dim` 配色的单横分隔线(`LAYOUT_CONFIG.dividerThin`)
47
+ *
48
+ * @param theme - 主题样式提供者
49
+ * @param model - 问卷表单数据模型(提供题目数、作答完成度)
50
+ * @param currentTab - 激活页索引(`0 ~ questionCount - 1` 为题页,`questionCount` 为总览页)
51
+ * @param renderWidth - 终端可视宽度限制
52
+ * @returns Tab 导航栏终端行数组(至少含末尾分隔线一行;超宽时标签折为多行)
53
+ */
54
+ export declare function buildTabLines(theme: Theme, model: AskAuthorFormModel, currentTab: number, renderWidth: number): string[];
55
+ /**
56
+ * 原地追加底部按键提示行(统一 `dim` 配色 + 单空格悬挂缩进,超宽时折行)
57
+ * @param lines - 输出行数组(原地追加)
58
+ * @param parts - 按键提示片段(只读;空数组时仍追加一行以保持布局稳定)
59
+ * @param theme - 主题样式提供者
60
+ * @param renderWidth - 终端可视宽度限制(超宽时按单空格悬挂缩进折行)
61
+ */
62
+ export declare function pushHelpFooter(lines: string[], parts: readonly string[], theme: Theme, renderWidth: number): void;
63
+ /**
64
+ * 原地追加一条外层框架水平分隔线(`accent` 配色)
65
+ * @param lines - 输出行数组(原地追加)
66
+ * @param theme - 主题样式提供者
67
+ * @param width - 分隔线可视宽度(`<= 0` 时追加空串,不产出超宽行)
68
+ */
69
+ export declare function pushFrameRule(lines: string[], theme: Theme, width: number): void;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @file view-summary.ts
3
+ * @description 纯函数视图排版层:答卷总览确认页的内容行与底部按键提示构建器
4
+ *
5
+ * 核心架构特性:
6
+ * - 全部函数为无状态纯函数(无 `this` / 缓存 / IO):内容构建器返回未切片的完整内容行数组,提示构建器返回提示片段数组
7
+ * - 多选分支一律经 `model.getSelectedIndices`(升序不变量由模型层集中保证)
8
+ *
9
+ * 依赖方向:view-summary.ts → format.ts / model.ts / texts.ts / theme.ts;禁止反向导入 `component.ts`
10
+ */
11
+ import type { AskAuthorFormModel } from "./model.js";
12
+ import type { Theme } from "./theme.js";
13
+ /**
14
+ * 构建总览页底部按键提示片段(依据题目数量动态增删跳转题目提示)
15
+ * @param model - 问卷表单数据模型(提供题目总数)
16
+ * @returns 包含切换题目 / 滚动 / 提交 / 取消四项,题目数 > 1 时额外插入跳转题目提示
17
+ */
18
+ export declare function buildSummaryHelpParts(model: AskAuthorFormModel): string[];
19
+ /**
20
+ * 构建未切片的完整答卷总览内容行
21
+ * 逐题输出四段(状态徽章 + 题目标题 / 已选项 / 选项专属批注 / 题目整体批注),题间以空行分隔;显式跳过与仅批注提交的题目同样入列,不静默消失
22
+ * @param model - 问卷表单数据模型(作答状态的唯一权威来源)
23
+ * @param theme - 主题样式提供者
24
+ * @param contentWidth - 总览主体可用列宽(列),超宽行沿各段悬挂前缀折行
25
+ * @returns 完整未切片的内容行数组;无效题目跳过,无有效题目时返回空数组
26
+ */
27
+ export declare function buildSubmitSummaryContentLines(model: AskAuthorFormModel, theme: Theme, contentWidth: number): string[];