Skip to content

Conversation

@zhangmo8
Copy link
Collaborator

@zhangmo8 zhangmo8 commented Jan 16, 2026

Summary by CodeRabbit

Release Notes

  • New Features

    • Integrated Voice.ai Text-to-Speech provider for voice agent calls with configurable audio format, language, and model settings
    • Added audio message block rendering with playback controls
    • Voice call dialog accessible from chat input with start/stop controls
  • Performance

    • Optimized message runtime cache for faster thread clearing
    • Improved message cloning efficiency using structured cloning techniques
    • Enhanced message list rendering with intersection-based visibility tracking
  • Improvements

    • Tab-aware error handling for better multi-tab experience
    • Updated message handling for streaming and variant scenarios
    • Enhanced settings panel for Voice AI configuration

✏️ Tip: You can customize this high-level summary in your review settings.

zhangmo8 and others added 4 commits January 15, 2026 16:03
* docs(spec): add tool output guardrails

* fix(agent): tool output guardrails

* fix(agent): standardize tool offload extension

* feat: extract path to session

* fix: review issue

* fix: error response on renderer

* feat: add read_file pagination and whitelist-based tool offload

- Raise offload threshold from 3000 to 5000 characters
- Add whitelist for tools that require offload (execute_command, directory_tree, etc.)
- Add offset/limit params to read_file for pagination support
- Auto-truncate large files at 4500 chars with metadata hint
- Prevents infinite offload loop when reading offloaded files

* fix: independent reasoning time for each thinking block in agent loop
@zhangmo8 zhangmo8 marked this pull request as draft January 16, 2026 02:34
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 16, 2026

📝 Walkthrough

Walkthrough

This PR introduces Voice.ai TTS provider integration with configuration UI, replaces the virtual-scroller-based message list with IntersectionObserver-driven box rendering, optimizes message runtime caching with O(1) thread lookups via reverse indexing, refactors message cloning to use structuredClone with selective deep-copying, and adds per-tab error routing in the streaming pipeline. Includes documentation of performance optimizations and a message rendering refactor plan.

Changes

Cohort / File(s) Summary
Documentation & Analysis
docs/OPTIMIZATION_ANALYSIS.md, docs/OPTIMIZATION_SUMMARY.zh.md, docs/specs/message-render-refactor/todo.md
New documentation files detailing implemented optimizations (message cache, cloning, filtering) and planned refactors (virtual-scroller replacement with intersection-observer box rendering and height persistence).
Voice.ai Provider Integration
src/main/presenter/configPresenter/providers.ts, src/main/presenter/llmProviderPresenter/managers/providerInstanceManager.ts, src/main/presenter/llmProviderPresenter/providers/voiceAIProvider.ts
Registers new Voice.ai TTS provider with configuration defaults, adds provider factory mappings, and implements full VoiceAIProvider class with speech generation, voice discovery, and audio handling.
Voice.ai Settings UI
src/renderer/settings/components/ModelProviderSettingsDetail.vue, src/renderer/settings/components/VoiceAIProviderConfig.vue
Adds Voice.ai configuration panel with audio format, language, model, and agentId selectors plus temperature/topP sliders.
Voice.ai Chat Integration
src/renderer/src/components/chat-input/ChatInput.vue
Integrates voice-agent-widget UI with dynamic script loading, call dialog, and state management for Voice.ai calling feature.
Message Rendering Refactor
src/renderer/src/components/message/MessageList.vue, src/renderer/src/components/message/MessageBox.vue, src/renderer/src/components/message/MessageMinimap.vue
Replaces vue-virtual-scroller with IntersectionObserver-based box rendering, introduces height caching, prefetch logic, and streaming placeholder handling. MessageBox component manages per-message visibility and height observation.
Message Block Audio
src/renderer/src/components/message/MessageBlockAudio.vue, src/renderer/src/components/message/MessageItemAssistant.vue
New audio block component with playback controls and error handling; MessageItemAssistant detects and renders audio blocks.
Scrolling & Visibility Composables
src/renderer/src/composables/message/useBoxIntersection.ts, src/renderer/src/composables/message/useMessageScroll.ts
New IntersectionObserver-based composable for box visibility tracking with pre-render buffering; useMessageScroll removes virtual-scroller retry logic and uses container-based scrolling.
Message Runtime Cache & Cloning
src/renderer/src/lib/messageRuntimeCache.ts, src/main/presenter/agentPresenter/message/messageUtils.ts, src/main/presenter/agentPresenter/message/messageCompressor.ts, src/main/presenter/agentPresenter/message/messageTruncator.ts
Introduces threadToMessagesMap reverse index for O(1) cache clearing; extracts shared cloneMessageWithContent utility using structuredClone with selective deep-cloning for tool_call blocks; applies pre-filter-then-clone optimization in truncation flow.
Streaming Pipeline & Per-Tab Routing
src/main/presenter/agentPresenter/index.ts, src/main/presenter/agentPresenter/streaming/llmEventHandler.ts, src/main/presenter/agentPresenter/streaming/streamGenerationHandler.ts, src/main/presenter/agentPresenter/streaming/streamUpdateScheduler.ts
Adds optional tabId parameter to continueLoop, retryMessage, regenerateFromUserMessage; introduces sendStreamError and sendStreamEvent helpers for tab-aware error/event routing; updates streaming handlers to use per-tab dispatch.
Renderer Store & Chat State
src/renderer/src/stores/chat.ts, src/renderer/src/stores/providerStore.ts
Introduces messageListVersion and messageItems array with rebuild/append/update utilities; adds VoiceAIConfig type and get/update operations for Voice.ai settings; updates retryMessage, continueStream, regenerateFromUserMessage to propagate tabId.
Configuration & Custom Elements
electron.vite.config.ts, vitest.config.renderer.ts, vitest.config.ts
Adds isCustomElement helper recognizing 'voice-agent-widget' and 'ui-resource-renderer' tags for Vue template compiler.
Type Definitions & Icons
src/shared/chat.d.ts, src/shared/types/core/chat.ts, src/shared/types/presenters/agent.presenter.d.ts, src/renderer/src/components/icons/ModelIcon.vue
Extends AssistantMessageBlock.type union to include 'audio'; updates IAgentPresenter method signatures with tabId parameter; adds voiceai icon mapping.
Internationalization
src/renderer/src/i18n/en-US/chat.json, src/renderer/src/i18n/en-US/settings.json, src/renderer/src/i18n/zh-CN/chat.json, src/renderer/src/i18n/zh-CN/settings.json
Adds audio (play, pause) and call (title, description, start, stop, end) translations; adds Voice.ai TTS settings translations (audioFormat, language, model, agentId, temperature, topP) in both English and Chinese.
Backup
src/renderer/src/components/message/MessageList.vue.backup
Full backup of previous MessageList.vue implementation using vue-virtual-scroller.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ChatInput as ChatInput.vue
    participant VoiceWidget as voice-agent-widget
    participant Provider as VoiceAIProvider
    participant API as Voice.ai API
    participant Renderer as MessageItemAssistant

    User->>ChatInput: Click Voice Call Button
    ChatInput->>ChatInput: Load VoiceAI config & ensure widget script
    ChatInput->>VoiceWidget: Initialize with agentId + apiKey
    VoiceWidget->>VoiceWidget: Open dialog, user speaks
    User->>VoiceWidget: Say words to agent
    VoiceWidget->>API: Send audio stream
    API->>Provider: Process & generate TTS response
    Provider->>API: Request speech synthesis
    API->>VoiceWidget: Return audio data
    VoiceWidget->>ChatInput: Emit end event with audio block
    ChatInput->>Renderer: Insert audio message block
    Renderer->>Renderer: Detect isAudioBlock & render MessageBlockAudio
    Renderer-->>User: Display audio with play controls
