diff --git a/apps/web/tests/expected/step-process/turn-collapsed.md b/apps/web/tests/expected/step-process/turn-collapsed.md index 7db41bc5e2..078a45bf6c 100644 --- a/apps/web/tests/expected/step-process/turn-collapsed.md +++ b/apps/web/tests/expected/step-process/turn-collapsed.md @@ -1,3 +1,3 @@ -- button "Took 0s": - - text: Took 0s +- button "Took 1s": + - text: Took 1s - img diff --git a/packages/client/ui-chat/README.i18n.yaml b/packages/client/ui-chat/README.i18n.yaml index 2d107d9eb3..2e20b22009 100644 --- a/packages/client/ui-chat/README.i18n.yaml +++ b/packages/client/ui-chat/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/client/ui-chat/README.md -README.md: 1233caad1785a140b38c086c483d34065ef3a6cc -README.zh.md: 1f73f7007cb4565847bedb845c09f37acc511b25 +README.md: 0ae315e51f27219d46e47ca999bb912b2fe20e1f +README.zh.md: eab14ae3c6c48e0dedf4aa23dbd7e48a41cfb161 diff --git a/packages/client/ui-chat/README.md b/packages/client/ui-chat/README.md index 1233caad17..0ae315e51f 100644 --- a/packages/client/ui-chat/README.md +++ b/packages/client/ui-chat/README.md @@ -67,7 +67,7 @@ The completed-turn action footer starts 20px below the preceding prose or extens Each reasoning row starts collapsed, including during streaming and in reasoning-only replies. Clicking the row opens or closes its complete Markdown; incoming answer text, Tool calls, and stream completion preserve that choice. Expanded reasoning uses compact secondary typography: headings add bold weight without changing text size, line height, or color. The collapsed summary stays on one line, follows the latest reasoning line while streaming, and shows the first line after settlement. -Settings → General exposes the persisted `Work details` preference in the `ui-chat` namespace: `Compact` (default), `Detailed`, and `Expanded`. Compact folds completed Turns; Detailed and Expanded omit the whole-Turn control. Detailed defaults step groups to collapsed; Expanded defaults them to expanded. Both allow manual step toggles. The saved `transcriptView` field accepts legacy `normal`, which the client reads as `expanded` without rewriting storage; explicit choices save only the three current values. The opening System prompt remains independently visible before the opening User. In Compact and Detailed, consecutive process rows between Assistant responses default to collapsed, including reasoning preceding response text in the same node. The live title reports the latest running tool category, otherwise analysis activity. Retry rows remain independent of secondary process groups. A following Assistant response closes the range as soon as visible reply content starts. Completed titles aggregate distinct calls by category, including repeated paths, and show the three most frequent categories without counts; ties retain first appearance order. Two categories use “and” (Chinese “并”, sharing an initial “已” only when both phrases have it); three use commas, and more than three add “etc.” (Chinese “等”). English continuation phrases start lowercase. With no tool statistics, completion uses the analysis fallback. System prompts, context injection, and permission metadata never contribute categories. Expanded ranges scroll within the smaller of 400px and half the viewport height. At `turn/end`, its latest Step becomes the final-answer boundary only when it contains non-blank text, an image, or an unknown visible block—and no Tool-call block; preceding Context injection, reasoning, earlier Assistant material, Tool rows, and Retry rows then collapse by default. The control appears at `turn/start` and stays expanded and disabled during execution. It displays `Worked for …` (Chinese `已处理…`), ticking each second with a minimum of 1s. Normal completion fixes the duration, displays `Took …` (Chinese `用时…`), and defaults to collapsed. Cancellation displays `Stopped` (Chinese `已停止`); failure displays `Failed` (Chinese `处理失败`). Cancelled and failed Turns stay expanded with a disabled control. Missing start timestamps omit the duration. A full-width divider below the summary separates it from the answer or expanded process rows. User and steering messages, System prompt, error, max-token, and turn-tail rows stay outside, and a closed Turn with no final answer folds all process rows while keeping independent rows visible. A newly available process control is inserted without changing the relative order of existing rows: opening human input precedes the control and process rows from their first projection, while System prompt remains above that input. While older history remains available through Load earlier, closed Turns whose `turn/start` is loaded can fold independently, including Turns awakened by context injection. A Turn missing its start waits until pagination supplies it or exhausts history; unrelated unloaded Turns do not block folding. Stable Chat Node Seats preserve row placement, hidden members add no flow spacing, and a closed control sits 8px above its answer only when no independent input intervenes. Completion collapse does not depend on tail-follow position, so a reader above the tail may see the transcript reflow. An automatic collapse that would hide keyboard focus keeps the group open and leaves focus in place; a manual close focuses the process control before hiding its members. Closing a whole Turn resets its nested groups and tool details to collapsed; hidden process renderers remount while their seats stay stable. The session-scoped store records expanded completed answer generations; different answer generations start collapsed. +Settings → General exposes the persisted `Work details` preference in the `ui-chat` namespace: `Compact` (default), `Detailed`, and `Expanded`. Compact folds completed Turns; Detailed and Expanded omit the whole-Turn control. Detailed defaults step groups to collapsed; Expanded defaults them to expanded. Both allow manual step toggles. The saved `transcriptView` field accepts legacy `normal`, which the client reads as `expanded` without rewriting storage; explicit choices save only the three current values. The opening System prompt remains independently visible before the opening User. In Compact and Detailed, consecutive process rows between Assistant responses default to collapsed, including reasoning preceding response text in the same node. The live title reports the latest running tool category, otherwise analysis activity. Retry rows remain independent of secondary process groups. A following Assistant response closes the range as soon as visible reply content starts. Completed titles aggregate distinct calls by category, including repeated paths, and show the three most frequent categories without counts; ties retain first appearance order. Two categories use “and” (Chinese “并”, sharing an initial “已” only when both phrases have it); three use commas, and more than three add “etc.” (Chinese “等”). English continuation phrases start lowercase. With no tool statistics, completion uses the analysis fallback. System prompts, context injection, and permission metadata never contribute categories. Expanded ranges scroll within the smaller of 400px and half the viewport height. At `turn/end`, its latest Step becomes the final-answer boundary only when it contains non-blank text, an image, or an unknown visible block—and no Tool-call block; preceding Context injection, reasoning, earlier Assistant material, Tool rows, and Retry rows then collapse by default. The control appears at `turn/start` and stays expanded and disabled during execution. It displays `Worked for …` (Chinese `已处理…`), ticking each second with a minimum of 1s. Normal completion fixes the duration at a minimum of 1s, displays `Took …` (Chinese `用时…`), and defaults to collapsed. Cancellation displays `Stopped` (Chinese `已停止`); failure displays `Failed` (Chinese `处理失败`). Cancelled and failed Turns stay expanded with a disabled control. Missing start timestamps omit the duration. A full-width divider below the summary separates it from the answer or expanded process rows. User and steering messages, System prompt, error, max-token, and turn-tail rows stay outside, and a closed Turn with no final answer folds all process rows while keeping independent rows visible. A newly available process control is inserted without changing the relative order of existing rows: opening human input precedes the control and process rows from their first projection, while System prompt remains above that input. While older history remains available through Load earlier, closed Turns whose `turn/start` is loaded can fold independently, including Turns awakened by context injection. A Turn missing its start waits until pagination supplies it or exhausts history; unrelated unloaded Turns do not block folding. Stable Chat Node Seats preserve row placement, hidden members add no flow spacing, and a closed control sits 8px above its answer only when no independent input intervenes. Completion collapse does not depend on tail-follow position, so a reader above the tail may see the transcript reflow. An automatic collapse that would hide keyboard focus keeps the group open and leaves focus in place; a manual close focuses the process control before hiding its members. Closing a whole Turn resets its nested groups and tool details to collapsed; hidden process renderers remount while their seats stay stable. The session-scoped store records expanded completed answer generations; different answer generations start collapsed. ----- diff --git a/packages/client/ui-chat/README.zh.md b/packages/client/ui-chat/README.zh.md index 1f73f7007c..eab14ae3c6 100644 --- a/packages/client/ui-chat/README.zh.md +++ b/packages/client/ui-chat/README.zh.md @@ -67,7 +67,7 @@ Assistant Markdown 中的 HTTP(S) 链接在普通点击时会在该类型已注 每个思考行初始都折叠,包括流式输出期间和仅含思考的回复。点击该行可展开或收起完整 Markdown;后续回答文本、工具调用和流结束都保留用户的选择。展开的思考采用紧凑的次级排版:标题只加粗,不改变字号、行高或颜色。折叠摘要保持单行,在流式输出期间跟随最新思考行,结算后显示第一行。 -「设置 → 通用设置」提供持久化到 `ui-chat` 命名空间的「工作过程展示」偏好:简洁(默认)、详细、完全展开。简洁折叠已完成轮次;详细和完全展开不显示整轮控件。详细默认收起步骤组,完全展开默认展开步骤组,两者均允许手动切换。持久化字段 `transcriptView` 接受旧值 `normal`,客户端将其读取为 `expanded`,不主动改写存储;用户选择后仅保存三个新值。开场系统提示词独立显示于开场 User 上方。简洁与详细模式下,Assistant 回复之间的连续过程行都默认收起,同一节点内正文之前的推理也归入过程组。进行中的标题显示最新运行工具的类别,没有运行工具时显示分析阶段。重试行独立展示,不归入二级过程组。下一条 Assistant 回复开始输出可见正文时,该组转为完成。完成标题按类别统计不同调用,同一路径的多次调用分别计数,显示调用最多的前三类但不显示数量;次数相同按首次出现顺序排列。两类用「并」(英文为 “and”)连接,仅当两项均以「已」开头时共享首个「已」;三类用逗号连接,超过三类追加「等」(英文为 “etc.”)。英文后续动作短语首字母小写。没有工具统计时,使用分析完成的回退文案。系统提示词、上下文注入与权限元数据不计入类别。展开区在 400px 与视口高度的一半之中取较小值作为最大高度,超出后内部滚动。到 `turn/end` 时,最后一个步骤只有在包含非空文本、图片或未知可见块且不含工具调用块时才成为最终答案边界;边界之前的上下文注入、推理、较早 Assistant 内容、工具行与重试行随后默认收起。控件在 `turn/start` 时出现,执行期间始终展开且不可点击。标题显示「已处理…」(英文为 `Worked for …`),每秒更新用时,最少显示 1 秒。正常完成后固定时长,显示「用时…」(英文为 `Took …`),默认收起。取消后显示「已停止」(英文为 `Stopped`);失败后显示「处理失败」(英文为 `Failed`)。取消或失败的轮次始终展开,控件不可点击。缺少起点时间戳时省略时长。摘要下方的通栏分隔线将其与正文或展开后的过程行隔开。用户与 steering 消息、系统提示词、错误、最大 token 与 turn-tail 行留在过程组外;关闭时没有最终答案的轮次会收起所有过程行,独立行保持可见。新的过程控件插入时不会改变既有行的相对顺序:开场人工输入从首次投影起便位于控件和过程行之前,系统提示词则始终位于该输入上方。仍可通过「加载更早」获取历史时,已加载 `turn/start` 的已关闭轮次可以独立折叠,包括由上下文注入唤起的轮次。缺少起点的轮次会等待分页补齐起点或加载完历史;其他尚未加载的轮次不会阻止折叠。稳定 Chat Node Seat 保持行的位置,隐藏成员不产生消息流间距;只有中间没有独立输入时,收起控件才与正文相隔 8px。完成后的收起不依赖是否跟随尾部,因此正在上方阅读的用户可能看到 transcript 重排。若自动收起会隐藏当前键盘焦点,则过程组保持展开且焦点留在原处;手动收起会先把焦点移到过程控件,再隐藏成员。收起整轮会把内部过程组和工具详情重置为折叠;隐藏的过程 renderer 重新挂载,seat 保持稳定。会话作用域存储记录完成后的手动展开正文 generation;不同正文 generation 均默认收起。 +「设置 → 通用设置」提供持久化到 `ui-chat` 命名空间的「工作过程展示」偏好:简洁(默认)、详细、完全展开。简洁折叠已完成轮次;详细和完全展开不显示整轮控件。详细默认收起步骤组,完全展开默认展开步骤组,两者均允许手动切换。持久化字段 `transcriptView` 接受旧值 `normal`,客户端将其读取为 `expanded`,不主动改写存储;用户选择后仅保存三个新值。开场系统提示词独立显示于开场 User 上方。简洁与详细模式下,Assistant 回复之间的连续过程行都默认收起,同一节点内正文之前的推理也归入过程组。进行中的标题显示最新运行工具的类别,没有运行工具时显示分析阶段。重试行独立展示,不归入二级过程组。下一条 Assistant 回复开始输出可见正文时,该组转为完成。完成标题按类别统计不同调用,同一路径的多次调用分别计数,显示调用最多的前三类但不显示数量;次数相同按首次出现顺序排列。两类用「并」(英文为 “and”)连接,仅当两项均以「已」开头时共享首个「已」;三类用逗号连接,超过三类追加「等」(英文为 “etc.”)。英文后续动作短语首字母小写。没有工具统计时,使用分析完成的回退文案。系统提示词、上下文注入与权限元数据不计入类别。展开区在 400px 与视口高度的一半之中取较小值作为最大高度,超出后内部滚动。到 `turn/end` 时,最后一个步骤只有在包含非空文本、图片或未知可见块且不含工具调用块时才成为最终答案边界;边界之前的上下文注入、推理、较早 Assistant 内容、工具行与重试行随后默认收起。控件在 `turn/start` 时出现,执行期间始终展开且不可点击。标题显示「已处理…」(英文为 `Worked for …`),每秒更新用时,最少显示 1 秒。正常完成后固定时长,最少显示 1 秒,显示「用时…」(英文为 `Took …`),默认收起。取消后显示「已停止」(英文为 `Stopped`);失败后显示「处理失败」(英文为 `Failed`)。取消或失败的轮次始终展开,控件不可点击。缺少起点时间戳时省略时长。摘要下方的通栏分隔线将其与正文或展开后的过程行隔开。用户与 steering 消息、系统提示词、错误、最大 token 与 turn-tail 行留在过程组外;关闭时没有最终答案的轮次会收起所有过程行,独立行保持可见。新的过程控件插入时不会改变既有行的相对顺序:开场人工输入从首次投影起便位于控件和过程行之前,系统提示词则始终位于该输入上方。仍可通过「加载更早」获取历史时,已加载 `turn/start` 的已关闭轮次可以独立折叠,包括由上下文注入唤起的轮次。缺少起点的轮次会等待分页补齐起点或加载完历史;其他尚未加载的轮次不会阻止折叠。稳定 Chat Node Seat 保持行的位置,隐藏成员不产生消息流间距;只有中间没有独立输入时,收起控件才与正文相隔 8px。完成后的收起不依赖是否跟随尾部,因此正在上方阅读的用户可能看到 transcript 重排。若自动收起会隐藏当前键盘焦点,则过程组保持展开且焦点留在原处;手动收起会先把焦点移到过程控件,再隐藏成员。收起整轮会把内部过程组和工具详情重置为折叠;隐藏的过程 renderer 重新挂载,seat 保持稳定。会话作用域存储记录完成后的手动展开正文 generation;不同正文 generation 均默认收起。 ----- diff --git a/packages/client/ui-chat/src/client/chat/TurnProcessNodeView.tsx b/packages/client/ui-chat/src/client/chat/TurnProcessNodeView.tsx index 7a306fbef8..e506c615ba 100644 --- a/packages/client/ui-chat/src/client/chat/TurnProcessNodeView.tsx +++ b/packages/client/ui-chat/src/client/chat/TurnProcessNodeView.tsx @@ -26,7 +26,7 @@ export const TurnProcessNodeView = memo(function TurnProcessNodeView({ const running = turn?.status === 'open' const reason = turn?.end?.data.reason.kind const duration = turn?.start === undefined ? undefined - : formatRunDuration(Math.max(running ? 1000 : 0, (turn.end?.time ?? now) - turn.start.time), t) + : formatRunDuration(Math.max(1000, (turn.end?.time ?? now) - turn.start.time), t) // Other end reasons retain elapsed time; only cancellation and failure replace it. const label = reason === 'aborted' ? t('message.stopped') : reason === 'error' ? t('message.turnProcess.failed') diff --git a/packages/client/ui-chat/tests/chat-view.client.spec.tsx b/packages/client/ui-chat/tests/chat-view.client.spec.tsx index 3ed8d81a3e..621be76f93 100644 --- a/packages/client/ui-chat/tests/chat-view.client.spec.tsx +++ b/packages/client/ui-chat/tests/chat-view.client.spec.tsx @@ -580,6 +580,9 @@ describe('Chat node rendering', () => { describe('ChatView', () => { it.each([ + [0, 'Took 1s'], + [250, 'Took 1s'], + [999, 'Took 1s'], [4_000, 'Took 4s'], [65_000, 'Took 1m 05s'], [3_665_000, 'Took 1h 01m 05s'],