-
Notifications
You must be signed in to change notification settings - Fork 614
Feat/codeoptimize #1026
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat/codeoptimize #1026
Conversation
- 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
WalkthroughRefactors 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
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
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)
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
✨ Finishing touches🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (8)
🧰 Additional context used📓 Path-based instructions (4)src/renderer/src/**/*📄 CodeRabbit inference engine (.cursor/rules/i18n.mdc)
Files:
src/renderer/src/**📄 CodeRabbit inference engine (AGENTS.md)
Files:
src/renderer/src/i18n/**/*.{ts,json,yml,yaml}📄 CodeRabbit inference engine (AGENTS.md)
Files:
**/*.{ts,tsx,js,jsx,vue,css,scss,md,json,yml,yaml}📄 CodeRabbit inference engine (AGENTS.md)
Files:
⏰ 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)
🔇 Additional comments (8)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
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. |
…ts and composables
|
✅ 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. |
|
Creating a PR to put the unit tests in... The changes have been created in this pull request: View PR |
- 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.
… settings integration
CodeRabbit Generated Unit Tests: Add renderer unit tests for components and composables
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 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-originsubstantially weakens sandboxing. Default to no scripts, keep same-origin only for head edits, and gate scripts via an explicit prop. Also add atitlefor 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 explicitallowScriptsprop (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 whenviewportSizechanges (onload won’t re-fire). Extract updater and call from watch.
setupIframe()assignsonload, so subsequentwatch()re-calls won’t run the meta logic. Extract the logic intoapplyViewportMeta()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 errorsSet 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 timeoutUse 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 logsLogging 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 injectiondescription/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, '&') .replace(/"/g, '"') .replace(/</g, '<') .replace(/>/g, '>') }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 contentSetting 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 = svgAlso 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 computedsandboxAttrandiframeTitlefor 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 constfor 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 usingattachTo.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 JSDOMDocument. Also add a spec that switchesviewportSizefrom mobile→tablet and asserts inline styles update andapplyViewportMeta()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 guidelinesSeveral 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‑platformUsing 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 flagsCall 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 = truesrc/renderer/src/composables/message/useMessageRetry.ts (3)
8-19: Avoid index‑keyed Map and any; key by messageId with a typed interfaceIndex 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 returnIf 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 existsThe 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 labelPrevent 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) andformatWaitTime(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 byidto 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,
getTargetRectreturns null and leaves behavior tocaptureAndCopy. Fail fast with a clear log andfalse.- 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; avoidas 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’tawaita sync function; add iframe case.
exportCodeis sync; removeawaitto keep tests precise.- Add a case to exercise iframe path (
text/htmlorapplication/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 inhandleDragLeave, which is critical for preventing UI flicker during drag operations.Consider adding a test that uses
vi.useFakeTimers()andvi.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
falseforisDark, but the realuseThemeStorelikely 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
refimport 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
captureMessagebut only asserts the return value istrue. It doesn't verify thatcaptureAndCopywas 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
componentKeydoesn'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-ignoredirective 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
descriptionprop is typed asstringbut 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 titleIf 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 dragenterImproves 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 initComments 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 optimizationYou 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
1000as a fallback divisor whencontextLengthis undefined. This magic number should be extracted to a named constant with documentation explaining its purpose, or the logic should ensurecontextLengthis 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.9and0.8are 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 usingwatchEffector restructuring the watch.The
isFirstRunflag pattern (lines 49-56) is a workaround to skip the initial watch callback when usingimmediate: true. Consider these alternatives:
- Remove
immediate: trueif the initial run is not needed- Use two separate watchers: one without immediate, one with
- Use
watchEffectif appropriate for your use caseAlso,
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 coercereasoningsafely.- 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 tonulland log with context.Avoid
{}truthiness traps; usenullfor “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 inhandleModelUpdate.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: Avoidas anyin 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: GuardgetAppVersion()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
_deepchatHandledbut 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‑importingtokenx.
tokenxcan 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
|
@codex review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 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.globalsRun 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 {} || truesrc/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 hash-full blockclasses 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 includeMain (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.globalssrc/renderer/src/components/chat-input/composables/useInputSettings.ts (4)
15-18: Add explicit type for settings ref (strict TS).Give
settingsa 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
dragCounterbelow 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 ofany.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.
ChatConfigandModelChooserare 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: Reduceanyusage for strict typing.
useInputHistory(null as any, t)breaks strictness. Consider overloadinguseInputHistoryto acceptEditor | nulland callingsetEditorlater (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
📒 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.jsonsrc/renderer/src/components/chat-input/composables/useInputSettings.tssrc/renderer/src/components/chat-input/composables/useDragAndDrop.tssrc/renderer/src/components/artifacts/MermaidArtifact.vuesrc/renderer/src/composables/useArtifactExport.tssrc/renderer/src/i18n/fa-IR/artifacts.jsonsrc/renderer/src/composables/useArtifactCodeEditor.tssrc/renderer/src/i18n/zh-HK/artifacts.jsonsrc/renderer/src/i18n/en-US/common.jsonsrc/renderer/src/i18n/zh-CN/artifacts.jsonsrc/renderer/src/components/artifacts/ArtifactDialog.vuesrc/renderer/src/components/chat-input/ChatInput.vuesrc/renderer/src/i18n/zh-TW/artifacts.jsonsrc/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.jsonsrc/renderer/src/components/chat-input/composables/useInputSettings.tssrc/renderer/src/components/chat-input/composables/useDragAndDrop.tssrc/renderer/src/components/artifacts/MermaidArtifact.vuesrc/renderer/src/composables/useArtifactExport.tssrc/renderer/src/i18n/fa-IR/artifacts.jsonsrc/renderer/src/composables/useArtifactCodeEditor.tssrc/renderer/src/i18n/zh-HK/artifacts.jsonsrc/renderer/src/i18n/en-US/common.jsonsrc/renderer/src/i18n/zh-CN/artifacts.jsonsrc/renderer/src/components/artifacts/ArtifactDialog.vuesrc/renderer/src/components/chat-input/ChatInput.vuesrc/renderer/src/i18n/zh-TW/artifacts.jsonsrc/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.jsonsrc/renderer/src/i18n/fa-IR/artifacts.jsonsrc/renderer/src/i18n/zh-HK/artifacts.jsonsrc/renderer/src/i18n/en-US/common.jsonsrc/renderer/src/i18n/zh-CN/artifacts.jsonsrc/renderer/src/i18n/zh-TW/artifacts.jsonsrc/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.jsonsrc/renderer/src/components/chat-input/composables/useInputSettings.tssrc/renderer/src/components/chat-input/composables/useDragAndDrop.tssrc/renderer/src/components/artifacts/MermaidArtifact.vuesrc/renderer/src/composables/useArtifactExport.tssrc/renderer/src/i18n/fa-IR/artifacts.jsonsrc/renderer/src/composables/useArtifactCodeEditor.tssrc/renderer/src/i18n/zh-HK/artifacts.jsonsrc/renderer/src/i18n/en-US/common.jsonsrc/renderer/src/i18n/zh-CN/artifacts.jsonsrc/renderer/src/components/artifacts/ArtifactDialog.vuevitest.config.tssrc/renderer/src/components/chat-input/ChatInput.vuesrc/renderer/src/i18n/zh-TW/artifacts.jsonsrc/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.tssrc/renderer/src/components/chat-input/composables/useDragAndDrop.tssrc/renderer/src/composables/useArtifactExport.tssrc/renderer/src/composables/useArtifactCodeEditor.tsvitest.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.tssrc/renderer/src/components/chat-input/composables/useDragAndDrop.tssrc/renderer/src/composables/useArtifactExport.tssrc/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.tssrc/renderer/src/components/chat-input/composables/useDragAndDrop.tssrc/renderer/src/composables/useArtifactExport.tssrc/renderer/src/composables/useArtifactCodeEditor.tsvitest.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.tssrc/renderer/src/components/chat-input/composables/useDragAndDrop.tssrc/renderer/src/components/artifacts/MermaidArtifact.vuesrc/renderer/src/composables/useArtifactExport.tssrc/renderer/src/composables/useArtifactCodeEditor.tssrc/renderer/src/components/artifacts/ArtifactDialog.vuesrc/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.tssrc/renderer/src/components/chat-input/composables/useDragAndDrop.tssrc/renderer/src/components/artifacts/MermaidArtifact.vuesrc/renderer/src/composables/useArtifactExport.tssrc/renderer/src/composables/useArtifactCodeEditor.tssrc/renderer/src/components/artifacts/ArtifactDialog.vuesrc/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.tssrc/renderer/src/components/chat-input/composables/useDragAndDrop.tssrc/renderer/src/components/artifacts/MermaidArtifact.vuesrc/renderer/src/composables/useArtifactExport.tssrc/renderer/src/composables/useArtifactCodeEditor.tssrc/renderer/src/components/artifacts/ArtifactDialog.vuesrc/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.tssrc/renderer/src/components/chat-input/composables/useDragAndDrop.tssrc/renderer/src/components/artifacts/MermaidArtifact.vuesrc/renderer/src/composables/useArtifactExport.tssrc/renderer/src/composables/useArtifactCodeEditor.tssrc/renderer/src/components/artifacts/ArtifactDialog.vuesrc/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.tssrc/renderer/src/components/chat-input/composables/useDragAndDrop.tssrc/renderer/src/components/artifacts/MermaidArtifact.vuesrc/renderer/src/composables/useArtifactExport.tssrc/renderer/src/composables/useArtifactCodeEditor.tssrc/renderer/src/components/artifacts/ArtifactDialog.vuesrc/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.tssrc/renderer/src/components/chat-input/composables/useDragAndDrop.tssrc/renderer/src/components/artifacts/MermaidArtifact.vuesrc/renderer/src/composables/useArtifactExport.tssrc/renderer/src/composables/useArtifactCodeEditor.tssrc/renderer/src/components/artifacts/ArtifactDialog.vuevitest.config.tssrc/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.tssrc/renderer/src/components/chat-input/composables/useDragAndDrop.tssrc/renderer/src/components/artifacts/MermaidArtifact.vuesrc/renderer/src/composables/useArtifactExport.tssrc/renderer/src/composables/useArtifactCodeEditor.tssrc/renderer/src/components/artifacts/ArtifactDialog.vuevitest.config.tssrc/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.tssrc/renderer/src/components/chat-input/composables/useDragAndDrop.tssrc/renderer/src/components/artifacts/MermaidArtifact.vuesrc/renderer/src/components/artifacts/ArtifactDialog.vuesrc/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.tssrc/renderer/src/components/chat-input/composables/useDragAndDrop.tssrc/renderer/src/components/artifacts/MermaidArtifact.vuesrc/renderer/src/composables/useArtifactExport.tssrc/renderer/src/composables/useArtifactCodeEditor.tssrc/renderer/src/components/artifacts/ArtifactDialog.vuesrc/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.tssrc/renderer/src/components/chat-input/composables/useDragAndDrop.tssrc/renderer/src/components/artifacts/MermaidArtifact.vuesrc/renderer/src/composables/useArtifactExport.tssrc/renderer/src/composables/useArtifactCodeEditor.tssrc/renderer/src/components/artifacts/ArtifactDialog.vuevitest.config.tssrc/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.vuesrc/renderer/src/components/artifacts/ArtifactDialog.vuesrc/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.vuesrc/renderer/src/components/artifacts/ArtifactDialog.vuesrc/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.vuesrc/renderer/src/components/artifacts/ArtifactDialog.vuesrc/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')andt('common.unknownError')for localized messages- Using
textContentinstead ofinnerHTMLfor 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 OKImport is correct for SFC transforms. Nothing else needed here.
77-77: Timeouts OK; consider per-project overrides only if neededKeeping 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: trueIf disabled due to known issues, consider a tooltip explaining why.
|
Codex Review: Didn't find any major issues. 👍 ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
* 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>
make every components neaty and easy to use.
Summary by CodeRabbit
New Features
Bug Fixes
Documentation
Tests