Loading
sequenceDiagram
    participant Container as MessageList Container
    participant Observer as IntersectionObserver
    participant BoxComponent as MessageBox.vue
    participant Renderer as Vue Renderer
    participant DOM as Browser DOM

    Container->>Observer: Setup with rootMargin & threshold
    Container->>Container: Render v-for MessageBox items
    BoxComponent->>DOM: Mount element
    BoxComponent->>Observer: Register element via observeBox
    Observer->>Observer: Monitor visibility changes
    Observer-->>BoxComponent: Emit visibility change (inView true/false)
    BoxComponent->>BoxComponent: Update cachedHeight on resize
    DOM->>BoxComponent: ResizeObserver fires height change
    BoxComponent->>Container: Emit height-change event
    Container->>Container: Update cachedHeights map
    Container->>Container: Re-run applyPreRenderBuffer logic
    Observer->>Container: Update visibilityMap with buffer
    Renderer->>Renderer: Conditional render based on inView || !cachedHeight
    DOM-->>User: Display in-view messages, collapse cached out-of-view
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • zerob13
  • deepinfect

Poem

🐰 A voice now sings through deepchat's halls,
Boxes dance where scroller sprawls,
Cache grows swift with threads held tight,
Cloning lean, rendering right—
Whisper magic, tab-aware light! ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Refactor/message list' is vague and uses a generic naming convention that doesn't clearly convey the main changes in the changeset. Consider a more descriptive title that highlights the primary changes, such as 'Replace vue-virtual-scroller with intersection-observer-based rendering' or 'Implement message list virtualization refactor with Voice.ai TTS integration'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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.

zhangmo8 and others added 2 commits January 16, 2026 10:34
* chore: release v0.5.6-beta.5

