Skip to content

Conversation

@deepinfect
Copy link
Collaborator

@deepinfect deepinfect commented Oct 18, 2025

make every components neaty and easy to use.

Summary by CodeRabbit

  • New Features

    • Rich chat input with drag‑and‑drop/paste file support, mentions, editor, model selector, configurable send/stop, and chat/new‑thread variants.
    • Modular chat configuration UI with reusable field components and model‑aware settings.
    • Enhanced artifact viewer with device viewport selector, preview/code modes, and export/copy options.
    • Message minimap, action buttons, and capture/export tools.
  • Bug Fixes

    • Provider initialization failures are now logged.
  • Documentation

    • Added component refactor guide and Vue component analysis report.
  • Tests

    • Many new unit tests for components and composables.

deepinsect added 10 commits October 14, 2025 18:01
- Introduced ConfigFieldHeader component for consistent field headers.
- Added ConfigInputField, ConfigSelectField, ConfigSliderField, and ConfigSwitchField components for various input types.
- Created types for field configurations in types.ts to standardize field definitions.
- Implemented useChatConfigFields composable to manage field configurations dynamically.
- Added useModelCapabilities and useModelTypeDetection composables for handling model-specific capabilities and requirements.
- Developed useSearchConfig and useThinkingBudget composables for managing search and budget configurations.
- Added `useInputHistory` composable for managing input history and navigation.
- Implemented methods for setting, clearing, and confirming history placeholders.
- Integrated arrow key navigation for browsing through input history.

feat: enhance mention data handling in prompt input

- Created `useMentionData` composable to aggregate mention data from selected files and MCP resources.
- Implemented watchers to update mention data based on selected files, MCP resources, tools, and prompts.

feat: manage prompt input configuration with store synchronization

- Developed `usePromptInputConfig` composable for managing model configuration.
- Implemented bidirectional sync between local config and chat store.
- Added debounced watcher to reduce updates and improve performance.

feat: streamline TipTap editor operations in prompt input

- Introduced `usePromptInputEditor` composable for managing TipTap editor lifecycle and content transformation.
- Implemented methods for handling mentions, pasting content, and clearing editor content.

feat: handle file operations in prompt input

- Created `usePromptInputFiles` composable for managing file selection, paste, and drag-drop operations.
- Implemented methods for processing files, handling dropped files, and clearing selected files.

feat: manage rate limit status in prompt input

- Developed `useRateLimitStatus` composable for displaying and polling rate limit status.
- Implemented methods for handling rate limit events and computing status icons, classes, and tooltips.
…e documentation

- Refactor ArtifactDialog.vue to use composables for view mode, viewport size, code editor, and export functionality
- Simplify HTMLArtifact.vue by removing drag-resize logic and using fixed viewport dimensions
- Clean up MermaidArtifact.vue styling and structure
- Update component refactoring guide to reflect new patterns and best practices
- Adjust prompt input composable to allow delayed editor initialization
- Update internationalization files for new responsive label
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 18, 2025

Walkthrough

Refactors renderer UI into composition-driven modules: replaces legacy PromptInput with a new ChatInput and many composables, introduces data-driven ChatConfig field components and types, centralizes artifact view/export/viewport logic, updates provider model capability mapping and provider init error handling, adds i18n keys and ~30 unit tests.

Changes

Cohort / File(s) Summary
Docs
docs/components_refact_guide.md, docs/vue-components-analysis.md
Added a Vue 3 component refactor guide and a static analysis report for Vue components.
Provider / init
src/main/presenter/configPresenter/modelConfig.ts, src/main/presenter/llmProviderPresenter/baseProvider.ts
Read reasoning/search from nested model.*?.supported; add catch/log in provider initialization promise chain.
ChatConfig UI & types
src/renderer/src/components/ChatConfig.vue , src/renderer/src/components/ChatConfig/*, src/renderer/src/components/ChatConfig/types.ts, src/renderer/src/composables/useChatConfigFields.ts
Converted ChatConfig to data-driven field configs; added header/slider/input/select/switch components and field types; introduced useChatConfigFields composable.
New ChatInput + toolbar
src/renderer/src/components/chat-input/ChatInput.vue, src/renderer/src/components/chat-input/components/ToolbarButton.vue
Added new ChatInput SFC and toolbar button component (rich editor, file handling, model chooser, config popover).
ChatInput composables
src/renderer/src/components/chat-input/composables/*
Added many composables: useContextLength, useDragAndDrop, useInputHistory, useInputSettings, useMentionData, usePromptInputConfig, usePromptInputEditor, usePromptInputFiles, useRateLimitStatus, useSendButtonState.
Removed legacy inputs
src/renderer/src/components/ChatInput.vue (deleted), src/renderer/src/components/prompt-input/PromptInput.vue (deleted)
Removed legacy PromptInput/older ChatInput SFCs (functionality migrated).
Message area & message utilities
src/renderer/src/components/message/MessageList.vue, src/renderer/src/components/message/MessageActionButtons.vue, src/renderer/src/composables/message/*, src/renderer/src/composables/message/types.ts
Reworked MessageList to use composables (scroll, minimap, retry, capture, clean dialog), added MessageActionButtons and message-related types/composables.
Artifacts: preview, code editor, export
src/renderer/src/components/artifacts/ArtifactDialog.vue, src/renderer/src/components/artifacts/HTMLArtifact.vue, src/renderer/src/components/artifacts/MermaidArtifact.vue, src/renderer/src/composables/useArtifact*, src/renderer/src/composables/useViewportSize.ts
Centralized view-mode, code editor integration, export/copy flows and viewport sizing; removed per-component drag/resize in HTMLArtifact and replaced with fixed viewport sizes and composables.
Model / capability helpers
src/renderer/src/composables/useModelCapabilities.ts, src/renderer/src/composables/useModelTypeDetection.ts, src/renderer/src/composables/useSearchConfig.ts, src/renderer/src/composables/useThinkingBudget.ts
Added composables for model capability fetching, model-type flags, search-config visibility, and thinking-budget validation.
Artifact code editor & context
src/renderer/src/composables/useArtifactCodeEditor.ts, src/renderer/src/composables/useArtifactContext.ts, src/renderer/src/composables/useArtifactExport.ts, src/renderer/src/composables/useArtifactViewMode.ts
Added Monaco/code language handling, artifact context keying, export/copy flows, and view-mode manager.
UI components & small additions
src/renderer/src/components/ChatConfig/Config*.vue, src/renderer/src/components/message/MessageActionButtons.vue, src/renderer/src/components/ChatConfig/*
Added multiple small field UI components (header, slider, input, select, switch) and MessageActionButtons.
i18n
src/renderer/src/i18n/*/artifacts.json, src/renderer/src/i18n/*/common.json
Added responsive and mermaid.renderError translations across locales; added unknownError keys in common.json locales.
Tests & Vitest config
test/main/..., test/renderer/... (many new), vitest.config.ts
Added many new unit tests for composables/components; updated vitest config to per-project setup with Vue plugin and project-specific aliases.
Minor
src/shadcn/components/ui/popover/PopoverContent.vue
Removed an extraneous blank line.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant User
    participant ChatInput as ChatInput (new)
    participant Editor as usePromptInputEditor
    participant Files as usePromptInputFiles
    participant Config as usePromptInputConfig
    participant Store as ChatStore

    User->>ChatInput: Type / mention / paste / drop
    ChatInput->>Editor: update/insert content
    ChatInput->>Files: process dropped/pasted files
    Files-->>ChatInput: selectedFiles (reactive)
    Editor-->>ChatInput: inputText (reactive)

    User->>ChatInput: Press Enter
    ChatInput->>Config: validate & build payload
    alt valid
        Config->>Store: create/send message (with files/flags)
        Store->>ChatInput: ack/state update
    else invalid
        ChatInput->>User: show feedback / disable send
    end
Loading
sequenceDiagram
    autonumber
    participant MessageList
    participant Scroll as useMessageScroll
    participant Minimap as useMessageMinimap
    participant DOM

    MessageList->>Scroll: setupScrollObserver()
    Scroll->>DOM: attach container & anchor
    DOM->>Scroll: onScroll -> updateScrollInfo (debounced)
    Scroll->>MessageList: expose scrollInfo & aboveThreshold
    MessageList->>Minimap: handleHover(messageId)
    Minimap-->>MessageList: hoveredMessageId (readonly)
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested labels

codex

Suggested reviewers

  • deepinfect
  • zerob13

Poem

🐰 I hopped through fields and composable lanes,

Split editors, hooks, and tidy refrains.
Artifacts exported, viewports aligned,
Tests guard the meadow I helped refine.
A little rabbit cheer — code neat and kind!

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title Check ❓ Inconclusive The PR title "Feat/codeoptimize" is vague and uses a non-descriptive term that fails to clearly convey what was actually changed. While the term "codeoptimize" is technically related to the changeset (which does involve code refactoring and optimization), the title reads more like a branch name than a proper PR title. The actual changes are substantial and specific: extraction of composables from the ChatConfig component, restructuring of chat input and message handling, removal of the old PromptInput component, and reorganization of artifact rendering logic. A developer scanning the history would struggle to understand the primary purpose of these changes from "Feat/codeoptimize" alone, as the term is too broad and generic. Consider revising the title to be more specific and descriptive. For example: "Refactor: Extract composables and restructure ChatConfig component" or "Refactor: Modularize chat input and message handling with composition API" would better communicate the scope and intent of these changes. The title should follow conventional commit format and clearly indicate that this is a significant component refactoring, not just a generic code optimization.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/codeoptimize

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0d59f6f and 9ca7764.

📒 Files selected for processing (8)
  • src/renderer/src/i18n/fa-IR/common.json (1 hunks)
  • src/renderer/src/i18n/fr-FR/common.json (1 hunks)
  • src/renderer/src/i18n/ja-JP/common.json (1 hunks)
  • src/renderer/src/i18n/ko-KR/common.json (1 hunks)
  • src/renderer/src/i18n/pt-BR/common.json (1 hunks)
  • src/renderer/src/i18n/ru-RU/common.json (1 hunks)
  • src/renderer/src/i18n/zh-HK/common.json (1 hunks)
  • src/renderer/src/i18n/zh-TW/common.json (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
src/renderer/src/**/*

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

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

Files:

  • src/renderer/src/i18n/zh-TW/common.json
  • src/renderer/src/i18n/ja-JP/common.json
  • src/renderer/src/i18n/zh-HK/common.json
  • src/renderer/src/i18n/ru-RU/common.json
  • src/renderer/src/i18n/ko-KR/common.json
  • src/renderer/src/i18n/fa-IR/common.json
  • src/renderer/src/i18n/fr-FR/common.json
  • src/renderer/src/i18n/pt-BR/common.json
src/renderer/src/**

📄 CodeRabbit inference engine (AGENTS.md)

Place Vue 3 app source under src/renderer/src (components, stores, views, i18n, lib)

Files:

  • src/renderer/src/i18n/zh-TW/common.json
  • src/renderer/src/i18n/ja-JP/common.json
  • src/renderer/src/i18n/zh-HK/common.json
  • src/renderer/src/i18n/ru-RU/common.json
  • src/renderer/src/i18n/ko-KR/common.json
  • src/renderer/src/i18n/fa-IR/common.json
  • src/renderer/src/i18n/fr-FR/common.json
  • src/renderer/src/i18n/pt-BR/common.json
src/renderer/src/i18n/**/*.{ts,json,yml,yaml}

📄 CodeRabbit inference engine (AGENTS.md)

Store i18n resources under src/renderer/src/i18n

Files:

  • src/renderer/src/i18n/zh-TW/common.json
  • src/renderer/src/i18n/ja-JP/common.json
  • src/renderer/src/i18n/zh-HK/common.json
  • src/renderer/src/i18n/ru-RU/common.json
  • src/renderer/src/i18n/ko-KR/common.json
  • src/renderer/src/i18n/fa-IR/common.json
  • src/renderer/src/i18n/fr-FR/common.json
  • src/renderer/src/i18n/pt-BR/common.json
