@sendevia/pi-ask-author 1.0.6 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -14
- package/dist/component.d.ts +23 -93
- package/dist/format.d.ts +5 -5
- package/dist/i18n.d.ts +52 -0
- package/dist/index.d.ts +15 -39
- package/dist/index.js +1 -1
- package/dist/layout.d.ts +78 -0
- package/dist/llm-texts.d.ts +179 -0
- package/dist/model.d.ts +106 -12
- package/dist/novel-markdown.d.ts +2 -2
- package/dist/novel-syntax.d.ts +38 -0
- package/dist/schema.d.ts +115 -3
- package/dist/texts.d.ts +183 -424
- 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 +127 -0
- package/dist/view-rows.d.ts +11 -2
- package/dist/view-summary.d.ts +42 -8
- package/lang/en_US.json +172 -0
- package/lang/zh_CN.json +150 -0
- package/package.json +10 -10
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file tool-card.ts
|
|
3
|
+
* @description 会话回放卡片渲染:`renderCall` 的摘要派生与 `renderResult` 的三态明细
|
|
4
|
+
*
|
|
5
|
+
* 核心架构特性:
|
|
6
|
+
* - 调用摘要与执行路径共用 `sanitizeAskAuthorArgs`,别名、包装键、终端转义清洗与数量上限只有一条规则
|
|
7
|
+
* - 渲染器只读取宿主传入的入参与详情,不持有组件实例;槽位既有 `Text` 复用由 `lastComponent` 提供
|
|
8
|
+
* - 缓存只存与主题无关的派生数据,按 args 对象身份失效;主题着色每帧现场计算
|
|
9
|
+
*
|
|
10
|
+
* 依赖方向:tool-card.ts → model.ts(类型)/ sanitize.ts / schema.ts(类型)/ texts.ts / theme.ts / tool-result.ts;被 index.ts 注册
|
|
11
|
+
*/
|
|
12
|
+
import type { ToolDefinition, ToolRenderResultOptions } from "@earendil-works/pi-coding-agent";
|
|
13
|
+
import { type Component } from "@earendil-works/pi-tui";
|
|
14
|
+
import type { AskAuthorResult } from "./model.js";
|
|
15
|
+
import type { AskAuthorParams, RawParams } from "./schema.js";
|
|
16
|
+
import type { Theme } from "./theme.js";
|
|
17
|
+
import { type AskAuthorToolResult } from "./tool-result.js";
|
|
18
|
+
/** 调用卡片派生数据:与主题无关的纯计算结果(颜色合成每帧按当前主题现场计算) */
|
|
19
|
+
export interface CallCardData {
|
|
20
|
+
/** 卡片标题(已清洗的 `formTitle`) */
|
|
21
|
+
title: string;
|
|
22
|
+
/** 计数标签(多题模式为 `2 题 • 6 选项`,单题模式仅 `6 选项`) */
|
|
23
|
+
countLabel: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* `renderCall` 记忆化状态载荷:宿主按工具执行行创建一次、跨帧复用的可变对象(`ToolRenderContext.state`,同一行的 `renderCall` 与 `renderResult` 共享该实例)
|
|
27
|
+
* 以 **args 对象身份** 为键;宿主经 `ToolExecutionComponent.updateArgs` 赋入新对象,身份变化即失效重新计算
|
|
28
|
+
*/
|
|
29
|
+
export interface CallRenderState {
|
|
30
|
+
/** 上一次派生所用的 args 对象身份(仅作 `===` 引用比对,不读取任何属性) */
|
|
31
|
+
cachedArgs?: object;
|
|
32
|
+
/** 与 `cachedArgs` 配对的派生数据;身份命中时直接复用 */
|
|
33
|
+
cachedCard?: CallCardData;
|
|
34
|
+
}
|
|
35
|
+
/** 卡片渲染上下文(从宿主公开的 `ToolDefinition` 派生,避免依赖未从包入口导出的内部类型) */
|
|
36
|
+
type CardRenderContext = Parameters<NonNullable<ToolDefinition<typeof AskAuthorParams, AskAuthorResult, CallRenderState>["renderCall"]>>[2];
|
|
37
|
+
/**
|
|
38
|
+
* 派生调用卡片数据(标题 + 计数标签:题目数 > 1 时附带题量,单题模式仅选项数)
|
|
39
|
+
* 本函数不构建表单模型:流式调用期间 `showIf` 可能引用尚未到达的题目,模型构造会因条件非法而抛出
|
|
40
|
+
* @param args - 本次渲染的原始工具入参(流式期间可能为不完整对象)
|
|
41
|
+
* @param state - 宿主按工具行复用、跨帧身份稳定的可变状态载荷
|
|
42
|
+
* @returns 派生卡片数据(args 身份命中时直接复用)
|
|
43
|
+
*/
|
|
44
|
+
export declare function resolveCallCard(args: RawParams, state: CallRenderState): CallCardData;
|
|
45
|
+
/**
|
|
46
|
+
* 会话回放调用卡片渲染(复用槽位既有 Text;摘要按 args 身份记忆化)
|
|
47
|
+
* `args` 由宿主原样透传(流式期间可能不完整且未经 Schema 校验),摘要派生统一经 `sanitizeAskAuthorArgs` 收敛
|
|
48
|
+
* @param args - 原始工具入参(运行时未经清洗,可能为不完整对象)
|
|
49
|
+
* @param theme - 当前 TUI 主题实例
|
|
50
|
+
* @param context - 宿主渲染上下文(`state` 记忆化载荷 / `lastComponent` 槽位复用)
|
|
51
|
+
* @returns 调用卡片 `Text`(复用 `context.lastComponent` 就地更新)
|
|
52
|
+
*/
|
|
53
|
+
export declare function renderCallCard(args: RawParams, theme: Theme, context: CardRenderContext): Component;
|
|
54
|
+
/**
|
|
55
|
+
* 会话回放结果卡片渲染(错误 / 取消 / 完成三态,展开态追加答卷明细)
|
|
56
|
+
* 错误态优先于取消态判定:`details.error` 为真值时即使 `cancelled` 为 true 也按错误呈现
|
|
57
|
+
* @param result - 工具执行结果(`details` 必须经由 {@link narrowResultDetails} 收窄)
|
|
58
|
+
* @param options - 宿主渲染选项(仅使用 `expanded`;本工具不上报流式部分结果)
|
|
59
|
+
* @param theme - 当前 TUI 主题实例
|
|
60
|
+
* @param context - 宿主渲染上下文(`lastComponent` 槽位复用)
|
|
61
|
+
* @returns 结果卡片 `Text`(复用 `context.lastComponent` 就地更新)
|
|
62
|
+
*/
|
|
63
|
+
export declare function renderResultCard(result: AskAuthorToolResult, options: ToolRenderResultOptions, theme: Theme, context: CardRenderContext): Component;
|
|
64
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file tool-result.ts
|
|
3
|
+
* @description `ask_author` 的四类结果工厂与结果详情的宿主边界收窄
|
|
4
|
+
*
|
|
5
|
+
* 核心架构特性:
|
|
6
|
+
* - 完成 / 取消 / 结构化错误 / 运行期异常四类出口集中在此,封套前缀保持字节稳定以提升 Prompt Cache 命中率
|
|
7
|
+
* - 失败类结果同时设置 `isError: true`,让宿主按错误结果处理;`content` 仍携带面向 LLM 的封套与纠正指引
|
|
8
|
+
* - `narrowResultDetails` 是 `renderResult` 收到的不可信 `details` 的唯一类型收窄点
|
|
9
|
+
*
|
|
10
|
+
* 依赖方向:tool-result.ts → model.ts / texts.ts(类型与文案);被 index.ts 与 tool-card.ts 消费
|
|
11
|
+
*/
|
|
12
|
+
import type { AgentToolResult } from "@earendil-works/pi-coding-agent";
|
|
13
|
+
import { type AskAuthorFormModel, type AskAuthorResult, type AuthorAnswerItem, type NormalizedQuestion } from "./model.js";
|
|
14
|
+
/** `ask_author` 工具执行结果结构(基于 Pi 官方 `AgentToolResult` 结构特化) */
|
|
15
|
+
export type AskAuthorToolResult = AgentToolResult<AskAuthorResult>;
|
|
16
|
+
/** 结果构造上下文:题目快照与标题/描述的可选覆盖(缺省值统一由 {@link resolveResultContext} 收敛) */
|
|
17
|
+
export interface ResultContext {
|
|
18
|
+
/** 题目快照(缺省空数组:取消/错误可能发生在模型构建前) */
|
|
19
|
+
questions?: NormalizedQuestion[];
|
|
20
|
+
/** 问卷总标题(缺省 `TEXTS.fallbacks.defaultFormTitle`) */
|
|
21
|
+
formTitle?: string;
|
|
22
|
+
/** 问卷全局描述(缺省 undefined,不携带该字段内容) */
|
|
23
|
+
formDescription?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 构造取消结果(智能体封套 `[Author Decision Cancelled]` + `cancelled: true`,`answers` 恒为空数组)
|
|
27
|
+
* @param ctx - 结果构造上下文(题目快照与被取消时的表单快照)
|
|
28
|
+
* @returns 取消结果(`content` 为取消封套单行文本,`details` 不含 `error` 字段)
|
|
29
|
+
*/
|
|
30
|
+
export declare function createCancelledResult(ctx?: ResultContext): AskAuthorToolResult;
|
|
31
|
+
/**
|
|
32
|
+
* 构造错误结果(智能体错误封套 + 用户界面提示;作为工具错误结果返回,`details` 仍可用于渲染)
|
|
33
|
+
* @param reason - 面向智能体的原因
|
|
34
|
+
* @param guidance - 面向智能体的纠正指引
|
|
35
|
+
* @param userFacingError - 面向用户的界面提示
|
|
36
|
+
* @param ctx - 结果构造上下文(缺省值由 `resolveResultContext` 收敛)
|
|
37
|
+
* @returns 错误结果对象(`isError: true`、`cancelled: false` + `error` 详情,无作答内容)
|
|
38
|
+
*/
|
|
39
|
+
export declare function createErrorResult(reason: string, guidance: string, userFacingError: string, ctx?: ResultContext): AskAuthorToolResult;
|
|
40
|
+
/**
|
|
41
|
+
* 构造运行期异常结果(模型构建抛出错误与 `execute` 异常捕获两条路径共用本工厂)
|
|
42
|
+
* @param err - 异常对象或消息(字符串经 {@link toErrorMessage} 原样返回)
|
|
43
|
+
* @param ctx - 结果构造上下文(题目快照与表单快照)
|
|
44
|
+
* @returns 运行期异常结果(封套原因携带 {@link toErrorMessage} 归一后的消息)
|
|
45
|
+
*/
|
|
46
|
+
export declare function createRuntimeErrorResult(err: unknown, ctx?: ResultContext): AskAuthorToolResult;
|
|
47
|
+
/**
|
|
48
|
+
* 构造完成结果(`[Author Decision Finalized]` 封套正文由模型编译,空答卷渲染为空答卷提示)
|
|
49
|
+
* @param formModel - 本次请示的表单模型(详情表单快照来源,与组件回传结果同源)
|
|
50
|
+
* @param answers - 组件编译完成的答卷条目(封套正文与详情 `answers` 共用同一数组)
|
|
51
|
+
* @returns 完成结果(`cancelled: false`、无 `error` 字段;`answers` 为空数组时正文为 `TEXTS.markdown.envelopeEmpty`)
|
|
52
|
+
*/
|
|
53
|
+
export declare function createSuccessResult(formModel: AskAuthorFormModel, answers: AuthorAnswerItem[]): AskAuthorToolResult;
|
|
54
|
+
/**
|
|
55
|
+
* 结果卡片宿主边界的**唯一**类型收窄点:把 `renderResult` 的 `details` 从不可信类型提升为 `AskAuthorResult`
|
|
56
|
+
* 会话回放时 `details` 由转录 JSON 反序列化而来(运行期形状不可信),故先做形状闸门再断言一次;数组元素由本扩展自身写入,不在闸门范围
|
|
57
|
+
* 校验项:普通对象、`cancelled`/`answers`/`questions`/`formTitle` 键存在性、`cancelled` 布尔、`answers`/`questions` 数组、`formTitle` 字符串
|
|
58
|
+
* 可选 `error` / `formDescription` 不设闸门:二者仅参与字符串插值,非字符串至多渲染为退化文本
|
|
59
|
+
* @param value - 宿主传入的不可信 `details` 值
|
|
60
|
+
* @returns 通过形状闸门的 `AskAuthorResult`
|
|
61
|
+
* @throws 任一校验项失败时抛出错误(就地崩溃)
|
|
62
|
+
*/
|
|
63
|
+
export declare function narrowResultDetails(value: unknown): AskAuthorResult;
|
|
64
|
+
/**
|
|
65
|
+
* 将单条答案展开为明细内容行(多选以 `TEXTS.common.optionSeparator` 连接并附选项批注角标,零勾选回退 `未选`;单选无选中项时回退题目补充说明,两者皆空再回退 `未选`)
|
|
66
|
+
* @param ans - 答卷条目
|
|
67
|
+
* @returns 单行明细文本(不含换行;题目级补充说明以 `TEXTS.card.questionNoteTag` 角标追加)
|
|
68
|
+
*/
|
|
69
|
+
export declare function formatAnswerLine(ans: AuthorAnswerItem): string;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file view-editor.ts
|
|
3
|
+
* @description 补充说明编辑器的全屏视图构建器
|
|
4
|
+
*
|
|
5
|
+
* 核心架构特性:
|
|
6
|
+
* - 只根据编辑目标拼装标题、上下文与编辑器行,编辑器实例由组件传入并保持焦点状态
|
|
7
|
+
* - 不读取也不写入组件的输入模式标记;离开输入模式仍由组件在按键路径决定
|
|
8
|
+
*
|
|
9
|
+
* 依赖方向:view-editor.ts → format.ts / model.ts / texts.ts / theme.ts / view-rows.ts;禁止反向导入 component.ts
|
|
10
|
+
*/
|
|
11
|
+
import type { Editor } from "@earendil-works/pi-tui";
|
|
12
|
+
import type { AskAuthorFormModel, EditTarget } from "./model.js";
|
|
13
|
+
import type { Theme } from "./theme.js";
|
|
14
|
+
/**
|
|
15
|
+
* 渲染补充说明编辑器全屏模式(标题 / 上下文背景 / 编辑框 / 页脚按键提示 / 底部框架线)
|
|
16
|
+
* @param theme - 当前 TUI 主题
|
|
17
|
+
* @param model - 问卷模型(读取题目与选项文案)
|
|
18
|
+
* @param tab - 当前题目索引
|
|
19
|
+
* @param target - 编辑目标(题目整体或指定选项)
|
|
20
|
+
* @param editor - 内置编辑器实例
|
|
21
|
+
* @param renderWidth - 终端可视宽度
|
|
22
|
+
* @returns 编辑器页的完整终端行;题目越界时返回空数组
|
|
23
|
+
*/
|
|
24
|
+
export declare function renderEditorPage(theme: Theme, model: AskAuthorFormModel, tab: number, target: EditTarget | undefined, editor: Editor, renderWidth: number): string[];
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file view-question.ts
|
|
3
|
+
* @description 题目作答页的视图构建器:题头、选项菜单、双栏 / 单栏预览与底部按键提示
|
|
4
|
+
*
|
|
5
|
+
* 核心架构特性:
|
|
6
|
+
* - 只读取组件传入的视口状态并返回终端行,滚动位移与光标写入仍由组件在输入路径完成
|
|
7
|
+
* - 预览完整内容按「题号 + 条目 + 列宽 + 批注」缓存,左栏按「题号 + 光标 + 位移 + revision + 列宽」缓存;`clear()` 由组件在状态突变与主题切换时调用
|
|
8
|
+
* - 双栏与单栏的判定、视口高度与帮助行组装集中在此,组件只做模式选择
|
|
9
|
+
*
|
|
10
|
+
* 依赖方向:view-question.ts → format.ts / layout.ts / model.ts / novel-markdown.ts / texts.ts / theme.ts / view-rows.ts;禁止反向导入 component.ts
|
|
11
|
+
*/
|
|
12
|
+
import { type AskAuthorFormModel, type MenuItem } from "./model.js";
|
|
13
|
+
import type { Theme } from "./theme.js";
|
|
14
|
+
/** 题目作答页的只读视口状态(全部字段来自组件,构建器不写回) */
|
|
15
|
+
export interface QuestionViewState {
|
|
16
|
+
/** 当前题目索引(0-based;调用方保证不是总览页) */
|
|
17
|
+
readonly tab: number;
|
|
18
|
+
/** 当前聚焦菜单项索引 */
|
|
19
|
+
readonly optionIndex: number;
|
|
20
|
+
/** 左栏菜单滚动位移(行) */
|
|
21
|
+
readonly listScrollOffset: number;
|
|
22
|
+
/** 预览区滚动位移(行) */
|
|
23
|
+
readonly previewScrollOffset: number;
|
|
24
|
+
/** 底部临时状态通知(可为空) */
|
|
25
|
+
readonly statusMessage: string | null;
|
|
26
|
+
/** 当前题目的菜单项(由模型派生并记忆化) */
|
|
27
|
+
readonly menuItems: readonly MenuItem[];
|
|
28
|
+
}
|
|
29
|
+
/** 预览视口观察值:渲染后回传组件,供输入路径归一化滚动位移 */
|
|
30
|
+
export interface PreviewViewport {
|
|
31
|
+
/** 未切片的完整内容行数 */
|
|
32
|
+
readonly total: number;
|
|
33
|
+
/** 本帧视口高度(行) */
|
|
34
|
+
readonly viewHeight: number;
|
|
35
|
+
}
|
|
36
|
+
/** 题目作答页渲染结果 */
|
|
37
|
+
export interface QuestionViewResult {
|
|
38
|
+
/** 整页终端行(含底部状态、帮助与框架线) */
|
|
39
|
+
readonly lines: string[];
|
|
40
|
+
/** 本帧预览视口;未渲染预览时为 undefined(沿用上一帧观察值) */
|
|
41
|
+
readonly previewViewport?: PreviewViewport;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* 双栏右侧预览视口高度(随终端高度收缩;区间 `[6, LAYOUT_CONFIG.splitPreviewHeight]`)
|
|
45
|
+
* @param terminalRows - 终端可视行数
|
|
46
|
+
* @returns 视口最大行数
|
|
47
|
+
*/
|
|
48
|
+
export declare function splitPreviewHeight(terminalRows: number): number;
|
|
49
|
+
/**
|
|
50
|
+
* 单栏内联预览视口高度(随终端高度收缩;区间 `[3, LAYOUT_CONFIG.singlePreviewHeight]`)
|
|
51
|
+
* @param terminalRows - 终端可视行数
|
|
52
|
+
* @returns 视口最大行数
|
|
53
|
+
*/
|
|
54
|
+
export declare function singlePreviewHeight(terminalRows: number): number;
|
|
55
|
+
/**
|
|
56
|
+
* 题目作答页视图构建器(持有预览与左栏缓存;模型与主题在构造期固定)
|
|
57
|
+
*/
|
|
58
|
+
export declare class QuestionView {
|
|
59
|
+
private readonly theme;
|
|
60
|
+
private readonly model;
|
|
61
|
+
/** 预览框完整渲染行缓存池(键:题号 + 条目身份 + 列宽 + 补充说明;超容量按插入序淘汰) */
|
|
62
|
+
private readonly previewCache;
|
|
63
|
+
/** 左栏菜单行缓存(键:题号 + 光标 + 位移 + revision + 左栏宽) */
|
|
64
|
+
private leftColCache?;
|
|
65
|
+
/**
|
|
66
|
+
* @param theme - 当前 TUI 主题
|
|
67
|
+
* @param model - 问卷模型(只读访问)
|
|
68
|
+
*/
|
|
69
|
+
constructor(theme: Theme, model: AskAuthorFormModel);
|
|
70
|
+
/** 清空预览池与左栏缓存(状态突变、主题切换与组件回收时调用) */
|
|
71
|
+
clear(): void;
|
|
72
|
+
/** 只清空左栏菜单行缓存(焦点变化时调用,保留已解析的预览内容) */
|
|
73
|
+
clearColumnCache(): void;
|
|
74
|
+
/**
|
|
75
|
+
* 渲染题目作答页
|
|
76
|
+
* @param state - 只读视口状态(当前题、光标、滚动位移、状态通知与菜单项)
|
|
77
|
+
* @param renderWidth - 终端布局列宽
|
|
78
|
+
* @param terminalRows - 终端可视行数(决定预览视口高度)
|
|
79
|
+
* @returns 整页终端行与本帧预览视口
|
|
80
|
+
*/
|
|
81
|
+
render(state: QuestionViewState, renderWidth: number, terminalRows: number): QuestionViewResult;
|
|
82
|
+
/**
|
|
83
|
+
* 获取聚焦条目的预渲染完整内容行(`previewCache` 透明备忘缓存,跨条目 / 切换题目零重复解析)
|
|
84
|
+
* @param tab - 题目索引
|
|
85
|
+
* @param item - 目标菜单项
|
|
86
|
+
* @param customText - 题目整体补充说明
|
|
87
|
+
* @param optionNote - 选项专属补充说明
|
|
88
|
+
* @param targetWidth - 预览可用列宽
|
|
89
|
+
* @returns 未切片的完整内容行数组;`item` 缺省时返回空数组
|
|
90
|
+
*/
|
|
91
|
+
private previewContentLines;
|
|
92
|
+
/**
|
|
93
|
+
* 计算并切片本帧预览框行(纯计算,不写回滚动位移)
|
|
94
|
+
* @param tab - 题目索引
|
|
95
|
+
* @param item - 目标菜单项
|
|
96
|
+
* @param customText - 题目整体补充说明
|
|
97
|
+
* @param optionNote - 选项专属补充说明
|
|
98
|
+
* @param targetWidth - 预览可用列宽
|
|
99
|
+
* @param viewHeight - 视口最大行数
|
|
100
|
+
* @param scrollOffset - 当前预览滚动位移
|
|
101
|
+
* @returns 本帧可视内容行与视口观察值
|
|
102
|
+
*/
|
|
103
|
+
private buildPreviewBox;
|
|
104
|
+
/**
|
|
105
|
+
* 渲染双栏视图(左选项菜单 + 右推演 / 分镜预览框)
|
|
106
|
+
* @param lines - 输出行数组(原地追加)
|
|
107
|
+
* @param q - 当前题目
|
|
108
|
+
* @param st - 作答状态
|
|
109
|
+
* @param state - 只读视口状态
|
|
110
|
+
* @param renderWidth - 终端可用宽度
|
|
111
|
+
* @param terminalRows - 终端可视行数
|
|
112
|
+
* @returns 本帧预览视口观察值
|
|
113
|
+
*/
|
|
114
|
+
private renderSplitView;
|
|
115
|
+
/**
|
|
116
|
+
* 遍历输出菜单项行,单栏模式下在聚焦项下方内联展开预览
|
|
117
|
+
* @param lines - 输出行数组(原地追加)
|
|
118
|
+
* @param q - 当前题目
|
|
119
|
+
* @param st - 作答状态
|
|
120
|
+
* @param state - 只读视口状态
|
|
121
|
+
* @param rowWidth - 选项行可用列宽
|
|
122
|
+
* @param expandFocused - 是否展开聚焦项预览(窗口外条目不访问)
|
|
123
|
+
* @param terminalRows - 终端可视行数
|
|
124
|
+
* @returns 本帧聚焦项预览视口;未展开预览时 undefined
|
|
125
|
+
*/
|
|
126
|
+
private pushListItems;
|
|
127
|
+
}
|
package/dist/view-rows.d.ts
CHANGED
|
@@ -39,6 +39,8 @@ export declare function formatMenuItemRow(theme: Theme, item: MenuItem, isFocuse
|
|
|
39
39
|
* 渲染完整 Tab 导航栏区块(各题标签 + 提交页标签 + 末尾单横分隔线)
|
|
40
40
|
*
|
|
41
41
|
* 算法不变量:
|
|
42
|
+
* - 只为当前可见题目产出标签(序条件隐藏的题目不出现在导航栏),标签序号为可见题目的展示序号
|
|
43
|
+
* - 题目标签文本优先取题目的 `tabLabel`(超过 `LAYOUT_CONFIG.tabLabelMaxWidth` 时截断),未声明时回退题号文案;
|
|
42
44
|
* - 题目标签以 `ICONS.checked` / `ICONS.unchecked` 与 `success` / `muted` 配色表达完成度,激活题页恒为
|
|
43
45
|
* `selectedBg` + `accent` 的 `bold` 反白(激活提交页为 `selectedBg` + `success`)
|
|
44
46
|
* - 提交页标签在全部作答完成时用 `success`,否则 `dim`;激活判定为 `currentTab === model.questionCount`
|
|
@@ -46,12 +48,19 @@ export declare function formatMenuItemRow(theme: Theme, item: MenuItem, isFocuse
|
|
|
46
48
|
* `dim` 配色的单横分隔线(`LAYOUT_CONFIG.dividerThin`)
|
|
47
49
|
*
|
|
48
50
|
* @param theme - 主题样式提供者
|
|
49
|
-
* @param model -
|
|
50
|
-
* @param currentTab -
|
|
51
|
+
* @param model - 问卷表单数据模型(提供可见题目、作答完成度)
|
|
52
|
+
* @param currentTab - 激活页索引(题目顺序索引;`questionCount` 为总览页)
|
|
51
53
|
* @param renderWidth - 终端可视宽度限制
|
|
52
54
|
* @returns Tab 导航栏终端行数组(至少含末尾分隔线一行;超宽时标签折为多行)
|
|
53
55
|
*/
|
|
54
56
|
export declare function buildTabLines(theme: Theme, model: AskAuthorFormModel, currentTab: number, renderWidth: number): string[];
|
|
57
|
+
/**
|
|
58
|
+
* 原地追加底部状态通知行(无文案时不追加)
|
|
59
|
+
* @param lines - 输出行数组(原地追加)
|
|
60
|
+
* @param theme - 主题样式提供者
|
|
61
|
+
* @param statusMessage - 当前状态通知(可为空)
|
|
62
|
+
*/
|
|
63
|
+
export declare function pushStatusMessage(lines: string[], theme: Theme, statusMessage: string | null): void;
|
|
55
64
|
/**
|
|
56
65
|
* 原地追加底部按键提示行(统一 `dim` 配色 + 单空格悬挂缩进,超宽时折行)
|
|
57
66
|
* @param lines - 输出行数组(原地追加)
|
package/dist/view-summary.d.ts
CHANGED
|
@@ -1,27 +1,61 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file view-summary.ts
|
|
3
|
-
* @description
|
|
3
|
+
* @description 答卷总览确认页的视图构建层:内容行、分页、提示与操作栏
|
|
4
4
|
*
|
|
5
5
|
* 核心架构特性:
|
|
6
|
-
* -
|
|
6
|
+
* - 内容构建器与提示构建器为无状态纯函数;`SummaryView` 只持有按(列宽, `revision`)失效的内容行缓存
|
|
7
7
|
* - 多选分支一律经 `model.getSelectedIndices`(升序不变量由模型层集中保证)
|
|
8
8
|
*
|
|
9
|
-
* 依赖方向:view-summary.ts → format.ts / model.ts / texts.ts / theme.ts;禁止反向导入 `component.ts`
|
|
9
|
+
* 依赖方向:view-summary.ts → format.ts / layout.ts / model.ts / texts.ts / theme.ts / view-rows.ts;禁止反向导入 `component.ts`
|
|
10
10
|
*/
|
|
11
11
|
import type { AskAuthorFormModel } from "./model.js";
|
|
12
12
|
import type { Theme } from "./theme.js";
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
* @param model -
|
|
16
|
-
* @returns 包含切换题目 / 滚动 / 提交 /
|
|
14
|
+
* 构建总览页底部按键提示片段(依据可见题目数量动态增删跳转题目提示)
|
|
15
|
+
* @param model - 问卷表单数据模型(提供可见题目数与作答完成度)
|
|
16
|
+
* @returns 包含切换题目 / 滚动 / 提交 / 取消四项,可见题目数 > 1 时额外插入跳转题目提示
|
|
17
17
|
*/
|
|
18
18
|
export declare function buildSummaryHelpParts(model: AskAuthorFormModel): string[];
|
|
19
|
+
/**
|
|
20
|
+
* 总览页视口高度(随终端高度自适应;区间 `[4, LAYOUT_CONFIG.summaryPageSize]`)
|
|
21
|
+
* @param terminalRows - 终端可视行数
|
|
22
|
+
* @returns 单页最大内容行数
|
|
23
|
+
*/
|
|
24
|
+
export declare function summaryPageSize(terminalRows: number): number;
|
|
25
|
+
/**
|
|
26
|
+
* 答卷总览与提交页构建器(持有内容行缓存,模型与主题在构造期固定)
|
|
27
|
+
*/
|
|
28
|
+
export declare class SummaryView {
|
|
29
|
+
private readonly theme;
|
|
30
|
+
private readonly model;
|
|
31
|
+
/** 未切片内容行缓存(键:列宽 + `revision`) */
|
|
32
|
+
private cache?;
|
|
33
|
+
/**
|
|
34
|
+
* @param theme - 当前 TUI 主题
|
|
35
|
+
* @param model - 问卷模型(只读访问)
|
|
36
|
+
*/
|
|
37
|
+
constructor(theme: Theme, model: AskAuthorFormModel);
|
|
38
|
+
/** 清空内容行缓存(状态突变、主题切换与组件回收时调用) */
|
|
39
|
+
clear(): void;
|
|
40
|
+
/** 上一次构建的内容行总数(供组件在输入路径归一化滚动位移;尚未渲染时为 0) */
|
|
41
|
+
get contentLineCount(): number;
|
|
42
|
+
/**
|
|
43
|
+
* 渲染总览页
|
|
44
|
+
* @param scrollOffset - 当前滚动位移(在本地收敛,不写回)
|
|
45
|
+
* @param pageSize - 单页最大内容行数
|
|
46
|
+
* @param statusMessage - 底部临时状态通知(可为空)
|
|
47
|
+
* @param renderWidth - 终端布局列宽
|
|
48
|
+
* @returns 总览页完整终端行(含标题、提示、状态、帮助与底部框架线)
|
|
49
|
+
*/
|
|
50
|
+
render(scrollOffset: number, pageSize: number, statusMessage: string | null, renderWidth: number): string[];
|
|
51
|
+
}
|
|
19
52
|
/**
|
|
20
53
|
* 构建未切片的完整答卷总览内容行
|
|
21
|
-
* 逐题输出四段(状态徽章 + 题目标题 / 已选项 / 选项专属批注 /
|
|
54
|
+
* 逐题输出四段(状态徽章 + 题目标题 / 已选项 / 选项专属批注 / 题目整体批注),题间以空行分隔;隐条件隐藏的题目不入列,
|
|
55
|
+
* 显式跳过与仅批注提交的题目同样入列;题目序号为可见题目的展示序号
|
|
22
56
|
* @param model - 问卷表单数据模型(作答状态的唯一权威来源)
|
|
23
57
|
* @param theme - 主题样式提供者
|
|
24
58
|
* @param contentWidth - 总览主体可用列宽(列),超宽行沿各段悬挂前缀折行
|
|
25
|
-
* @returns
|
|
59
|
+
* @returns 完整未切片的内容行数组;无效题目跳过,无可见题目时返回空数组
|
|
26
60
|
*/
|
|
27
61
|
export declare function buildSubmitSummaryContentLines(model: AskAuthorFormModel, theme: Theme, contentWidth: number): string[];
|
package/lang/en_US.json
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tool": { "label": "Consult Author" },
|
|
3
|
+
"command": {
|
|
4
|
+
"name": "ask-author",
|
|
5
|
+
"description": "Show the ask_author consultation tool reference",
|
|
6
|
+
"notify": "Ask the AI to call ask_author in your prompt to consult you on creative decisions"
|
|
7
|
+
},
|
|
8
|
+
"common": { "optionSeparator": ", ", "bulletPrefix": "• ", "helpSeparator": " • " },
|
|
9
|
+
"tabs": {
|
|
10
|
+
"questionTab": { "template": "Q{index}", "params": ["index"] },
|
|
11
|
+
"submitTab": { "template": "{icon.check} Submit" }
|
|
12
|
+
},
|
|
13
|
+
"view": {
|
|
14
|
+
"modeSingle": "[Single]",
|
|
15
|
+
"conditionalBadge": "[Conditional]",
|
|
16
|
+
"modeMultiAtLeast": { "template": "[Multi, at least {min}{maxPart}]", "params": ["min", "maxPart"] },
|
|
17
|
+
"modeMultiOptional": { "template": "[Multi, optional{maxPart}]", "params": ["maxPart"] },
|
|
18
|
+
"modeMultiUnconstrained": { "template": "[Multi{maxPart}]", "params": ["maxPart"] },
|
|
19
|
+
"modeMultiMaxPart": { "template": ", up to {max}", "params": ["max"] },
|
|
20
|
+
"attachedNoteBanner": { "template": "{icon.note} Question note: {text}", "params": ["text"] },
|
|
21
|
+
"optionAttachedNoteBanner": {
|
|
22
|
+
"template": "{icon.note} [{label}] option note: {text}",
|
|
23
|
+
"params": ["label", "text"]
|
|
24
|
+
},
|
|
25
|
+
"listTopMore": { "template": " {icon.scrollUp} {count} more above", "params": ["count"] },
|
|
26
|
+
"listBottomMore": { "template": " {icon.scrollDown} {count} more below", "params": ["count"] },
|
|
27
|
+
"optionNotePanelTitle": "Option Note",
|
|
28
|
+
"storyDescriptionPanelTitle": "Plot Deduction",
|
|
29
|
+
"storyDraftPanelTitle": "Draft / Storyboard Preview",
|
|
30
|
+
"customNotePanelTitle": "Question Note",
|
|
31
|
+
"confirmPanelTitle": "Controls",
|
|
32
|
+
"customNotePanelCurrent": "Current content:",
|
|
33
|
+
"previewScrollInfo": {
|
|
34
|
+
"template": "(lines {start}-{end}/{total}, use [ ] to scroll)",
|
|
35
|
+
"params": ["start", "end", "total"]
|
|
36
|
+
},
|
|
37
|
+
"previewNoDraft": "(nothing to preview)",
|
|
38
|
+
"customNotePanelTips": [
|
|
39
|
+
"• Press \"N\", or select this item and press \"Enter\", to write or edit the question note",
|
|
40
|
+
"• Press \"x\" to clear the question note",
|
|
41
|
+
"• The note is submitted together with the selected options and their option notes"
|
|
42
|
+
],
|
|
43
|
+
"confirmPanelTips": [
|
|
44
|
+
"• Press `Enter` to confirm this question and move on to the next one",
|
|
45
|
+
"• Confirming the last question opens the summary and submit page"
|
|
46
|
+
],
|
|
47
|
+
"sectionTitle": { "template": "### {title}", "params": ["title"] },
|
|
48
|
+
"fieldLabel": { "template": "**{label}**", "params": ["label"] },
|
|
49
|
+
"italicLine": { "template": "_{text}_", "params": ["text"] }
|
|
50
|
+
},
|
|
51
|
+
"menu": {
|
|
52
|
+
"customNoteLabel": { "template": "{icon.note} Question note" },
|
|
53
|
+
"customNoteLabelFilled": { "template": "{icon.note} Question note (filled)" },
|
|
54
|
+
"confirmDetailMulti": { "template": "{selectedCount} selected", "params": ["selectedCount", "minSelect"] },
|
|
55
|
+
"confirmDetailMultiBelowMin": {
|
|
56
|
+
"template": "{selectedCount}/{minSelect} selected",
|
|
57
|
+
"params": ["selectedCount", "minSelect"]
|
|
58
|
+
},
|
|
59
|
+
"confirmLabel": {
|
|
60
|
+
"template": "{icon.check} Confirm question ({detail}{noteSuffix})",
|
|
61
|
+
"params": ["detail", "noteSuffix"]
|
|
62
|
+
},
|
|
63
|
+
"confirmNoteSuffix": " + note",
|
|
64
|
+
"confirmDesc": "Confirm and continue to the next question or the summary"
|
|
65
|
+
},
|
|
66
|
+
"editor": {
|
|
67
|
+
"title": { "template": " {icon.note} Question note ({questionTitle})", "params": ["questionTitle"] },
|
|
68
|
+
"optionTitle": {
|
|
69
|
+
"template": " {icon.note} Option note ({optionLabel} - {questionTitle})",
|
|
70
|
+
"params": ["questionTitle", "optionLabel"]
|
|
71
|
+
},
|
|
72
|
+
"footerHelp": " [Enter] save and return • [Esc/Ctrl+C] cancel"
|
|
73
|
+
},
|
|
74
|
+
"status": {
|
|
75
|
+
"questionNoteSaved": "Question note saved",
|
|
76
|
+
"noteCleared": "Question note cleared",
|
|
77
|
+
"optionNoteSaved": { "template": "Saved note for {label}", "params": ["label"] },
|
|
78
|
+
"optionNoteSavedUnselected": {
|
|
79
|
+
"template": "Saved note for {label} (option not selected; submitted as a note only)",
|
|
80
|
+
"params": ["label"]
|
|
81
|
+
},
|
|
82
|
+
"optionNoteSavedAtLimit": {
|
|
83
|
+
"template": "Limit reached (up to {max} options): note saved, option left unselected",
|
|
84
|
+
"params": ["max"]
|
|
85
|
+
},
|
|
86
|
+
"optionNoteCleared": { "template": "Cleared note for {label}", "params": ["label"] },
|
|
87
|
+
"selectedUpToLimit": { "template": "Selection limit reached (up to {limit} items)", "params": ["limit"] },
|
|
88
|
+
"maxSelectExceeded": {
|
|
89
|
+
"template": "Limit reached (up to {max} items): press \"Space\" to deselect an item first",
|
|
90
|
+
"params": ["max"]
|
|
91
|
+
},
|
|
92
|
+
"minSelectRequired": {
|
|
93
|
+
"template": "Below minimum (at least {min} required): press \"Space\" to select more, or press \"n/N\" to write a note",
|
|
94
|
+
"params": ["min"]
|
|
95
|
+
},
|
|
96
|
+
"pleaseAnswerBeforeSubmit": "Question not answered: press \"Space/Enter\" to pick an option, or press \"n/N\" to write a note",
|
|
97
|
+
"editorDiscardConfirm": "Press Esc/Ctrl+C again to discard changes (unsaved content will be lost); keep typing to cancel",
|
|
98
|
+
"errorNonTui": "The current environment is not an interactive terminal, so the interface cannot start; retry in an interactive TUI session",
|
|
99
|
+
"errorNoQuestions": "No valid question or option was provided; provide at least one question with options",
|
|
100
|
+
"errorInvalidCondition": {
|
|
101
|
+
"template": "Invalid adaptive condition: {details} (make showIf reference an earlier question and an existing option label, or use answered)",
|
|
102
|
+
"params": ["details"]
|
|
103
|
+
},
|
|
104
|
+
"errorExecutionFailed": {
|
|
105
|
+
"template": "Component failure: {reason} (fix the arguments and retry)",
|
|
106
|
+
"params": ["reason"]
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"help": {
|
|
110
|
+
"cancel": "Esc/Ctrl+C cancel",
|
|
111
|
+
"clearNote": "x clear note",
|
|
112
|
+
"confirmQuestion": "Enter confirm",
|
|
113
|
+
"confirmSubmit": "Enter submit",
|
|
114
|
+
"editNote": "n question note",
|
|
115
|
+
"editOptionNote": "n option note",
|
|
116
|
+
"editQuestionNote": "N question note",
|
|
117
|
+
"jumpQuestion": "1-9 jump to question",
|
|
118
|
+
"moveCursor": "↑↓/jk move",
|
|
119
|
+
"scrollDraft": "[ ] scroll draft",
|
|
120
|
+
"scrollSummary": "↑↓/[ ] scroll",
|
|
121
|
+
"selectOptionSingle": "Space/Enter select",
|
|
122
|
+
"switchQuestion": "Tab/←→/hl switch question",
|
|
123
|
+
"toggleAll": "a select/clear all",
|
|
124
|
+
"toggleCheckMulti": "Space toggle"
|
|
125
|
+
},
|
|
126
|
+
"summary": {
|
|
127
|
+
"title": "[Answer Summary]",
|
|
128
|
+
"statusDone": "[Done]",
|
|
129
|
+
"statusPending": "[Pending]",
|
|
130
|
+
"questionPrefix": { "template": "Q{index}: {title}", "params": ["index", "title"] },
|
|
131
|
+
"selectedLabel": "Selected:",
|
|
132
|
+
"optionNoteBadge": { "template": "({icon.note} note)" },
|
|
133
|
+
"optionNotePrefix": { "template": "{icon.note} Option note ({optLabel}):", "params": ["optLabel"] },
|
|
134
|
+
"customNoteLabel": { "template": "{icon.note} Question note:" },
|
|
135
|
+
"allDonePrompt": {
|
|
136
|
+
"template": " {icon.check} All questions answered; press Enter/Ctrl+S to submit, Tab/hl/1-9 to revise"
|
|
137
|
+
},
|
|
138
|
+
"pendingWarning": {
|
|
139
|
+
"template": " {icon.unchecked} Some questions are unanswered; press Enter to answer, Tab/hl/1-9 to switch"
|
|
140
|
+
},
|
|
141
|
+
"topMore": { "template": " {icon.scrollUp} {count} more lines above", "params": ["count"] },
|
|
142
|
+
"bottomMore": { "template": " {icon.scrollDown} {count} more lines below", "params": ["count"] }
|
|
143
|
+
},
|
|
144
|
+
"card": {
|
|
145
|
+
"callQuestionAndOptionCount": {
|
|
146
|
+
"template": "{qCount} questions • {optCount} options",
|
|
147
|
+
"params": ["qCount", "optCount"]
|
|
148
|
+
},
|
|
149
|
+
"callOptionCountOnly": { "template": "{optCount} options", "params": ["optCount"] },
|
|
150
|
+
"resultCancelled": { "template": "{icon.cancel} Cancelled" },
|
|
151
|
+
"resultErrorShort": { "template": "{icon.error} Failed" },
|
|
152
|
+
"resultCompleted": { "template": "{icon.check} Completed" },
|
|
153
|
+
"resultCount": { "template": "{n} items", "params": ["n"] },
|
|
154
|
+
"optionBullet": { "template": "• {title}: {content}", "params": ["title", "content"] },
|
|
155
|
+
"optionWithNote": { "template": "{label} [{icon.note} {note}]", "params": ["label", "note"] },
|
|
156
|
+
"questionNoteTag": { "template": " ({icon.note} question note: {text})", "params": ["text"] },
|
|
157
|
+
"errorBullet": { "template": "• Error: {err}", "params": ["err"] }
|
|
158
|
+
},
|
|
159
|
+
"fallbacks": {
|
|
160
|
+
"defaultFormTitle": "Creative Decision",
|
|
161
|
+
"defaultQuestionTitle": "Choose the next creative option",
|
|
162
|
+
"defaultCustomPlaceholder": "Enter overall requirements, foreshadowing details, or revision notes for this question...",
|
|
163
|
+
"defaultOptionCustomPlaceholder": "Enter specific requirements or revision details for this option...",
|
|
164
|
+
"defaultCustomChoiceName": "Custom option",
|
|
165
|
+
"defaultOptionLabel": "Default option",
|
|
166
|
+
"unnamedOptionLabel": "Unnamed option",
|
|
167
|
+
"unselectedOption": "Not selected",
|
|
168
|
+
"autoQuestionId": { "template": "q_{n}", "params": ["n"] },
|
|
169
|
+
"autoQuestionTitle": { "template": "Question {n}", "params": ["n"] },
|
|
170
|
+
"autoOptionLabel": { "template": "Option {n}", "params": ["n"] }
|
|
171
|
+
}
|
|
172
|
+
}
|