* chore: typecheck with tsgo (#1278)

* feat: image left align

* feat: app settings skills (#1283)

* docs: add spec for app-settings

* feat: implement chat-driven settings control with skill gating

Add a safe, validated API for modifying DeepChat application settings via natural language. Settings changes are controlled by a dedicated 'deepchat-settings' skill to ensure tools are only available when contextually relevant.

Key features:
- Skill-gated tool injection: settings tools only appear when deepchat-settings skill is active
- Safe settings apply API with Zod validation and strict allowlist
- Support for toggles (sound, copy COT, chat mode) and enums (language, theme, font size)
- Defense-in-depth: runtime skill verification before applying changes
- Deep-linked settings navigation for unsupported/complex settings
- Comprehensive test coverage for validation, mapping, and skill gating

Changes:
- Add ChatSettingsToolHandler with validated apply/open methods
- Integrate with AgentToolManager for tool definition gating
- Create deepchat-settings built-in skill with clear activation rules
- Add shared types for requests/responses (chatSettings.ts)
- Implement settings window navigation with SECTION_ALIASES
- Add unit tests for handler and integration tests for tool gating
- Translate spec documents (plan.md, spec.md, tasks.md) to Chinese
- Fix type errors in getCurrentValue and OPEN_SECTION_VALUES

* refactor: remove chatMode from settings control and add permission service

Remove chatMode setting from the allowlist as it requires conversation-scoped updates that are better handled separately. Add permission checking for settings window opening to provide user control over settings navigation.

Key changes:
- Remove setChatMode tool and related schemas from ChatSettingsToolHandler
- Add SettingsPermissionService for managing tool approvals (one-time and session)
- Add permission check for deepchat_settings_open tool
- Update PermissionHandler to handle settings permission grants
- Add rememberable flag to permission request structure
- Update AgentToolManager to consume approvals before opening settings
- Add settingsPermissionService to main presenter index
- Clear settings approvals when conversation ends
- Update spec documents to reflect removed chatMode feature
- Remove chatMode-related tests and types

This ensures settings window opening requires explicit user approval and provides a cleaner separation of concerns for chat mode management.

* docs: translate to en

* feat: voice ai text to speech

* feat: voice ai call phone

* Initial plan

* perf: optimize message cache and cloning operations

- Add reverse index for O(1) thread lookup in messageRuntimeCache
- Extract duplicate cloneMessageWithContent to shared utility
- Replace JSON.parse/stringify with structuredClone
- Filter before cloning in messageTruncator to avoid wasted work

Co-authored-by: zhangmo8 <43628500+zhangmo8@users.noreply.github.com>

* docs: add comprehensive performance optimization analysis

Add detailed optimization analysis document covering:
- Implemented optimizations (cache, cloning, filtering)
- High priority opportunities (provider consolidation, IPC batching)
- Medium/low priority improvements (DB indices, token caching)
- Performance metrics and recommendations

Co-authored-by: zhangmo8 <43628500+zhangmo8@users.noreply.github.com>

* docs: add Chinese optimization summary

Add user-friendly Chinese summary of optimization work

Co-authored-by: zhangmo8 <43628500+zhangmo8@users.noreply.github.com>

---------

Co-authored-by: zerob13 <zerob13@gmail.com>
Co-authored-by: xiaomo <wegi866@gmail.com>
Co-authored-by: wanna <wanna.w@binarywalk.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: zhangmo8 <43628500+zhangmo8@users.noreply.github.com>
@zhangmo8 zhangmo8 marked this pull request as ready for review January 21, 2026 05:52
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: 18

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
src/renderer/src/lib/messageRuntimeCache.ts (1)

14-21: Resolve the Prettier CI warning for this file.

CI flagged formatting issues here—please re-run the formatter (e.g., pnpm run format / prettier --write) before merging.

src/main/presenter/agentPresenter/streaming/streamGenerationHandler.ts (1)

1-11: Run Prettier to fix formatting issues.

The CI pipeline flagged Prettier formatting issues. Run pnpm run format or prettier --write on this file.

src/renderer/src/components/message/MessageMinimap.vue (1)

1-1: Run Prettier to fix formatting issues.

The CI pipeline flagged Prettier formatting issues. Run pnpm run format on this file.

src/main/presenter/agentPresenter/index.ts (1)

228-236: Error messages should be in English.

Per coding guidelines, all logs and comments must be in English. These error messages are in Chinese:

  • Line 230: '只能重试助手消息''Can only retry assistant messages'
  • Line 236: '找不到对应的用户消息''Cannot find the corresponding user message'
🔧 Suggested fix
     if (message.role !== 'assistant') {
-      throw new Error('只能重试助手消息')
+      throw new Error('Can only retry assistant messages')
     }

     const userMessage = await this.messageManager.getMessage(message.parentId || '')
     if (!userMessage) {
-      throw new Error('找不到对应的用户消息')
+      throw new Error('Cannot find the corresponding user message')
     }
🤖 Fix all issues with AI agents
In `@docs/OPTIMIZATION_ANALYSIS.md`:
- Line 9: Update the header "## ✅ Implemented Optimizations (PR `#xxx`)" in the
docs so it either contains the actual PR number or removes the placeholder;
locate the heading text "Implemented Optimizations (PR `#xxx`)" and replace "PR
`#xxx`" with the real PR ID (e.g., "PR `#1234`") or delete the parenthetical
entirely to leave "## ✅ Implemented Optimizations".
- Line 3: The line using emphasis "*Generated: 2026-01-20*" in
docs/OPTIMIZATION_ANALYSIS.md should be converted to a proper Markdown heading
to satisfy MD036; replace the emphasized text with an appropriate heading level
(e.g., "## Generated: 2026-01-20" or "# Generated: 2026-01-20") so the content
uses a heading instead of emphasis.

In `@docs/OPTIMIZATION_SUMMARY.zh.md`:
- Around line 5-15: Update the documentation in
src/renderer/src/lib/messageRuntimeCache.ts description to avoid claiming O(1)
for clearing a thread; change the complexity line to state O(thread_messages)
(or O(m) where m is number of messages in that thread) since
clearCachedMessagesForThread still iterates that thread’s messages; mention that
the reverse index threadToMessagesMap reduces global scanning but per-thread
deletion is proportional to the number of messages in that thread, and keep the
note about ensuring atomic cleanup of cache, threadToMessagesMap (threadMap),
and domInfo in the same operation.
- Around line 146-150: The verification note is stale because MessageList.vue
was refactored away from virtual scrollers; update the "虚拟滚动" bullet in the
verification list to reflect the new rendering approach (remove the claim that
MessageList.vue 使用 vue-virtual-scroller and replace it with a statement such as
MessageList.vue 已改用新的渲染方式/非虚拟滚动渲染 或 simply mark it as "已切换为非虚拟滚动渲染" so the doc
matches the current implementation).

In `@src/main/presenter/agentPresenter/message/messageTruncator.ts`:
- Around line 53-63: Prettier formatting errors are present after refactoring
the message filtering/clone block; run the project's formatter (pnpm run format)
to fix spacing/linebreaks and ensure the block around contextMessages, messages,
cloneMessageWithContent, userMessage and selectedMessages adheres to Prettier
rules; if you prefer manual fixes, reformat the chain
(filter(...).reverse().map(...)) and surrounding whitespace to match the repo's
Prettier config so the pipeline passes.

In `@src/main/presenter/llmProviderPresenter/providers/voiceAIProvider.ts`:
- Around line 66-69: The proxy agent is never closed before being discarded
which can leak sockets; in onProxyResolved() call void this.proxyAgent?.close()
before setting this.proxyAgent = undefined and this.proxyUrl = undefined, and in
getFetchOptions() detect when the proxy URL changes or becomes invalid and call
void this.proxyAgent?.close() before replacing or clearing
this.proxyAgent/proxyUrl so the underlying socket pool is cleaned up (use the
existing proxyAgent and proxyUrl symbols and the onProxyResolved and
getFetchOptions methods to locate where to insert the calls).
- Around line 91-141: The non-streaming methods completions, summaries, and
generateText call generateSpeech() but ignore its returned audio, causing
callers to pay for TTS while only receiving text in LLMResponse; update these
methods to either (A) capture the audio result from generateSpeech(modelId,
temperature) and return it in the response (extend LLMResponse to include an
audio/audioMime/audioUrl field and populate it in completions, summaries, and
generateText), or (B) explicitly fail fast by throwing a clear error from
completions/summaries/generateText instructing callers to use the streaming path
(coreStream) for audio—pick one approach and apply consistently to all three
methods (completions, summaries, generateText) so generateSpeech’s output is not
discarded.

In `@src/renderer/settings/components/VoiceAIProviderConfig.vue`:
- Around line 196-248: The current debounced function persistUpdates(updates)
causes lost changes because it only sends partial objects; replace it with a
debounced function that reads the current reactive refs and persists the full
config to providerStore.updateVoiceAIConfig. Concretely, change persistUpdates
to a debounced zero-arg async function (using useDebounceFn) that builds {
audioFormat: audioFormat.value, model: ttsModel.value, language: language.value,
temperature: temperature.value, topP: topP.value, agentId: agentId.value } and
calls providerStore.updateVoiceAIConfig(fullConfig), then update all watchers
and methods (watchers on audioFormat, ttsModel, language, agentId and
onTemperatureChange/onTopPChange) to call persistUpdates() without passing
partial objects so every change persists the full current config.

In `@src/renderer/src/components/chat-input/ChatInput.vue`:
- Around line 859-863: The loadVoiceAIConfig function must guard against
exceptions from providerStore.getVoiceAIConfig to avoid unhandled rejections and
inconsistent call state; wrap the await call in a try/catch, on error log or
swallow the error (using your existing logger or console.error) and set
voiceAIAgentId.value to a safe default (e.g., '') or leave it unchanged so
downstream click/watchers don't break, ensuring any UI/call state is reset or
left consistent after failure.
- Around line 434-459: The Tailwind classes w-105 and max-w-105 in ChatInput.vue
(inside the Dialog / DialogContent and the inner div that contains
voice-agent-widget and uses callDialogOpen/callWidgetKey) are not standard and
must be replaced or defined: either swap them for valid utilities (for example
use w-96 and max-w-lg or another appropriate w-/max-w- class) on DialogContent
and the inner div, or add a tailwind.config.ts that extends the theme
spacing/sizing to include a 105 key so w-105 and max-w-105 become valid; update
the classes accordingly where Dialog, DialogContent, and the container div
render the voice-agent-widget.
- Around line 864-945: The dynamic injection in ensureVoiceAIWidgetScript
currently appends an external script without integrity or CSP protections;
update this flow to (1) add a Subresource Integrity (integrity) attribute and
crossorigin when creating the script element with id 'voice-ai-widget-script'
and ensure you verify the SRI hash matches the hosted file, (2) avoid relying on
disabled web security — remove or set webSecurity to true in the window creation
logic (refer to where webSecurity is set in FloatingChatWindow.ts) and (3)
enable contextIsolation: true in the main/shell window options, and (4)
add/require a Content-Security-Policy meta header or HTTP header that
allow-lists only the necessary voice.ai origin (or host the script locally) so
hasVoiceWidgetDefinition/ensureVoiceAIWidgetScript only runs under these
hardened conditions; also ensure voiceWidgetScriptPromise handling still
resolves/rejects appropriately if SRI validation fails.

In `@src/renderer/src/components/message/MessageBlockAudio.vue`:
- Around line 14-21: The <audio> element isn't wired to set audioError when
loading fails; add an error handler on the audio tag (e.g.,
`@error`="onAudioError") and implement an onAudioError method that sets the
component's audioError reactive flag to true (and optionally logs or emits the
error). Also clear audioError when a new source is loaded (watch audioSrc or
resolvedAudioData and reset audioError = false, or handle
`@loadedmetadata/`@canplay to clear it). Apply the same change for the second
audio instance mentioned (lines 64-110), referencing audioSrc, resolvedAudioData
and audioError so the error banner appears correctly.

In `@src/renderer/src/components/message/MessageBox.vue`:
- Around line 53-97: The watch on props.inView only calls setupResizeObserver
and never removes existing observers, causing leaks; update the watcher callback
to call cleanupResizeObserver() when isInView is false and ensure
cleanupResizeObserver() is also called before creating a new observer inside
setupResizeObserver to avoid duplicates (refer to setupResizeObserver,
cleanupResizeObserver, the watcher on props.inView, and onBeforeUnmount). Also
remove the scoped CSS rules for .message-box and .message-box-content and
replace them with Tailwind utility classes (e.g., use "relative" on the root
element and "w-full" on the content) to follow the directory styling guideline.

In `@src/renderer/src/components/message/MessageItemAssistant.vue`:
- Around line 173-187: The URL extension detection in isAudioBlock incorrectly
uses lower.includes(ext) against block.image_data.data which causes false
positives (e.g., "/mp3info/"). Update isAudioBlock to parse the URL when data
starts with "imgcache://", "http://", or "https://" and check the actual file
extension at the end of the path (ignoring query/hash) rather than using
substring includes; use a URL/pathname extraction or a regex that matches
/\.(mp3|wav|m4a|aac|flac|ogg|opus|webm)$/i against the pathname, referencing
AUDIO_EXTENSIONS and the isAudioBlock function to locate where to change the
logic.

In `@src/renderer/src/components/message/MessageList.vue.backup`:
- Around line 141-156: The backup uses the old useMessageScroll signature and a
removed export: remove dynamicScrollerRef from the useMessageScroll call and
drop handleVirtualScrollUpdate from the destructuring (keep messagesContainer,
aboveThreshold, scrollToBottom, scrollToMessage, handleScroll, updateScrollInfo,
setupScrollObserver), or alternatively exclude this backup from
typechecking/build (e.g., move out of src glob or rename file) so
tsconfig.app.json no longer includes it; update the call site for
useMessageScroll and the destructured symbols to match the current API.

In `@src/renderer/src/i18n/en-US/chat.json`:
- Around line 101-111: The new "audio" and "call" translation sections (keys:
audio.play, audio.pause, call.title, call.description, call.start, call.stop,
call.end) are only in en-US and zh-CN; add the same keys with appropriate
localized values (or placeholder copies of the English strings) to the remaining
locale JSON files da-DK, fa-IR, fr-FR, he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-HK,
and zh-TW so all 12 locale files contain identical key sets and avoid
fallback/missing-key issues.

In `@src/renderer/src/i18n/en-US/settings.json`:
- Around line 496-527: Add the missing provider.voiceai.* key structure (the
same keys as in the en-US "voiceai" block: title, description, audioFormat
{label, placeholder, helper}, language {label, placeholder, helper}, model
{label, placeholder, helper}, agentId {label, placeholder, helper}, temperature
{label, helper}, topP {label, helper}) to each of the 10 locale settings.json
files (da-DK, fa-IR, fr-FR, he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-HK, zh-TW),
preserving identical key names and JSON structure; if translations are not
available, insert the English strings as placeholders, ensure keys are nested
under provider.voiceai (matching the existing en-US/zh-CN structure), and
validate the JSON to avoid syntax errors.

In `@src/renderer/src/i18n/zh-CN/settings.json`:
- Around line 397-428: Add the missing provider.voiceai translation block (use
the same keys as in the new "voiceai" object: title, description,
audioFormat.{label,placeholder,helper}, language.{label,placeholder,helper},
model.{label,placeholder,helper}, agentId.{label,placeholder,helper},
temperature.{label,helper}, topP.{label,helper}) into each of the listed locale
files (da-DK, fa-IR, fr-FR, he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-HK, zh-TW),
preserving the exact key names and nesting under the provider (or equivalent)
section, and translate or copy the text from en-US/zh-CN as appropriate so all
12 locales contain a consistent provider.voiceai block.
🧹 Nitpick comments (12)
src/renderer/src/components/chat-input/ChatInput.vue (3)

475-481: Use the required shadcn import alias.

Guidelines specify @/shadcn/components/ui/* for shadcn imports.

♻️ Suggested import update
-import {
-  Dialog,
-  DialogContent,
-  DialogDescription,
-  DialogHeader,
-  DialogTitle
-} from '@shadcn/components/ui/dialog'
+import {
+  Dialog,
+  DialogContent,
+  DialogDescription,
+  DialogHeader,
+  DialogTitle
+} from '@/shadcn/components/ui/dialog'

As per coding guidelines, ...


947-953: Consider awaiting widget readiness before opening the dialog.

Right now the dialog opens even if the widget fails to load, which can yield a blank UI. Gate on voiceWidgetReady after ensureVoiceAIWidgetScript().

♻️ Suggested flow
 const startVoiceCall = async () => {
   await loadVoiceAIConfig()
   if (!voiceAIAgentId.value || !voiceAIApiKey.value) return
-  void ensureVoiceAIWidgetScript()
+  await ensureVoiceAIWidgetScript()
+  if (!voiceWidgetReady.value) return
   callWidgetKey.value += 1
   callDialogOpen.value = true
 }

1324-1333: Prefer Tailwind-configured animation over scoped CSS.

Guidelines call for Tailwind-based styling; consider moving this keyframe to Tailwind config and using an animate-* utility.

As per coding guidelines, ...

src/renderer/src/composables/message/useBoxIntersection.ts (1)

63-76: Avoid O(n²) index lookups when computing the buffer.

allIds.indexOf for each visible id scales poorly. You can compute min/max visible indices in a single pass.

♻️ Suggested refactor
-    const visibleIndices = visibleIds.map((id) => allIds.indexOf(id))
-    const minVisibleIndex = Math.min(...visibleIndices)
-    const maxVisibleIndex = Math.max(...visibleIndices)
+    let minVisibleIndex = Number.POSITIVE_INFINITY
+    let maxVisibleIndex = Number.NEGATIVE_INFINITY
+    allIds.forEach((id, index) => {
+      if (visibilityMap.value.get(id)) {
+        if (index < minVisibleIndex) minVisibleIndex = index
+        if (index > maxVisibleIndex) maxVisibleIndex = index
+      }
+    })
+    if (!Number.isFinite(minVisibleIndex)) {
+      return
+    }
src/shared/chat.d.ts (1)

84-148: Consider adding an audio_data field for consistency with image_data.

The 'audio' type is added to the union, but there's no corresponding audio_data field like image_data (Line 140-143). Based on the isAudioBlock logic in MessageItemAssistant.vue, audio blocks currently rely on reusing image_data or content fields.

If audio blocks will have their own dedicated data structure (e.g., audio format, duration metadata), consider adding:

audio_data?: {
  data: string
  mimeType: string
}

Otherwise, if reusing existing fields is intentional, this is acceptable as-is.

src/renderer/src/components/message/MessageItemAssistant.vue (1)

110-128: Chinese comments should be in English.

Per coding guidelines, all code comments must be in English. Multiple Chinese comments exist in this file:

  • Line 110: 分支会话确认对话框
  • Line 189: 定义事件
  • Line 201: 获取当前会话ID
  • Line 204: currentVariantIndex: 0 = 主消息, 1-N = 对应的变体索引
  • Line 213: 获取当前显示的消息(根据变体索引)
  • Line 223: 计算当前消息的所有变体...
  • Line 250: 计算变体总数
  • Line 253: 获取当前显示的内容
  • Line 263: 监听 allVariants 长度变化...
  • Line 286: 分支会话对话框
  • Line 290, 294, 300: Fork dialog comments
  • Line 306: 创建对话分支失败:
  • Line 370, 376: Copy image comments
  • Line 388: Expose the handleAction method... (this one is already English)

As per coding guidelines, these should be translated to English.

Also applies to: 189-199, 201-211, 213-221, 223-248, 250-251, 253-261, 263-278, 286-297, 300-308, 369-380, 388-391

src/main/presenter/agentPresenter/message/messageCompressor.ts (1)

17-45: Consider avoiding double cloning for assistant messages.

In compressToolCallsFromContext, all messages are cloned at Line 56. Then for each assistant message, removeToolCallsFromAssistant clones again at Line 26. This results in assistant messages being cloned twice.

Since messagesToEdit already contains clones, removeToolCallsFromAssistant could potentially operate on the message directly (or receive a flag indicating the message is already a clone). However, keeping the defensive clone in removeToolCallsFromAssistant ensures it remains safe to call independently.

This is a minor optimization opportunity and the current implementation is functionally correct.

♻️ Optional optimization

If performance becomes a concern, consider adding an optional parameter to skip cloning when the caller guarantees the message is already a copy:

-function removeToolCallsFromAssistant(message: Message): {
+function removeToolCallsFromAssistant(message: Message, skipClone = false): {
   updatedMessage: Message
   removedTokens: number
   removedToolCalls: number
 } {
   if (message.role !== 'assistant' || !Array.isArray(message.content)) {
     return { updatedMessage: message, removedTokens: 0, removedToolCalls: 0 }
   }

-  const clonedMessage = cloneMessageWithContent(message)
+  const clonedMessage = skipClone ? message : cloneMessageWithContent(message)

Then call with removeToolCallsFromAssistant(message, true) at Line 75.

Also applies to: 47-92

src/main/presenter/agentPresenter/message/messageUtils.ts (1)

10-17: Consider type handling for non-assistant message blocks.

The cast block as AssistantMessageBlock on Line 12 assumes all content blocks are assistant message blocks. If Message.content can contain blocks from user messages (e.g., with different types), this cast could lose type information.

Consider using a more generic type or conditional type narrowing:

♻️ Suggested refinement
   if (Array.isArray(message.content)) {
     cloned.content = message.content.map((block) => {
-      const clonedBlock: AssistantMessageBlock = { ...(block as AssistantMessageBlock) }
+      const clonedBlock = { ...block }
       if (block.type === 'tool_call' && block.tool_call) {
-        clonedBlock.tool_call = { ...block.tool_call }
+        (clonedBlock as typeof block).tool_call = { ...block.tool_call }
       }
       return clonedBlock
     })
src/renderer/src/components/message/MessageBox.vue (1)

100-107: Prefer Tailwind classes over scoped CSS here.

These styles can be replaced by Tailwind utilities (e.g., relative, w-full) and the scoped block removed.

As per coding guidelines, use Tailwind CSS instead of scoped styles.

♻️ Example refactor
-  <div
+  <div
     ref="boxRef"
     :id="`message-${id}`"
     :data-message-id="id"
     :data-index="index"
-    class="message-box w-full break-all"
+    class="w-full break-all relative"
     :style="boxStyle"
   >
-    <div v-if="inView || !cachedHeight" class="message-box-content">
+    <div v-if="inView || !cachedHeight" class="w-full">
       <slot />
     </div>
   </div>
-<style scoped>
-.message-box {
-  position: relative;
-}
-
-.message-box-content {
-  width: 100%;
-}
-</style>
src/renderer/src/stores/providerStore.ts (1)

8-16: VoiceAI configuration type is well-defined.

The VoiceAIConfig type covers the essential TTS parameters. Consider exporting this type if it needs to be used by other components.

💡 Optional: Export the type for reuse
-type VoiceAIConfig = {
+export type VoiceAIConfig = {
   audioFormat: string
   model: string
   language: string
   temperature: number
   topP: number
   agentId: string
 }
src/renderer/src/components/message/MessageList.vue (1)

245-278: Good enhancement for copy image reliability.

The added checks ensure the message content is fully rendered (not just the placeholder box) before capturing.

Consider extracting the 100ms timeout to a named constant for clarity.

💡 Optional: Extract magic number
 const HIGHLIGHT_DURATION = 2000
 const MAX_REASONABLE_HEIGHT = 2000
+const RENDER_WAIT_TIMEOUT = 100
 // ...
 
-      await new Promise((resolve) => setTimeout(resolve, 100)) // Wait for render
+      await new Promise((resolve) => setTimeout(resolve, RENDER_WAIT_TIMEOUT))
src/main/presenter/llmProviderPresenter/providers/voiceAIProvider.ts (1)

242-259: Normalize/validate TTS config inputs.
Consider lowercasing audioFormat and validating temperature/topP ranges against Voice.ai docs to avoid preventable API errors.

🔧 Example normalization for audioFormat
-    const audioFormat =
-      this.configPresenter.getSetting<string>('voiceAI_audioFormat') || DEFAULT_AUDIO_FORMAT
+    const audioFormat =
+      (this.configPresenter.getSetting<string>('voiceAI_audioFormat') || DEFAULT_AUDIO_FORMAT)
+        .trim()
+        .toLowerCase()

@@ -0,0 +1,325 @@
# DeepChat Performance Optimization Analysis

*Generated: 2026-01-20*
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 | 🟡 Minor

Replace emphasis with a proper heading to satisfy MD036.

Markdownlint flags emphasis used as a heading here.

🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

3-3: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

🤖 Prompt for AI Agents
In `@docs/OPTIMIZATION_ANALYSIS.md` at line 3, The line using emphasis
"*Generated: 2026-01-20*" in docs/OPTIMIZATION_ANALYSIS.md should be converted
to a proper Markdown heading to satisfy MD036; replace the emphasized text with
an appropriate heading level (e.g., "## Generated: 2026-01-20" or "# Generated:
2026-01-20") so the content uses a heading instead of emphasis.


This document provides a comprehensive analysis of performance optimization opportunities identified in the DeepChat codebase. The analysis covers memory management, algorithmic efficiency, code duplication, and architectural improvements.

## ✅ Implemented Optimizations (PR #xxx)
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 | 🟡 Minor

Resolve the “PR #xxx” placeholder.

Please replace with the actual PR number or remove the placeholder to avoid ambiguity.

🤖 Prompt for AI Agents
In `@docs/OPTIMIZATION_ANALYSIS.md` at line 9, Update the header "## ✅ Implemented
Optimizations (PR `#xxx`)" in the docs so it either contains the actual PR number
or removes the placeholder; locate the heading text "Implemented Optimizations
(PR `#xxx`)" and replace "PR `#xxx`" with the real PR ID (e.g., "PR `#1234`") or
delete the parenthetical entirely to leave "## ✅ Implemented Optimizations".

Comment on lines +5 to +15
### 1. 消息运行时缓存优化 (O(n) → O(1))
**文件**: `src/renderer/src/lib/messageRuntimeCache.ts`

**问题**:
- `clearCachedMessagesForThread()` 需要遍历所有条目,时间复杂度为 O(n)
- 对于有大量消息和会话的应用效率低下

**解决方案**:
- 添加反向索引 `threadToMessagesMap: Map<threadId, Set<messageId>>`
- 时间复杂度从 O(n) 降低到 O(1)
- 确保三个 Map(cache、threadMap、domInfo)的原子性清理
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 | 🟡 Minor

Document complexity as O(thread_messages), not O(1).

The reverse index avoids scanning all cached messages, but clearing a thread still iterates that thread’s messages. Updating the wording avoids overstating complexity.

📝 Suggested doc fix
-### 1. 消息运行时缓存优化 (O(n) → O(1))
+### 1. 消息运行时缓存优化 (O(n) → O(m))
@@
-- 时间复杂度从 O(n) 降低到 O(1)
+- 时间复杂度从 O(n) 降低到 O(m)(m 为该线程的消息数)
📝 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
### 1. 消息运行时缓存优化 (O(n) → O(1))
**文件**: `src/renderer/src/lib/messageRuntimeCache.ts`
**问题**:
- `clearCachedMessagesForThread()` 需要遍历所有条目,时间复杂度为 O(n)
- 对于有大量消息和会话的应用效率低下
**解决方案**:
- 添加反向索引 `threadToMessagesMap: Map<threadId, Set<messageId>>`
- 时间复杂度从 O(n) 降低到 O(1)
- 确保三个 Map(cache、threadMap、domInfo)的原子性清理
### 1. 消息运行时缓存优化 (O(n) → O(m))
**文件**: `src/renderer/src/lib/messageRuntimeCache.ts`
**问题**:
- `clearCachedMessagesForThread()` 需要遍历所有条目,时间复杂度为 O(n)
- 对于有大量消息和会话的应用效率低下
**解决方案**:
- 添加反向索引 `threadToMessagesMap: Map<threadId, Set<messageId>>`
- 时间复杂度从 O(n) 降低到 O(m)(m 为该线程的消息数)
- 确保三个 Map(cache、threadMap、domInfo)的原子性清理
🤖 Prompt for AI Agents
In `@docs/OPTIMIZATION_SUMMARY.zh.md` around lines 5 - 15, Update the
documentation in src/renderer/src/lib/messageRuntimeCache.ts description to
avoid claiming O(1) for clearing a thread; change the complexity line to state
O(thread_messages) (or O(m) where m is number of messages in that thread) since
clearCachedMessagesForThread still iterates that thread’s messages; mention that
the reverse index threadToMessagesMap reduces global scanning but per-thread
deletion is proportional to the number of messages in that thread, and keep the
note about ensuring atomic cleanup of cache, threadToMessagesMap (threadMap),
and domInfo in the same operation.

Comment on lines +146 to +150
## 验证结果 ✅

- ✅ 定时器清理审查:已确认关键文件中存在正确的清理实现
- ✅ 虚拟滚动:MessageList.vue 已正确使用 vue-virtual-scroller
- ✅ 事件监听器:遵循一致的清理模式
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 | 🟡 Minor

Update verification note to match the new rendering approach.

The PR refactors MessageList away from virtual scrollers, so the current bullet is stale.

📝 Suggested doc fix
-- ✅ 虚拟滚动:MessageList.vue 已正确使用 vue-virtual-scroller
+- ✅ 消息渲染:MessageList.vue 已切换为 IntersectionObserver 驱动的非虚拟渲染
📝 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
## 验证结果 ✅
- ✅ 定时器清理审查:已确认关键文件中存在正确的清理实现
-虚拟滚动:MessageList.vue 已正确使用 vue-virtual-scroller
- ✅ 事件监听器:遵循一致的清理模式
## 验证结果 ✅
- ✅ 定时器清理审查:已确认关键文件中存在正确的清理实现
-消息渲染:MessageList.vue 已切换为 IntersectionObserver 驱动的非虚拟渲染
- ✅ 事件监听器:遵循一致的清理模式
🧰 Tools
🪛 LanguageTool

[uncategorized] ~148-~148: 动词的修饰一般为‘形容词(副词)+地+动词’。您的意思是否是:正确"地"清理
Context: ...--- ## 验证结果 ✅ - ✅ 定时器清理审查:已确认关键文件中存在正确的清理实现 - ✅ 虚拟滚动:MessageList.vue 已正确使用 vue-...

(wb4)


[uncategorized] ~150-~150: 动词的修饰一般为‘形容词(副词)+地+动词’。您的意思是否是:一致"地"清理
Context: ...正确使用 vue-virtual-scroller - ✅ 事件监听器:遵循一致的清理模式 - ✅ 错误处理:大多数异步操作有适当的错误处理

(wb4)

🤖 Prompt for AI Agents
In `@docs/OPTIMIZATION_SUMMARY.zh.md` around lines 146 - 150, The verification
note is stale because MessageList.vue was refactored away from virtual
scrollers; update the "虚拟滚动" bullet in the verification list to reflect the new
rendering approach (remove the claim that MessageList.vue 使用
vue-virtual-scroller and replace it with a statement such as MessageList.vue
已改用新的渲染方式/非虚拟滚动渲染 或 simply mark it as "已切换为非虚拟滚动渲染" so the doc matches the
current implementation).

Comment on lines +53 to +63
// Filter first, then clone only what's needed - avoid wasting work
const messages = contextMessages
.filter((msg) => msg.id !== userMessage?.id)
.map((msg) => cloneMessageWithContent(msg))
.filter((msg) => msg.id !== userMessage?.id && msg.status === 'sent')
.reverse()
let selectedMessages = messages.filter((msg) => msg.status === 'sent')

if (selectedMessages.length === 0) {
.map((msg) => cloneMessageWithContent(msg))
if (messages.length === 0) {
return []
}

let selectedMessages = messages
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 | 🟡 Minor

Fix Prettier formatting issues flagged by pipeline.

The pipeline indicates Prettier formatting issues in this file. The refactored code logic is correct - filtering before cloning is a good optimization.

🔧 Suggested fix
   // Filter first, then clone only what's needed - avoid wasting work
   const messages = contextMessages
     .filter((msg) => msg.id !== userMessage?.id && msg.status === 'sent')
     .reverse()
     .map((msg) => cloneMessageWithContent(msg))
-  
+
   if (messages.length === 0) {
     return []
   }

-  let selectedMessages = messages
-
+  let selectedMessages = messages

Run pnpm run format to auto-fix formatting.

📝 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
// Filter first, then clone only what's needed - avoid wasting work
const messages = contextMessages
.filter((msg) => msg.id !== userMessage?.id)
.map((msg) => cloneMessageWithContent(msg))
.filter((msg) => msg.id !== userMessage?.id && msg.status === 'sent')
.reverse()
let selectedMessages = messages.filter((msg) => msg.status === 'sent')
if (selectedMessages.length === 0) {
.map((msg) => cloneMessageWithContent(msg))
if (messages.length === 0) {
return []
}
let selectedMessages = messages
// Filter first, then clone only what's needed - avoid wasting work
const messages = contextMessages
.filter((msg) => msg.id !== userMessage?.id && msg.status === 'sent')
.reverse()
.map((msg) => cloneMessageWithContent(msg))
if (messages.length === 0) {
return []
}
let selectedMessages = messages
🤖 Prompt for AI Agents
In `@src/main/presenter/agentPresenter/message/messageTruncator.ts` around lines
53 - 63, Prettier formatting errors are present after refactoring the message
filtering/clone block; run the project's formatter (pnpm run format) to fix
spacing/linebreaks and ensure the block around contextMessages, messages,
cloneMessageWithContent, userMessage and selectedMessages adheres to Prettier
rules; if you prefer manual fixes, reformat the chain
(filter(...).reverse().map(...)) and surrounding whitespace to match the repo's
Prettier config so the pipeline passes.

Comment on lines +173 to +187
const AUDIO_EXTENSIONS = ['.mp3', '.wav', '.m4a', '.aac', '.flac', '.ogg', '.opus', '.webm']
const isAudioBlock = (block: AssistantMessageBlock): boolean => {
if (block.type === 'audio') return true
if (block.type !== 'image') return false
const mimeType = block.image_data?.mimeType?.toLowerCase() || ''
if (mimeType.startsWith('audio/')) return true
const data = block.image_data?.data || ''
if (data.startsWith('data:audio/')) return true
if (data.startsWith('imgcache://') || data.startsWith('http://') || data.startsWith('https://')) {
const lower = data.toLowerCase()
return AUDIO_EXTENSIONS.some((ext) => lower.includes(ext))
}
return false
}
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 | 🟡 Minor

URL extension detection may produce false positives.

The check lower.includes(ext) (Line 184) can match unintended substrings. For example, a URL like https://example.com/mp3info/file.txt would incorrectly match .mp3.

Consider using a more precise check:

🔧 Suggested fix
  if (data.startsWith('imgcache://') || data.startsWith('http://') || data.startsWith('https://')) {
    const lower = data.toLowerCase()
-   return AUDIO_EXTENSIONS.some((ext) => lower.includes(ext))
+   // Extract path and check if it ends with audio extension
+   try {
+     const url = new URL(data.startsWith('imgcache://') ? data.replace('imgcache://', 'https://') : data)
+     const path = url.pathname.toLowerCase()
+     return AUDIO_EXTENSIONS.some((ext) => path.endsWith(ext))
+   } catch {
+     // Fallback for malformed URLs
+     return AUDIO_EXTENSIONS.some((ext) => lower.endsWith(ext))
+   }
  }
🤖 Prompt for AI Agents
In `@src/renderer/src/components/message/MessageItemAssistant.vue` around lines
173 - 187, The URL extension detection in isAudioBlock incorrectly uses
lower.includes(ext) against block.image_data.data which causes false positives
(e.g., "/mp3info/"). Update isAudioBlock to parse the URL when data starts with
"imgcache://", "http://", or "https://" and check the actual file extension at
the end of the path (ignoring query/hash) rather than using substring includes;
use a URL/pathname extraction or a regex that matches
/\.(mp3|wav|m4a|aac|flac|ogg|opus|webm)$/i against the pathname, referencing
AUDIO_EXTENSIONS and the isAudioBlock function to locate where to change the
logic.

Comment on lines +141 to +156
// Scroll management
const scroll = useMessageScroll({
dynamicScrollerRef,
shouldAutoFollow,
scrollAnchor
})
const {
messagesContainer,
aboveThreshold,
scrollToBottom,
scrollToMessage,
handleScroll,
updateScrollInfo,
setupScrollObserver,
handleVirtualScrollUpdate
} = scroll
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

🧩 Analysis chain

🏁 Script executed:

# First, find build configuration files to check if .backup files are excluded
find . -type f \( -name "vite.config.*" -o -name "tsconfig.json" -o -name "tsconfig.*.json" -o -name "webpack.config.*" -o -name ".eslintignore" -o -name ".gitignore" \) | head -20

Repository: ThinkInAIXYZ/deepchat

Length of output: 134


🏁 Script executed:

# Look for the useMessageScroll composable implementation
fd -t f "useMessageScroll" --exec cat {} \; 2>/dev/null || rg -l "useMessageScroll" --type ts --type vue

Repository: ThinkInAIXYZ/deepchat

Length of output: 6884


🏁 Script executed:

# Check if the backup file exists and examine its content around the mentioned lines
cat -n src/renderer/src/components/message/MessageList.vue.backup 2>/dev/null | head -200

Repository: ThinkInAIXYZ/deepchat

Length of output: 8247


🏁 Script executed:

# Check tsconfig.json for exclusions
cat tsconfig.json | head -50

Repository: ThinkInAIXYZ/deepchat

Length of output: 324


🏁 Script executed:

# Check tsconfig.app.json for exclusions
cat tsconfig.app.json

Repository: ThinkInAIXYZ/deepchat

Length of output: 1153


🏁 Script executed:

# Check .gitignore for .backup exclusions
cat .gitignore

Repository: ThinkInAIXYZ/deepchat

Length of output: 349


🏁 Script executed:

# Check the backup file around lines 679-693 for additional references
sed -n '670,700p' src/renderer/src/components/message/MessageList.vue.backup

Repository: ThinkInAIXYZ/deepchat

Length of output: 943


🏁 Script executed:

# Final verification: confirm handleVirtualScrollUpdate is NOT in the useMessageScroll return object
rg "handleVirtualScrollUpdate" src/renderer/src/composables/message/useMessageScroll.ts

Repository: ThinkInAIXYZ/deepchat

Length of output: 47


Update this backup to the new useMessageScroll API (or exclude it from builds).

The backup file includes dynamicScrollerRef when calling useMessageScroll and attempts to destructure handleVirtualScrollUpdate, both of which have been removed from the current API. Since tsconfig.app.json includes src/renderer/src/**/*, this file will be typechecked and will fail.

🛠️ Minimal API-alignment patch
 const scroll = useMessageScroll({
-  dynamicScrollerRef,
   shouldAutoFollow,
   scrollAnchor
 })
 const {
   messagesContainer,
   aboveThreshold,
   scrollToBottom,
   scrollToMessage,
   handleScroll,
   updateScrollInfo,
   setupScrollObserver,
-  handleVirtualScrollUpdate
 } = scroll
@@
   void chatStore.prefetchMessagesForRange(safeStart, safeEnd)
   recordVisibleDomInfo()
-  handleVirtualScrollUpdate()
   setupMessageResizeObserver()
 }