**/*.{ts,tsx,js,jsx,vue,css,scss,md,json,yml,yaml}

📄 CodeRabbit inference engine (AGENTS.md)

Prettier style: single quotes, no semicolons, print width 100; run pnpm run format

Files:

  • src/renderer/src/i18n/zh-TW/common.json
  • src/renderer/src/i18n/ja-JP/common.json
  • src/renderer/src/i18n/zh-HK/common.json
  • src/renderer/src/i18n/ru-RU/common.json
  • src/renderer/src/i18n/ko-KR/common.json
  • src/renderer/src/i18n/fa-IR/common.json
  • src/renderer/src/i18n/fr-FR/common.json
  • src/renderer/src/i18n/pt-BR/common.json
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-check (x64)
🔇 Additional comments (8)
src/renderer/src/i18n/fa-IR/common.json (1)

78-79: LGTM!

Proper JSON syntax with trailing comma adjustment and appropriate Farsi translation for the new unknownError key. Correctly located under src/renderer/src/i18n/.

src/renderer/src/i18n/zh-TW/common.json (1)

78-79: LGTM!

Valid JSON with proper trailing comma and correct Traditional Chinese translation. File location aligns with coding guidelines.

src/renderer/src/i18n/zh-HK/common.json (1)

78-79: LGTM!

Valid JSON formatting with appropriate Hong Kong Chinese translation for unknownError. Correct file location.

src/renderer/src/i18n/ru-RU/common.json (1)

78-79: LGTM!

Valid JSON with correct Russian translation for unknownError. Proper trailing comma placement and file location per guidelines.

src/renderer/src/i18n/ko-KR/common.json (1)

78-79: LGTM!

Valid JSON with proper Korean translation for unknownError key. Trailing comma correctly placed and file location aligns with guidelines.

src/renderer/src/i18n/fr-FR/common.json (1)

78-79: LGTM!

Valid JSON with correct French translation for unknownError. Proper formatting and correct file location.

src/renderer/src/i18n/ja-JP/common.json (1)

78-79: LGTM!

Valid JSON with correct Japanese translation for unknownError key. Proper trailing comma and file location per guidelines.

src/renderer/src/i18n/pt-BR/common.json (1)

78-79: LGTM!

Valid JSON with correct Brazilian Portuguese translation for unknownError. Proper formatting and file location alignment.


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.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 19, 2025

Note

Unit test generation is an Early Access feature. Expect some limitations and changes as we gather feedback and continue to improve it.


Generating unit tests... This may take up to 20 minutes.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 19, 2025

UTG Post-Process Complete

No new issues were detected in the generated code and all check runs have completed. The unit test generation process has completed successfully.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 19, 2025

Creating a PR to put the unit tests in...

The changes have been created in this pull request: View PR

deepinsect and others added 4 commits October 19, 2025 10:26
- Added `useInputHistory` composable for managing input history and placeholder navigation.
- Implemented methods for setting, clearing, and confirming history placeholders.
- Integrated arrow key navigation for cycling through input history.

feat: enhance mention data handling in chat input

- Created `useMentionData` composable to manage mention data aggregation.
- Implemented watchers for selected files and MCP resources/tools/prompts to update mention data.

feat: manage prompt input configuration and synchronization

- Developed `usePromptInputConfig` composable for managing model configuration.
- Implemented bidirectional sync between local config refs and chat store.
- Added debounced watcher to reduce updates to the store.

feat: manage prompt input editor operations

- Introduced `usePromptInputEditor` composable for handling TipTap editor operations.
- Implemented content transformation, mention insertion, and paste handling.
- Added methods for handling editor updates and restoring focus.

feat: handle prompt input files management

- Created `usePromptInputFiles` composable for managing file operations in prompt input.
- Implemented file selection, paste, drag-drop, and prompt files integration.

feat: implement rate limit status management

- Developed `useRateLimitStatus` composable for managing rate limit status display and polling.
- Added methods for retrieving rate limit status icon, class, tooltip, and wait time formatting.
CodeRabbit Generated Unit Tests: Add renderer unit tests for components and composables
@deepinfect deepinfect marked this pull request as ready for review October 19, 2025 08:33
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: 39

Caution

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

⚠️ Outside diff range comments (14)
src/renderer/src/i18n/zh-TW/artifacts.json (1)

38-39: Remove duplicate key "copyImageFailedDesc".

The key "copyImageFailedDesc" appears twice (lines 38–39). Remove the duplicate to avoid JSON inconsistency.

Apply this diff:

  "copyAsImage": "複製為圖片",
  "copyImageSuccessDesc": "圖片已復製到剪貼板",
- "copyImageFailedDesc": "無法複製圖片到剪貼板",
  "desktop": "桌面",
src/renderer/src/i18n/fr-FR/artifacts.json (1)

39-46: Remove duplicate key "copyImageFailedDesc".

The key "copyImageFailedDesc" appears twice (lines 39 and 46). Remove one occurrence to avoid JSON inconsistency.

Apply this diff:

  "copyImageFailedDesc": "Impossible de copier des images dans le presse-papiers",
  "desktop": "Bureau",
  "tablet": "Tablette",
  "mobile": "Mobile",
  "responsive": "Responsive",
  "width": "Largeur",
  "height": "Hauteur",
- "copyImageFailedDesc": "Impossible de copier l'image dans le presse-papiers",
src/renderer/src/i18n/ru-RU/artifacts.json (1)

39-46: Remove duplicate key "copyImageFailedDesc".

The key "copyImageFailedDesc" appears twice (lines 39 and 46). Remove one occurrence to avoid JSON inconsistency.

Apply this diff:

  "copyImageFailedDesc": "Невозможно скопировать картинки в буфер обмена",
  "desktop": "Рабочий стол",
  "tablet": "Планшет",
  "mobile": "Мобильный",
  "responsive": "Responsive",
  "width": "Ширина",
  "height": "Высота",
- "copyImageFailedDesc": "Не удалось скопировать изображение в буфер обмена",
src/renderer/src/i18n/ja-JP/artifacts.json (1)

39-46: Remove duplicate key "copyImageFailedDesc".

The key "copyImageFailedDesc" appears twice (lines 39 and 46). Remove one occurrence to avoid JSON inconsistency.

Apply this diff:

  "copyImageFailedDesc": "画像のコピーに失敗しました",
  "desktop": "デスクトップ",
  "tablet": "タブレット",
  "mobile": "モバイル",
  "responsive": "レスポンシブ",
  "width": "幅",
  "height": "高さ",
- "copyImageFailedDesc": "画像をコピーできませんでした",
src/renderer/src/i18n/fa-IR/artifacts.json (1)

39-39: Critical: Duplicate key "copyImageFailedDesc" breaks JSON validity.

Line 46 repeats the key "copyImageFailedDesc" with the same value already present at line 39. JSON does not support duplicate keys; this will either cause parsing errors or silently overwrite the first entry, corrupting the locale data.

Remove the duplicate at line 46:

   "height": "ارتفاع",
-  "copyImageFailedDesc": "رونویسی تصویر به بریده‌دان انجام نشد",
   "noSvgContent": "بدون محتوای SVG در دسترس نیست",

Also applies to: 46-46

src/renderer/src/components/artifacts/HTMLArtifact.vue (3)

8-14: Harden iframe sandbox; disable scripts by default, add prop-gated override, and add an accessible title.

Running untrusted HTML with allow-scripts allow-same-origin substantially weakens sandboxing. Default to no scripts, keep same-origin only for head edits, and gate scripts via an explicit prop. Also add a title for a11y. As per coding guidelines.

Apply this diff in template to bind sandbox dynamically and add a title:

-          sandbox="allow-scripts allow-same-origin"
+          :sandbox="sandboxAttr"
+          :title="iframeTitle"

And in script add a prop, computed sandbox, and title (see diffs below for props/additions).


29-39: Add explicit allowScripts prop (default false) to gate script execution.

Make the risk opt-in and explicit. As per coding guidelines.

-const props = defineProps<{
+const props = withDefaults(defineProps<{
   block: {
     artifact: {
       type: string
       title: string
     }
     content: string
   }
   isPreview: boolean
   viewportSize?: 'desktop' | 'tablet' | 'mobile'
-}>()
+  allowScripts?: boolean
+}>(), {
+  allowScripts: false
+})

70-97: Bug: viewport meta isn’t updated when viewportSize changes (onload won’t re-fire). Extract updater and call from watch.

setupIframe() assigns onload, so subsequent watch() re-calls won’t run the meta logic. Extract the logic into applyViewportMeta() and call it both on load and on size changes.

+const applyViewportMeta = (doc: Document) => {
+  const viewportSize = props.viewportSize || 'desktop'
+  let viewportContent = 'width=device-width, initial-scale=1.0'
+  if (viewportSize === 'mobile' || viewportSize === 'tablet') {
+    const width = VIEWPORT_SIZES[viewportSize].width
+    viewportContent = `width=${width}, initial-scale=1.0`
+  }
+  // Remove existing viewport meta
+  const existingViewport = doc.querySelector('meta[name="viewport"]')
+  if (existingViewport) existingViewport.remove()
+  // Add new viewport meta
+  const viewportMeta = doc.createElement('meta')
+  viewportMeta.name = 'viewport'
+  viewportMeta.content = viewportContent
+  doc.head.appendChild(viewportMeta)
+}
+
 const setupIframe = () => {
   if (props.isPreview && iframeRef.value) {
     const iframe = iframeRef.value
     iframe.onload = () => {
       const doc = iframe.contentDocument
       if (!doc) return
-
-      // Add viewport meta tag
-      const viewportSize = props.viewportSize || 'desktop'
-      let viewportContent = 'width=device-width, initial-scale=1.0'
-      if (viewportSize === 'mobile' || viewportSize === 'tablet') {
-        const width = VIEWPORT_SIZES[viewportSize].width
-        viewportContent = `width=${width}, initial-scale=1.0`
-      }
-      // Remove existing viewport meta tag
-      const existingViewport = doc.querySelector('meta[name="viewport"]')
-      if (existingViewport) {
-        existingViewport.remove()
-      }
-      // Add new viewport meta tag
-      const viewportMeta = doc.createElement('meta')
-      viewportMeta.name = 'viewport'
-      viewportMeta.content = viewportContent
-      doc.head.appendChild(viewportMeta)
+      applyViewportMeta(doc)
 
       // Add base styles
       const resetCSS = `
         * {
           margin: 0;
           padding: 0;
           box-sizing: border-box;
         }
         html, body {
           height: 100%;
           font-family: Arial, sans-serif;
         }
         img {
           max-width: 100%;
           height: auto;
         }
         a {
           text-decoration: none;
           color: inherit;
         }
       `
       const styleElement = doc.createElement('style')
       styleElement.textContent = resetCSS
       doc.head.appendChild(styleElement)
     }
   }
 }
 
-// Watch viewport size changes
-watch(
-  () => props.viewportSize,
-  () => {
-    setupIframe()
-  }
-)
+// Watch viewport size changes: update meta immediately
+watch(
+  () => props.viewportSize,
+  () => {
+    const doc = iframeRef.value?.contentDocument
+    if (doc) applyViewportMeta(doc)
+  }
+)

Also applies to: 129-135

src/main/presenter/llmProviderPresenter/baseProvider.ts (4)

106-121: Initialization flow sets isInitialized too early and swallows async errors

Set isInitialized only after successful init, await the chain, and propagate failure. Current try/catch won’t catch async errors inside the .then chain.

Apply:

-  protected async init() {
-    if (this.provider.enable) {
-      try {
-        this.isInitialized = true
-        this.fetchModels()
-          .then(() => {
-            return this.autoEnableModelsIfNeeded()
-          })
-          .then(() => {
-            console.info('Provider initialized successfully:', this.provider.name)
-          })
-          .catch((error) => {
-            // Handle errors from fetchModels() and autoEnableModelsIfNeeded()
-            console.warn('Provider initialization failed:', this.provider.name, error)
-          })
-        // Check if we need to automatically enable all models
-      } catch (error) {
-        console.warn('Provider initialization failed:', this.provider.name, error)
-      }
-    }
-  }
+  protected async init() {
+    if (!this.provider.enable) return
+    try {
+      await this.fetchModels()
+      await this.autoEnableModelsIfNeeded()
+      this.isInitialized = true
+      console.info('Provider initialized successfully:', this.provider.name)
+    } catch (error) {
+      this.isInitialized = false
+      console.warn('Provider initialization failed:', this.provider.name, error)
+      throw error
+    }
+  }

162-177: fetchModels try/catch is ineffective for promise errors; also not using fetch timeout

Use await so errors are caught; optionally enforce getModelFetchTimeout() to avoid hangs.

Apply:

-  public async fetchModels(): Promise<MODEL_META[]> {
-    try {
-      return this.fetchProviderModels().then((models) => {
-        console.log('Fetched models:', models?.length, this.provider.id)
-        this.models = models
-        this.configPresenter.setProviderModels(this.provider.id, models)
-        return models
-      })
-    } catch (e) {
-      console.error('Failed to fetch models:', e)
-      if (!this.models) {
-        this.models = []
-      }
-      return []
-    }
-  }
+  public async fetchModels(): Promise<MODEL_META[]> {
+    try {
+      const withTimeout = <T>(p: Promise<T>, ms: number) =>
+        new Promise<T>((resolve, reject) => {
+          const t = setTimeout(() => reject(new Error(`fetchProviderModels timeout after ${ms}ms`)), ms)
+          p.then((v) => { clearTimeout(t); resolve(v) }, (e) => { clearTimeout(t); reject(e) })
+        })
+      const models = await withTimeout(this.fetchProviderModels(), this.getModelFetchTimeout())
+      console.info('Fetched models:', models?.length, this.provider.id)
+      this.models = models
+      this.configPresenter.setProviderModels(this.provider.id, models)
+      return models
+    } catch (e) {
+      console.error('Failed to fetch models:', e)
+      this.models = this.models ?? []
+      return []
+    }
+  }

425-526: Log redaction: avoid dumping full model output/user content to logs

Logging match and content can leak user data/PII and bloat logs. Log minimal context and error, not raw payload.

Apply:

-            console.error('Error parsing function call JSON:', parseError, match, content)
+            console.error('Error parsing function call JSON:', {
+              error: (parseError as Error)?.message ?? String(parseError),
+              note: 'suppressed payload for privacy'
+            })

And:

-                console.error('Unknown function call format:', parsedCall)
+                console.error('Unknown function call format', { note: 'payload suppressed' })
-                console.error('Failed to parse with jsonrepair:', repairError)
+                console.error('Failed to parse with jsonrepair:', (repairError as Error)?.message ?? repairError)

657-684: Escape XML attribute values to prevent malformed XML/prompt injection

description/type/name/param values can contain quotes/angle brackets. Escape before interpolation.

Apply:

-        const { name, description, parameters } = tool.function
+        const { name, description, parameters } = tool.function
         const { properties, required = [] } = parameters
@@
-            const descriptionAttr = paramDef.description
-              ? ` description="${paramDef.description}"`
+            const descriptionAttr = paramDef.description
+              ? ` description="${escapeXmlAttr(paramDef.description)}"`
               : ''
-            const typeAttr = paramDef.type ? ` type="${paramDef.type}"` : ''
+            const typeAttr = paramDef.type ? ` type="${escapeXmlAttr(paramDef.type)}"` : ''
@@
-        return `<tool name="${name}" description="${description}">
+        return `<tool name="${escapeXmlAttr(name)}" description="${escapeXmlAttr(description)}">

Add helper (outside this method, e.g., below class or as a private static):

function escapeXmlAttr(v: string): string {
  return v
    .replace(/&/g, '&amp;')
    .replace(/"/g, '&quot;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
}
src/renderer/src/components/ChatView.vue (1)

88-96: i18n: replace hardcoded Chinese “新会话” with a translation key.

All user-facing strings in renderer must use vue‑i18n.

As per coding guidelines

-  if (route.query.modelId && route.query.providerId) {
-    const threadId = await chatStore.createThread('新会话', {
+  if (route.query.modelId && route.query.providerId) {
+    const threadId = await chatStore.createThread(t('chat.newThread'), {
       modelId: route.query.modelId as string,
       providerId: route.query.providerId as string,
       artifacts: settingsStore.artifactsEffectEnabled ? 1 : 0
     })
     chatStore.setActiveThread(threadId)
   }
...
-    if (route.query.modelId && route.query.providerId) {
-      const threadId = await chatStore.createThread('新会话', {
+    if (route.query.modelId && route.query.providerId) {
+      const threadId = await chatStore.createThread(t('chat.newThread'), {
         modelId: route.query.modelId as string,
         providerId: route.query.providerId as string,
         artifacts: settingsStore.artifactsEffectEnabled ? 1 : 0
       })
       chatStore.setActiveThread(threadId)
     }

Add in <script setup>:

+import { useI18n } from 'vue-i18n'
+const { t } = useI18n()

Also applies to: 102-110

src/renderer/src/components/artifacts/MermaidArtifact.vue (1)

53-59: Critical: XSS risk by injecting raw innerHTML from content

Setting mermaidRef.innerHTML = props.block.content injects arbitrary HTML before mermaid sanitizes anything. Even with securityLevel: 'strict', this exposes the DOM to untrusted markup.

Use mermaid.render to generate SVG from plain text, then inject the library’s output:

-// 清空之前的内容
-mermaidRef.value.innerHTML = props.block.content
-
-// 使用 mermaid API 重新渲染
-await mermaid.run({
-  nodes: [mermaidRef.value]
-})
+// Clear existing content
+mermaidRef.value.replaceChildren()
+const id = `mermaid-${Date.now()}-${Math.random().toString(36).slice(2)}`
+const { svg } = await mermaid.render(id, props.block.content)
+mermaidRef.value.innerHTML = svg

Also change initialization to avoid auto-run:

-mermaid.initialize({
-  startOnLoad: true,
+mermaid.initialize({
+  startOnLoad: false,

As per security best practices.

🧹 Nitpick comments (70)
src/renderer/src/components/artifacts/HTMLArtifact.vue (4)

41-55: Wire computed sandboxAttr and iframeTitle for template bindings.

Keep logic close to props and ensure an accessible title. As per coding guidelines.

 const iframeRef = ref<HTMLIFrameElement>()
 
+const sandboxAttr = computed(
+  () => (props.allowScripts ? 'allow-scripts allow-same-origin' : 'allow-same-origin')
+)
+
+const iframeTitle = computed(() => props.block?.artifact?.title || 'HTML preview')
+
 const viewportClasses = computed(() => {
   const size = props.viewportSize || 'desktop'
   const baseClasses = 'html-iframe-wrapper transition-all duration-300 ease-in-out'

23-27: Type-safety and reuse: mark VIEWPORT_SIZES as const and consider centralizing.

Add as const for exact typings and move this map to a shared constants module to keep sizes consistent across components.

-const VIEWPORT_SIZES = {
+const VIEWPORT_SIZES = {
   tablet: { width: 768, height: 1024 },
   mobile: { width: 375, height: 667 }
-}
+} as const

59-68: Minor: return typed style object and include overflow control if needed.

If content often overflows, consider overflow: 'hidden' for mobile/tablet frames. Optional.

-  if (size === 'mobile' || size === 'tablet') {
+  if (size === 'mobile' || size === 'tablet') {
     const dimensions = VIEWPORT_SIZES[size]
-    return {
+    return {
       width: `${dimensions.width}px`,
-      height: `${dimensions.height}px`
+      height: `${dimensions.height}px`
+      // overflow: 'hidden'
     }
   }
 
   return {}

98-121: Make the CSS reset minimal or configurable.

The hardcoded reset (e.g., font-family: Arial) may clash with authored content. Consider:

  • Minimal reset (margin/box-sizing only), or
  • A prop (e.g., injectResetCss?: boolean) defaulting to true, or
  • Namespace the reset under :root/scoped container.
test/renderer/components/HTMLArtifact.test.ts (2)

7-17: Ensure proper cleanup when using attachTo.

Unmount to avoid leaking nodes across tests.

   const wrapper = mount(HTMLArtifact, {
     props: {
       block: {
         content: '<html><body>Hello</body></html>',
         artifact: { type: 'text/html', title: 'doc' }
       },
       isPreview: true,
       viewportSize: 'mobile'
     },
     attachTo: document.body
   })
+  try {
+    // ... assertions ...
+  } finally {
+    wrapper.unmount()
+  }

5-29: Add tests for tablet/desktop and for meta update on viewport change.

After extracting applyViewportMeta() (see component diff), you can unit-test it directly with a JSDOM Document. Also add a spec that switches viewportSize from mobile→tablet and asserts inline styles update and applyViewportMeta() is invoked.

I can draft these tests once the helper is extracted; want me to open a follow-up PR?

src/main/presenter/llmProviderPresenter/baseProvider.ts (2)

140-155: Duplicate status checks likely wrong (manual-modified vs enabled use same predicate)

Both hasManuallyModifiedModels and hasEnabledModels call getModelStatus(providerId, model.id) identically. Confirm intended semantics; one should detect any explicit user override (defined/changed), the other actual “enabled” state.

Example:

const status = this.configPresenter.getModelStatus(providerId, model.id)
// manual change: status !== undefined
// enabled: status === true (or matches your enabled value)

18-29: Use English for comments per repo guidelines

Several doc comments are in Chinese. Please translate comments to English to comply with “Use English for all logs and comments”. Business/user prompts can remain localized.

As per coding guidelines

Also applies to: 128-157, 179-201

vitest.config.ts (1)

8-21: Ensure alias replacements are cross‑platform

Using path.resolve may introduce backslashes on Windows within regex replacement strings containing $1. Prefer posix join or normalize to forward slashes.

Example:

import path from 'path'
const r = (p: string) => path.posix.join(process.cwd().replace(/\\/g, '/'), p)
{ find: /^@\/(components|composables|stores|assets|i18n|views)\//, replacement: `${r('src/renderer/src')}/$1/` }
src/renderer/src/composables/message/useMessageMinimap.ts (1)

16-24: Prefer store actions over directly mutating store flags

Call artifactStore.hideArtifact() rather than assigning isOpen, to keep side effects centralized. Same for opening message navigation if an action exists.

-    if (artifactStore.isOpen) {
-      artifactStore.isOpen = false
+    if (artifactStore.isOpen) {
+      artifactStore.hideArtifact()
       chatStore.isMessageNavigationOpen = true
src/renderer/src/composables/message/useMessageRetry.ts (3)

8-19: Avoid index‑keyed Map and any; key by messageId with a typed interface

Index keys desync on insert/remove; any defeats TS. Type the ref and key by message.id.

Apply:

-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  const assistantRefs = ref(new Map<number, any>())
+  type AssistantRef = { handleAction?: (action: 'retry') => unknown | Promise<unknown> }
+  const assistantRefs = ref(new Map<string, AssistantRef>())
@@
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  const setAssistantRef = (index: number) => (el: any) => {
-    if (el) {
-      assistantRefs.value.set(index, el)
+  const setAssistantRef = (messageId: string) => (el: AssistantRef | null) => {
+    if (el) {
+      assistantRefs.value.set(messageId, el)
     } else {
-      assistantRefs.value.delete(index)
+      assistantRefs.value.delete(messageId)
     }
   }

Usage site: pass message.id instead of index when binding refs.


21-38: Await retry action and guard for promise return

If handleAction is async, not awaiting may cause race conditions and false positives.

-          if (assistantRef && typeof assistantRef.handleAction === 'function') {
-            assistantRef.handleAction('retry')
+          if (assistantRef?.handleAction) {
+            await assistantRef.handleAction('retry')
             triggered = true
           }

If your UI expects fire‑and‑forget, keep as is; otherwise prefer await.


40-51: Consider using store.retryMessage when no assistant ref exists

The store exposes retryMessage(messageId) which better models “retry this assistant message” semantics than regenerateFromUserMessage (restarts from user). Verify desired behavior.

Example:

// if next assistant exists but no ref action:
await chatStore.retryMessage(messages.value[i].id)
src/renderer/src/components/chat-input/components/ToolbarButton.vue (1)

4-17: Add button type and accessible label

Prevent unintended form submits and improve a11y for icon‑only buttons.

-      <Button
+      <Button
         variant="outline"
         size="icon"
+        type="button"
         :class="[
           'w-7 h-7 text-xs rounded-lg',
           variant === 'chat' ? 'text-accent-foreground' : '',
           isActive ? 'text-primary' : ''
         ]"
+        :aria-label="tooltip"
         @click="handleClick"
       >
docs/components_refact_guide.md (1)

1-549: Comprehensive refactoring guide added.

This documentation provides valuable patterns and best practices for Vue 3 component refactoring. The guide is well-structured with practical examples covering composable extraction, performance optimization, testing strategies, and common pitfalls.

The static analysis tool flagged a few minor Chinese grammar suggestions (lines 11, 273, 463, 551) related to adverb-verb constructions. These are optional stylistic improvements and don't affect the technical content or clarity of the guide.

src/renderer/src/components/chat-input/composables/useRateLimitStatus.ts (1)

172-190: Consider extracting duplicated wait time calculation.

The wait time calculation logic appears in both getRateLimitStatusTooltip (lines 172-174) and formatWaitTime (lines 185-187). Extracting this into a helper function would improve maintainability.

// Add internal helper
const calculateWaitTime = (): number => {
  if (!rateLimitStatus.value?.config.enabled) return 0
  
  const intervalSeconds = 1 / rateLimitStatus.value.config.qpsLimit
  return Math.ceil(
    (rateLimitStatus.value.lastRequestTime + intervalSeconds * 1000 - Date.now()) / 1000
  )
}

// Then use in both functions
const getRateLimitStatusTooltip = (): string => {
  // ... existing checks ...
  const waitTime = calculateWaitTime()
  return t('chat.input.rateLimitWaitingTooltip', { seconds: waitTime, interval: intervalSeconds })
}

const formatWaitTime = (): string => {
  // ... existing checks ...
  const waitTime = calculateWaitTime()
  return t('chat.input.rateLimitWait', { seconds: Math.max(0, waitTime) })
}
src/renderer/src/components/chat-input/composables/useMentionData.ts (3)

26-42: Initialize file mentions immediately; tighten source.

Use the ref as the watch source and populate at mount to avoid empty mention list until the first change.

-  watch(
-    () => selectedFiles.value,
+  watch(
+    selectedFiles,
     () => {
       mentionData.value = mentionData.value
         .filter((item) => item.type !== 'item' || item.category !== 'files')
         .concat(
           selectedFiles.value.map((file) => ({
             id: file.metadata.fileName,
             label: file.metadata.fileName,
             icon: file.mimeType?.startsWith('image/') ? 'lucide:image' : 'lucide:file',
             type: 'item' as const,
             category: 'files' as const
           }))
         )
     },
-    { deep: true }
+    { deep: true, immediate: true }
   )
```<!-- review_comment_end -->

---

`74-81`: **Avoid “undefined ” in tool labels; safely join parts.**

`tool.server.icons` may be falsy and produce `undefined ` in the label.


```diff
-            label: `${tool.server.icons}${' '}${tool.function.name ?? ''}`,
+            label: [tool.server.icons, tool.function.name ?? ''].filter(Boolean).join(' '),

22-43: Consider a single computed/unified updater to prevent duplication and races.

Multiple watchers mutate a shared global mentionData. Prefer deriving a single array (computed or one watcher that rebuilds per source change) and de-duping by id to avoid duplicates across quick successive updates.

If helpful, I can propose a computed-based version that merges all categories and ensures uniqueness by id.

Also applies to: 47-64, 68-85, 89-106

src/renderer/src/composables/useArtifactExport.ts (3)

91-94: Adopt structured logging per guidelines; replace bare console.error.

Logs should include level, timestamp, and context; avoid raw console.* in renderer.

As per coding guidelines

Example:

logger.error({
  msg: 'Failed to export SVG',
  artifactId: artifact?.id,
  err
})

Apply similarly in copy paths. If no logger exists, I can add a minimal structured logger wrapper.

Also applies to: 117-119, 167-170


148-165: Be robust to missing target elements; short‑circuit before capture.

If selectors don’t match, getTargetRect returns null and leaves behavior to captureAndCopy. Fail fast with a clear log and false.

-      const success = await captureAndCopy({
+      const el = document.querySelector(targetSelector)
+      if (!el) {
+        console.error('Copy as image: target element not found', { targetSelector })
+        return false
+      }
+      const success = await captureAndCopy({
         container: containerSelector,
-        getTargetRect: () => {
-          const element = document.querySelector(targetSelector)
-          if (!element) return null
-          const rect = element.getBoundingClientRect()
+        getTargetRect: () => {
+          const rect = el.getBoundingClientRect()
           return {
             x: Math.round(rect.x),
             y: Math.round(rect.y),
             width: Math.round(rect.width),
             height: Math.round(rect.height)
           }
         },

100-106: Set mime type per content when exporting code.

Use a closer content type for better OS handling (md/html/jsx) instead of always text/plain.

-    const blob = new Blob([artifact.content], { type: 'text/plain' })
+    const type =
+      extension === 'md'
+        ? 'text/markdown'
+        : extension === 'html'
+          ? 'text/html'
+          : 'text/plain'
+    const blob = new Blob([artifact.content], { type })
test/renderer/composables/useArtifactExport.test.ts (3)

20-21: Use strict typing; avoid as any.

Aligns with strict TS guideline.

-import { useArtifactExport } from '@/composables/useArtifactExport'
+import { useArtifactExport } from '@/composables/useArtifactExport'
+import type { ArtifactState } from '@/stores/artifact'
...
-const mkArtifact = (type: string, content: string, title = 'artifact') =>
-  ({ type, content, title }) as any
+const mkArtifact = (type: string, content: string, title = 'artifact'): ArtifactState => ({
+  id: 'a1',
+  type,
+  title,
+  content,
+  status: 'loaded'
+})

36-46: Minor: don’t await a sync function; add iframe case.

  • exportCode is sync; remove await to keep tests precise.
  • Add a case to exercise iframe path (text/html or application/vnd.ant.react).
-    await api.exportCode(mkArtifact('text/markdown', '# hello', 'readme'))
+    api.exportCode(mkArtifact('text/markdown', '# hello', 'readme'))

Additional test:

it('copyAsImage handles iframe artifacts', async () => {
  const capture = vi.fn().mockResolvedValue(true)
  const api = useArtifactExport(capture)
  const ok = await api.copyAsImage(mkArtifact('text/html', '<div/>'), {
    isDark: false,
    version: '1.0.0',
    texts: { brand: 'DeepChat', tip: 'tip' }
  })
  expect(ok).toBe(true)
  expect(capture).toHaveBeenCalled()
})

11-18: Restore spies between tests to avoid leakage.

import { afterEach } from 'vitest'

afterEach(() => {
  vi.restoreAllMocks()
})
src/renderer/src/components/ChatView.vue (4)

12-18: Switch to lazy‑loaded ChatInput to reduce initial bundle (optional).

For faster startup, load ChatInput asynchronously.

-<script setup lang="ts">
+<script setup lang="ts">
+import { defineAsyncComponent } from 'vue'
...
-import ChatInput from './chat-input/ChatInput.vue'
+const ChatInput = defineAsyncComponent(() => import('./chat-input/ChatInput.vue'))

Also applies to: 26-26


1-21: Comments must be in English per guidelines.

Update Chinese comments to English (e.g., “Message list”, “Input area”, “Listen streaming response”, “Auto-scroll if user didn’t scroll up”, “Watch route changes to create thread”, “Cleanup listeners”).

As per coding guidelines

Also applies to: 61-87, 98-119


35-37: Type refs explicitly to keep strict TS.

-const messageList = ref()
-const chatInput = ref()
+const messageList = ref<InstanceType<typeof MessageList> | null>(null)
+const chatInput = ref<InstanceType<typeof ChatInput> | null>(null)

49-55: Optional UX: prefer nextTick over setTimeout for focus.

-  setTimeout(() => {
-    chatInput.value?.restoreFocus()
-  }, 100)
+  await nextTick()
+  chatInput.value?.restoreFocus()

Apply similarly after END/ERROR events.

Also applies to: 68-79, 81-86

test/renderer/components/MessageActionButtons.test.ts (1)

11-20: Add assertions for button count and use more robust selectors.

The test relies on index-based button access without verifying the number of buttons found. If the component structure changes or buttons are conditionally rendered in unexpected ways, this test may access undefined elements or the wrong buttons.

Apply this diff to make the test more robust:

     // Find buttons by their component type and index
     const buttons = wrapper.findAllComponents({ name: 'Button' })
+    expect(buttons).toHaveLength(2)
 
     // First button should be clean (new-chat)
     await buttons[0].trigger('click')
-    expect(wrapper.emitted().clean).toBeTruthy()
+    expect(wrapper.emitted('clean')).toBeTruthy()
 
     // Second button should be scroll-to-bottom
     await buttons[1].trigger('click')
-    expect(wrapper.emitted()['scroll-to-bottom']).toBeTruthy()
+    expect(wrapper.emitted('scroll-to-bottom')).toBeTruthy()
test/renderer/composables/useDragAndDrop.test.ts (1)

4-15: Consider testing the timer-based drag-leave behavior.

The test bypasses the 50ms debounce timer by calling resetDragState() directly. While this validates the reset logic, it doesn't test the actual timer-based behavior in handleDragLeave, which is critical for preventing UI flicker during drag operations.

Consider adding a test that uses vi.useFakeTimers() and vi.advanceTimersByTime(50) to verify the drag state clears correctly after the debounce period:

it('clears drag state after debounce timer', async () => {
  vi.useFakeTimers()
  const api = useDragAndDrop()
  const evt = { dataTransfer: { types: ['Files'] } } as any as DragEvent
  
  api.handleDragEnter(evt)
  expect(api.isDragging.value).toBe(true)
  
  api.handleDragLeave()
  // Should still be dragging immediately
  expect(api.isDragging.value).toBe(true)
  
  // Advance timer past debounce
  vi.advanceTimersByTime(50)
  expect(api.isDragging.value).toBe(false)
  
  vi.useRealTimers()
})
test/renderer/composables/useMessageCapture.test.ts (2)

22-24: Mock inconsistency: theme store should return reactive ref.

The mock returns a primitive false for isDark, but the real useThemeStore likely returns a reactive ref. This inconsistency could mask bugs where the composable expects reactive values.

 vi.mock('@/stores/theme', () => ({
   useThemeStore: () => ({
-    isDark: false
+    isDark: ref(false)
   })
 }))

Add ref import at the top:

+import { ref } from 'vue'
 import { describe, it, expect, vi, beforeEach } from 'vitest'

35-55: Weak assertions: test only checks boolean return value.

The test creates DOM elements and calls captureMessage but only asserts the return value is true. It doesn't verify that captureAndCopy was called with correct arguments, or that the capture logic identified the correct elements.

Enhance assertions to verify behavior:

     const ok = await api.captureMessage({
       messageId: 'a1',
       parentId: 'u1',
       modelInfo: { model_name: 'm', model_provider: 'p' }
     })
     expect(ok).toBe(true)
+
+    // Verify captureAndCopy was called
+    const mockCapture = vi.mocked(usePageCapture().captureAndCopy)
+    expect(mockCapture).toHaveBeenCalledTimes(1)
test/renderer/composables/useArtifactContext.test.ts (1)

6-25: LGTM with a suggestion: test coverage is solid.

The test effectively validates the composable's key construction, reactivity, and componentKey increment behavior. The comments explain the scenario well (mimicking store.showArtifact() behavior).

Optional: Consider adding a test case to verify the "first run skip" behavior explicitly, ensuring componentKey doesn't increment on initial watch execution:

it('does not increment componentKey on initial execution', () => {
  const art = ref<any>({ id: 'art-1' })
  const threadId = ref<string | null>('t-1')
  const messageId = ref<string | null>('m-1')
  const { componentKey } = useArtifactContext(art, threadId, messageId)
  
  const initialKey = componentKey.value
  // componentKey should remain stable after initial run
  expect(componentKey.value).toBe(initialKey)
})
test/renderer/composables/useMessageScroll.test.ts (2)

15-18: Avoid @ts-ignore: use proper type assertion.

The @ts-ignore directive suppresses all type checking for the line. Use a proper type assertion instead to maintain type safety while addressing the specific type incompatibility.

 beforeEach(() => {
-  // @ts-ignore
-  global.IntersectionObserver = IO as any
+  global.IntersectionObserver = IO as unknown as typeof IntersectionObserver
 })

4-14: IntersectionObserver mock is minimal but functional.

The mock IO class provides the minimum interface needed for the test. However, it doesn't track observed targets or support disconnection verification, which could be useful for more thorough testing.

Consider enhancing the mock if you need to verify observer behavior:

class IO {
  cb: any
  targets: Set<Element> = new Set()
  
  constructor(cb: any) {
    this.cb = cb
  }
  
  observe(target: Element) {
    this.targets.add(target)
    this.cb([{ isIntersecting: false, target }])
  }
  
  unobserve(target: Element) {
    this.targets.delete(target)
  }
  
  disconnect() {
    this.targets.clear()
  }
}
src/renderer/src/components/ChatConfig/ConfigSliderField.vue (1)

10-19: Consider making description optional.

The description prop is typed as string but based on the component's usage pattern and similar field components, it's likely that description should be optional since not all fields may have descriptions.

 const props = defineProps<{
   icon: string
   label: string
-  description: string
+  description?: string
   modelValue: number
   min: number
   max: number
   step: number
   formatter?: (value: number) => string
 }>()
docs/vue-components-analysis.md (2)

76-76: Minor: use hyphen in compound adjective.

The phrase "High Priority Refactoring" should be hyphenated when used as a compound adjective.

-### High Priority Refactoring Targets
+### High-Priority Refactoring Targets

139-144: Markdown: specify language for fenced code block.

The code block should have a language identifier for proper syntax highlighting and markdown linting compliance.

-```
+```text
 Files by size category:
 - 1000+ lines:  2 files
 - 500-999 lines: 7 files
 - 300-499 lines: 31 files

</blockquote></details>
<details>
<summary>src/renderer/src/components/ChatConfig/ConfigSwitchField.vue (1)</summary><blockquote>

`7-15`: **Optional: use defineModel for simpler v-model**

You can replace modelValue/update boilerplate with defineModel for clarity (Vue 3.3+).


Example:

```diff
-defineProps<{
-  label: string
-  modelValue: boolean
-}>()
-const emit = defineEmits<{ 'update:modelValue': [value: boolean] }>()
+const props = defineProps<{ label: string }>()
+const checked = defineModel<boolean>({ default: false })

Template:

-:model-value="modelValue"
-@update:model-value="(val) => emit('update:modelValue', val)"
+:model-value="checked"
+@update:model-value="(val) => (checked = val)"

Also applies to: 22-25

src/renderer/src/components/ChatConfig/ConfigFieldHeader.vue (1)

38-39: Optional: preserve full value via title

If value may be truncated, add a title for hover disclosure.

-<span v-if="value !== undefined" class="text-xs text-muted-foreground">{{ value }}</span>
+<span v-if="value !== undefined" class="text-xs text-muted-foreground" :title="String(value)">{{ value }}</span>
src/renderer/src/components/chat-input/composables/useDragAndDrop.ts (1)

18-25: Optional: also preventDefault on dragenter

Improves consistency across browsers and stops unintended default behaviors.

-const handleDragEnter = (e: DragEvent) => {
+const handleDragEnter = (e: DragEvent) => {
+  e.preventDefault()
   dragCounter.value++
   if (e.dataTransfer?.types.includes('Files')) {
     isDragging.value = true
   }
 }
src/renderer/src/components/artifacts/MermaidArtifact.vue (2)

33-41: Use English comments per guidelines; consider one-time init

Comments are in Chinese. Convert to English. Also, multiple component mounts may reinitialize mermaid globally. Centralize initialization (idempotent module) to avoid config races.

  • Move initialize into a shared util (e.g., lib/mermaid.ts) with a guard.
  • Replace Chinese comments with concise English.

9-14: Render-on-change logic: minor optimization

You can drop the extra newIsPreview !== oldIsPreview check; rendering when content changes is enough, and when preview toggles true, run once.

-watch(
-  [() => props.block.content, () => props.isPreview],
-  async ([newContent, newIsPreview], [oldContent, oldIsPreview]) => {
-    if (newIsPreview && (newContent !== oldContent || newIsPreview !== oldIsPreview)) {
+watch(
+  [() => props.block.content, () => props.isPreview],
+  async ([newContent, newIsPreview], [oldContent]) => {
+    if (newIsPreview && newContent !== oldContent) {
       await nextTick()
       renderDiagram()
     }
   }
 )

Also applies to: 67-76

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

26-26: Extract duplicated selector to a constant.

The selector '.message-list-container' is duplicated on lines 26 and 129. Extract it to a constant at the module level for easier maintenance.

+const MESSAGE_LIST_CONTAINER_SELECTOR = '.message-list-container'
+
 export function useMessageCapture() {
   // ...
   const getContainer = () => {
     if (!containerCache) {
-      containerCache = document.querySelector('.message-list-container')
+      containerCache = document.querySelector(MESSAGE_LIST_CONTAINER_SELECTOR)
     }
     return containerCache
   }

Then update line 129 similarly.

src/renderer/src/components/chat-input/composables/useContextLength.ts (2)

32-34: Document the fallback value rationale.

Line 33 uses 1000 as a fallback divisor when contextLength is undefined. This magic number should be extracted to a named constant with documentation explaining its purpose, or the logic should ensure contextLength is always provided.

+const DEFAULT_CONTEXT_LENGTH = 1000 // Fallback for percentage calculation
+
 const currentContextLengthPercentage = computed(() => {
-  return currentContextLength.value / (contextLength ?? 1000)
+  return currentContextLength.value / (contextLength ?? DEFAULT_CONTEXT_LENGTH)
 })

44-49: Consider extracting threshold constants.

The percentage thresholds 0.9 and 0.8 are hardcoded magic numbers. Extracting them to named constants would improve maintainability.

+const CONTEXT_LENGTH_CRITICAL_THRESHOLD = 0.9
+const CONTEXT_LENGTH_WARNING_THRESHOLD = 0.8
+
 const contextLengthStatusClass = computed(() => {
   const percentage = currentContextLengthPercentage.value
-  if (percentage > 0.9) return 'text-red-600'
-  if (percentage > 0.8) return 'text-yellow-600'
+  if (percentage > CONTEXT_LENGTH_CRITICAL_THRESHOLD) return 'text-red-600'
+  if (percentage > CONTEXT_LENGTH_WARNING_THRESHOLD) return 'text-yellow-600'
   return 'text-muted-foreground'
 })
src/renderer/src/composables/useArtifactContext.ts (1)

49-60: Consider using watchEffect or restructuring the watch.

The isFirstRun flag pattern (lines 49-56) is a workaround to skip the initial watch callback when using immediate: true. Consider these alternatives:

  1. Remove immediate: true if the initial run is not needed
  2. Use two separate watchers: one without immediate, one with
  3. Use watchEffect if appropriate for your use case

Also, flush: 'sync' (line 59) causes the watcher to run synchronously, which can impact performance. Only use sync flush if immediate synchronous updates are critical.

Based on VueUse patterns (per learnings):

- let isFirstRun = true
- watch(
-   activeArtifactContext,
-   () => {
-     if (isFirstRun) {
-       isFirstRun = false
-       return
-     }
-     componentKey.value++
-   },
-   { immediate: true, flush: 'sync' }
- )
+ watch(
+   activeArtifactContext,
+   () => {
+     componentKey.value++
+   },
+   { flush: 'post' } // or remove flush option to use default 'pre'
+ )

If you truly need the immediate run behavior, document why sync flush is required.

src/renderer/src/composables/useChatConfigFields.ts (1)

231-254: Complex visibility condition - consider extracting.

The search strategy field has three chained conditions (lines 231-234). While the logic is correct, consider extracting this to a computed property for better readability:

+ const showSearchStrategyField = computed(() => {
+   return (
+     options.showSearchConfig.value &&
+     options.hasSearchStrategyOption.value &&
+     options.enableSearch.value
+   )
+ })
+
  const selectFields = computed<SelectFieldConfig[]>(() => {
    const fields: SelectFieldConfig[] = []
    // ... other fields ...
    
    // Search Strategy
-   if (
-     options.showSearchConfig.value &&
-     options.hasSearchStrategyOption.value &&
-     options.enableSearch.value
-   ) {
+   if (showSearchStrategyField.value) {
      fields.push({
        // ...
      })
    }
src/renderer/src/composables/useModelTypeDetection.ts (1)

65-71: Add contextual, structured error logging and boolean coercion.

Log with context instead of raw console.error(error), and coerce reasoning safely.

-    try {
-      const modelConfig = await settingsStore.getModelConfig(modelId.value, providerId.value)
-      modelReasoning.value = modelConfig.reasoning || false
-    } catch (error) {
-      modelReasoning.value = false
-      console.error(error)
-    }
+    try {
+      const modelConfig = await settingsStore.getModelConfig(modelId.value, providerId.value)
+      modelReasoning.value = Boolean(modelConfig?.reasoning)
+    } catch (error) {
+      modelReasoning.value = false
+      console.error('[useModelTypeDetection] fetchModelReasoning failed', {
+        modelId: modelId.value,
+        providerId: providerId.value,
+        error
+      })
+    }

As per coding guidelines.

src/renderer/src/composables/useModelCapabilities.ts (1)

76-83: Normalize empty capability objects to null and log with context.

Avoid {} truthiness traps; use null for “unknown/unsupported”, and include context in error logs.

-      capabilitySupportsReasoning.value = typeof sr === 'boolean' ? sr : null
-      capabilityBudgetRange.value = br || {}
-      capabilitySupportsSearch.value = typeof ss === 'boolean' ? ss : null
-      capabilitySearchDefaults.value = sd || null
+      capabilitySupportsReasoning.value = typeof sr === 'boolean' ? sr : null
+      capabilityBudgetRange.value =
+        br && (br.min != null || br.max != null || br.default != null) ? br : null
+      capabilitySupportsSearch.value = typeof ss === 'boolean' ? ss : null
+      capabilitySearchDefaults.value =
+        sd && Object.keys(sd).length > 0 ? sd : null
     } catch (error) {
-      resetCapabilities()
-      console.error(error)
+      resetCapabilities()
+      console.error('[useModelCapabilities] fetchCapabilities failed', {
+        providerId: providerId.value,
+        modelId: modelId.value,
+        error
+      })

As per coding guidelines.

src/renderer/src/components/chat-input/composables/usePromptInputConfig.ts (3)

169-179: Await async updates and handle errors in handleModelUpdate.

Avoid unhandled promise rejections; add try/catch and await both calls.

-const handleModelUpdate = (model: MODEL_META) => {
-  chatStore.updateChatConfig({
-    modelId: model.id,
-    providerId: model.providerId
-  })
-
-  configPresenter.setSetting('preferredModel', {
-    modelId: model.id,
-    providerId: model.providerId
-  })
-}
+const handleModelUpdate = async (model: MODEL_META) => {
+  try {
+    await chatStore.updateChatConfig({
+      modelId: model.id,
+      providerId: model.providerId
+    })
+    await configPresenter.setSetting('preferredModel', {
+      modelId: model.id,
+      providerId: model.providerId
+    })
+  } catch (error) {
+    console.error('[usePromptInputConfig] handleModelUpdate failed', {
+      modelId: model.id,
+      providerId: model.providerId,
+      error
+    })
+  }
+}

As per coding guidelines.


216-231: Eagerly load defaults when model/provider is already set.

Add a watcher on (modelId, providerId) with { immediate: true } to populate limits and defaults on first use.

   watch(
     [
       configTemperature,
       configContextLength,
       configMaxTokens,
       configSystemPrompt,
       configArtifacts,
       configThinkingBudget,
       configEnableSearch,
       configForcedSearch,
       configSearchStrategy,
       configReasoningEffort,
       configVerbosity
     ],
     syncConfigToStore
   )
+
+  // Load model defaults on mount and whenever model/provider changes
+  watch(
+    [() => chatStore.chatConfig.providerId, () => chatStore.chatConfig.modelId],
+    () => loadModelConfig(),
+    { immediate: true }
+  )

200-213: Avoid as any in store update payload.

Use a typed partial to keep strict typing and avoid accidental shape mismatches.

Would you like me to propose a typed Partial<CONVERSATION_SETTINGS> import and update the call site? As per coding guidelines.

src/renderer/src/components/artifacts/ArtifactDialog.vue (2)

369-371: Guard getAppVersion() with try/catch.

Prevent unhandled rejections during mount.

-onMounted(async () => {
-  appVersion.value = await devicePresenter.getAppVersion()
-})
+onMounted(async () => {
+  try {
+    appVersion.value = await devicePresenter.getAppVersion()
+  } catch (error) {
+    console.error('[ArtifactDialog] getAppVersion failed', { error })
+    appVersion.value = ''
+  }
+})

14-16: Use English for comments.

Template comments are in Chinese; switch to English per repo guidelines.

As per coding guidelines.

Also applies to: 26-27, 55-55, 72-72

src/renderer/src/components/chat-input/composables/usePromptInputFiles.ts (3)

145-162: Set the paste de-dup guard flag.

You check _deepchatHandled but never set it; set it after handling to avoid double-processing.

   const handlePaste = async (e: ClipboardEvent, fromCapture = false) => {
     // Avoid double-processing only for bubble-phase handler
     if (!fromCapture && (e as any)?._deepchatHandled) return
@@
-      if (selectedFiles.value.length > 0) {
+      if (selectedFiles.value.length > 0) {
         emit('file-upload', selectedFiles.value)
       }
     }
+    ;(e as any)._deepchatHandled = true
   }

84-87: Improve error logs with context.

Include filename/path and use consistent messages.

-      console.error('File processing failed:', error)
+      console.error('[usePromptInputFiles] File processing failed', { name: file?.name, error })
@@
-      console.error('Dropped file processing failed:', error)
+      console.error('[usePromptInputFiles] Dropped file processing failed', {
+        name: file?.name,
+        path,
+        error
+      })

As per coding guidelines.

Also applies to: 108-111


13-13: Optional: reduce initial bundle by lazy‑importing tokenx.

tokenx can be large; dynamic import only when needed.

-import { approximateTokenSize } from 'tokenx'
+// Lazy import when needed to reduce initial bundle cost
+let approximateTokenSize: ((s: string) => number) | undefined
+async function ensureTokenx() {
+  if (!approximateTokenSize) {
+    const mod = await import('tokenx')
+    approximateTokenSize = mod.approximateTokenSize
+  }
+}

Then before first usage:

-          token: approximateTokenSize(fileItem.content || ''),
+          token: (await (ensureTokenx(), approximateTokenSize!))(fileItem.content || ''),

Also applies to: 229-231

src/renderer/src/composables/useArtifactCodeEditor.ts (1)

100-141: Reduce redundant updateCode calls and avoid deep watch churn.

Current watchers can push the same content multiple times and deep-watch the whole artifact object.

  • Watch only needed keys: artifact.language, artifact.type, artifact.content.
  • Skip update when content and language are unchanged.

Example:

-  watch(
-    artifact,
-    (newArtifact) => {
+  watch(
+    () => ({ lang: artifact.value?.language, type: artifact.value?.type, content: artifact.value?.content }),
+    ({ lang, type, content }) => {
-      if (!newArtifact) {
+      if (!artifact.value) {
         codeLanguage.value = ''
         return
       }
-      const normalizedLanguage = normalizeLanguage(newArtifact)
+      const normalizedLanguage = normalizeLanguage(artifact.value)
       if (normalizedLanguage !== codeLanguage.value) {
         codeLanguage.value = normalizedLanguage
       }
       // Skip mermaid language detection
       if (codeLanguage.value === 'mermaid') {
         return
       }
-      const newCode = newArtifact.content || ''
+      const newCode = content || ''
       // Detect language if not explicitly set
       if (!codeLanguage.value) {
         throttledDetectLanguage(newCode)
       }
-      updateCode(newCode, codeLanguage.value)
+      updateCode(newCode, codeLanguage.value || 'plaintext')
     },
     {
       immediate: true,
-      deep: true
+      deep: false
     }
   )

Also applies to: 144-154

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

58-75: Scope message query to the container to avoid unintended scrolling.

Querying the whole document can target the wrong list if multiple exist.

Apply:

-  const scrollToMessage = (messageId: string) => {
+  const scrollToMessage = (messageId: string) => {
     nextTick(() => {
-      const messageElement = document.querySelector(`[data-message-id="${messageId}"]`)
+      const container = messagesContainer.value
+      const messageElement = container?.querySelector<HTMLElement>(
+        `[data-message-id="${messageId}"]`
+      )
       if (messageElement) {
         messageElement.scrollIntoView({
           behavior: 'smooth',
           block: 'start'
         })
         // 添加高亮效果
         messageElement.classList.add('message-highlight')
         setTimeout(() => {
           messageElement.classList.remove('message-highlight')
         }, 2000)
       }
       updateScrollInfo()
     })
   }
src/renderer/src/components/chat-input/composables/useInputHistory.ts (1)

14-27: Expose readonly state and use proper i18n typing for t.

  • Avoid wrapping simple refs in new computed instances; return readonly refs.
  • Type t as Composer['t'] for correct options typing.

Apply:

-import { ref, computed } from 'vue'
+import { ref, computed, readonly } from 'vue'
+import type { Composer } from 'vue-i18n'

-export function useInputHistory(editor: Editor | null, t: (key: string) => string) {
+export function useInputHistory(editor: Editor | null, t: Composer['t']) {

 // ...

   return {
-    // State (readonly)
-    currentHistoryPlaceholder: computed(() => currentHistoryPlaceholder.value),
-    showHistoryPlaceholder: computed(() => showHistoryPlaceholder.value),
+    // State (readonly)
+    currentHistoryPlaceholder: readonly(currentHistoryPlaceholder),
+    showHistoryPlaceholder: readonly(showHistoryPlaceholder),
     dynamicPlaceholder,
     // Methods
     setEditor,
     setHistoryPlaceholder,
     clearHistoryPlaceholder,
     handleArrowKey,
     confirmHistoryPlaceholder,
     addToHistory,
     initHistory,
     updatePlaceholder
   }

Also applies to: 125-141

src/renderer/src/components/chat-input/ChatInput.vue (2)

60-78: Add aria-labels to icon-only buttons for accessibility.

Provide screen-reader labels using i18n keys.

Apply:

 <Button
   variant="outline"
   size="icon"
   :class="['w-7 h-7 text-xs rounded-lg', variant === 'chat' ? 'text-accent-foreground' : '']"
   @click="files.openFilePicker"
+  :aria-label="t('chat.input.fileSelect')"
 >
 ...
 <Button
   variant="outline"
   :class="['w-7 h-7 text-xs rounded-lg', variant === 'chat' ? 'text-accent-foreground' : '', settings.webSearch ? 'text-primary' : '']"
   :dir="langStore.dir"
   size="icon"
   @click="onWebSearchClick"
+  :aria-label="t('chat.features.webSearch')"
 >
 ...
 <Button
   class="h-7 w-7 rounded-md border border-border/60 ..."
   size="icon"
   variant="outline"
+  :aria-label="t('chat.input.openSettings')"
 >
 ...
 <Button
   v-if="!isStreaming || variant === 'newThread'"
   variant="default"
   size="icon"
   class="w-7 h-7 text-xs rounded-lg"
   :disabled="disabledSend"
   @click="emitSend"
+  :aria-label="t('chat.input.send')"
 >
 ...
 <Button
   v-else-if="isStreaming && variant === 'chat'"
   key="cancel"
   variant="outline"
   size="icon"
   class="w-7 h-7 text-xs rounded-lg bg-card backdrop-blur-lg"
   @click="handleCancel"
+  :aria-label="t('chat.input.stop')"
 >

Also applies to: 83-101, 187-201, 224-247


326-340: Remove or implement unused props (rows, maxRows).

These props are declared but not used; either wire them to editor height or drop them to avoid confusion.

Example wiring:

-      class:
-        'outline-none ... min-h-12 max-h-28 overflow-y-auto'
+      class: `outline-none focus:outline-none focus-within:outline-none overflow-y-auto min-h-[${props.rows ?? 1}rem] max-h-[calc(${props.maxRows ?? 10}*1.75rem)]`

Also applies to: 371-374

src/renderer/src/components/ChatConfig/types.ts (2)

5-12: Clarify i18n contract for user-visible strings (labels, descriptions, titles).

Renderer must use i18n keys, not hardcoded strings. Please either:

  • Make these fields explicitly hold i18n keys (e.g., labelKey, descriptionKey, titleKey, option.labelKey), and let UI components call t(); or
  • Document that label/description/title/option.label must already be localized via t() before being passed.

This avoids accidental hardcoded UI text.

As per coding guidelines

Also applies to: 37-40, 71-75


5-75: Prefer type aliases over interfaces for consistency.

Codebase guideline prefers types over interfaces. Consider converting these interfaces to type aliases for consistency and better union/utility composition.

As per coding guidelines

src/renderer/src/components/message/MessageList.vue (3)

189-201: Pass the ref itself to useElementBounding for safety/reactivity.

Passing messageList.value can be undefined at capture time and loses ref reactivity.

-  const { height } = useElementBounding(messageList.value)
+  const { height } = useElementBounding(messageList)

79-91: Lazy-load heavy UI components to improve startup.

Define async components for MessageActionButtons and MessageMinimap; keep ReferencePreview sync.

-// === Vue Core ===
-import { ref, onMounted, nextTick, watch, computed, toRef } from 'vue'
+// === Vue Core ===
+import { ref, onMounted, nextTick, watch, computed, toRef, defineAsyncComponent } from 'vue'
@@
-// === Components ===
+// === Components ===
 import MessageItemAssistant from './MessageItemAssistant.vue'
 import MessageItemUser from './MessageItemUser.vue'
-import MessageActionButtons from './MessageActionButtons.vue'
+const MessageActionButtons = defineAsyncComponent(() => import('./MessageActionButtons.vue'))
 import ReferencePreview from './ReferencePreview.vue'
-import MessageMinimap from './MessageMinimap.vue'
+const MessageMinimap = defineAsyncComponent(() => import('./MessageMinimap.vue'))

Implement proper lazy loading in renderer components. As per coding guidelines


179-187: Avoid magic setTimeout for initial scroll; prefer frame/flush-based timing.

Replace fixed 100ms delay with nextTick + requestAnimationFrame or wait for container size readiness to reduce flakiness.

-onMounted(() => {
-  // Initialize scroll and visibility
-  setTimeout(() => {
-    scrollToBottom()
-    nextTick(() => {
-      visible.value = true
-      setupScrollObserver()
-      updateScrollInfo()
-    })
-  }, 100)
+onMounted(async () => {
+  await nextTick()
+  requestAnimationFrame(() => {
+    scrollToBottom()
+    visible.value = true
+    setupScrollObserver()
+    updateScrollInfo()
+  })
src/renderer/src/components/ChatConfig.vue (1)

162-170: Hoist icon map to a constant to avoid re-allocating per compute.

Micro-optimization and clearer intent.

-const modelTypeIcon = computed(() => {
-  const icons = {
-    chat: 'lucide:message-circle',
-    imageGeneration: 'lucide:image',
-    embedding: 'lucide:layers',
-    rerank: 'lucide:arrow-up-down'
-  }
-  return icons[props.modelType || 'chat']
-})
+const MODEL_TYPE_ICON: Record<'chat' | 'imageGeneration' | 'embedding' | 'rerank', string> = {
+  chat: 'lucide:message-circle',
+  imageGeneration: 'lucide:image',
+  embedding: 'lucide:layers',
+  rerank: 'lucide:arrow-up-down'
+}
+const modelTypeIcon = computed(() => MODEL_TYPE_ICON[props.modelType || 'chat'])

feat: enhance artifact dialog with improved error handling and localization
fix: update Mermaid artifact rendering error handling and localization
fix: improve input settings error handling and state management
fix: update drag and drop composable to handle drag events correctly
fix: update Vitest configuration for better project structure and alias resolution
@zerob13
Copy link
Collaborator

zerob13 commented Oct 19, 2025

@codex review

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: 5

Caution

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

⚠️ Outside diff range comments (1)
src/renderer/src/components/artifacts/ArtifactDialog.vue (1)

12-13: Fix Tailwind important syntax.

The '!' must prefix the utility (e.g., max-lg:!w-3/4). Suffix '!' won’t be parsed by Tailwind.

-      class="absolute right-0 top-0 bottom-0 w-[calc(60%-104px)] border-l shadow-lg flex flex-col max-lg:w-3/4! max-lg:bg-white max-lg:dark:bg-black! z-60"
+      class="absolute right-0 top-0 bottom-0 w-[calc(60%-104px)] border-l shadow-lg flex flex-col max-lg:!w-3/4 max-lg:bg-white max-lg:dark:!bg-black z-60"
♻️ Duplicate comments (11)
src/renderer/src/i18n/zh-TW/artifacts.json (1)

43-43: Translate "responsive" to Traditional Chinese.

The value remains in English ("Responsive"), but all other entries are in Traditional Chinese. Use "響應式" or "回應式" to maintain localization consistency.

Apply this diff:

-  "responsive": "Responsive",
+  "responsive": "響應式",
src/renderer/src/i18n/ru-RU/artifacts.json (1)

43-43: Translate "responsive" to Russian.

The value is still in English. Use "Адаптивный" (adaptive/responsive design context) to maintain localization consistency.

Apply this diff:

-  "responsive": "Responsive",
+  "responsive": "Адаптивный",
src/renderer/src/i18n/fa-IR/artifacts.json (1)

43-43: Translate "responsive" to Persian.

The value is still in English. Use "پاسخ‌گو" (the appropriate Persian term for responsive design) to maintain localization consistency.

Apply this diff:

-  "responsive": "Responsive",
+  "responsive": "پاسخ‌گو",
src/renderer/src/i18n/zh-HK/artifacts.json (1)

43-43: Translate "responsive" to Traditional Chinese.

The value remains in English ("Responsive"), but all other entries are in Traditional Chinese. Use "響應式" or "回應式" to maintain localization consistency.

Apply this diff:

-  "responsive": "Responsive",
+  "responsive": "響應式",
vitest.config.ts (1)

10-31: Renderer project config looks solid; confirm paths and trim duplication

  • Good move to scope '@/' to renderer root; this addresses the earlier '@/lib/*' misrouting. Please align tsconfig paths accordingly and verify targets exist. Based on learnings.
  • Minor: drop the per-project globals; keep the top-level one.

Apply diffs:

-          include: ['test/renderer/**/*.{test,spec}.{js,ts}'],
+          include: ['test/renderer/**/*.{test,spec}.{js,ts,tsx}'],

