Skip to content

Conversation

@Simon-He95
Copy link
Collaborator

@Simon-He95 Simon-He95 commented Oct 11, 2025

Summary by CodeRabbit

  • Bug Fixes

    • Streaming messages update reliably; previous blocks finalize before new content to prevent stuck “loading” states.
    • Reasoning sections render consistently with correct status transitions.
    • Typing sound plays at the appropriate time during updates.
    • Error notifications show accurate messages and only when the window is unfocused.
    • Variant replies update the correct slot and the main message when active.
  • Refactor

    • Simplified message enrichment and update flow for fewer glitches and a more consistent UI.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 11, 2025

Walkthrough

The chat store’s streaming update logic was reworked: content and reasoning_content blocks now finalize prior blocks to success before appending or creating new loading blocks; message enrichment precedes UI updates; variant vs non-variant update paths were unified; error handling and notifications were centralized and gated by window focus.

Changes

Cohort / File(s) Summary
Chat streaming and error handling refactor
src/renderer/src/stores/chat.ts
- Normalize content block flow: finalize previous block to success, then append or create loading content; typing sound moved post-block handling.
- Mirror flow for reasoning_content blocks with status management and appends.
- Cache/generating updates: always fetch, enrich, then update parent/variants; unify variant vs non-variant paths.
- Centralized error extraction and notifications after focus checks; consistent try/catch around enrichment and UI updates.
- End-of-stream handling updated for variants and main message resolution.
- Reduced nesting by enriching first, then updating UI.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Stream as Model Stream
  participant Store as Chat Store
  participant Enricher as Enrich Message
  participant UI as UI State
  participant Notify as Notifications
  participant Focus as Window Focus

  rect rgb(240,248,255)
  note over Store,UI: Token/content arrives
  Stream->>Store: content | reasoning_content | error
  end

  alt content or reasoning_content
    Store->>Store: Finalize previous block to success
    Store->>Store: Create/append current loading block
    Store->>UI: Update blocks (loading)
    Store-->>UI: Play typing sound (if applicable)
  else error chunk
    Store->>Enricher: Extract/derive error message
    Enricher-->>Store: enriched error
  end

  rect rgb(245,255,250)
  note over Store,Enricher: Periodic/EOF enrichment
  Store->>Enricher: Fetch and enrich latest message
  Enricher-->>Store: enrichedMessage
  end

  alt Variant message
    Store->>UI: Update parent message's variants
    opt If main message active
      Store->>UI: Update main message from variant
    end
  else Non-variant message
    Store->>UI: Update message in place
  end

  alt Error detected
    Store->>Focus: isFocused?
    Focus-->>Store: true/false
    opt If not focused
      Store->>Notify: Emit error notification
    end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

I tap my paws on streams of thought,
Blocks line up—success!—no knots.
Variants hop to parent’s side,
Errors whisper, we won’t hide.
Enriched crumbs mark every trail,
The UI’s garden—none shall fail.
Thump-thump: shipped without a tail! 🐇✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title concisely summarizes the primary refactoring by indicating streamlined message handling and improved error notification logic within the chat module. It directly reflects the extensive updates to content block flow, error handling, and notification placement described in the changeset. The phrasing follows conventional commit style and avoids unnecessary detail while remaining specific.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
src/renderer/src/stores/chat.ts (1)

614-625: Reasoning stream suffers the same drop + premature finalization.

Identical to the content path: when the prior block isn’t reasoning_content, the new reasoning text never gets stored and active tool-call blocks are forced to success. Finalize first, then create a new reasoning block; only append when the existing block already matches.