Also applies to: 679-693

🤖 Prompt for AI Agents
In `@src/renderer/src/components/message/MessageList.vue.backup` around lines 141
- 156, The backup uses the old useMessageScroll signature and a removed export:
remove dynamicScrollerRef from the useMessageScroll call and drop
handleVirtualScrollUpdate from the destructuring (keep messagesContainer,
aboveThreshold, scrollToBottom, scrollToMessage, handleScroll, updateScrollInfo,
setupScrollObserver), or alternatively exclude this backup from
typechecking/build (e.g., move out of src glob or rename file) so
tsconfig.app.json no longer includes it; update the call site for
useMessageScroll and the destructured symbols to match the current API.

Comment on lines +101 to +111
"audio": {
"play": "Play",
"pause": "Pause"
},
"call": {
"title": "Voice.ai Call",
"description": "Click to connect to Voice.ai voice agent, speak after the call is connected.",
"start": "Start Voice Call",
"stop": "End Call",
"end": "Hang Up"
},
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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
python - <<'PY'
import json, glob, sys

files = glob.glob('src/renderer/src/i18n/*/chat.json')
required = [
  ('audio', 'play'),
  ('audio', 'pause'),
  ('call', 'title'),
  ('call', 'description'),
  ('call', 'start'),
  ('call', 'stop'),
  ('call', 'end'),
]