-          globals: true
+          // uses top-level test.globals

Run to verify aliases and tsconfig:

#!/bin/bash
set -euo pipefail

echo "== Check tsconfig paths for @/* and @shared =="
fd -H '^tsconfig(\..*)?\.json$' | xargs -I {} sh -c 'echo "--- {} ---"; cat "{}" | jq ".compilerOptions.paths // {}"'

echo "== Ensure aliased directories exist =="
for d in src/renderer/src src/renderer/shell src/shared src/shadcn src/main; do
  [ -d "$d" ] && echo "OK: $d" || echo "MISSING: $d"
done

echo "== Locate potential misroutes =="
rg -nP -C1 "(from|import)\\s+['\"]@/lib/[^'\\\"]+" src test || true

echo "== Show electron.vite config aliases (if present) =="
fd -a electron.vite.config | xargs -I {} rg -n "alias|resolve" -C2 {} || true
src/renderer/src/components/chat-input/ChatInput.vue (2)

298-301: PascalCase import fixed for McpToolsList.

Import now follows convention; no further action.


581-603: Great cleanup: listeners removed and editor destroyed on unmount.

Memory-leak risk addressed by unregistering handlers and destroying the editor.

Also applies to: 605-620

src/renderer/src/composables/useArtifactExport.ts (1)

