-
Notifications
You must be signed in to change notification settings - Fork 516
feat: html预览新增尺寸切换 #618
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: html预览新增尺寸切换 #618
Conversation
WalkthroughAdds viewport emulation to HTML artifact previews: device toggle (desktop/tablet/mobile), resizable draggable iframe with edge/corner handles, dimension inputs bound via v-model between ArtifactDialog and HTMLArtifact, and new localization keys for device/dimension labels across multiple locales. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant ArtifactDialog
participant HTMLArtifact
User->>ArtifactDialog: Open HTML artifact preview
ArtifactDialog->>ArtifactDialog: Render device toggle + dimension inputs
User->>ArtifactDialog: Select device / edit dimensions
ArtifactDialog->>HTMLArtifact: pass :viewport-size and v-model dimensions
HTMLArtifact->>HTMLArtifact: setupIframe() (inject meta, base CSS)
HTMLArtifact->>HTMLArtifact: render iframe with computed styles
alt tablet/mobile selected
User->>HTMLArtifact: Drag resize handle
HTMLArtifact->>HTMLArtifact: compute constrained size, throttled emit
HTMLArtifact->>ArtifactDialog: update v-model dimensions
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
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: 7
🔭 Outside diff range comments (1)
src/renderer/src/components/artifacts/HTMLArtifact.vue (1)
520-529
: Security: Restore HTML content sanitizationI wasn’t able to find any other DOMPurify usage in the codebase, and the
sanitizedContent
computed property inHTMLArtifact.vue
(lines 520–529) is commented out. Rendering rawprops.block.content
into the iframe’ssrcdoc
can still allow XSS—even with sandbox attributes. Please address this by either:
Uncommenting/reinstating the DOMPurify sanitization in
HTMLArtifact.vue
and usingsanitizedContent
for the iframe’ssrcdoc
, for example:-// const sanitizedContent = computed(() => { -// if (!props.block.content) return '' -// return DOMPurify.sanitize(props.block.content, { … }) -// }) +const sanitizedContent = computed(() => { + if (!props.block.content) return '' + return DOMPurify.sanitize(props.block.content, { + WHOLE_DOCUMENT: true, + ADD_TAGS: ['script', 'style'], + ADD_ATTR: ['src', 'style', 'onclick'], + ALLOWED_URI_REGEXP: + /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp|xxx):|[^a-z]|[a-z+.]+(?:[^a-z+.:]|$))/i + }) +})…and swap the iframe’s
srcdoc="props.block.content"
tosrcdoc="sanitizedContent"
.Or ensure that all HTML content passed into this component has already been sanitized upstream—and document where that happens.
File needing changes:
•src/renderer/src/components/artifacts/HTMLArtifact.vue
(around lines 520–529)
🧹 Nitpick comments (2)
src/renderer/src/i18n/en-US/artifacts.json (1)
39-39
: Inconsistent punctuation with sibling keys
copyImageFailedDesc
ends with a period, whereascopyFailedDesc
and most other descriptions do not. Align punctuation to avoid UI inconsistency.- "copyImageFailedDesc": "Could not copy image to clipboard.", + "copyImageFailedDesc": "Could not copy image to clipboard"src/renderer/src/components/artifacts/HTMLArtifact.vue (1)
297-307
: Consider adjusting throttle rate for better performance balance.While the throttling implementation is good, 125fps (8ms) might be excessive. Most displays run at 60Hz, so 60fps (16ms) would provide smooth updates while reducing CPU usage.
-const UPDATE_THROTTLE = 8 // 约125fps,比60fps更流畅 +const UPDATE_THROTTLE = 16 // 60fps - matches typical display refresh rates
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
src/renderer/src/components/artifacts/ArtifactDialog.vue
(4 hunks)src/renderer/src/components/artifacts/HTMLArtifact.vue
(4 hunks)src/renderer/src/i18n/en-US/artifacts.json
(1 hunks)src/renderer/src/i18n/ko-KR/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-HK/artifacts.json
(1 hunks)src/renderer/src/i18n/zh-TW/artifacts.json
(1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
src/renderer/src/**/*
Instructions used from:
Sources:
📄 CodeRabbit Inference Engine
- .cursor/rules/i18n.mdc
src/renderer/src/i18n/**/*.json
Instructions used from:
Sources:
📄 CodeRabbit Inference Engine
- .cursor/rules/i18n.mdc
src/renderer/**
Instructions used from:
Sources:
📄 CodeRabbit Inference Engine
- .cursor/rules/project-structure.mdc
🧠 Learnings (8)
src/renderer/src/i18n/ko-KR/artifacts.json (6)
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-06-30T12:23:45.479Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Keep the structure of translation files consistent across all languages
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-06-30T12:23:45.479Z
Learning: Applies to src/renderer/src/i18n/**/*.json : When adding a new translation, add shared translations to common.json and language-specific translations to the respective language file; keep all language files' keys consistent
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-06-30T12:23:45.479Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Each language must have a separate JSON file in src/renderer/src/i18n/, and shared translation keys must be placed in common.json
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-06-30T12:23:45.479Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Regularly check for and remove unused translation keys from translation files
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-06-30T12:23:45.479Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Translation key naming must use dot-separated hierarchy, lowercase letters, and meaningful descriptive names (e.g., 'common.button.submit')
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-06-30T12:23:45.479Z
Learning: Applies to src/renderer/src/**/* : All user-facing strings in the renderer must use i18n keys (do not hardcode user-visible text in code; use vue-i18n translation keys instead)
src/renderer/src/i18n/en-US/artifacts.json (7)
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-06-30T12:23:45.479Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Keep the structure of translation files consistent across all languages
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-06-30T12:23:45.479Z
Learning: Applies to src/renderer/src/i18n/**/*.json : When adding a new translation, add shared translations to common.json and language-specific translations to the respective language file; keep all language files' keys consistent
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-06-30T12:23:45.479Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Each language must have a separate JSON file in src/renderer/src/i18n/, and shared translation keys must be placed in common.json
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-06-30T12:23:45.479Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Translation key naming must use dot-separated hierarchy, lowercase letters, and meaningful descriptive names (e.g., 'common.button.submit')
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-06-30T12:23:45.479Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Regularly check for and remove unused translation keys from translation files
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-06-30T12:23:45.479Z
Learning: Applies to src/renderer/src/**/* : All user-facing strings in the renderer must use i18n keys (do not hardcode user-visible text in code; use vue-i18n translation keys instead)
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-06-30T12:23:45.479Z
Learning: Applies to src/renderer/src/**/* : Do not hardcode user-facing text in code; always use the translation system (vue-i18n) for all user-visible strings
src/renderer/src/i18n/zh-HK/artifacts.json (5)
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-06-30T12:23:45.479Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Keep the structure of translation files consistent across all languages
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-06-30T12:23:45.479Z
Learning: Applies to src/renderer/src/i18n/**/*.json : When adding a new translation, add shared translations to common.json and language-specific translations to the respective language file; keep all language files' keys consistent
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-06-30T12:23:45.479Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Each language must have a separate JSON file in src/renderer/src/i18n/, and shared translation keys must be placed in common.json
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-06-30T12:23:45.479Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Regularly check for and remove unused translation keys from translation files
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-06-30T12:23:45.479Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Translation key naming must use dot-separated hierarchy, lowercase letters, and meaningful descriptive names (e.g., 'common.button.submit')
src/renderer/src/i18n/zh-TW/artifacts.json (7)
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-06-30T12:23:45.479Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Keep the structure of translation files consistent across all languages
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-06-30T12:23:45.479Z
Learning: Applies to src/renderer/src/i18n/**/*.json : When adding a new translation, add shared translations to common.json and language-specific translations to the respective language file; keep all language files' keys consistent
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-06-30T12:23:45.479Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Each language must have a separate JSON file in src/renderer/src/i18n/, and shared translation keys must be placed in common.json
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-06-30T12:23:45.479Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Regularly check for and remove unused translation keys from translation files
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-06-30T12:23:45.479Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Translation key naming must use dot-separated hierarchy, lowercase letters, and meaningful descriptive names (e.g., 'common.button.submit')
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-06-30T12:23:45.479Z
Learning: Applies to src/renderer/src/**/* : All user-facing strings in the renderer must use i18n keys (do not hardcode user-visible text in code; use vue-i18n translation keys instead)
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-06-30T12:23:45.479Z
Learning: Applies to src/renderer/src/**/* : Do not hardcode user-facing text in code; always use the translation system (vue-i18n) for all user-visible strings
src/renderer/src/i18n/zh-CN/artifacts.json (7)
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-06-30T12:23:45.479Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Keep the structure of translation files consistent across all languages
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-06-30T12:23:45.479Z
Learning: Applies to src/renderer/src/i18n/**/*.json : When adding a new translation, add shared translations to common.json and language-specific translations to the respective language file; keep all language files' keys consistent
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-06-30T12:23:45.479Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Each language must have a separate JSON file in src/renderer/src/i18n/, and shared translation keys must be placed in common.json
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-06-30T12:23:45.479Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Regularly check for and remove unused translation keys from translation files
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-06-30T12:23:45.479Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Translation key naming must use dot-separated hierarchy, lowercase letters, and meaningful descriptive names (e.g., 'common.button.submit')
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-06-30T12:23:45.479Z
Learning: Applies to src/renderer/src/**/* : All user-facing strings in the renderer must use i18n keys (do not hardcode user-visible text in code; use vue-i18n translation keys instead)
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-06-30T12:23:45.479Z
Learning: Applies to src/renderer/src/**/* : Do not hardcode user-facing text in code; always use the translation system (vue-i18n) for all user-visible strings
src/renderer/src/i18n/ru-RU/artifacts.json (5)
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-06-30T12:23:45.479Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Keep the structure of translation files consistent across all languages
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-06-30T12:23:45.479Z
Learning: Applies to src/renderer/src/i18n/**/*.json : When adding a new translation, add shared translations to common.json and language-specific translations to the respective language file; keep all language files' keys consistent
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-06-30T12:23:45.479Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Each language must have a separate JSON file in src/renderer/src/i18n/, and shared translation keys must be placed in common.json
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-06-30T12:23:45.479Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Translation key naming must use dot-separated hierarchy, lowercase letters, and meaningful descriptive names (e.g., 'common.button.submit')
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/i18n.mdc:0-0
Timestamp: 2025-06-30T12:23:45.479Z
Learning: Applies to src/renderer/src/i18n/**/*.json : Regularly check for and remove unused translation keys from translation files
src/renderer/src/components/artifacts/ArtifactDialog.vue (5)
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/pinia-best-practices.mdc:0-0
Timestamp: 2025-06-30T12:24:10.749Z
Learning: Applies to src/renderer/src/stores/**/*.{vue,ts,tsx,js,jsx} : Use modules to organize related state and actions
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/pinia-best-practices.mdc:0-0
Timestamp: 2025-06-30T12:24:10.749Z
Learning: Applies to src/renderer/src/stores/**/*.{vue,ts,tsx,js,jsx} : Keep the store focused on global state, not component-specific data
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/pinia-best-practices.mdc:0-0
Timestamp: 2025-06-30T12:24:10.749Z
Learning: Applies to src/renderer/src/stores/**/*.{vue,ts,tsx,js,jsx} : Implement proper state persistence for maintaining data across sessions
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/vue-shadcn.mdc:0-0
Timestamp: 2025-06-23T13:06:15.335Z
Learning: Use VueUse for common composables and utility functions to promote code reuse.
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/error-logging.mdc:0-0
Timestamp: 2025-06-30T12:23:33.814Z
Learning: Applies to **/*.tsx : 使用错误边界捕获渲染错误
src/renderer/src/components/artifacts/HTMLArtifact.vue (8)
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/vue-best-practices.mdc:0-0
Timestamp: 2025-06-23T13:06:02.806Z
Learning: In Vue.js applications, prefer the Composition API for better code organization and reusability.
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/vue-shadcn.mdc:0-0
Timestamp: 2025-06-23T13:06:15.336Z
Learning: Use <script setup> syntax for concise Vue 3 component definitions.
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/vue-shadcn.mdc:0-0
Timestamp: 2025-06-23T13:06:15.336Z
Learning: Implement custom composables for reusable logic in Vue 3/Nuxt 3 projects.
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/vue-shadcn.mdc:0-0
Timestamp: 2025-06-23T13:06:15.335Z
Learning: Use template syntax for declarative rendering in Vue components.
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/vue-shadcn.mdc:0-0
Timestamp: 2025-06-23T13:06:15.336Z
Learning: Leverage ref, reactive, and computed for reactive state management in the Composition API.
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/error-logging.mdc:0-0
Timestamp: 2025-06-30T12:23:33.814Z
Learning: Applies to **/*.tsx : 使用错误边界捕获渲染错误
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/vue-shadcn.mdc:0-0
Timestamp: 2025-06-23T13:06:15.335Z
Learning: Leverage defineComponent and PropType for strong typing in Vue 3 components.
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: .cursor/rules/vue-shadcn.mdc:0-0
Timestamp: 2025-06-23T13:06:15.335Z
Learning: Use VueUse for common composables and utility functions to promote code reuse.
🔇 Additional comments (7)
src/renderer/src/components/artifacts/HTMLArtifact.vue (2)
610-623
: Excellent event listener cleanup implementation.The cleanup logic properly handles:
- Removing all event listeners on unmount
- Clearing timers to prevent memory leaks
- Resetting body styles if unmounting during drag operation
15-21
: Smart solution for iframe interaction during resize.The iframe mask overlay elegantly prevents the iframe from capturing mouse events during drag operations. The implementation using Vue's reactivity system is clean and efficient.
Also applies to: 319-328
src/renderer/src/components/artifacts/ArtifactDialog.vue (5)
52-93
: Device viewport toggle implementation looks good with proper i18n usage.The device size toggle buttons are well-structured with conditional rendering, proper styling, and correct i18n key usage. The implementation follows Vue best practices with reactive state binding.
95-135
: Dimension input controls are well-implemented with appropriate constraints.The dimension input section provides good UX with:
- Proper min/max constraints for each device type
- Responsive design with conditional rendering
- Correct v-model bindings with number modifier
- Proper i18n usage for labels
The input ranges are appropriate:
- Tablet: 320-1200px width, 426-1400px height
- Mobile: 320-480px width, 426-1000px height
247-251
: Reactive state variables are properly typed and initialized.The viewport state management is well-designed with:
- Proper TypeScript typing for viewportSize
- Reasonable default dimensions for tablet and mobile
- Consistent naming conventions
The default dimensions follow common device standards:
- Tablet: 768x1024 (iPad-like dimensions)
- Mobile: 375x667 (iPhone-like dimensions)
322-324
: setViewportSize method is simple and correct.The method properly updates the viewport size state with appropriate typing.
204-208
: Props binding to HTMLArtifact component is correctly implemented.The component properly passes all viewport-related props and v-model bindings to the HTMLArtifact component:
- viewport-size as a prop
- v-model bindings for all dimension variables
This follows Vue 3 patterns correctly and enables two-way data binding for the dimension controls.
"copyImageFailedDesc": "이미지를 클립보드에 복사할 수 없습니다", | ||
"desktop": "데스크톱", | ||
"tablet": "태블릿", | ||
"mobile": "모바일", | ||
"width": "너비", | ||
"height": "높이" |
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.
💡 Verification agent
🧩 Analysis chain
Generic terms should live in common.json
and follow hierarchical keys
desktop
, tablet
, mobile
, width
, and height
are generic UI words, not artifact-specific.
Per i18n guidelines, shared strings must be placed under src/renderer/src/i18n/**/common.json
using dot-separated, lower-case keys (e.g., common.device.desktop
, common.dimension.width
). Keeping them here duplicates data and will drift across modules/languages.
Suggest moving these entries to common.json
(all languages) and updating components to reference the new keys.
🏁 Script executed:
#!/bin/bash
# Locate all common.json files in the i18n directory
find src/renderer/src/i18n -type f -name 'common.json'
# Check for existing definitions of the generic keys in those common.json files
rg '"desktop"' -g 'src/renderer/src/i18n/**/common.json' -A2 || echo "desktop not found in any common.json"
rg '"tablet"' -g 'src/renderer/src/i18n/**/common.json' -A2 || echo "tablet not found in any common.json"
rg '"mobile"' -g 'src/renderer/src/i18n/**/common.json' -A2 || echo "mobile not found in any common.json"
rg '"width"' -g 'src/renderer/src/i18n/**/common.json' -A2 || echo "width not found in any common.json"
rg '"height"' -g 'src/renderer/src/i18n/**/common.json' -A2 || echo "height not found in any common.json"
Length of output: 1129
Move shared UI terms into common.json
These generic keys—desktop
, tablet
, mobile
, width
, and height
—belong in your shared translations, not in the artifact-specific file. Please:
• Remove these entries from each src/renderer/src/i18n/*/artifacts.json
(e.g. lines 39–44 in ko-KR/artifacts.json
).
• Add them under logical, dot-separated keys in every src/renderer/src/i18n/*/common.json
, for example:
{
…,
+ "device": {
+ "desktop": "Desktop translation",
+ "tablet": "Tablet translation",
+ "mobile": "Mobile translation"
+ },
+ "dimension": {
+ "width": "Width translation",
+ "height": "Height translation"
+ }
}
• Update all component references:
- Change
$t('artifacts.desktop')
→$t('common.device.desktop')
- Similarly for
tablet
,mobile
,width
, andheight
This ensures consistency across modules and prevents drift.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/renderer/src/i18n/ko-KR/artifacts.json lines 39 to 44, remove the keys
desktop, tablet, mobile, width, and height as they are generic UI terms. Add
these keys instead to src/renderer/src/i18n/ko-KR/common.json under a logical
namespace like common.device for desktop, tablet, and mobile, and
common.dimension for width and height. Then update all component code to replace
references from $t('artifacts.desktop') to $t('common.device.desktop') and
similarly for the other keys to ensure consistent shared translations.
"copyImageFailedDesc": "Could not copy image to clipboard.", | ||
"desktop": "Desktop", | ||
"tablet": "Tablet", | ||
"mobile": "Mobile", | ||
"width": "Width", | ||
"height": "Height" |
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.
💡 Verification agent
🧩 Analysis chain
Move generic labels to common.json
& adopt hierarchical naming
Same concern as other locales: these five labels are application-wide and should be under common.json
using a dot-separated namespace (common.device.*
, common.dimension.*
) for consistency and re-use.
🏁 Script executed:
#!/bin/bash
# Locate common.json in the i18n directory
COMMON_JSON=$(fd --full-path 'common\.json' src/renderer/src/i18n | head -n1)
echo "common.json path: ${COMMON_JSON:-not found}"
# Check whether each generic key already lives in common.json
for KEY in desktop tablet mobile width height; do
if [[ -z "$COMMON_JSON" ]]; then
echo "Skipping check for '$KEY': common.json not found"
else
if grep -q "\"$KEY\"" "$COMMON_JSON"; then
echo "✔ '$KEY' found in common.json"
else
echo "✘ '$KEY' not present in common.json"
fi
fi
done
# Search for these keys in each locale's artifacts.json
echo -e "\nScanning locale-specific artifacts.json files for generic labels..."
for FILE in src/renderer/src/i18n/*/artifacts.json; do
echo "File: $FILE"
grep -En '"(desktop|tablet|mobile|width|height)"' "$FILE" || echo " – no generic labels here"
done
Length of output: 4813
Extract generic labels into common.json with hierarchical keys
These device and dimension labels are used application-wide and should live in each locale’s common.json
with a dot-separated namespace, not under artifacts.json
. Please:
• Add to src/renderer/src/i18n/<locale>/common.json
(creating the file if missing):
"common.device.desktop": "Desktop",
"common.device.tablet": "Tablet",
"common.device.mobile": "Mobile",
"common.dimension.width": "Width",
"common.dimension.height": "Height"
(adjust target translations in non-en locales)
• Remove from every artifacts.json
where they currently appear:
- src/renderer/src/i18n/en-US/artifacts.json (lines 39–44)
- src/renderer/src/i18n/ko-KR/artifacts.json (lines 40–44)
- src/renderer/src/i18n/ru-RU/artifacts.json (lines 40–44)
- src/renderer/src/i18n/zh-CN/artifacts.json (lines 40–44)
- src/renderer/src/i18n/zh-HK/artifacts.json (lines 40–44)
- src/renderer/src/i18n/zh-TW/artifacts.json (lines 40–44)
This keeps translation keys consistent, reusable, and aligned with our dot-hierarchy convention.
🤖 Prompt for AI Agents
In src/renderer/src/i18n/en-US/artifacts.json lines 39 to 44, the device and
dimension labels are incorrectly placed under artifacts.json. Move these keys to
src/renderer/src/i18n/en-US/common.json using a dot-separated namespace like
"common.device.desktop" and "common.dimension.width". Remove these keys from
artifacts.json and replicate this change for all other locale folders mentioned,
ensuring translations are updated accordingly. This will centralize common
labels and maintain consistency across locales.
"copyFailedDesc": "无法复制内容到剪贴板", | ||
"desktop": "桌面", | ||
"tablet": "平板", | ||
"mobile": "手机", | ||
"width": "宽度", | ||
"height": "高度" |
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.
💡 Verification agent
🧩 Analysis chain
将通用词条迁移至 common.json
并使用分层命名
desktop
、tablet
、mobile
、width
、height
属于全局共用词汇,应放入各语言的 common.json
,并采用 common.device.desktop
、common.dimension.width
等点分层级命名,避免多处重复。
Here’s a quick check to locate your shared common.json
and see if those keys already live there:
🏁 Script executed:
#!/bin/bash
# Find common.json under the i18n directory and verify global keys
common_paths=$(fd -HI common.json src/renderer/src/i18n)
if [ -z "$common_paths" ]; then
echo "No common.json found under src/renderer/src/i18n"
exit 0
fi
echo "common.json found at:"
echo "$common_paths"
for path in $common_paths; do
echo "Checking keys in $path"
for key in desktop tablet mobile width height; do
echo -n " $key: "
grep -n "\"$key\"" "$path" || echo "Not found"
done
done
Length of output: 10631
Move global translation keys into common.json
with hierarchical naming
The keys "desktop"
, "tablet"
, "mobile"
, "width"
and "height"
are shared across multiple contexts and should live in each language’s common.json
under dot-separated namespaces.
• Remove these entries from
src/renderer/src/i18n/zh-CN/artifacts.json (lines 39–44)
• In src/renderer/src/i18n/zh-CN/common.json, add:
+ "common.device.desktop": "桌面",
+ "common.device.tablet": "平板",
+ "common.device.mobile": "手机",
+ "common.dimension.width": "宽度",
+ "common.dimension.height": "高度"
• Repeat the same additions in every other language’s common.json
to keep keys consistent.
• Update any code or templates that referenced artifacts.desktop
/tablet
/… to use common.device.desktop
/common.dimension.width
, etc.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/renderer/src/i18n/zh-CN/artifacts.json lines 39 to 44, remove the keys
"desktop", "tablet", "mobile", "width", and "height" as they are global
translation keys. Add these keys with appropriate hierarchical namespaces (e.g.,
device.desktop, device.tablet, device.mobile, dimension.width, dimension.height)
into src/renderer/src/i18n/zh-CN/common.json. Repeat this addition in all other
language common.json files to maintain consistency. Finally, update all code and
templates referencing these keys from artifacts to use the new paths under
common, such as common.device.desktop or common.dimension.width.
"copyImageFailedDesc": "無法複製圖片到剪貼板", | ||
"desktop": "桌面", | ||
"tablet": "平板", | ||
"mobile": "手機", | ||
"width": "寬度", | ||
"height": "高度" |
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.
🛠️ Refactor suggestion
將共用字串移至 common.json
並使用階層式 key
同上:請把「desktop / tablet / mobile / width / height」移到 common.json
,並使用如 common.device.desktop
的點分命名,以維持多語系一致性。
🤖 Prompt for AI Agents
In src/renderer/src/i18n/zh-HK/artifacts.json lines 39 to 44, the keys
"desktop", "tablet", "mobile", "width", and "height" should be moved to
src/renderer/src/i18n/zh-HK/common.json to centralize shared strings. After
moving, update their keys to use hierarchical dot notation like
"common.device.desktop" to maintain consistency across languages. Then, replace
their usage in artifacts.json with references to these new keys.
"copyImageFailedDesc": "無法複製圖片到剪貼板", | ||
"desktop": "桌面", | ||
"tablet": "平板", | ||
"mobile": "手機", | ||
"width": "寬度", | ||
"height": "高度" |
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.
🛠️ Refactor suggestion
共用詞條應集中於 common.json
與其他語系一致,請將此處新增的五個通用詞條遷移至 common.json
,並採用點分層級命名。
🤖 Prompt for AI Agents
In src/renderer/src/i18n/zh-TW/artifacts.json lines 39 to 44, the five common
terms added should be moved to the common.json file to maintain consistency
across languages. Relocate these entries to common.json and rename them using
dot-separated hierarchical keys to reflect their shared usage properly.
"desktop": "Рабочий стол", | ||
"tablet": "Планшет", | ||
"mobile": "Мобильный", | ||
"width": "Ширина", | ||
"height": "Высота" |
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.
💡 Verification agent
🧩 Analysis chain
Verify translation consistency across all language files.
The new translation keys are properly formatted. However, according to the coding guidelines, we need to ensure these keys are added to all language files to maintain consistency.
🏁 Script executed:
#!/bin/bash
# Description: Verify that the new translation keys are present in all language files
# Find all artifacts.json files in different languages
echo "Checking for new keys in all language files..."
for file in $(fd -t f "artifacts.json" src/renderer/src/i18n/); do
echo -e "\n=== $file ==="
# Check for each new key
for key in "desktop" "tablet" "mobile" "width" "height"; do
if rg -q "\"$key\":" "$file"; then
echo "✓ Key '$key' found"
else
echo "✗ Key '$key' missing"
fi
done
done
# Also check if these keys might be better suited for common.json
echo -e "\n=== Checking if similar keys exist in common.json files ==="
fd -t f "common.json" src/renderer/src/i18n/ -x rg -l "\"(desktop|tablet|mobile|width|height)\":" {} \;
Length of output: 10099
Add missing translation keys in artifacts.json
for all languages
The new keys (desktop
, tablet
, mobile
, width
, height
) are present in en-US, ko-KR, ru-RU, zh-CN, zh-HK, and zh-TW but are missing in several language files. They should remain in artifacts.json
(no duplicates found in common.json
).
Please add the following keys (with appropriate translations) to each of these files:
- src/renderer/src/i18n/fa-IR/artifacts.json
- src/renderer/src/i18n/fr-FR/artifacts.json
- src/renderer/src/i18n/ja-JP/artifacts.json
Ensure the keys follow the existing structure and dot-separated naming (artifacts.desktop
, etc.) so that all language files remain consistent.
🤖 Prompt for AI Agents
In src/renderer/src/i18n/fa-IR/artifacts.json, fr-FR/artifacts.json, and
ja-JP/artifacts.json, add the missing translation keys "desktop", "tablet",
"mobile", "width", and "height" with appropriate translations. Follow the
existing JSON structure and ensure the keys use the same dot-separated naming
convention as in other language files to maintain consistency across all
artifacts.json files.
<!-- 左侧拖动手柄 --> | ||
<div | ||
v-if="viewportSize !== 'desktop'" | ||
class="absolute top-1/2 transform -translate-y-1/2 w-2 cursor-col-resize transition-all duration-200 group" | ||
:class="{ | ||
'bg-blue-500 bg-opacity-30': isDragging, | ||
'hover:bg-blue-500 hover:bg-opacity-20': !isDragging | ||
}" | ||
@mousedown="handleLeftDragStart" | ||
:style="{ | ||
left: `calc(50% - ${(viewportSize === 'mobile' ? (mobileWidth || 390) : (tabletWidth || 680)) / 2}px - 1px)`, | ||
height: `${viewportSize === 'mobile' ? (mobileHeight || 844) : (tabletHeight || 800)}px` | ||
}" | ||
> | ||
<div class="absolute inset-y-0 left-1/2 transform -translate-x-1/2 w-0.5 bg-blue-500 opacity-0 group-hover:opacity-100 transition-opacity duration-200" | ||
:class="{ 'opacity-100': isDragging }"></div> | ||
</div> | ||
|
||
<!-- 右侧拖动手柄 --> | ||
<div | ||
v-if="viewportSize !== 'desktop'" | ||
class="absolute top-1/2 transform -translate-y-1/2 w-2 cursor-col-resize transition-all duration-200 group" | ||
:class="{ | ||
'bg-blue-500 bg-opacity-30': isDragging, | ||
'hover:bg-blue-500 hover:bg-opacity-20': !isDragging | ||
}" | ||
@mousedown="handleRightDragStart" | ||
:style="{ | ||
right: `calc(50% - ${(viewportSize === 'mobile' ? (mobileWidth || 390) : (tabletWidth || 680)) / 2}px - 1px)`, | ||
height: `${viewportSize === 'mobile' ? (mobileHeight || 844) : (tabletHeight || 800)}px` | ||
}" | ||
> | ||
<div class="absolute inset-y-0 left-1/2 transform -translate-x-1/2 w-0.5 bg-blue-500 opacity-0 group-hover:opacity-100 transition-opacity duration-200" | ||
:class="{ 'opacity-100': isDragging }"></div> | ||
</div> | ||
|
||
<!-- 上侧拖动手柄 --> | ||
<div | ||
v-if="viewportSize !== 'desktop'" | ||
class="absolute left-1/2 transform -translate-x-1/2 h-2 cursor-row-resize transition-all duration-200 group" | ||
:class="{ | ||
'bg-blue-500 bg-opacity-30': isDragging, | ||
'hover:bg-blue-500 hover:bg-opacity-20': !isDragging | ||
}" | ||
@mousedown="handleTopDragStart" | ||
:style="{ | ||
top: `calc(50% - ${(viewportSize === 'mobile' ? (mobileHeight || 844) : (tabletHeight || 800)) / 2}px - 1px)`, | ||
width: `${viewportSize === 'mobile' ? (mobileWidth || 390) : (tabletWidth || 680)}px` | ||
}" | ||
> | ||
<div class="absolute inset-x-0 top-1/2 transform -translate-y-1/2 h-0.5 bg-blue-500 opacity-0 group-hover:opacity-100 transition-opacity duration-200" | ||
:class="{ 'opacity-100': isDragging }"></div> | ||
</div> | ||
|
||
<!-- 下侧拖动手柄 --> | ||
<div | ||
v-if="viewportSize !== 'desktop'" | ||
class="absolute left-1/2 transform -translate-x-1/2 h-2 cursor-row-resize transition-all duration-200 group" | ||
:class="{ | ||
'bg-blue-500 bg-opacity-30': isDragging, | ||
'hover:bg-blue-500 hover:bg-opacity-20': !isDragging | ||
}" | ||
@mousedown="handleBottomDragStart" | ||
:style="{ | ||
bottom: `calc(50% - ${(viewportSize === 'mobile' ? (mobileHeight || 844) : (tabletHeight || 800)) / 2}px - 1px)`, | ||
width: `${viewportSize === 'mobile' ? (mobileWidth || 390) : (tabletWidth || 680)}px` | ||
}" | ||
> | ||
<div class="absolute inset-x-0 top-1/2 transform -translate-y-1/2 h-0.5 bg-blue-500 opacity-0 group-hover:opacity-100 transition-opacity duration-200" | ||
:class="{ 'opacity-100': isDragging }"></div> | ||
</div> | ||
|
||
<!-- 左上角拖动手柄 --> | ||
<div | ||
v-if="viewportSize !== 'desktop'" | ||
class="absolute w-3 h-3 cursor-nw-resize transition-all duration-200 group" | ||
:class="{ | ||
'bg-blue-500 bg-opacity-30': isDragging, | ||
'hover:bg-blue-500 hover:bg-opacity-20': !isDragging | ||
}" | ||
@mousedown="(e) => handleCornerDragStart(e, 'nw')" | ||
:style="{ | ||
left: `calc(50% - ${(viewportSize === 'mobile' ? (mobileWidth || 390) : (tabletWidth || 680)) / 2}px - 1px)`, | ||
top: `calc(50% - ${(viewportSize === 'mobile' ? (mobileHeight || 844) : (tabletHeight || 800)) / 2}px - 1px)` | ||
}" | ||
> | ||
<div class="absolute inset-0 border-l-2 border-t-2 border-blue-500 opacity-0 group-hover:opacity-100 transition-opacity duration-200" | ||
:class="{ 'opacity-100': isDragging }"></div> | ||
</div> | ||
|
||
<!-- 右上角拖动手柄 --> | ||
<div | ||
v-if="viewportSize !== 'desktop'" | ||
class="absolute w-3 h-3 cursor-ne-resize transition-all duration-200 group" | ||
:class="{ | ||
'bg-blue-500 bg-opacity-30': isDragging, | ||
'hover:bg-blue-500 hover:bg-opacity-20': !isDragging | ||
}" | ||
@mousedown="(e) => handleCornerDragStart(e, 'ne')" | ||
:style="{ | ||
right: `calc(50% - ${(viewportSize === 'mobile' ? (mobileWidth || 390) : (tabletWidth || 680)) / 2}px - 1px)`, | ||
top: `calc(50% - ${(viewportSize === 'mobile' ? (mobileHeight || 844) : (tabletHeight || 800)) / 2}px - 1px)` | ||
}" | ||
> | ||
<div class="absolute inset-0 border-r-2 border-t-2 border-blue-500 opacity-0 group-hover:opacity-100 transition-opacity duration-200" | ||
:class="{ 'opacity-100': isDragging }"></div> | ||
</div> | ||
|
||
<!-- 左下角拖动手柄 --> | ||
<div | ||
v-if="viewportSize !== 'desktop'" | ||
class="absolute w-3 h-3 cursor-sw-resize transition-all duration-200 group" | ||
:class="{ | ||
'bg-blue-500 bg-opacity-30': isDragging, | ||
'hover:bg-blue-500 hover:bg-opacity-20': !isDragging | ||
}" | ||
@mousedown="(e) => handleCornerDragStart(e, 'sw')" | ||
:style="{ | ||
left: `calc(50% - ${(viewportSize === 'mobile' ? (mobileWidth || 390) : (tabletWidth || 680)) / 2}px - 1px)`, | ||
bottom: `calc(50% - ${(viewportSize === 'mobile' ? (mobileHeight || 844) : (tabletHeight || 800)) / 2}px - 1px)` | ||
}" | ||
> | ||
<div class="absolute inset-0 border-l-2 border-b-2 border-blue-500 opacity-0 group-hover:opacity-100 transition-opacity duration-200" | ||
:class="{ 'opacity-100': isDragging }"></div> | ||
</div> | ||
|
||
<!-- 右下角拖动手柄 --> | ||
<div | ||
v-if="viewportSize !== 'desktop'" | ||
class="absolute w-3 h-3 cursor-se-resize transition-all duration-200 group" | ||
:class="{ | ||
'bg-blue-500 bg-opacity-30': isDragging, | ||
'hover:bg-blue-500 hover:bg-opacity-20': !isDragging | ||
}" | ||
@mousedown="(e) => handleCornerDragStart(e, 'se')" | ||
:style="{ | ||
right: `calc(50% - ${(viewportSize === 'mobile' ? (mobileWidth || 390) : (tabletWidth || 680)) / 2}px - 1px)`, | ||
bottom: `calc(50% - ${(viewportSize === 'mobile' ? (mobileHeight || 844) : (tabletHeight || 800)) / 2}px - 1px)` | ||
}" | ||
> | ||
<div class="absolute inset-0 border-r-2 border-b-2 border-blue-500 opacity-0 group-hover:opacity-100 transition-opacity duration-200" | ||
:class="{ 'opacity-100': isDragging }"></div> | ||
</div> |
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.
🛠️ Refactor suggestion
Add accessibility support for resize handles.
The resize handles lack keyboard support and ARIA attributes, making them inaccessible to users who rely on keyboards or screen readers.
Consider adding:
- ARIA labels for screen readers:
<div
v-if="viewportSize !== 'desktop'"
class="absolute top-1/2 transform -translate-y-1/2 w-2 cursor-col-resize transition-all duration-200 group"
+ role="separator"
+ :aria-label="`Resize ${viewportSize} preview width from left`"
+ tabindex="0"
:class="{
- Keyboard event handlers:
@mousedown="handleLeftDragStart"
+ @keydown="handleKeyboardResize"
- Add a keyboard resize handler:
const handleKeyboardResize = (e: KeyboardEvent) => {
if (props.viewportSize === 'desktop') return
const step = e.shiftKey ? 10 : 1
let currentWidth = props.viewportSize === 'mobile'
? props.mobileWidth || 390
: props.tabletWidth || 680
switch(e.key) {
case 'ArrowLeft':
currentWidth -= step
break
case 'ArrowRight':
currentWidth += step
break
default:
return
}
e.preventDefault()
// Apply constraints and emit updates
const { minWidth, maxWidth } = dragConstraints.value
currentWidth = Math.max(minWidth, Math.min(maxWidth, currentWidth))
if (props.viewportSize === 'mobile') {
emit('update:mobileWidth', currentWidth)
} else {
emit('update:tabletWidth', currentWidth)
}
}
🤖 Prompt for AI Agents
In src/renderer/src/components/artifacts/HTMLArtifact.vue between lines 24 and
166, the resize handles lack accessibility features such as keyboard support and
ARIA attributes. To fix this, add appropriate ARIA labels to each resize handle
for screen reader clarity, implement keyboard event handlers to capture key
presses, and integrate a keyboard resize handler function that adjusts the width
based on arrow key inputs with shift key support for larger steps. Ensure the
handler respects viewport constraints and emits the updated width values
accordingly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fix i18n
Run pnpm run i18n
pnpm run i18n
shell: /usr/bin/bash -e {0}
env:
PNPM_HOME: /home/runner/setup-pnpm/node_modules/.bin
[email protected] i18n /home/runner/work/deepchat/deepchat
i18n-check -s zh-CN -f i18next --locales src/renderer/src/i18n
i18n translations checker
Source: zh-CN
Selected format is: i18next
Found missing keys!
┌────────────────────────────────────────────┬─────────┐
│ file │ key │
├────────────────────────────────────────────┼─────────┤
│ src/renderer/src/i18n/fa-IR/artifacts.json │ desktop │
│ src/renderer/src/i18n/fa-IR/artifacts.json │ tablet │
│ src/renderer/src/i18n/fa-IR/artifacts.json │ mobile │
│ src/renderer/src/i18n/fa-IR/artifacts.json │ width │
│ src/renderer/src/i18n/fa-IR/artifacts.json │ height │
│ src/renderer/src/i18n/fr-FR/artifacts.json │ desktop │
│ src/renderer/src/i18n/fr-FR/artifacts.json │ tablet │
│ src/renderer/src/i18n/fr-FR/artifacts.json │ mobile │
│ src/renderer/src/i18n/fr-FR/artifacts.json │ width │
│ src/renderer/src/i18n/fr-FR/artifacts.json │ height │
│ src/renderer/src/i18n/ja-JP/artifacts.json │ desktop │
│ src/renderer/src/i18n/ja-JP/artifacts.json │ tablet │
│ src/renderer/src/i18n/ja-JP/artifacts.json │ mobile │
│ src/renderer/src/i18n/ja-JP/artifacts.json │ width │
│ src/renderer/src/i18n/ja-JP/artifacts.json │ height │
└────────────────────────────────────────────┴─────────┘
No invalid translations found!
Done in 0.06s.
ELIFECYCLE Command failed with exit code 1.
Checking formatting... |
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: 3
♻️ Duplicate comments (1)
src/renderer/src/i18n/en-US/artifacts.json (1)
40-44
: Move generic device/dimension labels to common.json and adopt hierarchical keys.These labels are app-wide, not artifact-specific. To maximize reuse and stay consistent with our i18n structure, place them under common.json using a dot namespace (e.g., common.device.desktop, common.dimension.width), then reference them from the UI. This also avoids duplicating the same keys across multiple artifacts.* files and locales.
Apply this diff here (remove the artifact-scoped copies):
- "desktop": "Desktop", - "tablet": "Tablet", - "mobile": "Mobile", - "width": "Width", - "height": "Height",Add these to src/renderer/src/i18n/en-US/common.json (create if missing):
{ "common": { "device": { "desktop": "Desktop", "tablet": "Tablet", "mobile": "Mobile" }, "dimension": { "width": "Width", "height": "Height" } } }Then update component usage, for example:
- t('common.device.desktop'), t('common.device.tablet'), t('common.device.mobile')
- t('common.dimension.width'), t('common.dimension.height')
Run to locate and update any artifact-scoped usages across locales and components:
#!/bin/bash # Find generic keys under artifacts.json across locales rg -nP '"(desktop|tablet|mobile|width|height)"\s*:' src/renderer/src/i18n/*/artifacts.json # Find UI references that may need to switch to common.* keys rg -nP --type=vue -C2 "\$t\(['\"](artifacts\.)?(desktop|tablet|mobile|width|height)['\"]\)"
🧹 Nitpick comments (1)
src/renderer/src/i18n/fr-FR/artifacts.json (1)
40-44
: French wording + key placement: consider “Ordinateur”; move shared keys to common.json
- Wording: “Desktop” as a device type is more idiomatically “Ordinateur” in FR UI. “Bureau” can be confused with the desktop workspace. Recommend switching to “Ordinateur”.
- i18n structure: Per our learning that shared strings belong in common.json, width/height are generic and likely reusable; move them to src/renderer/src/i18n/*/common.json and reference from there. Keep device labels under artifacts only if they are artifact-specific.
Apply this diff here (artifacts.json):
- "desktop": "Bureau", + "desktop": "Ordinateur", "tablet": "Tablette", "mobile": "Mobile", - "width": "Largeur", - "height": "Hauteur",And add to FR common.json (create or merge appropriately):
{ "width": "Largeur", "height": "Hauteur" }Remember to add equivalent entries to other locales’ common.json files and update component lookups accordingly (e.g., $t('common.width'), $t('common.height')).
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (10)
src/renderer/src/components/artifacts/ArtifactDialog.vue
(4 hunks)src/renderer/src/i18n/en-US/artifacts.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/ru-RU/artifacts.json
(1 hunks)src/renderer/src/i18n/zh-CN/artifacts.json
(1 hunks)src/renderer/src/i18n/zh-HK/artifacts.json
(1 hunks)src/renderer/src/i18n/zh-TW/artifacts.json
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
- src/renderer/src/i18n/zh-CN/artifacts.json
- src/renderer/src/i18n/zh-TW/artifacts.json
- src/renderer/src/i18n/zh-HK/artifacts.json
- src/renderer/src/i18n/ru-RU/artifacts.json
- src/renderer/src/i18n/ko-KR/artifacts.json
- src/renderer/src/components/artifacts/ArtifactDialog.vue
- src/renderer/src/i18n/fa-IR/artifacts.json
🧰 Additional context used
📓 Path-based instructions (1)
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/ja-JP/artifacts.json
src/renderer/src/i18n/en-US/artifacts.json
src/renderer/src/i18n/fr-FR/artifacts.json
🧠 Learnings (1)
📚 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/i18n/common.json : Shared translation keys must be placed in 'src/renderer/src/i18n/common.json'
Applied to files:
src/renderer/src/i18n/fr-FR/artifacts.json
🪛 Biome (2.1.2)
src/renderer/src/i18n/fr-FR/artifacts.json
[error] 39-39: The key copyImageFailedDesc was already declared.
This where a duplicated key was declared again.
If a key is defined multiple times, only the last definition takes effect. Previous definitions are ignored.
(lint/suspicious/noDuplicateObjectKeys)
"copyImageFailedDesc": "Impossible de copier des images dans le presse-papiers", | ||
"desktop": "Bureau", | ||
"tablet": "Tablette", | ||
"mobile": "Mobile", | ||
"width": "Largeur", | ||
"height": "Hauteur", | ||
"copyImageFailedDesc": "Impossible de copier l'image dans le presse-papiers", |
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.
Duplicate key "copyImageFailedDesc" — one entry will be ignored
JSON object has two copyImageFailedDesc keys; only the last (singular) takes effect and Biome flags this. Keep a single key to avoid confusion and CI noise.
Apply this diff to drop the earlier duplicate (plural) and keep the existing singular entry:
- "copyImageFailedDesc": "Impossible de copier des images dans le presse-papiers",
Alternative (if you truly need plural wording): introduce a distinct key and update call sites accordingly.
- "copyImageFailedDesc": "Impossible de copier des images dans le presse-papiers",
+ "copyImagesFailedDesc": "Impossible de copier des images dans le presse-papiers",
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
"copyImageFailedDesc": "Impossible de copier des images dans le presse-papiers", | |
"desktop": "Bureau", | |
"tablet": "Tablette", | |
"mobile": "Mobile", | |
"width": "Largeur", | |
"height": "Hauteur", | |
"copyImageFailedDesc": "Impossible de copier l'image dans le presse-papiers", | |
@@ src/renderer/src/i18n/fr-FR/artifacts.json | |
- "copyImageFailedDesc": "Impossible de copier des images dans le presse-papiers", | |
"desktop": "Bureau", | |
"tablet": "Tablette", | |
"mobile": "Mobile", | |
"width": "Largeur", | |
"height": "Hauteur", | |
"copyImageFailedDesc": "Impossible de copier l'image dans le presse-papiers", |
🧰 Tools
🪛 Biome (2.1.2)
[error] 39-39: The key copyImageFailedDesc was already declared.
This where a duplicated key was declared again.
If a key is defined multiple times, only the last definition takes effect. Previous definitions are ignored.
(lint/suspicious/noDuplicateObjectKeys)
🤖 Prompt for AI Agents
In src/renderer/src/i18n/fr-FR/artifacts.json around lines 39 to 45 there are
two duplicate keys "copyImageFailedDesc" (one plural and one singular) which
causes the first to be ignored; remove the earlier plural entry ("Impossible de
copier des images dans le presse-papiers") so only the existing singular key
"Impossible de copier l'image dans le presse-papiers" remains, or if plural
wording is actually needed, create a new distinct key (e.g.
"copyImagesFailedDesc") and update any call sites to use the new key.
"copyImageFailedDesc": "Impossible de copier des images dans le presse-papiers", | ||
"desktop": "Bureau", | ||
"tablet": "Tablette", | ||
"mobile": "Mobile", | ||
"width": "Largeur", | ||
"height": "Hauteur", |
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.
💡 Verification agent
🧩 Analysis chain
Verify cross-locale consistency and usage before merging
Ensure these keys exist consistently across locales and that components reference the right namespace (artifacts vs common). I can help automate the check.
Run:
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Check for duplicate keys in i18n JSON files =="
# Detect duplicate keys robustly using Python's object_pairs_hook
python - <<'PY'
import json, sys, os
from pathlib import Path
def find_dupes(p):
with open(p, 'r', encoding='utf-8') as f:
pairs = []
def hook(pairs_in):
pairs.extend(pairs_in)
return dict(pairs_in)
try:
json.load(f, object_pairs_hook=hook)
except json.JSONDecodeError as e:
print(f"[JSON ERROR] {p}: {e}")
return
seen, dupes = set(), set()
for k,_ in pairs:
if k in seen: dupes.add(k)
else: seen.add(k)
if dupes:
print(f"[DUPLICATES] {p}: {', '.join(sorted(dupes))}")
root = Path("src/renderer/src/i18n")
for fp in root.rglob("*.json"):
find_dupes(fp)
PY
echo
echo "== Scan for width/height keys in artifacts vs common =="
rg -n --glob 'src/renderer/src/i18n/**/{artifacts,common}.json' -e '\"(width|height)\"\s*:'
echo
echo "== Find usages in Vue components to confirm namespaces =="
rg -n --type vue -e "\$t\(['\"](artifacts|common)\.(desktop|tablet|mobile|width|height)['\"]\)"
Length of output: 14133
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Compare artifacts.json keys across locales =="
python - <<'PY'
import json
from pathlib import Path
root = Path("src/renderer/src/i18n")
locale_artifacts = {}
for locale_dir in root.iterdir():
if locale_dir.is_dir():
f = locale_dir / "artifacts.json"
if f.exists():
with f.open(encoding='utf-8') as src:
data = json.load(src)
locale_artifacts[locale_dir.name] = set(data.keys())
all_keys = set().union(*locale_artifacts.values())
print("Union of keys:", sorted(all_keys))
for locale, keys in locale_artifacts.items():
missing = sorted(all_keys - keys)
if missing:
print(f"Locale {locale} missing keys: {missing}")
PY
echo
echo "== Duplicates in fr-FR/artifacts.json =="
python - <<'PY'
import json
from collections import Counter
from pathlib import Path
pairs = []
def hook(pairs_in):
pairs.extend(pairs_in)
return dict(pairs_in)
f = Path("src/renderer/src/i18n/fr-FR/artifacts.json")
with f.open(encoding='utf-8') as src:
json.load(src, object_pairs_hook=hook)
dupes = sorted([k for k, count in Counter([k for k,_ in pairs]).items() if count > 1])
print(dupes or "None")
PY
echo
echo "== width/height in common.json per locale =="
python - <<'PY'
import json
from pathlib import Path
root = Path("src/renderer/src/i18n")
for locale_dir in root.iterdir():
if locale_dir.is_dir():
common = locale_dir / "common.json"
if common.exists():
data = json.load(common.open(encoding='utf-8'))
found = [k for k in ("width","height") if k in data]
if found:
print(f"{locale_dir.name}: {found}")
else:
print(f"{locale_dir.name}: common.json missing")
PY
echo
echo "== Vue component usages for artifacts vs common =="
rg -n --type vue "\$t\('artifacts\." || true
echo
rg -n --type vue "\$t\('common\." || true
Length of output: 1394
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== List first 20 .vue files =="
find src/renderer/src -type f -name '*.vue' | head -n 20
echo
echo "== Search for artifacts namespace usage for desktop, tablet, mobile, width, height == "
grep -RInE "\\$t\\(['\"]artifacts\\.(desktop|tablet|mobile|width|height)['\"]\\)" --include="*.vue" src/renderer/src || true
echo
echo "== Search for common namespace usage for desktop, tablet, mobile, width, height == "
grep -RInE "\\$t\\(['\"]common\\.(desktop|tablet|mobile|width|height)['\"]\\)" --include="*.vue" src/renderer/src || true
Length of output: 1114
Remove duplicate translation key in fr-FR artifacts.json
• src/renderer/src/i18n/fr-FR/artifacts.json
- The key
copyImageFailedDesc
appears twice. Please remove the extra entry to eliminate the duplicate.
• Cross-locale consistency
- All other
artifacts.json
files share the same set of keys (no missing translations).
• Namespace scope
- Neither
width
norheight
are present in anycommon.json
(they’re correctly scoped underartifacts.json
).
• Component usage
- No occurrences of
$t('artifacts.desktop')
,$t('artifacts.tablet')
,$t('artifacts.mobile')
,$t('artifacts.width')
or$t('artifacts.height')
were detected in.vue
files. Please verify that your components reference these keys (or update the code to use the proper namespace) before merging.
🧰 Tools
🪛 Biome (2.1.2)
[error] 39-39: The key copyImageFailedDesc was already declared.
This where a duplicated key was declared again.
If a key is defined multiple times, only the last definition takes effect. Previous definitions are ignored.
(lint/suspicious/noDuplicateObjectKeys)
🤖 Prompt for AI Agents
In src/renderer/src/i18n/fr-FR/artifacts.json around lines 39 to 44, the key
`copyImageFailedDesc` is duplicated; remove the extra `copyImageFailedDesc`
entry so each key appears only once, then run a quick JSON lint to ensure the
file remains valid and verify cross-locale parity; finally grep the codebase for
usages of `artifacts.desktop/tablet/mobile/width/height` and update any
component translation calls to the correct namespace if needed before
committing.
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: 2
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/HTMLArtifact.vue (1)
541-597
: Refactor setupIframe to apply viewport and style updates reactivelyThe current
setupIframe
only assigns anonload
handler, so changes toprops.viewportSize
or dimension props after the initial load won’t re-inject the meta tag or reset CSS. Factoring out anapplyViewport
helper and invoking it immediately whencontentDocument
exists—and again ononload
—ensures updates are applied both on mount and on subsequent prop changes. Additionally, dedupe the injected<style>
by assigning it a fixedid
.Affected locations in
src/renderer/src/components/artifacts/HTMLArtifact.vue
:
- setupIframe definition (≈ lines 541–561)
watch(() => props.viewportSize…)
(≈ lines 603–609)watch(() => [props.tabletWidth…])
(≈ lines 611–623)Proposed diff:
@@ src/renderer/src/components/artifacts/HTMLArtifact.vue -const setupIframe = () => { - if (props.isPreview && iframeRef.value) { - const iframe = iframeRef.value - iframe.onload = () => { - const doc = iframe.contentDocument - if (!doc) return - // …inject viewport & resetCSS… - } - } -} +const applyViewport = (doc: Document) => { + const size = props.viewportSize ?? 'desktop' + let content = 'width=device-width, initial-scale=1.0' + if (size === 'mobile') { + content = `width=${props.mobileWidth ?? 375}, initial-scale=1.0` + } else if (size === 'tablet') { + content = `width=${props.tabletWidth ?? 768}, initial-scale=1.0` + } + doc.querySelectorAll('meta[name="viewport"]').forEach(m => m.remove()) + const meta = doc.createElement('meta') + meta.name = 'viewport' + meta.content = content + doc.head.appendChild(meta) + + const RESET_ID = 'iframe-reset-style' + doc.getElementById(RESET_ID)?.remove() + const styleEl = doc.createElement('style') + styleEl.id = RESET_ID + styleEl.textContent = ` + *{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} + ` + doc.head.appendChild(styleEl) +} + +const setupIframe = () => { + if (!props.isPreview || !iframeRef.value) return + const iframe = iframeRef.value + const doc = iframe.contentDocument + if (doc) applyViewport(doc) + iframe.onload = () => { + iframe.contentDocument && applyViewport(iframe.contentDocument) + } +} @@ watch( () => props.viewportSize, () => { - setupIframe() + setupIframe() } ) @@ watch( () => [props.tabletWidth, props.mobileWidth, props.tabletHeight, props.mobileHeight], () => { if (resizeTimer) clearTimeout(resizeTimer) resizeTimer = setTimeout(() => { - setupIframe() + setupIframe() }, 100) } )
♻️ Duplicate comments (1)
src/renderer/src/components/artifacts/HTMLArtifact.vue (1)
24-42
: Make resize handles accessible (ARIA + keyboard), and localize labels.Handles are mouse-only and lack ARIA. Add role, orientation, tabindex, i18n’d aria-labels, and a keyboard handler (Left/Right/Up/Down with Shift for coarse steps). This was requested before; still applicable.
@@ - <div + <div v-if="viewportSize !== 'desktop'" class="absolute top-1/2 transform -translate-y-1/2 w-2 cursor-col-resize transition-all duration-200 group" + role="separator" + aria-orientation="vertical" + :aria-label="t('artifact.html.resize.left')" + tabindex="0" :class="{ 'bg-blue-500 bg-opacity-30': isDragging, 'hover:bg-blue-500 hover:bg-opacity-20': !isDragging }" - @mousedown="handleLeftDragStart" + @mousedown="handleLeftDragStart" + @keydown="handleKeyboardResize" @@ - <div + <div v-if="viewportSize !== 'desktop'" class="absolute top-1/2 transform -translate-y-1/2 w-2 cursor-col-resize transition-all duration-200 group" + role="separator" + aria-orientation="vertical" + :aria-label="t('artifact.html.resize.right')" + tabindex="0" :class="{ @@ - <div + <div v-if="viewportSize !== 'desktop'" class="absolute left-1/2 transform -translate-x-1/2 h-2 cursor-row-resize transition-all duration-200 group" + role="separator" + aria-orientation="horizontal" + :aria-label="t('artifact.html.resize.top')" + tabindex="0" @@ - <div + <div v-if="viewportSize !== 'desktop'" class="absolute left-1/2 transform -translate-x-1/2 h-2 cursor-row-resize transition-all duration-200 group" + role="separator" + aria-orientation="horizontal" + :aria-label="t('artifact.html.resize.bottom')" + tabindex="0" @@ - <div + <div v-if="viewportSize !== 'desktop'" class="absolute w-3 h-3 cursor-nw-resize transition-all duration-200 group" + role="separator" + :aria-label="t('artifact.html.resize.nw')" + tabindex="0" @mousedown="(e) => handleCornerDragStart(e, 'nw')" + @keydown="(e) => handleKeyboardResize(e, 'nw')" @@ - <div + <div v-if="viewportSize !== 'desktop'" class="absolute w-3 h-3 cursor-ne-resize transition-all duration-200 group" + role="separator" + :aria-label="t('artifact.html.resize.ne')" + tabindex="0" @mousedown="(e) => handleCornerDragStart(e, 'ne')" + @keydown="(e) => handleKeyboardResize(e, 'ne')" @@ - <div + <div v-if="viewportSize !== 'desktop'" class="absolute w-3 h-3 cursor-sw-resize transition-all duration-200 group" + role="separator" + :aria-label="t('artifact.html.resize.sw')" + tabindex="0" @mousedown="(e) => handleCornerDragStart(e, 'sw')" + @keydown="(e) => handleKeyboardResize(e, 'sw')" @@ - <div + <div v-if="viewportSize !== 'desktop'" class="absolute w-3 h-3 cursor-se-resize transition-all duration-200 group" + role="separator" + :aria-label="t('artifact.html.resize.se')" + tabindex="0" @mousedown="(e) => handleCornerDragStart(e, 'se')" + @keydown="(e) => handleKeyboardResize(e, 'se')"Add the handler and i18n usage:
@@ -import { ref, onMounted, computed, watch, onUnmounted } from 'vue' +import { ref, onMounted, computed, watch, onUnmounted } from 'vue' +import { useI18n } from 'vue-i18n' @@ +const { t } = useI18n() @@ +const handleKeyboardResize = (e: KeyboardEvent, corner?: 'nw'|'ne'|'sw'|'se') => { + if (props.viewportSize === 'desktop') return + const step = e.shiftKey ? 10 : 1 + const { minWidth, maxWidth, minHeight, maxHeight } = dragConstraints.value + let w = props.viewportSize === 'mobile' ? (props.mobileWidth ?? 390) : (props.tabletWidth ?? 680) + let h = props.viewportSize === 'mobile' ? (props.mobileHeight ?? 844) : (props.tabletHeight ?? 800) + switch (e.key) { + case 'ArrowLeft': w -= step; break + case 'ArrowRight': w += step; break + case 'ArrowUp': h -= step; break + case 'ArrowDown': h += step; break + default: return + } + w = Math.max(minWidth, Math.min(maxWidth, w)) + h = Math.max(minHeight, Math.min(maxHeight, h)) + e.preventDefault() + if (props.viewportSize === 'mobile') { + emit('update:mobileWidth', Math.round(w)) + emit('update:mobileHeight', Math.round(h)) + } else { + emit('update:tabletWidth', Math.round(w)) + emit('update:tabletHeight', Math.round(h)) + } +}Remember to add i18n keys (artifact.html.resize.*) for the aria-labels.
Also applies to: 45-62, 65-82, 85-102, 105-122, 125-142, 145-162, 165-182
🧹 Nitpick comments (5)
src/renderer/src/components/artifacts/HTMLArtifact.vue (5)
16-21
: Prefer Tailwind utilities over inline z-index style.Replace inline style with Tailwind for consistency.
- class="absolute inset-0 bg-transparent pointer-events-none" - :class="{ 'pointer-events-auto': isDragging }" - style="z-index: 1000" + class="absolute inset-0 bg-transparent pointer-events-none z-[1000]" + :class="{ 'pointer-events-auto': isDragging }"
265-309
: Reduce duplication in drag-start handlers.The left/right/top/bottom/corner handlers share common setup: set isDragging, direction, cache start positions/sizes, updateConstraints, mask, cursor, and listeners. Consolidate to a single startDrag({ axis|corner }) to reduce maintenance overhead.
Also applies to: 385-429, 455-483
285-287
: Consider Pointer Events for unified mouse/touch and more robust capture.Switching to pointerdown/pointermove/pointerup with setPointerCapture on the mask or document simplifies cross-input support and improves behavior on touch/pen devices.
I can draft a minimal pointer events patch if you want to support touch resizing in this PR.
Also applies to: 306-308, 403-405, 426-428, 479-481, 379-383
323-331
: Container-aware constraints for better UX.Constraints are based on window.innerWidth/Height, which may not match the scroll container width/height. Consider measuring the container rect to compute maxWidth/Height accurately.
- dragConstraints.value = { - minWidth: 320, - maxWidth: Math.min(window.innerWidth - 100, props.viewportSize === 'mobile' ? 480 : 1200), - minHeight: 426, - maxHeight: Math.min(window.innerHeight - 100, props.viewportSize === 'mobile' ? 1000 : 1400) - } + const host = iframeRef.value?.parentElement?.parentElement // container wrapping the iframe + const rect = host?.getBoundingClientRect() + dragConstraints.value = { + minWidth: 320, + maxWidth: Math.min((rect?.width ?? window.innerWidth) - 32, props.viewportSize === 'mobile' ? 480 : 1200), + minHeight: 426, + maxHeight: Math.min((rect?.height ?? window.innerHeight) - 32, props.viewportSize === 'mobile' ? 1000 : 1400) + }
217-223
: Use English for comments per repo guidelines.Several inline comments are in Chinese. Please convert them to concise English to match the codebase convention.
Example:
- “性能优化:缓存计算结果” → “Perf: cache computed constraints”
- “节流优化:使用更高效的方式” → “Throttle updates to ~125fps”
- “iframe遮罩控制函数” → “Iframe interaction mask control”
- “禁用iframe遮罩,恢复正常交互” → “Disable mask and restore interaction”
Also applies to: 311-314, 323-331, 333-343, 359-367, 373-383, 603-609, 611-624
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
src/renderer/src/components/artifacts/ArtifactDialog.vue
(4 hunks)src/renderer/src/components/artifacts/HTMLArtifact.vue
(4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/renderer/src/components/artifacts/ArtifactDialog.vue
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{ts,tsx,js,jsx,vue}
📄 CodeRabbit Inference Engine (CLAUDE.md)
Use English for logs and comments
Files:
src/renderer/src/components/artifacts/HTMLArtifact.vue
src/renderer/src/**/*.vue
📄 CodeRabbit Inference Engine (CLAUDE.md)
src/renderer/src/**/*.vue
: Use Composition API for all Vue 3 components
Use Tailwind CSS with scoped styles for styling
Organize components by feature in src/renderer/src/
Follow existing component patterns in src/renderer/src/ when creating new UI components
Use Composition API with proper TypeScript typing for new UI components
Implement responsive design with Tailwind CSS for new UI components
Add proper error handling and loading states for new UI componentsUse scoped styles to prevent CSS conflicts between components
Files:
src/renderer/src/components/artifacts/HTMLArtifact.vue
src/renderer/src/**/*.{ts,tsx,vue}
📄 CodeRabbit Inference Engine (CLAUDE.md)
src/renderer/src/**/*.{ts,tsx,vue}
: Use Pinia for frontend state management
Renderer to Main: Use usePresenter.ts composable for direct presenter method calls
Files:
src/renderer/src/components/artifacts/HTMLArtifact.vue
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/components/artifacts/HTMLArtifact.vue
src/renderer/**/*.{vue,ts,js,tsx,jsx}
📄 CodeRabbit Inference Engine (.cursor/rules/project-structure.mdc)
渲染进程代码放在
src/renderer
Files:
src/renderer/src/components/artifacts/HTMLArtifact.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/artifacts/HTMLArtifact.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/artifacts/HTMLArtifact.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/artifacts/HTMLArtifact.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.
Files:
src/renderer/src/components/artifacts/HTMLArtifact.vue
🧠 Learnings (3)
📚 Learning: 2025-07-21T01:45:33.790Z
Learnt from: CR
PR: ThinkInAIXYZ/deepchat#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-21T01:45:33.790Z
Learning: Applies to src/renderer/src/**/*.vue : Implement responsive design with Tailwind CSS for new UI components
Applied to files:
src/renderer/src/components/artifacts/HTMLArtifact.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/**/*.{vue} : Implement responsive design with Tailwind CSS; use a mobile-first approach.
Applied to files:
src/renderer/src/components/artifacts/HTMLArtifact.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/**/*.{vue} : Leverage ref, reactive, and computed for reactive state management.
Applied to files:
src/renderer/src/components/artifacts/HTMLArtifact.vue
🔇 Additional comments (2)
src/renderer/src/components/artifacts/HTMLArtifact.vue (2)
1-5
: Fix CI: run Prettier to resolve formatting check failures.CI flagged this file (and ArtifactDialog.vue). Please run the repo’s formatter locally and push the changes so the pipeline passes.
- If using pnpm (typical in this repo):
- pnpm prettier --write "src/renderer/src/components/artifacts/**/*.{vue,ts}"
- Or npm:
- npx prettier --write "src/renderer/src/components/artifacts/**/*.{vue,ts}"
232-244
: LGTM: viewport class/style computation is clear and typed.Computed classes and styles are straightforward, responsive, and align with Tailwind usage. Defaults are sensible.
Also applies to: 246-263
<iframe | ||
ref="iframeRef" | ||
:srcdoc="block.content" | ||
:class="viewportClasses" | ||
:style="viewportStyles" | ||
sandbox="allow-scripts allow-same-origin" | ||
></iframe> |
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.
🛠️ Refactor suggestion
Blocker: unsafe iframe sandbox + un-sanitized srcdoc (XSS / renderer escape risk).
You’re rendering arbitrary HTML via srcdoc and enabling both allow-scripts and allow-same-origin. This combo removes the unique-origin protection and lets the iframe run scripts in an origin that can access the parent if other constraints slip. DOMPurify is commented out, so content is not sanitized. In Electron (renderer), this is high risk even with sandbox.
Preferred fix (no same-origin, sanitize content, inject meta/styles before load):
- Build the final HTML in the parent using DOMParser + DOMPurify, inject the viewport meta and reset CSS into , then bind that to :srcdoc.
- Drop allow-same-origin so the preview stays isolated.
- This also obviates setupIframe, onload hooks, and watchers tied to it.
Apply this refactor:
@@
- <iframe
- ref="iframeRef"
- :srcdoc="block.content"
- :class="viewportClasses"
- :style="viewportStyles"
- sandbox="allow-scripts allow-same-origin"
- ></iframe>
+ <iframe
+ ref="iframeRef"
+ :srcdoc="iframeSrcdoc"
+ :class="viewportClasses"
+ :style="viewportStyles"
+ sandbox="allow-scripts"
+ ></iframe>
@@
-// import DOMPurify from 'dompurify'
+import DOMPurify from 'dompurify'
+import { useI18n } from 'vue-i18n'
@@
const iframeRef = ref<HTMLIFrameElement>()
@@
+// Build sanitized, augmented srcdoc safely in the parent context
+const iframeSrcdoc = computed(() => {
+ const raw = props.block?.content ?? ''
+ // Parse content so we can reliably inject into <head>
+ const parser = new DOMParser()
+ const doc = parser.parseFromString(raw, 'text/html')
+
+ // Ensure <head> exists
+ const head = doc.head ?? doc.createElement('head')
+ if (!doc.head) {
+ const htmlEl = doc.documentElement || doc.createElement('html')
+ htmlEl.appendChild(head)
+ if (!doc.documentElement) doc.appendChild(htmlEl)
+ }
+
+ // Compute viewport meta content based on selected viewport
+ const size = props.viewportSize ?? 'desktop'
+ let viewportContent = 'width=device-width, initial-scale=1.0'
+ if (size === 'mobile') {
+ const w = props.mobileWidth ?? 390
+ viewportContent = `width=${w}, initial-scale=1.0`
+ } else if (size === 'tablet') {
+ const w = props.tabletWidth ?? 680
+ viewportContent = `width=${w}, initial-scale=1.0`
+ }
+
+ // Replace existing viewport meta
+ doc.querySelectorAll('meta[name="viewport"]').forEach((m) => m.remove())
+ const viewportMeta = doc.createElement('meta')
+ viewportMeta.setAttribute('name', 'viewport')
+ viewportMeta.setAttribute('content', viewportContent)
+ head.appendChild(viewportMeta)
+
+ // Inject reset CSS (idempotent)
+ const RESET_ID = 'deepchat-iframe-reset'
+ doc.getElementById(RESET_ID)?.remove()
+ const style = doc.createElement('style')
+ style.id = RESET_ID
+ style.textContent = `
+ * { 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; }
+ `
+ head.appendChild(style)
+
+ // Sanitize full document HTML
+ const sanitized = DOMPurify.sanitize(doc.documentElement.outerHTML, {
+ WHOLE_DOCUMENT: true,
+ RETURN_TRUSTED_TYPE: false,
+ // Keep <script> if you explicitly want scripts to run; otherwise forbid them:
+ FORBID_TAGS: [], // or ['script'] to disable scripts inside preview
+ // Consider forbidding inline handlers if scripts are allowed:
+ // FORBID_ATTR: ['onerror','onload','onclick', 'onmouseover', 'onfocus', 'onblur']
+ })
+ return sanitized
+})
@@
-const setupIframe = () => {
- if (props.isPreview && iframeRef.value) {
- const iframe = iframeRef.value
- iframe.onload = () => {
- const doc = iframe.contentDocument
- if (!doc) return
- // 添加视口元标签
- const viewportSize = props.viewportSize || 'desktop'
- let viewportContent = 'width=device-width, initial-scale=1.0'
- if (viewportSize === 'mobile') {
- const width = props.mobileWidth || 375
- viewportContent = `width=${width}, initial-scale=1.0`
- } else if (viewportSize === 'tablet') {
- const width = props.tabletWidth || 768
- viewportContent = `width=${width}, initial-scale=1.0`
- }
- // 移除现有的视口元标签
- const existingViewport = doc.querySelector('meta[name="viewport"]')
- if (existingViewport) {
- existingViewport.remove()
- }
- // 添加新的视口元标签
- const viewportMeta = doc.createElement('meta')
- viewportMeta.name = 'viewport'
- viewportMeta.content = viewportContent
- doc.head.appendChild(viewportMeta)
- // 添加基础样式
- const resetCSS = `...`
- const styleElement = doc.createElement('style')
- styleElement.textContent = resetCSS
- doc.head.appendChild(styleElement)
- }
- }
-}
+// setupIframe no longer needed when srcdoc is pre-built; remove it
@@
-onMounted(() => {
- setupIframe()
-})
+// No onload hooks needed; srcdoc updates reactively via computed
@@
-// 监听视口尺寸变化
-watch(
- () => props.viewportSize,
- () => {
- setupIframe()
- }
-)
+// Not needed; iframeSrcdoc recomputes on prop changes
@@
-// 监听尺寸变化 - 防抖优化
-let resizeTimer: NodeJS.Timeout | null = null
-watch(
- () => [props.tabletWidth, props.mobileWidth, props.tabletHeight, props.mobileHeight],
- () => {
- if (resizeTimer) {
- clearTimeout(resizeTimer)
- }
- resizeTimer = setTimeout(() => {
- setupIframe()
- }, 100) // 100ms 防抖延迟
- }
-)
+// Not needed; iframeSrcdoc recomputes on prop changes
If you must keep script execution inside the preview, keep allow-scripts, but strongly avoid allow-same-origin and ensure robust sanitization. If you absolutely need same-origin access for other features, we need a threat model discussion.
Would you like me to follow through with a PR-ready commit for this refactor, and add minimal i18n keys for future ARIA text?
Also applies to: 541-597, 603-623
🤖 Prompt for AI Agents
In src/renderer/src/components/artifacts/HTMLArtifact.vue around lines 8 to 14,
you're currently binding raw block.content to the iframe srcdoc while using
sandbox="allow-scripts allow-same-origin" which creates an XSS/escape risk; fix
by sanitizing and constructing the full HTML string in the parent: parse
block.content with DOMParser, run it through DOMPurify, inject a <meta
name="viewport"> and reset CSS (and any minimal ARIA text) into the <head>, then
bind that sanitized full-document string to :srcdoc; remove allow-same-origin
from the sandbox attribute (keep allow-scripts only if you must run scripts) and
eliminate setupIframe/onload/watchers that attempted parent-iframe interactions
since same-origin is removed.
let resizeTimer: NodeJS.Timeout | null = null | ||
watch( | ||
() => [props.tabletWidth, props.mobileWidth, props.tabletHeight, props.mobileHeight], | ||
() => { | ||
if (resizeTimer) { | ||
clearTimeout(resizeTimer) | ||
} | ||
resizeTimer = setTimeout(() => { | ||
setupIframe() | ||
}, 100) // 100ms 防抖延迟 | ||
} |
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.
TS type fix: NodeJS.Timeout is wrong in the DOM context.
In browser/renderer builds, setTimeout returns number, not NodeJS.Timeout. Use ReturnType.
-let resizeTimer: NodeJS.Timeout | null = null
+let resizeTimer: ReturnType<typeof setTimeout> | null = null
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
let resizeTimer: NodeJS.Timeout | null = null | |
watch( | |
() => [props.tabletWidth, props.mobileWidth, props.tabletHeight, props.mobileHeight], | |
() => { | |
if (resizeTimer) { | |
clearTimeout(resizeTimer) | |
} | |
resizeTimer = setTimeout(() => { | |
setupIframe() | |
}, 100) // 100ms 防抖延迟 | |
} | |
let resizeTimer: ReturnType<typeof setTimeout> | null = null | |
watch( | |
() => [props.tabletWidth, props.mobileWidth, props.tabletHeight, props.mobileHeight], | |
() => { | |
if (resizeTimer) { | |
clearTimeout(resizeTimer) | |
} | |
resizeTimer = setTimeout(() => { | |
setupIframe() | |
}, 100) // 100ms 防抖延迟 | |
} | |
) |
🤖 Prompt for AI Agents
In src/renderer/src/components/artifacts/HTMLArtifact.vue around lines 612 to
622, the timer variable is typed as NodeJS.Timeout which is incorrect in a
browser/renderer context; change the declaration to use ReturnType<typeof
setTimeout> | null (or simply number | null) and keep clearTimeout/respective
usage as-is so the type matches the DOM setTimeout return value and avoids Node
types leaking into the frontend code.
新增了HTML预览的尺寸切换,尺寸边界可拖动。
测试平台:
界面如下:
桌面尺寸:



平板尺寸:
手机尺寸:
Summary by CodeRabbit
New Features
Localization