missing = {}
for f in sorted(files):
    with open(f, 'r', encoding='utf-8') as fh:
        data = json.load(fh)
    missing_keys = []
    for path in required:
        cur = data
        for key in path:
            if not isinstance(cur, dict) or key not in cur:
                missing_keys.append('.'.join(path))
                break
            cur = cur[key]
    if missing_keys:
        missing[f] = missing_keys

if missing:
    print("Missing keys:")
    for f, keys in sorted(missing.items()):
        print(f"{f}: {', '.join(keys)}")
    sys.exit(1)

print("All locales contain new keys.")
PY

Repository: ThinkInAIXYZ/deepchat

Length of output: 151


🏁 Script executed:

#!/bin/bash
# List all chat.json files
echo "Available locale chat.json files:"
fd "chat.json" src/renderer/src/i18n/

echo -e "\n--- Checking for audio/call keys ---"
# Expected locales: da-DK, en-US, fa-IR, fr-FR, he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-CN, zh-HK, zh-TW
for locale in da-DK en-US fa-IR fr-FR he-IL ja-JP ko-KR pt-BR ru-RU zh-CN zh-HK zh-TW; do
  file="src/renderer/src/i18n/$locale/chat.json"
  if [ -f "$file" ]; then
    # Check if audio and call sections exist
    if grep -q '"audio"' "$file" && grep -q '"call"' "$file"; then
      echo "$locale: ✓ audio and call sections present"
    else
      if ! grep -q '"audio"' "$file"; then
        echo "$locale: ✗ MISSING audio section"
      fi
      if ! grep -q '"call"' "$file"; then
        echo "$locale: ✗ MISSING call section"
      fi
    fi
  else
    echo "$locale: FILE NOT FOUND"
  fi
done

Repository: ThinkInAIXYZ/deepchat

Length of output: 1284


Add audio and call translation keys to all missing locales.

New audio and call sections are only present in en-US and zh-CN. They must be added to da-DK, fa-IR, fr-FR, he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-HK, and zh-TW to prevent missing translation fallbacks. Per the i18n guidelines, all 12 language files require consistent keys.

🤖 Prompt for AI Agents
In `@src/renderer/src/i18n/en-US/chat.json` around lines 101 - 111, The new
"audio" and "call" translation sections (keys: audio.play, audio.pause,
call.title, call.description, call.start, call.stop, call.end) are only in en-US
and zh-CN; add the same keys with appropriate localized values (or placeholder
copies of the English strings) to the remaining locale JSON files da-DK, fa-IR,
fr-FR, he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-HK, and zh-TW so all 12 locale
files contain identical key sets and avoid fallback/missing-key issues.

Comment on lines +496 to +527
"voiceai": {
"title": "Voice.ai Text-to-Speech",
"description": "Generate speech from text. Voices appear in the model list below.",
"audioFormat": {
"label": "Audio Format",
"placeholder": "Select format",
"helper": "MP3 is recommended for most cases."
},
"language": {
"label": "Language",
"placeholder": "e.g. en",
"helper": "Supported: en, ca, sv, es, fr, de, it, pt, pl, ru, nl."
},
"model": {
"label": "TTS Model",
"placeholder": "voiceai-tts-v1-latest",
"helper": "See the Voice.ai docs for supported models."
},
"agentId": {
"label": "Voice Agent ID",
"placeholder": "Paste voice agent ID",
"helper": "Required for real-time voice calls."
},
"temperature": {
"label": "Temperature",
"helper": "Controls randomness (0-2)."
},
"topP": {
"label": "Top P",
"helper": "Nucleus sampling (0-1)."
}
},
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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all i18n locale directories and check for voiceai keys in settings.json files
echo "Checking voiceai i18n keys presence in all locale settings.json files..."

# First, find the i18n directory structure
i18n_path="src/renderer/src/i18n"

if [ -d "$i18n_path" ]; then
  echo "Found i18n directory: $i18n_path"
  
  # List all locale directories
  locales=$(ls -d "$i18n_path"/*/ 2>/dev/null | xargs -n1 basename)
  echo "Locales found: $locales"
  echo ""
  
  # Check each locale
  for locale in $locales; do
    settings_file="$i18n_path/$locale/settings.json"
    if [ -f "$settings_file" ]; then
      if grep -q '"voiceai"' "$settings_file" 2>/dev/null; then
        # Count voiceai entries to verify completeness
        voiceai_count=$(grep -o '"voiceai"' "$settings_file" | wc -l)
        echo "$locale: voiceai keys found (count: $voiceai_count)"
      else
        echo "$locale: voiceai keys MISSING"
      fi
    else
      echo "? $locale: settings.json not found"
    fi
  done
else
  echo "i18n directory not found"
fi

Repository: ThinkInAIXYZ/deepchat

Length of output: 647


Missing voiceai i18n keys in 10 locale files.

The voiceai provider settings are only translated in en-US and zh-CN, but are missing from all other required locales (da-DK, fa-IR, fr-FR, he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-HK, zh-TW). Per i18n guidelines, all new translation keys must be added to ALL 12 language files with consistent key names across locales.

Add the complete provider.voiceai.* key structure to the remaining 10 locale settings.json files to comply with i18n requirements.

🤖 Prompt for AI Agents
In `@src/renderer/src/i18n/en-US/settings.json` around lines 496 - 527, Add the
missing provider.voiceai.* key structure (the same keys as in the en-US
"voiceai" block: title, description, audioFormat {label, placeholder, helper},
language {label, placeholder, helper}, model {label, placeholder, helper},
agentId {label, placeholder, helper}, temperature {label, helper}, topP {label,
helper}) to each of the 10 locale settings.json files (da-DK, fa-IR, fr-FR,
he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-HK, zh-TW), preserving identical key names
and JSON structure; if translations are not available, insert the English
strings as placeholders, ensure keys are nested under provider.voiceai (matching
the existing en-US/zh-CN structure), and validate the JSON to avoid syntax
errors.

Comment on lines +397 to +428
"voiceai": {
"title": "Voice.ai 文字转语音",
"description": "将文本生成语音,voice 会在下方模型列表中展示。",
"audioFormat": {
"label": "音频格式",
"placeholder": "选择格式",
"helper": "多数场景推荐 MP3。"
},
"language": {
"label": "语言",
"placeholder": "例如 en",
"helper": "支持语言:en, ca, sv, es, fr, de, it, pt, pl, ru, nl。"
},
"model": {
"label": "TTS 模型",
"placeholder": "voiceai-tts-v1-latest",
"helper": "可选模型请查看 Voice.ai 文档。"
},
"agentId": {
"label": "语音 Agent ID",
"placeholder": "粘贴 voice agent id",
"helper": "实时通话需要此 ID。"
},
"temperature": {
"label": "温度",
"helper": "随机性参数(0-2)。"
},
"topP": {
"label": "Top P",
"helper": "Nucleus 采样(0-1)。"
}
},
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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if voiceai translations exist in all language files
echo "Checking for voiceai translations in all locale files..."

for locale in da-DK en-US fa-IR fr-FR he-IL ja-JP ko-KR pt-BR ru-RU zh-HK zh-TW zh-CN; do
  file="src/renderer/src/i18n/${locale}/settings.json"
  if [ -f "$file" ]; then
    if grep -q '"voiceai"' "$file"; then
      echo "${locale}: voiceai section found"
    else
      echo "${locale}: voiceai section MISSING"
    fi
  else
    echo "? ${locale}: settings.json file not found"
  fi
done

Repository: ThinkInAIXYZ/deepchat

Length of output: 513


Add voiceai translations to all missing language files.

The voiceai section is added only to en-US and zh-CN, but per coding guidelines, new translations must be added to ALL 12 language files. Missing translations in: da-DK, fa-IR, fr-FR, he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-HK, zh-TW.

Add the corresponding provider.voiceai translations to these locale files with consistent key names.

🤖 Prompt for AI Agents
In `@src/renderer/src/i18n/zh-CN/settings.json` around lines 397 - 428, Add the
missing provider.voiceai translation block (use the same keys as in the new
"voiceai" object: title, description, audioFormat.{label,placeholder,helper},
language.{label,placeholder,helper}, model.{label,placeholder,helper},
agentId.{label,placeholder,helper}, temperature.{label,helper},
topP.{label,helper}) into each of the listed locale files (da-DK, fa-IR, fr-FR,
he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-HK, zh-TW), preserving the exact key names
and nesting under the provider (or equivalent) section, and translate or copy
the text from en-US/zh-CN as appropriate so all 12 locales contain a consistent
provider.voiceai block.

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.

4 participants