36-38: Mermaid extension mapping fixed.

The '.mmd' extension for 'application/vnd.ant.mermaid' is correct and addresses the prior bot suggestion.

src/renderer/src/composables/useArtifactCodeEditor.ts (2)

169-192: Editor cleanup paths look good.

Cleanup on preview toggle, element unmount, dialog close, and onBeforeUnmount addresses prior leak concerns.


50-53: Map SVG to Monaco’s 'xml' language id (not 'svg').

Monaco doesn’t ship 'svg'; use 'xml' for SVG syntax highlighting.

-    case 'image/svg+xml':
-      return 'svg'
+    case 'image/svg+xml':
+      return 'xml'
src/renderer/src/components/artifacts/ArtifactDialog.vue (1)

121-138: Great fixes: Pinia refs and lifecycle wiring.

Switching from toRef(() => ...) to storeToRefs and integrating useArtifactCodeEditor addresses prior reactivity and cleanup issues.

Also applies to: 157-176

🧹 Nitpick comments (22)
src/renderer/src/components/artifacts/MermaidArtifact.vue (1)

2-13: Consider semantic HTML for the code view.

The non-preview mode uses nested <pre><code> elements. This is correct semantically, but the inner <code> element has h-full block classes that may conflict with the <pre>'s natural display behavior.