-          const lastReasoningBlock = curMsg.content[curMsg.content.length - 1]
-          if (lastReasoningBlock) {
-            lastReasoningBlock.status = 'success'
-            if (lastReasoningBlock.type === 'reasoning_content') {
-              lastReasoningBlock.content += msg.reasoning_content
-            }
-          } else {
+          const lastReasoningBlock = curMsg.content[curMsg.content.length - 1]
+          if (lastReasoningBlock?.type === 'reasoning_content') {
+            lastReasoningBlock.content += msg.reasoning_content
+          } else {
+            finalizeLastBlock()
             curMsg.content.push({
               type: 'reasoning_content',
               content: msg.reasoning_content,
               status: 'loading',
               timestamp: Date.now()
             })
           }
🧹 Nitpick comments (1)
src/renderer/src/stores/chat.ts (1)

919-924: Keep the tuple typing instead of casting to string[].

cache.entries() yields [messageId, cached], so asserting to string[] erases the actual tuple shape and hides the cached payload type. Preserve the tuple typing (e.g., as [string, { message: AssistantMessage | UserMessage; threadId: string }] | undefined) to keep type safety.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 66f4c4c and 44dc880.

📒 Files selected for processing (1)
  • src/renderer/src/stores/chat.ts (3 hunks)
🧰 Additional context used
📓 Path-based instructions (15)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development-setup.mdc)

**/*.{js,jsx,ts,tsx}: 使用 OxLint 进行代码检查
Log和注释使用英文书写

**/*.{js,jsx,ts,tsx}: Use OxLint for JS/TS code; pre-commit hooks run lint-staged and typecheck
Use camelCase for variables and functions
Use PascalCase for types and classes
Use SCREAMING_SNAKE_CASE for constants

Files:

  • src/renderer/src/stores/chat.ts
src/{main,renderer}/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/electron-best-practices.mdc)

src/{main,renderer}/**/*.ts: Use context isolation for improved security
Implement proper inter-process communication (IPC) patterns
Optimize application startup time with lazy loading
Implement proper error handling and logging for debugging

Files:

  • src/renderer/src/stores/chat.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/error-logging.mdc)

**/*.{ts,tsx}: 始终使用 try-catch 处理可能的错误
提供有意义的错误信息
记录详细的错误日志
优雅降级处理
日志应包含时间戳、日志级别、错误代码、错误描述、堆栈跟踪(如适用)、相关上下文信息
日志级别应包括 ERROR、WARN、INFO、DEBUG
不要吞掉错误
提供用户友好的错误信息
实现错误重试机制
避免记录敏感信息
使用结构化日志
设置适当的日志级别

Files:

  • src/renderer/src/stores/chat.ts
src/renderer/src/**/*

📄 CodeRabbit inference engine (.cursor/rules/i18n.mdc)

src/renderer/src/**/*: All user-facing strings must use i18n keys (avoid hardcoded user-visible text in code)
Use the 'vue-i18n' framework for all internationalization in the renderer
Ensure all user-visible text in the renderer uses the translation system

Files:

  • src/renderer/src/stores/chat.ts
src/renderer/src/stores/**/*.{vue,ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/pinia-best-practices.mdc)

src/renderer/src/stores/**/*.{vue,ts,tsx,js,jsx}: Use modules to organize related state and actions
Implement proper state persistence for maintaining data across sessions
Use getters for computed state properties
Utilize actions for side effects and asynchronous operations
Keep the store focused on global state, not component-specific data

Files:

  • src/renderer/src/stores/chat.ts
src/renderer/**/*.{vue,ts,js,tsx,jsx}

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

渲染进程代码放在 src/renderer

Files:

  • src/renderer/src/stores/chat.ts
src/renderer/src/**/*.{vue,ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/vue-best-practices.mdc)

src/renderer/src/**/*.{vue,ts,tsx,js,jsx}: Use the Composition API for better code organization and reusability
Implement proper state management with Pinia
Utilize Vue Router for navigation and route management
Leverage Vue's built-in reactivity system for efficient data handling

Files:

  • src/renderer/src/stores/chat.ts
src/renderer/**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (.cursor/rules/vue-shadcn.mdc)

src/renderer/**/*.{ts,tsx,vue}: Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError).
Use TypeScript for all code; prefer types over interfaces.
Avoid enums; use const objects instead.
Use arrow functions for methods and computed properties.
Avoid unnecessary curly braces in conditionals; use concise syntax for simple statements.

