Uh oh!
There was an error while loading. Please reload this page.
feat(ui): group reasoning and tool calls into collapsible Processing blocks - #1320
Merged
Conversation
…ing blocks Group each maximal run of thinking + tool-call timeline entries between two assistant answer texts into one collapsed "Processing" block (#1307). Answer text stays a grouping boundary and always renders in place, so a turn can hold several Processing blocks; the expanded block replays the full timeline (the same 深度思考 disclosures and tool trows, nested one indent in). - materialize: add a `processing` TurnTimelineItem kind and group in a shared finalize pass used by both the settled (buildTurnTimeline) and live-overlay paths, so streaming and replayed history fold identically; projectTurnTools descends into blocks while preserving turn identity. - trow-summary/copy: summarizeProcessing / isProcessingRunning / processingNeedsAttention — reasoning count + tool buckets + red failed count, live current-activity line, waiting_permission force-open (errors stay collapsed), reusing the existing trow summary + disclosure state machine. - chat-turn: ProcessingBlock renders via the shared disclosure/SETTLE_FADE seams, collapsed by default with sticky manual toggle.
Show a collapsed "Processing" block (#1307): two think-then-call steps with a failing tool fold into one summary (思考 2 次 + tool counts + a red failed count) ahead of the assistant answer. Enumerate it in the chat-surface Storybook contract.
…inking runs bare Review feedback on #1307: - The collapsed summary line now rolls up tool activity only (「读取 1 个文件, 搜索 1 次,1 个失败」) — folded reasoning stays inside the block but is no longer counted; the settled `summary.thinking` copy is removed (zh/en). The live `thinkingActivity` fallback (「正在深度思考」 when the block's tools are done and thinking still streams) stays. - A run between two answer texts folds into a Processing block only when it contains at least one tools group; a pure-thinking run renders as the bare 深度思考 disclosure (including the live streaming path). groupProcessing applies the rule identically on the settled and live-overlay paths.
… render layer External review (Codex + Kimi) adjudication on #1307: baking the 'processing' kind into the shared TurnTimelineItem model forced every timeline-rewriting pass (overlayLiveTurn's flatten/refold, projectTurnTools' descent) to maintain the nesting invariant, and projectTurnTools missed it — shell-run folding could strip a block's every tool and strand an illegal thinking-only Processing block with an empty summary (P1). - materialize.ts reverts to main's flat TurnTimelineItem model; the processing kind, its child types, groupProcessing/flattenProcessing/ finalizeTimeline, overlayLiveTurn's flatten→refold, and projectTurnTools' processing branch are all removed. - New pure module timeline-fold.ts derives the folded view at render time: foldTimeline keeps answer text in place as the boundary, folds a maximal thinking+tools run into one block only when it contains a tools group, and gives each block a stable id from the preceding text's messageId ('start' at turn head) — so a block's React key survives its first tool being projected away without remounting or dropping a manual toggle (P2). - TurnView folds via useMemo(foldTimeline); ProcessingBlock consumes fold children and loses its dead onStreamingSettled prop; TurnTimelineEntry, timelineEntryKey, and the live-content check return to their flat forms. - Tests: materialize-turns.test.ts and live-turn-projection.test.ts restore their original stronger assertions (flatten helpers removed); the grouping cases move to timeline-fold.test.ts with a block-id stability case; new P1 regression (shell-run fold leaves a flat thinking-only timeline) and a render-level waiting_permission force-open test.
…y pick, and localization External review (Codex + Kimi) adjudication on #1307, live-summary findings: - The live line now appends the failed clause whenever the block already holds an errored tool (「正在运行测试,1 个失败」) — errored tools stay collapsed, so the summary must carry the failure signal before settle, matching the trow (P2). - The current activity is the LAST live entry in timeline order: children are walked in reverse and a still-streaming thinking block or a group's active tool wins, instead of flattening all tools and letting an earlier running tool outrank a later streaming reasoning block (P3). - The no-intent/no-displayName fallback routes through resolveToolDisplayName, so a bare load_tools call reads as the localized 「加载工具组」 (P3). The resolver and isConnectorTool move down into trow-summary.ts (the leaf module) to avoid an import cycle; presentation.ts re-exports them for its existing consumers.
…p is projected away
…collapse # Conflicts: # packages/ui/src/chat-turn.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes#1307.
During longer tasks, every reasoning step and tool call rendered as its own timeline item, pushing the actual answer far down the conversation. Now each maximal run of thinking + tool activity between two answer texts folds into one collapsed "Processing" block; answer text always renders in place, and the expanded panel replays the full original timeline (the same 深度思考 disclosures and tool trows, nothing is lost).
Behavior decisions, where they differ from the issue's suggested behavior:
tool-trow-summary.test.ts. Onlywaiting_permissionforces the block open; permission prompts themselves live outside the timeline and are unaffected.defaultOpen).The timeline model stays flat: the fold is a render-layer derivation (
packages/ui/src/timeline-fold.ts, applied viauseMemoinTurnView), so timeline-rewriting passes (overlayLiveTurn,projectTurnTools, shell-run folding) never maintain a nesting invariant — an invalid "tools-less Processing block" is unrepresentable by construction. Each block's React key derives from its preceding answer-text boundary, so projection removing a block's first tool cannot remount the disclosure or drop a manual toggle. Rendering reuses the existingCollapsibleprimitive,useToolDisclosurestate machine, andSETTLE_FADE; no new CSS.Verification
@maka/uitests 223/223,@maka/desktoptests 2761/2761 (both re-run independently of the implementation pass),npm run lint,npm run format:check, workspacetypecheck+build— all green.timeline-fold.test.ts(pure thinking stays bare, interleaved runs, text-boundary multi-block, id survives first-tool projection); shell-run-fold regression inmaterialize.test.ts(a turn whose only tool is projected away yields a flat['thinking']timeline); render-levelwaiting_permissionforce-open inprocessing-block.test.tsx; live-summary cases intool-trow-summary.test.ts(failed-count while running, last-live-activity pick, connector-tool localization).Processingstory inchat-surface.stories.tsx(storybook contract updated) covers the collapsed block with a failed tool ahead of the answer text.mainvs this branch), composed with ImageMagick only.Before / after (live app)
Expanded state — the same block manually opened, replaying the full timeline:
Review focus
packages/ui/src/timeline-fold.ts— the single place the fold rule lives;materialize.tsdiffers frommainonly by a doc comment stating the model stays flat.trow-summary.tsnow ownsresolveToolDisplayName/isConnectorTool(moved frompresentation.ts, which re-exports them) so the live Processing summary shares the localized name fallback without an import cycle.streaming-handoff.test.tsasserts the folded render;materialize-turns.test.tsandlive-turn-projection.test.tsare restored byte-identical tomain.