-
Notifications
You must be signed in to change notification settings - Fork 620
Refactor/message list #1279
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Refactor/message list #1279
Conversation
* 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
📝 WalkthroughWalkthroughThis 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
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
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. Comment |
* 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>
There was a problem hiding this 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 formatorprettier --writeon 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 formaton 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
voiceWidgetReadyafterensureVoiceAIWidgetScript().♻️ 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.indexOffor 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 anaudio_datafield for consistency withimage_data.The
'audio'type is added to the union, but there's no correspondingaudio_datafield likeimage_data(Line 140-143). Based on theisAudioBlocklogic inMessageItemAssistant.vue, audio blocks currently rely on reusingimage_dataorcontentfields.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,removeToolCallsFromAssistantclones again at Line 26. This results in assistant messages being cloned twice.Since
messagesToEditalready contains clones,removeToolCallsFromAssistantcould potentially operate on the message directly (or receive a flag indicating the message is already a clone). However, keeping the defensive clone inremoveToolCallsFromAssistantensures 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 AssistantMessageBlockon Line 12 assumes all content blocks are assistant message blocks. IfMessage.contentcan 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
VoiceAIConfigtype 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 lowercasingaudioFormatand validatingtemperature/topPranges 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* | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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".
| ### 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)的原子性清理 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| ### 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.
| ## 验证结果 ✅ | ||
|
|
||
| - ✅ 定时器清理审查:已确认关键文件中存在正确的清理实现 | ||
| - ✅ 虚拟滚动:MessageList.vue 已正确使用 vue-virtual-scroller | ||
| - ✅ 事件监听器:遵循一致的清理模式 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| ## 验证结果 ✅ | |
| - ✅ 定时器清理审查:已确认关键文件中存在正确的清理实现 | |
| - ✅ 虚拟滚动: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).
| // 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 = messagesRun 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.
| // 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.
| 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 | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| // Scroll management | ||
| const scroll = useMessageScroll({ | ||
| dynamicScrollerRef, | ||
| shouldAutoFollow, | ||
| scrollAnchor | ||
| }) | ||
| const { | ||
| messagesContainer, | ||
| aboveThreshold, | ||
| scrollToBottom, | ||
| scrollToMessage, | ||
| handleScroll, | ||
| updateScrollInfo, | ||
| setupScrollObserver, | ||
| handleVirtualScrollUpdate | ||
| } = scroll |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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 -20Repository: 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 vueRepository: 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 -200Repository: ThinkInAIXYZ/deepchat
Length of output: 8247
🏁 Script executed:
# Check tsconfig.json for exclusions
cat tsconfig.json | head -50Repository: ThinkInAIXYZ/deepchat
Length of output: 324
🏁 Script executed:
# Check tsconfig.app.json for exclusions
cat tsconfig.app.jsonRepository: ThinkInAIXYZ/deepchat
Length of output: 1153
🏁 Script executed:
# Check .gitignore for .backup exclusions
cat .gitignoreRepository: 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.backupRepository: 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.tsRepository: 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.
| "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" | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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.")
PYRepository: 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
doneRepository: 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.
| "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)." | ||
| } | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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"
fiRepository: 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.
| "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)。" | ||
| } | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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
doneRepository: 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.
Summary by CodeRabbit
Release Notes
New Features
Performance
Improvements
✏️ Tip: You can customize this high-level summary in your review settings.