Files:

  • src/renderer/src/stores/chat.ts
src/renderer/**/*.{vue,ts}

📄 CodeRabbit inference engine (.cursor/rules/vue-shadcn.mdc)

Implement lazy loading for routes and components.

Files:

  • src/renderer/src/stores/chat.ts
src/renderer/**/*.{ts,vue}

📄 CodeRabbit inference engine (.cursor/rules/vue-shadcn.mdc)

src/renderer/**/*.{ts,vue}: Use useFetch and useAsyncData for data fetching.
Implement SEO best practices using Nuxt's useHead and useSeoMeta.

Use Pinia for frontend state management (do not introduce alternative state libraries)

Files:

  • src/renderer/src/stores/chat.ts
**/*.{ts,tsx,js,vue}

📄 CodeRabbit inference engine (CLAUDE.md)

Use English for all logs and comments

Files:

  • src/renderer/src/stores/chat.ts
**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (CLAUDE.md)

Enable and adhere to strict TypeScript typing (avoid implicit any, prefer precise types)

Files:

  • src/renderer/src/stores/chat.ts
src/renderer/src/**

📄 CodeRabbit inference engine (AGENTS.md)

Put application code for the Vue app under src/renderer/src (components, stores, views, i18n, lib)

Files:

  • src/renderer/src/stores/chat.ts
src/renderer/src/**/*.{vue,ts}

📄 CodeRabbit inference engine (AGENTS.md)

All user-facing strings in the renderer must use vue-i18n keys defined in src/renderer/src/i18n

Files:

  • src/renderer/src/stores/chat.ts
