feat(agent): present reasoning parts inline in message stream (#122)#347
feat(agent): present reasoning parts inline in message stream (#122)#347hiqiancheng wants to merge 3 commits into
Conversation
PR #216 moved scripts/ci/pr-template-check.js to apps/desktop/scripts/ci/pr-template-check.js but did not update the workflow reference. This caused the PR Template Check workflow to fail with "Cannot find module" on every PR.
Instead of rendering all reasoning as a single collapsible block above the answer, reasoning chunks now create ordered `reasoning` parts in the message parts array. Each reasoning segment is rendered as a card with a brain icon and live duration, matching the tool call card style. The expanded content is shown in italic with light-gray styling. Reasoning cards auto-expand when created and auto-collapse when streaming ends, unless the user has manually toggled them. Duration is frozen when the segment ends and displayed as "已推理 Xs". Old messages with only a flat `reasoning` field continue to render via the legacy fallback section. Closes #122
There was a problem hiding this comment.
Sorry @hiqiancheng, you have reached your weekly rate limit of 500000 diff characters.
Please try again later or upgrade to continue using Sourcery
📝 WalkthroughSummary by CodeRabbit
WalkthroughThis PR implements interleaved reasoning presentation by modeling reasoning as discrete message parts instead of a single aggregate buffer. The projection service now streams reasoning chunks into dedicated ChangesInterleaved Reasoning Presentation
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Suggested labels
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/desktop/src/services/AgentService/task/projection/projection.ts (1)
429-440: 🧹 Nitpick | 🔵 Trivial | 💤 Low valueConsider resetting
activeReasoningPartIdin checkpoint restore for consistency.While
freezeActiveReasoningPartsafely handles a staleactiveReasoningPartId(by checking if the part exists), explicitly resetting the tracker duringrestoreCheckpointPresentationwould make the lifecycle clearer and avoid an unnecessary lookup on the first post-restore reasoning chunk.♻️ Suggested addition
private restoreCheckpointPresentation(): boolean { if (!this.lastCheckpointPresentation) { return false; } this.snapshot.sessionHistory = cloneValue(this.lastCheckpointPresentation.sessionHistory); this.activeAssistantMessageId = this.lastCheckpointPresentation.activeAssistantMessageId; this.response = this.lastCheckpointPresentation.response; this.reasoning = this.lastCheckpointPresentation.reasoning; + this.activeReasoningPartId = null; this.widgets.resetTransientState(); return true; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/desktop/src/services/AgentService/task/projection/projection.ts` around lines 429 - 440, The restoreCheckpointPresentation method should also clear the activeReasoningPartId to avoid a stale lookup later; update restoreCheckpointPresentation to set this.activeReasoningPartId (or the appropriate tracker field) to the value from lastCheckpointPresentation or to undefined/null as intended by the lifecycle so that freezeActiveReasoningPart won't have to handle a stale id on first post-restore reasoning chunk — locate restoreCheckpointPresentation, lastCheckpointPresentation, and activeReasoningPartId in projection.ts and add the explicit reset alongside restoring sessionHistory, activeAssistantMessageId, response, and reasoning before calling widgets.resetTransientState.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@apps/desktop/src/views/SearchView/components/ConversationPanel/components/AssistantMessage.vue`:
- Line 18: The div element in AssistantMessage.vue that currently reads as a
single long line containing both the v-if expression (message.reasoning &&
!hasReasoningParts) and the class attribute (class="reasoning-section mb-4
w-full") should be reformatted onto multiple lines to satisfy Prettier; locate
the div in the template (the element using v-if with message.reasoning and
hasReasoningParts) and break the attributes across lines (e.g., put the opening
tag, v-if, and class on separate lines or each attribute on its own line) so the
line length is reduced while preserving the same v-if condition and class name
values.
---
Outside diff comments:
In `@apps/desktop/src/services/AgentService/task/projection/projection.ts`:
- Around line 429-440: The restoreCheckpointPresentation method should also
clear the activeReasoningPartId to avoid a stale lookup later; update
restoreCheckpointPresentation to set this.activeReasoningPartId (or the
appropriate tracker field) to the value from lastCheckpointPresentation or to
undefined/null as intended by the lifecycle so that freezeActiveReasoningPart
won't have to handle a stale id on first post-restore reasoning chunk — locate
restoreCheckpointPresentation, lastCheckpointPresentation, and
activeReasoningPartId in projection.ts and add the explicit reset alongside
restoring sessionHistory, activeAssistantMessageId, response, and reasoning
before calling widgets.resetTransientState.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 23239d35-697a-4175-a7c4-447caa598d48
📒 Files selected for processing (6)
apps/desktop/src/i18n/messages.tsapps/desktop/src/services/AgentService/task/projection/projection.tsapps/desktop/src/types/session.tsapps/desktop/src/utils/session.tsapps/desktop/src/views/SearchView/components/ConversationPanel/components/AssistantMessage.vueapps/desktop/src/views/SearchView/components/ConversationPanel/components/ReasoningPartItem.vue
📜 Review details
🧰 Additional context used
🪛 ESLint
apps/desktop/src/views/SearchView/components/ConversationPanel/components/AssistantMessage.vue
[error] 18-18: Replace ·v-if="message.reasoning·&&·!hasReasoningParts"·class="reasoning-section·mb-4·w-full" with ⏎························v-if="message.reasoning·&&·!hasReasoningParts"⏎························class="reasoning-section·mb-4·w-full"⏎····················
(prettier/prettier)
🔇 Additional comments (14)
apps/desktop/src/types/session.ts (1)
78-91: LGTM!apps/desktop/src/utils/session.ts (1)
14-21: LGTM!apps/desktop/src/services/AgentService/task/projection/projection.ts (4)
196-204: LGTM!
208-208: LGTM!Also applies to: 247-248, 261-261, 291-291
452-470: LGTM!
493-495: LGTM!apps/desktop/src/views/SearchView/components/ConversationPanel/components/ReasoningPartItem.vue (4)
65-76: LGTM!
78-107: LGTM!Also applies to: 121-121
175-177: LGTM!Also applies to: 184-191
115-126: LGTM!Also applies to: 128-136, 152-173
apps/desktop/src/views/SearchView/components/ConversationPanel/components/AssistantMessage.vue (3)
264-266: LGTM!
71-75: LGTM!Also applies to: 172-176, 294-303
359-366: LGTM!apps/desktop/src/i18n/messages.ts (1)
508-510: LGTM!Also applies to: 1251-1253
| <!-- 推理过程显示(如果存在)--> | ||
| <div v-if="message.reasoning" class="reasoning-section mb-4 w-full"> | ||
| <!-- 推理过程显示(兼容老消息:没有 reasoning parts 时)--> | ||
| <div v-if="message.reasoning && !hasReasoningParts" class="reasoning-section mb-4 w-full"> |
There was a problem hiding this comment.
Fix prettier formatting violation.
The line exceeds prettier's preferred length and should be reformatted to multiple lines for readability.
🎨 Prettier fix
- <div v-if="message.reasoning && !hasReasoningParts" class="reasoning-section mb-4 w-full">
+ <div
+ v-if="message.reasoning && !hasReasoningParts"
+ class="reasoning-section mb-4 w-full"
+ >📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <div v-if="message.reasoning && !hasReasoningParts" class="reasoning-section mb-4 w-full"> | |
| <div | |
| v-if="message.reasoning && !hasReasoningParts" | |
| class="reasoning-section mb-4 w-full" | |
| > |
🧰 Tools
🪛 ESLint
[error] 18-18: Replace ·v-if="message.reasoning·&&·!hasReasoningParts"·class="reasoning-section·mb-4·w-full" with ⏎························v-if="message.reasoning·&&·!hasReasoningParts"⏎························class="reasoning-section·mb-4·w-full"⏎····················
(prettier/prettier)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@apps/desktop/src/views/SearchView/components/ConversationPanel/components/AssistantMessage.vue`
at line 18, The div element in AssistantMessage.vue that currently reads as a
single long line containing both the v-if expression (message.reasoning &&
!hasReasoningParts) and the class attribute (class="reasoning-section mb-4
w-full") should be reformatted onto multiple lines to satisfy Prettier; locate
the div in the template (the element using v-if with message.reasoning and
hasReasoningParts) and break the attributes across lines (e.g., put the opening
tag, v-if, and class on separate lines or each attribute on its own line) so the
line length is reduced while preserving the same v-if condition and class name
values.
Summary
When a model emits reasoning and answer text in alternating segments, the current architecture renders all reasoning as a single collapsible block above the answer, losing the temporal order. This PR introduces
ReasoningMessagePartto present reasoning segments inline within the message parts stream, styled as collapsible cards with a brain icon and live duration.Key behavior changes:
reasoningparts in the messageparts[]array, preserving their temporal relationship with text and tool call segmentsreasoningfield continue to render via the legacy fallback sectionRelated issue or RFC
AI assistance disclosure
projection.ts) and UI layer (AssistantMessage.vue, newReasoningPartItem.vue); no AgentService core or runtime impactTesting evidence
pnpm --filter desktop type:checkpassed (vue-tsc --noEmit, no errors)pnpm --filter desktop test:runpassed (133 files, 673 tests)Risk notes
AgentService, runtime, MCP, or schema impact: Low — only projection layer and UI components affectedScreenshots or recordings
N/A — UI changes are internal to the conversation panel; no screenshot provided (dev server not running due to disk constraints).
Checklist
[WIP]or similar title prefixes.AgentService, runtime, MCP, or schema boundaries, there is an accepted RFC.pnpm test:prfor this code PR, or this is a docs-only change.pnpm test:coverage:rustor relied on CI coverage evidence.pnpm test:e2elocally or documented why CI is the first valid proof.