Consider simplifying the markup or ensuring the height management works as intended across different content sizes.

vitest.config.ts (1)

33-50: Drop Vue plugin from main tests (optional) and broaden include

Main (node) tests typically don’t transform .vue files; removing the plugin speeds runs. Also include .mts for Node ESM tests.

Apply diffs:

-        plugins: [vue()],
         test: {
           name: 'main',
           environment: 'node',
-          include: ['test/main/**/*.{test,spec}.{js,ts}'],
+          include: ['test/main/**/*.{test,spec}.{js,ts,mts}'],
           setupFiles: ['./test/setup.ts'],
-          globals: true
+          // uses top-level test.globals
src/renderer/src/components/chat-input/composables/useInputSettings.ts (4)

15-18: Add explicit type for settings ref (strict TS).

Give settings a precise type to avoid structural drift and implicit anys.

-const settings = ref({
+type InputSettings = { deepThinking: boolean; webSearch: boolean }
+const settings = ref<InputSettings>({
   deepThinking: false,
   webSearch: false
 })

21-33: Return success flag and use structured logging in toggles.

Current pattern swallows the error at call site. Return boolean (or rethrow) so UI can notify users; log with level and context per guidelines.

-const toggleWebSearch = async () => {
+const toggleWebSearch = async (): Promise<boolean> => {
   const previousValue = settings.value.webSearch
   settings.value.webSearch = !settings.value.webSearch
   try {
     await configPresenter.setSetting('input_webSearch', settings.value.webSearch)
+    return true
   } catch (error) {
     settings.value.webSearch = previousValue
-    console.error('Failed to save web search setting:', error)
+    console.error('[ERROR]', {
+      ts: new Date().toISOString(),
+      code: 'INPUT_WEB_SEARCH_SAVE_FAILED',
+      message: 'Failed to save web search setting',
+      err: error
+    })
+    return false
   }
 }

As per coding guidelines


35-47: Mirror the same pattern for deepThinking toggle.

Return boolean and log with context.

-const toggleDeepThinking = async () => {
+const toggleDeepThinking = async (): Promise<boolean> => {
   const previousValue = settings.value.deepThinking
   settings.value.deepThinking = !settings.value.deepThinking
   try {
     await configPresenter.setSetting('input_deepThinking', settings.value.deepThinking)
+    return true
   } catch (error) {
     settings.value.deepThinking = previousValue
-    console.error('Failed to save deep thinking setting:', error)
+    console.error('[ERROR]', {
+      ts: new Date().toISOString(),
+      code: 'INPUT_DEEP_THINKING_SAVE_FAILED',
+      message: 'Failed to save deep thinking setting',
+      err: error
+    })
+    return false
   }
 }

As per coding guidelines


49-68: LGTM overall; error handling added. Consider surfacing user-facing notifications.

loadSettings/onMounted now guard failures. Next step: show i18n-based toasts on failure (no sensitive details).

As per coding guidelines

src/renderer/src/components/chat-input/composables/useDragAndDrop.ts (2)

45-60: Clamp counter to avoid negative values during rapid leave events.

Very fast enter/leave can drive dragCounter below zero briefly. Clamp to 0 to simplify reasoning.

-const handleDragLeave = () => {
-  dragCounter.value--
+const handleDragLeave = () => {
+  dragCounter.value = Math.max(0, dragCounter.value - 1)

85-95: Expose read-only state to consumers.

Prevent accidental external mutation by returning a readonly/computed view.

-return {
-  // State (readonly via computed would be ideal, but refs work for simple cases)
-  isDragging,
+import { readonly } from 'vue'
+return {
+  // State
+  isDragging: readonly(isDragging),
src/renderer/src/components/chat-input/ChatInput.vue (7)

483-489: Guard handleDrop with try/catch and user-friendly feedback.

External file ops can fail; keep UX responsive and log with context.

-const handleDrop = async (e: DragEvent) => {
-  drag.resetDragState()
-  if (e.dataTransfer?.files && e.dataTransfer.files.length > 0) {
-    await files.handleDrop(e.dataTransfer.files)
-  }
-}
+const handleDrop = async (e: DragEvent) => {
+  drag.resetDragState()
+  try {
+    if (e.dataTransfer?.files?.length) {
+      await files.handleDrop(e.dataTransfer.files)
+    }
+  } catch (error) {
+    console.error('[ERROR]', {
+      ts: new Date().toISOString(),
+      code: 'DROP_HANDLE_FAILED',
+      message: 'Failed to handle dropped files',
+      err: error
+    })
+    // TODO: show toast t('errors.fileDropFailed')
+  }
+}

As per coding guidelines


500-525: Wrap send flow in try/catch; don’t lose errors.

tiptapJSONtoMessageBlock/emit can fail; ensure graceful degradation and logging.

-const emitSend = async () => {
-  if (editorComposable.inputText.value.trim()) {
-    history.addToHistory(editorComposable.inputText.value.trim())
-    const blocks = await editorComposable.tiptapJSONtoMessageBlock(editor.getJSON())
-    const messageContent: UserMessageContent = {
+const emitSend = async () => {
+  const trimmed = editorComposable.inputText.value.trim()
+  if (trimmed) {
+    try {
+      history.addToHistory(trimmed)
+      const blocks = await editorComposable.tiptapJSONtoMessageBlock(editor.getJSON())
+      const messageContent: UserMessageContent = {
         text: editorComposable.inputText.value.trim(),
         files: files.selectedFiles.value,
         links: [],
         search: settings.value.webSearch,
         think: settings.value.deepThinking,
         content: blocks
-    }
-    emit('send', messageContent)
+      }
+      emit('send', messageContent)
+    } catch (error) {
+      console.error('[ERROR]', {
+        ts: new Date().toISOString(),
+        code: 'SEND_MESSAGE_FAILED',
+        message: 'Failed to build or emit message content',
+        err: error
+      })
+      // TODO: toast t('errors.sendFailed')
+      return
+    }
     editorComposable.inputText.value = ''
     editor.chain().clearContent().run()
     history.clearHistoryPlaceholder()
     files.clearFiles()
     nextTick(() => {
       editor.commands.focus()
     })
   }
 }

As per coding guidelines


527-529: Catch toggle failures and notify users.

Propagate or handle the boolean returned by toggleWebSearch (or catch errors) and show i18n toast.

-const onWebSearchClick = async () => {
-  await toggleWebSearch()
-}
+const onWebSearchClick = async () => {
+  try {
+    const ok = await toggleWebSearch()
+    if (!ok) {
+      // TODO: toast t('chat.input.webSearchToggleFailed')
+    }
+  } catch (error) {
+    console.error('[ERROR]', {
+      ts: new Date().toISOString(),
+      code: 'WEB_SEARCH_TOGGLE_FAILED',
+      message: 'Failed to toggle web search',
+      err: error
+    })
+  }
+}

As per coding guidelines


565-569: Type the custom event instead of any.

Use CustomEvent<string> (or the precise payload type) to keep strict typing.

-const handleContextMenuAskAI = (e: any) => {
-  editorComposable.inputText.value = e.detail
+const handleContextMenuAskAI = (e: Event) => {
+  const detail = (e as CustomEvent<string>).detail
+  editorComposable.inputText.value = detail
-  editor.commands.setContent(e.detail)
+  editor.commands.setContent(detail)
   editor.commands.focus()
 }

As per coding guidelines


277-301: Lazy‑load heavy popovers to reduce initial bundle.

ChatConfig and ModelChooser are only used when the popovers open. Load them on demand.

-import ChatConfig from '../ChatConfig.vue'
-import ModelChooser from '../ModelChooser.vue'
+import { defineAsyncComponent } from 'vue'
+const ChatConfig = defineAsyncComponent(() => import('../ChatConfig.vue'))
+const ModelChooser = defineAsyncComponent(() => import('../ModelChooser.vue'))

As per coding guidelines

Also applies to: 187-221


654-666: Scope the second style block (use :deep for TipTap placeholder).

Guideline: scoped styles for components. Convert global block to scoped with :deep(...).

-<style scoped>
+<style scoped>
 @reference '../../assets/style.css';
 ...
 </style>
-
-<style>
-@reference '../../assets/style.css';
-
-.tiptap p.is-editor-empty:first-child::before {
-  color: var(--muted-foreground);
-  content: attr(data-placeholder);
-  float: left;
-  height: 0;
-  pointer-events: none;
-}
-
-.dark .tiptap p.is-editor-empty:first-child::before,
-[data-theme='dark'] .tiptap p.is-editor-empty:first-child::before {
-  color: #ffffff80;
-}
-</style>
+<style scoped>
+@reference '../../assets/style.css';
+:deep(.tiptap p.is-editor-empty:first-child::before) {
+  color: var(--muted-foreground);
+  content: attr(data-placeholder);
+  float: left;
+  height: 0;
+  pointer-events: none;
+}
+:deep(.dark .tiptap p.is-editor-empty:first-child::before),
+:deep([data-theme='dark'] .tiptap p.is-editor-empty:first-child::before) {
+  color: #ffffff80;
+}
+</style>

As per coding guidelines

Also applies to: 667-682


364-416: Reduce any usage for strict typing.

  • useInputHistory(null as any, t) breaks strictness. Consider overloading useInputHistory to accept Editor | null and calling setEditor later (current logic already supports that).
  • The config stub uses as any. Define a minimal typed shape for the used fields to keep type-safety.
// Example
type PromptInputConfigLike = {
  activeModel: Ref<{ providerId: string; tags: string[] }>
  modelDisplayName: Ref<string>
  // ...only the fields used in this component
  handleModelUpdate: (model: unknown) => void
  loadModelConfig: () => Promise<void>
}

As per coding guidelines

Also applies to: 450-474

src/renderer/src/composables/useArtifactExport.ts (2)

90-91: Sanitize filenames before download.

Artifact titles may contain characters invalid on some filesystems. Sanitize to a safe filename.

-      downloadBlob(blob, `${artifact.title || 'artifact'}.svg`)
+      downloadBlob(blob, `${safeFilename(artifact.title || 'artifact')}.svg`)

Add a helper near downloadBlob:

 const downloadBlob = (blob: Blob, filename: string) => {
   const url = URL.createObjectURL(blob)
   const link = document.createElement('a')
   link.href = url
   link.download = filename
   document.body.appendChild(link)
   link.click()
   document.body.removeChild(link)
   URL.revokeObjectURL(url)
 }
+
+// Keep cross-platform filenames safe
+const safeFilename = (name: string): string =>
+  name.replace(/[\\/:*?"<>|]+/g, '_').slice(0, 100)

103-106: Set MIME type based on artifact to improve OS handling.

Use a closer MIME for markdown/html instead of always 'text/plain'.

-    const blob = new Blob([artifact.content], { type: 'text/plain' })
+    const type =
+      artifact.type === 'text/html'
+        ? 'text/html'
+        : artifact.type === 'text/markdown'
+          ? 'text/markdown'
+          : 'text/plain'
+    const blob = new Blob([artifact.content], { type })
src/renderer/src/components/artifacts/ArtifactDialog.vue (5)

255-261: Surface a toast on Export SVG failure.

Aligns with error-handling guidelines and copy handlers; provide user-friendly feedback.

 const handleExportSVG = async () => {
   try {
     await artifactExport.exportSVG(artifactStore.currentArtifact)
   } catch (error) {
-    console.error('Export SVG failed:', error)
+    console.error('Export SVG failed:', error)
+    toast({
+      title: t('artifacts.exportFailed'),
+      description: t('artifacts.exportSVGFailedDesc'),
+      variant: 'destructive'
+    })
   }
 }

323-331: Differentiate export actions (titles/icons) to avoid ambiguity.

Both buttons currently use the same title/icon. Use specific i18n keys for clarity.

     actions.push({
       key: 'exportSVG',
-      icon: 'lucide:download',
-      title: t('artifacts.export'),
+      icon: 'lucide:file-down',
+      title: t('artifacts.exportSVG'),
       onClick: handleExportSVG,
       visible: true
     })
...
   actions.push({
     key: 'exportCode',
-    icon: 'lucide:download',
-    title: t('artifacts.export'),
+    icon: 'lucide:code',
+    title: t('artifacts.exportCode'),
     onClick: handleExportCode,
     visible: true
   })

Also applies to: 352-358


25-54: Show viewport controls for React previews too (optional).

React artifacts render in an iframe; offering the same device dropdown improves parity.

-const shouldShowViewportControls = computed(() => {
-  return isPreview.value && artifactStore.currentArtifact?.type === 'text/html'
-})
+const shouldShowViewportControls = computed(() => {
+  const t = artifactStore.currentArtifact?.type
+  return isPreview.value && (t === 'text/html' || t === 'application/vnd.ant.react')
+})

Also applies to: 203-206


74-85: Add aria-labels to icon-only buttons for accessibility.

Tooltips are not enough for screen readers; mirror title into aria-label.

 <Button
   v-for="action in visibleActions"
   :key="action.key"
   variant="outline"
   size="sm"
   :title="action.title"
+  :aria-label="action.title"
   class="text-xs h-7"
   @click="action.onClick"
>

14-17: Use English for comments per guidelines.

Convert Chinese comments to English to comply with repo standards.

For example:

-// 顶部导航栏
+// Top action bar
-<!-- 设备选择下拉菜单 (仅在HTML预览时显示) -->
+<!-- Device dropdown (HTML/React preview only) -->
-// === 配置对象 ===
+// === Configuration objects ===

As per coding guidelines.

Also applies to: 26-54, 207-222, 223-252, 307-316, 365-369

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 15f0ec9 and 0d59f6f.

📒 Files selected for processing (22)
  • src/renderer/src/components/artifacts/ArtifactDialog.vue (6 hunks)
  • src/renderer/src/components/artifacts/MermaidArtifact.vue (2 hunks)
  • src/renderer/src/components/chat-input/ChatInput.vue (1 hunks)
  • src/renderer/src/components/chat-input/composables/useDragAndDrop.ts (1 hunks)
  • src/renderer/src/components/chat-input/composables/useInputSettings.ts (1 hunks)
  • src/renderer/src/composables/useArtifactCodeEditor.ts (1 hunks)
  • src/renderer/src/composables/useArtifactExport.ts (1 hunks)
  • src/renderer/src/i18n/en-US/artifacts.json (1 hunks)
  • src/renderer/src/i18n/en-US/common.json (1 hunks)
  • src/renderer/src/i18n/fa-IR/artifacts.json (1 hunks)
  • src/renderer/src/i18n/fr-FR/artifacts.json (1 hunks)
  • src/renderer/src/i18n/ja-JP/artifacts.json (1 hunks)
  • src/renderer/src/i18n/ko-KR/artifacts.json (1 hunks)
  • src/renderer/src/i18n/pt-BR/artifacts.json (1 hunks)
  • src/renderer/src/i18n/ru-RU/artifacts.json (1 hunks)
  • src/renderer/src/i18n/zh-CN/artifacts.json (1 hunks)
  • src/renderer/src/i18n/zh-CN/common.json (1 hunks)
  • src/renderer/src/i18n/zh-HK/artifacts.json (1 hunks)
  • src/renderer/src/i18n/zh-TW/artifacts.json (1 hunks)
  • test/renderer/composables/useDragAndDrop.test.ts (1 hunks)
  • test/renderer/composables/useMessageCapture.test.ts (1 hunks)
  • vitest.config.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
  • src/renderer/src/i18n/ja-JP/artifacts.json
  • src/renderer/src/i18n/ko-KR/artifacts.json
  • src/renderer/src/i18n/pt-BR/artifacts.json
  • test/renderer/composables/useMessageCapture.test.ts
  • test/renderer/composables/useDragAndDrop.test.ts
  • src/renderer/src/i18n/fr-FR/artifacts.json
  • src/renderer/src/i18n/en-US/artifacts.json
🧰 Additional context used
📓 Path-based instructions (20)
src/renderer/src/**/*

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

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

Files:

  • src/renderer/src/i18n/zh-CN/common.json
  • src/renderer/src/components/chat-input/composables/useInputSettings.ts
  • src/renderer/src/components/chat-input/composables/useDragAndDrop.ts
  • src/renderer/src/components/artifacts/MermaidArtifact.vue
  • src/renderer/src/composables/useArtifactExport.ts
  • src/renderer/src/i18n/fa-IR/artifacts.json
  • src/renderer/src/composables/useArtifactCodeEditor.ts
  • src/renderer/src/i18n/zh-HK/artifacts.json
  • src/renderer/src/i18n/en-US/common.json
  • src/renderer/src/i18n/zh-CN/artifacts.json
  • src/renderer/src/components/artifacts/ArtifactDialog.vue
  • src/renderer/src/components/chat-input/ChatInput.vue
  • src/renderer/src/i18n/zh-TW/artifacts.json
  • src/renderer/src/i18n/ru-RU/artifacts.json
src/renderer/src/**

📄 CodeRabbit inference engine (AGENTS.md)

Place Vue 3 app source under src/renderer/src (components, stores, views, i18n, lib)

Files:

  • src/renderer/src/i18n/zh-CN/common.json
  • src/renderer/src/components/chat-input/composables/useInputSettings.ts
  • src/renderer/src/components/chat-input/composables/useDragAndDrop.ts
  • src/renderer/src/components/artifacts/MermaidArtifact.vue
  • src/renderer/src/composables/useArtifactExport.ts
  • src/renderer/src/i18n/fa-IR/artifacts.json
  • src/renderer/src/composables/useArtifactCodeEditor.ts
  • src/renderer/src/i18n/zh-HK/artifacts.json
  • src/renderer/src/i18n/en-US/common.json
  • src/renderer/src/i18n/zh-CN/artifacts.json
  • src/renderer/src/components/artifacts/ArtifactDialog.vue
  • src/renderer/src/components/chat-input/ChatInput.vue
  • src/renderer/src/i18n/zh-TW/artifacts.json
  • src/renderer/src/i18n/ru-RU/artifacts.json
src/renderer/src/i18n/**/*.{ts,json,yml,yaml}

📄 CodeRabbit inference engine (AGENTS.md)

Store i18n resources under src/renderer/src/i18n

Files:

  • src/renderer/src/i18n/zh-CN/common.json
  • src/renderer/src/i18n/fa-IR/artifacts.json
  • src/renderer/src/i18n/zh-HK/artifacts.json
  • src/renderer/src/i18n/en-US/common.json
  • src/renderer/src/i18n/zh-CN/artifacts.json
  • src/renderer/src/i18n/zh-TW/artifacts.json
  • src/renderer/src/i18n/ru-RU/artifacts.json
**/*.{ts,tsx,js,jsx,vue,css,scss,md,json,yml,yaml}

📄 CodeRabbit inference engine (AGENTS.md)

Prettier style: single quotes, no semicolons, print width 100; run pnpm run format

Files:

  • src/renderer/src/i18n/zh-CN/common.json
  • src/renderer/src/components/chat-input/composables/useInputSettings.ts
  • src/renderer/src/components/chat-input/composables/useDragAndDrop.ts
  • src/renderer/src/components/artifacts/MermaidArtifact.vue
  • src/renderer/src/composables/useArtifactExport.ts
  • src/renderer/src/i18n/fa-IR/artifacts.json
  • src/renderer/src/composables/useArtifactCodeEditor.ts
  • src/renderer/src/i18n/zh-HK/artifacts.json
  • src/renderer/src/i18n/en-US/common.json
  • src/renderer/src/i18n/zh-CN/artifacts.json
  • src/renderer/src/components/artifacts/ArtifactDialog.vue
  • vitest.config.ts
  • src/renderer/src/components/chat-input/ChatInput.vue
  • src/renderer/src/i18n/zh-TW/artifacts.json
  • src/renderer/src/i18n/ru-RU/artifacts.json
**/*.{js,jsx,ts,tsx}

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

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

Files:

  • src/renderer/src/components/chat-input/composables/useInputSettings.ts
  • src/renderer/src/components/chat-input/composables/useDragAndDrop.ts
  • src/renderer/src/composables/useArtifactExport.ts
  • src/renderer/src/composables/useArtifactCodeEditor.ts
  • vitest.config.ts
src/{main,renderer}/**/*.ts

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

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

Files:

  • src/renderer/src/components/chat-input/composables/useInputSettings.ts
  • src/renderer/src/components/chat-input/composables/useDragAndDrop.ts
  • src/renderer/src/composables/useArtifactExport.ts
  • src/renderer/src/composables/useArtifactCodeEditor.ts
**/*.{ts,tsx}

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

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

Files:

  • src/renderer/src/components/chat-input/composables/useInputSettings.ts
  • src/renderer/src/components/chat-input/composables/useDragAndDrop.ts
  • src/renderer/src/composables/useArtifactExport.ts
  • src/renderer/src/composables/useArtifactCodeEditor.ts
  • vitest.config.ts
src/renderer/**/*.{vue,ts,js,tsx,jsx}

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

渲染进程代码放在 src/renderer

Files:

  • src/renderer/src/components/chat-input/composables/useInputSettings.ts
  • src/renderer/src/components/chat-input/composables/useDragAndDrop.ts
  • src/renderer/src/components/artifacts/MermaidArtifact.vue
  • src/renderer/src/composables/useArtifactExport.ts
  • src/renderer/src/composables/useArtifactCodeEditor.ts
  • src/renderer/src/components/artifacts/ArtifactDialog.vue
  • src/renderer/src/components/chat-input/ChatInput.vue
src/renderer/src/**/*.{vue,ts,tsx,js,jsx}

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

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

Files:

  • src/renderer/src/components/chat-input/composables/useInputSettings.ts
  • src/renderer/src/components/chat-input/composables/useDragAndDrop.ts
  • src/renderer/src/components/artifacts/MermaidArtifact.vue
  • src/renderer/src/composables/useArtifactExport.ts
  • src/renderer/src/composables/useArtifactCodeEditor.ts
  • src/renderer/src/components/artifacts/ArtifactDialog.vue
  • src/renderer/src/components/chat-input/ChatInput.vue
src/renderer/**/*.{ts,tsx,vue}

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

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

Files:

  • src/renderer/src/components/chat-input/composables/useInputSettings.ts
  • src/renderer/src/components/chat-input/composables/useDragAndDrop.ts
  • src/renderer/src/components/artifacts/MermaidArtifact.vue
  • src/renderer/src/composables/useArtifactExport.ts
  • src/renderer/src/composables/useArtifactCodeEditor.ts
  • src/renderer/src/components/artifacts/ArtifactDialog.vue
  • src/renderer/src/components/chat-input/ChatInput.vue
src/renderer/**/*.{vue,ts}

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

Implement lazy loading for routes and components.

Files:

  • src/renderer/src/components/chat-input/composables/useInputSettings.ts
  • src/renderer/src/components/chat-input/composables/useDragAndDrop.ts
  • src/renderer/src/components/artifacts/MermaidArtifact.vue
  • src/renderer/src/composables/useArtifactExport.ts
  • src/renderer/src/composables/useArtifactCodeEditor.ts
  • src/renderer/src/components/artifacts/ArtifactDialog.vue
  • src/renderer/src/components/chat-input/ChatInput.vue
src/renderer/**/*.{ts,vue}

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

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

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

Files:

  • src/renderer/src/components/chat-input/composables/useInputSettings.ts
  • src/renderer/src/components/chat-input/composables/useDragAndDrop.ts
  • src/renderer/src/components/artifacts/MermaidArtifact.vue
  • src/renderer/src/composables/useArtifactExport.ts
  • src/renderer/src/composables/useArtifactCodeEditor.ts
  • src/renderer/src/components/artifacts/ArtifactDialog.vue
  • src/renderer/src/components/chat-input/ChatInput.vue
**/*.{ts,tsx,js,vue}

📄 CodeRabbit inference engine (CLAUDE.md)

Use English for all logs and comments

Files:

  • src/renderer/src/components/chat-input/composables/useInputSettings.ts
  • src/renderer/src/components/chat-input/composables/useDragAndDrop.ts
  • src/renderer/src/components/artifacts/MermaidArtifact.vue
  • src/renderer/src/composables/useArtifactExport.ts
  • src/renderer/src/composables/useArtifactCodeEditor.ts
  • src/renderer/src/components/artifacts/ArtifactDialog.vue
  • vitest.config.ts
  • src/renderer/src/components/chat-input/ChatInput.vue
**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Use PascalCase for TypeScript types and classes

Files:

  • src/renderer/src/components/chat-input/composables/useInputSettings.ts
  • src/renderer/src/components/chat-input/composables/useDragAndDrop.ts
  • src/renderer/src/components/artifacts/MermaidArtifact.vue
  • src/renderer/src/composables/useArtifactExport.ts
  • src/renderer/src/composables/useArtifactCodeEditor.ts
  • src/renderer/src/components/artifacts/ArtifactDialog.vue
  • vitest.config.ts
  • src/renderer/src/components/chat-input/ChatInput.vue
src/renderer/src/components/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Organize UI components by feature within src/renderer/src/

Files:

  • src/renderer/src/components/chat-input/composables/useInputSettings.ts
  • src/renderer/src/components/chat-input/composables/useDragAndDrop.ts
  • src/renderer/src/components/artifacts/MermaidArtifact.vue
  • src/renderer/src/components/artifacts/ArtifactDialog.vue
  • src/renderer/src/components/chat-input/ChatInput.vue
src/renderer/src/**/*.{vue,ts}

📄 CodeRabbit inference engine (AGENTS.md)

All user-facing strings must use vue-i18n ($t/keys) rather than hardcoded literals

Files:

  • src/renderer/src/components/chat-input/composables/useInputSettings.ts
  • src/renderer/src/components/chat-input/composables/useDragAndDrop.ts
  • src/renderer/src/components/artifacts/MermaidArtifact.vue
  • src/renderer/src/composables/useArtifactExport.ts
  • src/renderer/src/composables/useArtifactCodeEditor.ts
  • src/renderer/src/components/artifacts/ArtifactDialog.vue
  • src/renderer/src/components/chat-input/ChatInput.vue
**/*.{ts,tsx,js,jsx,vue}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx,vue}: Use OxLint for JS/TS code; keep lint clean
Use camelCase for variables and functions
Use SCREAMING_SNAKE_CASE for constants

Files:

  • src/renderer/src/components/chat-input/composables/useInputSettings.ts
  • src/renderer/src/components/chat-input/composables/useDragAndDrop.ts
  • src/renderer/src/components/artifacts/MermaidArtifact.vue
  • src/renderer/src/composables/useArtifactExport.ts
  • src/renderer/src/composables/useArtifactCodeEditor.ts
  • src/renderer/src/components/artifacts/ArtifactDialog.vue
  • vitest.config.ts
  • src/renderer/src/components/chat-input/ChatInput.vue
src/renderer/src/**/*.vue

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

Use scoped styles to prevent CSS conflicts between components

Files:

  • src/renderer/src/components/artifacts/MermaidArtifact.vue
  • src/renderer/src/components/artifacts/ArtifactDialog.vue
  • src/renderer/src/components/chat-input/ChatInput.vue
src/renderer/{src,shell,floating}/**/*.vue

📄 CodeRabbit inference engine (CLAUDE.md)

src/renderer/{src,shell,floating}/**/*.vue: Use Vue 3 Composition API for all components
All user-facing strings must use i18n keys via vue-i18n (no hard-coded UI strings)
Use Tailwind CSS utilities and ensure styles are scoped in Vue components

Files:

  • src/renderer/src/components/artifacts/MermaidArtifact.vue
  • src/renderer/src/components/artifacts/ArtifactDialog.vue
  • src/renderer/src/components/chat-input/ChatInput.vue
src/renderer/**/*.vue

📄 CodeRabbit inference engine (AGENTS.md)

Name Vue component files in PascalCase (e.g., ChatInput.vue)

Files:

  • src/renderer/src/components/artifacts/MermaidArtifact.vue
  • src/renderer/src/components/artifacts/ArtifactDialog.vue
  • src/renderer/src/components/chat-input/ChatInput.vue
🧠 Learnings (6)
📚 Learning: 2025-09-06T03:07:23.817Z
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-06T03:07:23.817Z
Learning: Applies to src/renderer/{src,shell,floating}/**/*.vue : All user-facing strings must use i18n keys via vue-i18n (no hard-coded UI strings)

Applied to files:

  • src/renderer/src/components/artifacts/MermaidArtifact.vue
📚 Learning: 2025-10-14T08:02:59.495Z
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: AGENTS.md:0-0
Timestamp: 2025-10-14T08:02:59.495Z
Learning: Applies to src/renderer/src/**/*.{vue,ts} : All user-facing strings must use vue-i18n ($t/keys) rather than hardcoded literals

Applied to files:

  • src/renderer/src/components/artifacts/MermaidArtifact.vue
📚 Learning: 2025-07-21T01:46:30.354Z
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-07-21T01:46:30.354Z
Learning: Applies to src/renderer/src/**/* : All user-facing strings must use i18n keys (avoid hardcoded user-visible text in code)

Applied to files:

  • src/renderer/src/components/artifacts/MermaidArtifact.vue
📚 Learning: 2025-10-14T08:02:59.495Z
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: AGENTS.md:0-0
Timestamp: 2025-10-14T08:02:59.495Z
Learning: Use Vitest (+ jsdom) and Vue Test Utils as the testing framework

Applied to files:

  • vitest.config.ts
📚 Learning: 2025-10-14T08:02:59.495Z
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: AGENTS.md:0-0
Timestamp: 2025-10-14T08:02:59.495Z
Learning: Applies to src/renderer/**/*.vue : Name Vue component files in PascalCase (e.g., ChatInput.vue)

Applied to files:

  • src/renderer/src/components/chat-input/ChatInput.vue
📚 Learning: 2025-07-23T00:45:57.322Z
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/vue-shadcn.mdc:0-0
Timestamp: 2025-07-23T00:45:57.322Z
Learning: Applies to src/renderer/components/**/*.vue : Use PascalCase for component names (e.g., AuthWizard.vue).

Applied to files:

  • src/renderer/src/components/chat-input/ChatInput.vue
🧬 Code graph analysis (3)
src/renderer/src/components/chat-input/composables/useInputSettings.ts (1)
src/renderer/src/composables/usePresenter.ts (1)
  • usePresenter (103-105)
src/renderer/src/composables/useArtifactExport.ts (1)
src/renderer/src/stores/artifact.ts (1)
  • ArtifactState (4-11)
src/renderer/src/composables/useArtifactCodeEditor.ts (1)
src/renderer/src/stores/artifact.ts (1)
  • ArtifactState (4-11)
🔇 Additional comments (14)
src/renderer/src/i18n/zh-CN/common.json (1)

78-79: Translations added and formatted correctly.

Both the "newTab" and "unknownError" keys are properly formatted with correct Chinese translations and appropriate comma placement. JSON syntax is valid.

Please confirm both new keys ("newTab" and "unknownError") are referenced in the updated component code as part of this refactor.

src/renderer/src/i18n/en-US/common.json (1)

78-79: Translations added and formatted correctly.

Both the "newTab" and "unknownError" keys are properly formatted with clear English translations and appropriate comma placement. JSON syntax is valid. Changes are consistent with the zh-CN locale.

src/renderer/src/i18n/zh-TW/artifacts.json (1)

49-51: LGTM: Mermaid error message properly localized.

The Traditional Chinese translation for the Mermaid render error is correct and follows the established pattern.

src/renderer/src/i18n/ru-RU/artifacts.json (1)

50-52: LGTM: Mermaid error message properly localized.

The Russian translation is correct and consistent with the pattern established in other locales.

src/renderer/src/i18n/zh-CN/artifacts.json (2)

43-43: LGTM: "responsive" properly translated.

Unlike other locales, zh-CN correctly provides the Simplified Chinese translation "响应式". This is the correct approach.


49-51: LGTM: Mermaid error message properly localized.

The Simplified Chinese translation is correct and follows the established pattern.

src/renderer/src/i18n/fa-IR/artifacts.json (1)

50-52: LGTM: Mermaid error message properly localized.

The Persian translation is correct and consistent with the pattern in other locales.

src/renderer/src/i18n/zh-HK/artifacts.json (1)

49-51: LGTM: Mermaid error message properly localized.

The Traditional Chinese translation for the Mermaid render error is correct and follows the established pattern.

src/renderer/src/components/artifacts/MermaidArtifact.vue (2)

17-21: LGTM: i18n properly integrated.

The component correctly imports and initializes vue-i18n for localized error messages, following the coding guidelines.


63-79: LGTM: Error handling now uses i18n and safe DOM manipulation.

The error handling properly addresses the past review concerns by:

  • Using t('artifacts.mermaid.renderError') and t('common.unknownError') for localized messages
  • Using textContent instead of innerHTML for error display (XSS-safe)
  • Clearing previous content before inserting the error message

This implementation follows security and i18n best practices.

vitest.config.ts (2)

3-3: Vue plugin import OK

Import is correct for SFC transforms. Nothing else needed here.


77-77: Timeouts OK; consider per-project overrides only if needed

Keeping hookTimeout at 10s globally is fine. If renderer hooks are slower, move this into that project only.

src/renderer/src/components/chat-input/composables/useDragAndDrop.ts (1)

30-40: Good fix: dragover now prevents default.

Prevents browsers from blocking drops; cleanup of pending timer is correct.

src/renderer/src/components/artifacts/ArtifactDialog.vue (1)

343-349: Reconsider hiding “Copy as image” for HTML/React.

copyAsImage supports iframe artifacts via isHTMLIframe and wrapper selectors; UI hides it. Verify intent and enable if acceptable.

-    visible: artifact.type !== 'text/html' && artifact.type !== 'application/vnd.ant.react'
+    visible: true

If disabled due to known issues, consider a tooltip explaining why.

@chatgpt-codex-connector
Copy link

Codex Review: Didn't find any major issues. 👍

ℹ️ About Codex in GitHub

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

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

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

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@zerob13 zerob13 merged commit ed3d64a into dev Oct 19, 2025
2 checks passed
zerob13 added a commit that referenced this pull request Oct 22, 2025
* style(settings): format about page link handler (#1016)

* style(ollama): format model config handlers (#1018)

* fix: think content scroll issue (#1023)

* fix: remove shimmer for think content

* chore: update screen shot and fix scroll issue

* chore: update markdown renderer

* fix: import button bug and prevent backup overwriting during import (#1024)

* fix(sync): fix import button bug and prevent backup overwriting during import

* fix(sync): fix import button bug and prevent backup overwriting during import

* fix(sync): fix import button bug and prevent backup overwriting during import

* refactor(messageList): refactor message list ui components (#1026)

* feat: remove new thread button, add clean button.

* refactor(messageList): refactor message list ui components

* feat: add configurable fields for chat settings

- Introduced ConfigFieldHeader component for consistent field headers.
- Added ConfigInputField, ConfigSelectField, ConfigSliderField, and ConfigSwitchField components for various input types.
- Created types for field configurations in types.ts to standardize field definitions.
- Implemented useChatConfigFields composable to manage field configurations dynamically.
- Added useModelCapabilities and useModelTypeDetection composables for handling model-specific capabilities and requirements.
- Developed useSearchConfig and useThinkingBudget composables for managing search and budget configurations.

* feat: implement input history management in prompt input

- Added `useInputHistory` composable for managing input history and navigation.
- Implemented methods for setting, clearing, and confirming history placeholders.
- Integrated arrow key navigation for browsing through input history.

feat: enhance mention data handling in prompt input

- Created `useMentionData` composable to aggregate mention data from selected files and MCP resources.
- Implemented watchers to update mention data based on selected files, MCP resources, tools, and prompts.

feat: manage prompt input configuration with store synchronization

- Developed `usePromptInputConfig` composable for managing model configuration.
- Implemented bidirectional sync between local config and chat store.
- Added debounced watcher to reduce updates and improve performance.

feat: streamline TipTap editor operations in prompt input

- Introduced `usePromptInputEditor` composable for managing TipTap editor lifecycle and content transformation.
- Implemented methods for handling mentions, pasting content, and clearing editor content.

feat: handle file operations in prompt input

- Created `usePromptInputFiles` composable for managing file selection, paste, and drag-drop operations.
- Implemented methods for processing files, handling dropped files, and clearing selected files.

feat: manage rate limit status in prompt input

- Developed `useRateLimitStatus` composable for displaying and polling rate limit status.
- Implemented methods for handling rate limit events and computing status icons, classes, and tooltips.

* refactor(artifacts): migrate component logic to composables and update documentation

- Refactor ArtifactDialog.vue to use composables for view mode, viewport size, code editor, and export functionality
- Simplify HTMLArtifact.vue by removing drag-resize logic and using fixed viewport dimensions
- Clean up MermaidArtifact.vue styling and structure
- Update component refactoring guide to reflect new patterns and best practices
- Adjust prompt input composable to allow delayed editor initialization
- Update internationalization files for new responsive label

* fix(lint): unused variables

* fix(format): format code

* CodeRabbit Generated Unit Tests: Add renderer unit tests for components and composables

* feat: implement input history management in chat input component

- Added `useInputHistory` composable for managing input history and placeholder navigation.
- Implemented methods for setting, clearing, and confirming history placeholders.
- Integrated arrow key navigation for cycling through input history.

feat: enhance mention data handling in chat input

- Created `useMentionData` composable to manage mention data aggregation.
- Implemented watchers for selected files and MCP resources/tools/prompts to update mention data.

feat: manage prompt input configuration and synchronization

- Developed `usePromptInputConfig` composable for managing model configuration.
- Implemented bidirectional sync between local config refs and chat store.
- Added debounced watcher to reduce updates to the store.

feat: manage prompt input editor operations

- Introduced `usePromptInputEditor` composable for handling TipTap editor operations.
- Implemented content transformation, mention insertion, and paste handling.
- Added methods for handling editor updates and restoring focus.

feat: handle prompt input files management

- Created `usePromptInputFiles` composable for managing file operations in prompt input.
- Implemented file selection, paste, drag-drop, and prompt files integration.

feat: implement rate limit status management

- Developed `useRateLimitStatus` composable for managing rate limit status display and polling.
- Added methods for retrieving rate limit status icon, class, tooltip, and wait time formatting.

* feat: enhance chat input component with context length management and settings integration

* feat: update model configuration and enhance error handling in providers

* feat: add MCP tools list component and integrate with chat settings
feat: enhance artifact dialog with improved error handling and localization
fix: update Mermaid artifact rendering error handling and localization
fix: improve input settings error handling and state management
fix: update drag and drop composable to handle drag events correctly
fix: update Vitest configuration for better project structure and alias resolution

* fix(i18n): add unknownError translation

---------

Co-authored-by: deepinsect <deepinsect@github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* feat: add Poe provider integration and icon support  (#1028)

* feat: add Poe provider integration and icon support

* chore: format and lint

---------

Co-authored-by: zerob13 <zerob13@gmail.com>

* fix: make auto scroll works (#1030)

* fix: allow settings window links to open externally (#1029)

* fix(settings): allow target blank links

* fix: harden settings window link handling

* feat: enhance GitHub Copilot Device Flow with OAuth token management and API token retrieval (#1021)

* feat: enhance GitHub Copilot Device Flow with OAuth token management and API token retrieval

- Fixed request header for managing OAuth tokens and retrieving API tokens.
- Enhanced model definitions and added new models for better compatibility.

* fix: remove privacy related log

* fix: OAuth 2.0 for slow_down response

* fix: handle lint errors

* fix: provider fetched from publicdb

* fix(githubCopilotProvider): update request body logging format for clarity

* fix(githubCopilotProvider): improve error handling and logging in device flow

* feat(theme): fix message paragraph gap and toolcall block (#1031)

Co-authored-by: deepinsect <deepinsect@github.com>

* fix: scroll to bottom (#1034)

* fix: add debounce for renderer

* feat: add max wait for renderer

* chore(deps): upgrade markdown renderer add worker support

* chore: bump markdown version

* fix(build): use es module worker format (#1037)

* feat: remove function deleteOllamaModel (#1036)

* feat: remove function deleteOllamaModel

* fix(build): use es module worker format (#1037)

---------

Co-authored-by: duskzhen <zerob13@gmail.com>

* perf: update dependencies to use stream-monaco and bump vue-renderer-markdown version (#1038)

* feat(theme): add markdown layout style and table style (#1039)

* feat(theme): add markdown layout style and table style

* fix(lint): remove props

---------

Co-authored-by: deepinsect <deepinsect@github.com>

* feat: support effort and verbosity (#1040)

* chore: bump up version

* feat: add jiekou.ai as LLM provider (#1041)

* feat: add jiekou.ai as LLM provider

* fix: change api type to jiekou

---------

Co-authored-by: zerob13 <zerob13@gmail.com>

* chore: update provider db

---------

Co-authored-by: 韦伟 <xweimvp@gmail.com>
Co-authored-by: Happer <ericted8810us@gmail.com>
Co-authored-by: deepinsect <deepinsect@github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: cp90 <153345481+cp90-pixel@users.noreply.github.com>
Co-authored-by: Cedric <14017092+douyixuan@users.noreply.github.com>
Co-authored-by: Simon He <57086651+Simon-He95@users.noreply.github.com>
Co-authored-by: yyhhyyyyyy <yyhhyyyyyy8@gmail.com>
Co-authored-by: cnJasonZ <gbdzxalbb@qq.com>
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.

3 participants