**/*.{js,jsx,ts,tsx,vue}

📄 CodeRabbit inference engine (AGENTS.md)

Apply Prettier formatting: single quotes, no semicolons, max width 100

Files:

  • src/renderer/src/stores/chat.ts
🧬 Code graph analysis (1)
src/renderer/src/stores/chat.ts (1)
src/shared/chat.d.ts (2)
  • AssistantMessage (39-42)
  • UserMessage (34-37)
🪛 Biome (2.1.2)
src/renderer/src/stores/chat.ts

[error] 744-744: Comparing to itself is potentially pointless.

(lint/suspicious/noSelfCompare)


[error] 752-755: Comparing to itself is potentially pointless.

(lint/suspicious/noSelfCompare)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-check (x64)
🔇 Additional comments (1)
src/renderer/src/stores/chat.ts (1)

724-781: Unified focus-gated error notification flow looks solid.

Variant vs. non-variant updates now converge before emitting notifications, and the focus check prevents noisy popups. Nice cleanup.

Comment on lines +595 to 607
if (lastContentBlock) {
lastContentBlock.status = 'success'
if (lastContentBlock.type === 'content') {
lastContentBlock.content += msg.content
}
} else {
curMsg.content.push({
type: 'content',
content: msg.content,
status: 'loading',
timestamp: Date.now()
})
// 如果是新块的第一个字符,也播放声音
playTypewriterSound()
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Content chunks are dropped when the previous block isn’t content.

As soon as a text chunk arrives while the last block is a tool call/action/etc., this branch marks that block success but never appends a new content block, so the incoming text is lost. It also forces running tool-call blocks to success, breaking their lifecycle. Reuse finalizeLastBlock() before creating a fresh content block when the prior block isn’t textual, and only append to the existing block when it’s already content.

-          const lastContentBlock = curMsg.content[curMsg.content.length - 1]
-          if (lastContentBlock) {
-            lastContentBlock.status = 'success'
-            if (lastContentBlock.type === 'content') {
-              lastContentBlock.content += msg.content
-            }
-          } else {
+          const lastContentBlock = curMsg.content[curMsg.content.length - 1]
+          if (lastContentBlock?.type === 'content') {
+            lastContentBlock.content += msg.content
+          } else {
+            finalizeLastBlock()
             curMsg.content.push({
               type: 'content',
               content: msg.content,
               status: 'loading',
               timestamp: Date.now()
             })
           }
📝 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.

Suggested change
if (lastContentBlock) {
lastContentBlock.status = 'success'
if (lastContentBlock.type === 'content') {
lastContentBlock.content += msg.content
}
} else {
curMsg.content.push({
type: 'content',
content: msg.content,
status: 'loading',
timestamp: Date.now()
})
// 如果是新块的第一个字符,也播放声音
playTypewriterSound()
}
const lastContentBlock = curMsg.content[curMsg.content.length - 1]
if (lastContentBlock?.type === 'content') {
lastContentBlock.content += msg.content
} else {
finalizeLastBlock()
curMsg.content.push({
type: 'content',
content: msg.content,
status: 'loading',
timestamp: Date.now()
})
}
🤖 Prompt for AI Agents
In src/renderer/src/stores/chat.ts around lines 595 to 607: the current branch
marks the last block `success` and drops incoming text when the previous block
isn’t of type 'content'; instead call finalizeLastBlock() to properly complete
non-content blocks, then create and push a new 'content' block for the incoming
msg.content; only append to lastContentBlock.content when lastContentBlock
exists and lastContentBlock.type === 'content', otherwise create a fresh
{type:'content', content: msg.content, status:'loading', timestamp: Date.now()}
entry on curMsg.content so text is not lost and tool-call blocks keep their
lifecycle.

@zerob13
Copy link
Collaborator

zerob13 commented Oct 11, 2025

@codex review

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting

Comment on lines 592 to 606
// 处理普通内容
else if (msg.content) {
const lastContentBlock = curMsg.content[curMsg.content.length - 1]
if (lastContentBlock && lastContentBlock.type === 'content') {
lastContentBlock.content += msg.content
// 打字机音效,与实际数据流同步
playTypewriterSound()
} else {
if (lastContentBlock) {
lastContentBlock.status = 'success'
if (lastContentBlock) {
lastContentBlock.status = 'success'
if (lastContentBlock.type === 'content') {
lastContentBlock.content += msg.content
}
} else {
curMsg.content.push({
type: 'content',
content: msg.content,
status: 'loading',
timestamp: Date.now()
})

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Append a new content block when previous block is not text

In the streaming handler, when msg.content arrives and the last block exists but is not of type content, the new code marks that block as success and returns without pushing a new text block. Previously the else branch covered both "no block" and "non‑content block" so the plain response text was appended. With the current logic, any stream that first emits a tool/action/reasoning block and then regular content will drop the textual response entirely while still playing the typing sound. Users will observe empty assistant replies whenever content follows a different block type.

Useful? React with 👍 / 👎.

Comment on lines 612 to 623
// 处理推理内容
if (msg.reasoning_content) {
const lastReasoningBlock = curMsg.content[curMsg.content.length - 1]
if (lastReasoningBlock && lastReasoningBlock.type === 'reasoning_content') {
lastReasoningBlock.content += msg.reasoning_content
} else {
if (lastReasoningBlock) {
lastReasoningBlock.status = 'success'
if (lastReasoningBlock) {
lastReasoningBlock.status = 'success'
if (lastReasoningBlock.type === 'reasoning_content') {
lastReasoningBlock.content += msg.reasoning_content
}
} else {
curMsg.content.push({
type: 'reasoning_content',
content: msg.reasoning_content,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Push reasoning block when switching from other block types

The refactor applies the same pattern to msg.reasoning_content: if any block exists, it is marked success but a new reasoning block is only created when there were no blocks at all. When reasoning content is streamed after another block (e.g., a tool call or prior content), no reasoning block is appended and the explanation text disappears. The previous code handled this case by finalizing the prior block and then pushing a new reasoning block; that behaviour is now lost, so reasoning output never renders when it arrives after a different block type.

Useful? React with 👍 / 👎.

@zerob13 zerob13 merged commit 301b14f into ThinkInAIXYZ:dev Oct 11, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants