-
Notifications
You must be signed in to change notification settings - Fork 614
feat(ui): new prompt input and layout wip #965
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
Conversation
WalkthroughUpdates provider model schema to structured reasoning/search, adjusts sanitization and presenter mapping, and derives renderer defaults accordingly. Replaces ChatInput with new PromptInput featuring model chooser, files, and rate-limit/search controls. Adds numerous shadcn UI components/demos and updates settings shortcut UI. Minor layout tweaks and a dependency bump. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant ProvDB as Provider DB JSON
participant San as Sanitizer (scripts/fetch-provider-db.mjs)
participant Types as Model Types (shared/types)
participant Presenter as ConfigPresenter (main)
participant Renderer as Renderer Stores/UI
ProvDB->>San: Aggregate models (reasoning/search fields)
San->>Types: Validate via Zod (ReasoningSchema, SearchSchema)
San-->>Presenter: Sanitized provider models
Presenter->>Presenter: Map reasoning.supported, search.supported
Presenter-->>Renderer: Model meta with derived defaults (budget/strategy)
Renderer->>Renderer: Populate chat config defaults
sequenceDiagram
autonumber
participant User as User
participant PI as PromptInput.vue
participant RL as RateLimit Presenter
participant Chat as Chat Store
User->>PI: Type / paste / drag files
PI->>PI: Build editor content + file list
PI->>RL: Poll rate-limit status (on mount / interval)
User->>PI: Press Enter / click Send
PI->>Chat: emit send({ text, files, search, think })
Chat-->>PI: Ack and reset editor/files
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
* chore(shadcn): sync new ui components * chore(playground): showcase new shadcn suites * feat: replace kbg and kb for ShortcutSettings
|
@codex review |
|
Codex Review: Didn't find any major issues. Hooray! ℹ️ 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 |
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: 16
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
src/renderer/src/components/settings/ShortcutSettings.vue (1)
399-404: Re-enable shortcuts after canceling recording.When recording is canceled (Line 403), we never call
shortcutKeyStore.enableShortcutKey(), so global shortcuts remain disabled until another save path runs. Please re-enable the shortcuts when canceling.const cancelRecording = () => { tempShortcut.value = '' shortcutError.value = '' - stopRecording() + stopRecording() + shortcutKeyStore.enableShortcutKey() }src/renderer/src/components/ChatView.vue (1)
12-19: Expose restoreFocus() in PromptInput.vue
The component currently only exposesclearContent. Define arestoreFocus()method and include it indefineExpose(or remove the calls in ChatView.vue at lines 53, 77, 84) to avoid undefined-method errors.src/renderer/src/views/ChatTabView.vue (1)
114-121: Replace hardcoded string with i18n key.Line 119 contains a hardcoded user-facing string
'New Chat'. According to the coding guidelines, all user-facing strings insrc/renderer/src/**/*must use i18n keys via vue-i18n.Apply this diff to use i18n:
+import { useI18n } from 'vue-i18n' + +const { t } = useI18n() + const updateTitle = () => { const activeThread = chatStore.activeThread if (activeThread) { title.value = activeThread.title } else { - title.value = 'New Chat' + title.value = t('chat.newChat') // or appropriate i18n key } }As per coding guidelines.
src/renderer/src/views/PlaygroundTabView.vue (1)
56-195: Localize newly added playground copyAll user-facing strings in the renderer must come from vue-i18n resources. The new demo titles, descriptions, and section labels introduced here are hard-coded and bypass localization. Please move them into the translation files and reference the corresponding keys instead. As per coding guidelines
🧹 Nitpick comments (13)
src/shadcn/components/ui/kbd/KbdGroup.vue (1)
2-7: Switch imports to single quotes for guideline compliance.Our renderer codebase requires single-quoted imports (Prettier rule). Please swap the double quotes on Line 2 (and the HTMLAttributes type annotation) to single quotes to stay consistent.
src/shadcn/components/ui/kbd/Kbd.vue (1)
2-7: Use single quotes in import statements.Prettier config expects single-quoted module specifiers. Update the imports here to use single quotes so the file formats cleanly with our standard tooling.
src/shadcn/components/ui/empty/Empty.vue (1)
13-16: Consider breaking the class string for readability.Line 14 exceeds the 100-character width limit specified in the Prettier formatting guidelines.
Apply this diff to improve readability:
:class="cn( - 'flex min-w-0 flex-1 flex-col items-center justify-center gap-6 text-balance rounded-lg border-dashed p-6 text-center md:p-12', + 'flex min-w-0 flex-1 flex-col items-center justify-center gap-6', + 'text-balance rounded-lg border-dashed p-6 text-center md:p-12', props.class, )"src/shadcn/components/ui/field/FieldTitle.vue (1)
13-16: Consider breaking the class string for readability.Line 14 exceeds the 100-character width limit. Additionally, note that the
data-slotattribute uses "field-label" while the component is named "FieldTitle", which might cause minor confusion.Apply this diff to improve line length:
:class="cn( - 'flex w-fit items-center gap-2 text-sm leading-snug font-medium group-data-[disabled=true]/field:opacity-50', + 'flex w-fit items-center gap-2 text-sm leading-snug font-medium', + 'group-data-[disabled=true]/field:opacity-50', props.class, )"src/shadcn/components/ui/input-group/InputGroupText.vue (1)
12-15: Consider breaking the class string for readability.Line 13 significantly exceeds the 100-character width limit specified in the Prettier formatting guidelines.
Apply this diff to improve readability:
:class="cn( - 'text-muted-foreground flex items-center gap-2 text-sm [&_svg]:pointer-events-none [&_svg:not([class*=\'size-\'])]:size-4', + 'text-muted-foreground flex items-center gap-2 text-sm', + '[&_svg]:pointer-events-none [&_svg:not([class*=\'size-\'])]:size-4', props.class, )"src/renderer/src/views/playground/demos/ButtonGroupDemo.vue (1)
1-30: Consider using i18n for user-facing strings.The demo contains hardcoded user-facing strings (e.g., "Day", "Week", "Month", "Year", "Team access", etc.). While this is a demo file, the coding guidelines require all user-facing strings in
src/renderer/src/**/*to use i18n keys via vue-i18n.Consider extracting these strings to i18n translation files for consistency with the project's internationalization approach.
As per coding guidelines.
src/shadcn/components/ui/field/FieldLegend.vue (1)
5-8: Consider adding a default variant.The
variantprop has no default value. If this component is commonly used with one variant more than the other, consider setting a default usingwithDefaultsto improve ergonomics and reduce boilerplate at call sites.Example:
-const props = defineProps<{ +const props = withDefaults(defineProps<{ class?: HTMLAttributes["class"] variant?: "legend" | "label" -}>() +}>(), { + variant: "legend" +})src/shadcn/components/ui/item/ItemMedia.vue (1)
7-10: Consider adding a default variant.Similar to FieldLegend, the
variantprop lacks a default value. If one variant is more commonly used, consider setting it as the default usingwithDefaultsfor better developer experience.Example:
-const props = defineProps<{ +const props = withDefaults(defineProps<{ class?: HTMLAttributes["class"] variant?: ItemMediaVariants["variant"] -}>() +}>(), { + variant: "default" // or whatever the common case is +})src/shadcn/components/ui/field/Field.vue (1)
7-10: Consider adding a default orientation.The
orientationprop has no default value. Since field groups typically have a common orientation (likely "vertical" or "horizontal"), setting a default would improve usability and reduce the need for explicit prop passing in common cases.Example:
-const props = defineProps<{ +const props = withDefaults(defineProps<{ class?: HTMLAttributes["class"] orientation?: FieldVariants["orientation"] -}>() +}>(), { + orientation: "vertical" // or the most common case +})src/shadcn/components/ui/input-group/InputGroupAddon.vue (1)
14-23: Simplify redundant truthy check.Line 20 contains a redundant check:
currentTarget && currentTarget?.parentElement. SincecurrentTargetis already confirmed to be truthy before the optional chaining operator, the initial check is unnecessary.Apply this diff:
- if (currentTarget && currentTarget?.parentElement) { - currentTarget.parentElement?.querySelector("input")?.focus() + if (currentTarget?.parentElement) { + currentTarget.parentElement.querySelector("input")?.focus() }Note: Once you verify
currentTarget?.parentElementexists, you don't need optional chaining on the second reference since we're inside the if block.src/shared/types/model-db.ts (1)
125-150: Consider validating search_strategy against known values.The
getSearchhelper accepts any string forsearch_strategy. If there's a known set of valid strategies (e.g., 'turbo', 'max'), consider validating against that set to catch configuration errors early.Example validation:
function getSearch(obj: unknown): ProviderModel['search'] { if (!isRecord(obj)) return undefined const supported = getBoolean(obj, 'supported') const forced_search = getBoolean(obj, 'forced_search') const search_strategy = getString(obj, 'search_strategy') + // Optional: validate known strategies + if (search_strategy !== undefined && !['turbo', 'max'].includes(search_strategy)) { + console.warn(`Unknown search_strategy: ${search_strategy}`) + } if (supported !== undefined || forced_search !== undefined || search_strategy !== undefined) { return { supported, forced_search, search_strategy } } return undefined }src/shadcn/components/ui/button-group/index.ts (1)
8-23: Format long class strings to respect max width guideline.The base class string (line 9) and variant class strings (lines 13-16) exceed the 100-character max width specified in the Prettier coding guidelines. Consider breaking these into multiple lines for better readability and compliance.
As per coding guidelines: "Apply Prettier formatting: single quotes, no semicolons, max width 100"
Apply this diff to improve formatting:
export const buttonGroupVariants = cva( - "flex w-fit items-stretch [&>*]:focus-visible:z-10 [&>*]:focus-visible:relative [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md has-[>[data-slot=button-group]]:gap-2", + [ + 'flex w-fit items-stretch', + '[&>*]:focus-visible:z-10 [&>*]:focus-visible:relative', + "[&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit", + '[&>input]:flex-1', + 'has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md', + 'has-[>[data-slot=button-group]]:gap-2' + ].join(' '), { variants: { orientation: { - horizontal: - "[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none", - vertical: - "flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none", + horizontal: [ + '[&>*:not(:first-child)]:rounded-l-none', + '[&>*:not(:first-child)]:border-l-0', + '[&>*:not(:last-child)]:rounded-r-none' + ].join(' '), + vertical: [ + 'flex-col', + '[&>*:not(:first-child)]:rounded-t-none', + '[&>*:not(:first-child)]:border-t-0', + '[&>*:not(:last-child)]:rounded-b-none' + ].join(' ') }, }, defaultVariants: { - orientation: "horizontal", + orientation: 'horizontal' }, }, )src/shadcn/components/ui/input-group/InputGroup.vue (1)
1-35: LGTM with a minor formatting suggestion.The component correctly implements the input group wrapper with proper role attribute and comprehensive state styling. The class composition handles focus, error, and alignment variants appropriately.
Optional: Consider breaking up the very long class strings (lines 15, 19-22, 25, 28) for better readability, though this is common in Tailwind-heavy components:
:class="cn( - 'group/input-group border-input dark:bg-input/30 relative flex w-full items-center rounded-md border shadow-xs transition-[color,box-shadow] outline-none', + [ + 'group/input-group border-input dark:bg-input/30', + 'relative flex w-full items-center rounded-md border shadow-xs', + 'transition-[color,box-shadow] outline-none' + ].join(' '), 'h-9 min-w-0 has-[>textarea]:h-auto',
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (69)
package.json(1 hunks)scripts/fetch-provider-db.mjs(2 hunks)scripts/update-shadcn.js(1 hunks)src/main/presenter/configPresenter/index.ts(1 hunks)src/main/presenter/configPresenter/modelConfig.ts(1 hunks)src/renderer/src/components/ChatView.vue(2 hunks)src/renderer/src/components/TitleView.vue(1 hunks)src/renderer/src/components/message/MessageList.vue(1 hunks)src/renderer/src/components/prompt-input/ModelChooser.vue(1 hunks)src/renderer/src/components/prompt-input/PromptInput.vue(1 hunks)src/renderer/src/components/settings/ShortcutSettings.vue(4 hunks)src/renderer/src/views/ChatTabView.vue(2 hunks)src/renderer/src/views/PlaygroundTabView.vue(5 hunks)src/renderer/src/views/playground/demos/ButtonGroupDemo.vue(1 hunks)src/renderer/src/views/playground/demos/EmptyDemo.vue(1 hunks)src/renderer/src/views/playground/demos/FieldDemo.vue(1 hunks)src/renderer/src/views/playground/demos/InputGroupDemo.vue(1 hunks)src/renderer/src/views/playground/demos/ItemDemo.vue(1 hunks)src/renderer/src/views/playground/demos/KbdDemo.vue(1 hunks)src/renderer/src/views/playground/demos/SpinnerDemo.vue(1 hunks)src/shadcn/components/ui/button-group/ButtonGroup.vue(1 hunks)src/shadcn/components/ui/button-group/ButtonGroupSeparator.vue(1 hunks)src/shadcn/components/ui/button-group/ButtonGroupText.vue(1 hunks)src/shadcn/components/ui/button-group/index.ts(1 hunks)src/shadcn/components/ui/button/index.ts(1 hunks)src/shadcn/components/ui/empty/Empty.vue(1 hunks)src/shadcn/components/ui/empty/EmptyContent.vue(1 hunks)src/shadcn/components/ui/empty/EmptyDescription.vue(1 hunks)src/shadcn/components/ui/empty/EmptyHeader.vue(1 hunks)src/shadcn/components/ui/empty/EmptyMedia.vue(1 hunks)src/shadcn/components/ui/empty/EmptyTitle.vue(1 hunks)src/shadcn/components/ui/empty/index.ts(1 hunks)src/shadcn/components/ui/field/Field.vue(1 hunks)src/shadcn/components/ui/field/FieldContent.vue(1 hunks)src/shadcn/components/ui/field/FieldDescription.vue(1 hunks)src/shadcn/components/ui/field/FieldError.vue(1 hunks)src/shadcn/components/ui/field/FieldGroup.vue(1 hunks)src/shadcn/components/ui/field/FieldLabel.vue(1 hunks)src/shadcn/components/ui/field/FieldLegend.vue(1 hunks)src/shadcn/components/ui/field/FieldSeparator.vue(1 hunks)src/shadcn/components/ui/field/FieldSet.vue(1 hunks)src/shadcn/components/ui/field/FieldTitle.vue(1 hunks)src/shadcn/components/ui/field/index.ts(1 hunks)src/shadcn/components/ui/input-group/InputGroup.vue(1 hunks)src/shadcn/components/ui/input-group/InputGroupAddon.vue(1 hunks)src/shadcn/components/ui/input-group/InputGroupButton.vue(1 hunks)src/shadcn/components/ui/input-group/InputGroupInput.vue(1 hunks)src/shadcn/components/ui/input-group/InputGroupText.vue(1 hunks)src/shadcn/components/ui/input-group/InputGroupTextarea.vue(1 hunks)src/shadcn/components/ui/input-group/index.ts(1 hunks)src/shadcn/components/ui/item/Item.vue(1 hunks)src/shadcn/components/ui/item/ItemActions.vue(1 hunks)src/shadcn/components/ui/item/ItemContent.vue(1 hunks)src/shadcn/components/ui/item/ItemDescription.vue(1 hunks)src/shadcn/components/ui/item/ItemFooter.vue(1 hunks)src/shadcn/components/ui/item/ItemGroup.vue(1 hunks)src/shadcn/components/ui/item/ItemHeader.vue(1 hunks)src/shadcn/components/ui/item/ItemMedia.vue(1 hunks)src/shadcn/components/ui/item/ItemSeparator.vue(1 hunks)src/shadcn/components/ui/item/ItemTitle.vue(1 hunks)src/shadcn/components/ui/item/index.ts(1 hunks)src/shadcn/components/ui/kbd/Kbd.vue(1 hunks)src/shadcn/components/ui/kbd/KbdGroup.vue(1 hunks)src/shadcn/components/ui/kbd/index.ts(1 hunks)src/shadcn/components/ui/separator/Separator.vue(1 hunks)src/shadcn/components/ui/spinner/Spinner.vue(1 hunks)src/shadcn/components/ui/spinner/index.ts(1 hunks)src/shared/types/model-db.ts(4 hunks)test/main/presenter/providerDbModelConfig.test.ts(3 hunks)
🧰 Additional context used
📓 Path-based instructions (29)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development-setup.mdc)
**/*.{js,jsx,ts,tsx}: 使用 OxLint 进行代码检查
Log和注释使用英文书写
**/*.{js,jsx,ts,tsx}: Use OxLint for JS/TS code; pre-commit hooks run lint-staged and typecheck
Use camelCase for variables and functions
Use PascalCase for types and classes
Use SCREAMING_SNAKE_CASE for constants
Files:
src/shadcn/components/ui/spinner/index.tsscripts/update-shadcn.jssrc/shared/types/model-db.tssrc/shadcn/components/ui/empty/index.tssrc/shadcn/components/ui/kbd/index.tssrc/shadcn/components/ui/item/index.tssrc/shadcn/components/ui/field/index.tssrc/shadcn/components/ui/button-group/index.tssrc/main/presenter/configPresenter/modelConfig.tssrc/main/presenter/configPresenter/index.tssrc/shadcn/components/ui/button/index.tssrc/shadcn/components/ui/input-group/index.tstest/main/presenter/providerDbModelConfig.test.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/error-logging.mdc)
**/*.{ts,tsx}: 始终使用 try-catch 处理可能的错误
提供有意义的错误信息
记录详细的错误日志
优雅降级处理
日志应包含时间戳、日志级别、错误代码、错误描述、堆栈跟踪(如适用)、相关上下文信息
日志级别应包括 ERROR、WARN、INFO、DEBUG
不要吞掉错误
提供用户友好的错误信息
实现错误重试机制
避免记录敏感信息
使用结构化日志
设置适当的日志级别
Files:
src/shadcn/components/ui/spinner/index.tssrc/shared/types/model-db.tssrc/shadcn/components/ui/empty/index.tssrc/shadcn/components/ui/kbd/index.tssrc/shadcn/components/ui/item/index.tssrc/shadcn/components/ui/field/index.tssrc/shadcn/components/ui/button-group/index.tssrc/main/presenter/configPresenter/modelConfig.tssrc/main/presenter/configPresenter/index.tssrc/shadcn/components/ui/button/index.tssrc/shadcn/components/ui/input-group/index.tstest/main/presenter/providerDbModelConfig.test.ts
**/*.{ts,tsx,js,vue}
📄 CodeRabbit inference engine (CLAUDE.md)
Use English for all logs and comments
Files:
src/shadcn/components/ui/spinner/index.tssrc/shadcn/components/ui/field/FieldSet.vuescripts/update-shadcn.jssrc/shadcn/components/ui/input-group/InputGroupTextarea.vuesrc/shadcn/components/ui/separator/Separator.vuesrc/shared/types/model-db.tssrc/shadcn/components/ui/item/ItemMedia.vuesrc/renderer/src/components/message/MessageList.vuesrc/shadcn/components/ui/field/FieldGroup.vuesrc/shadcn/components/ui/button-group/ButtonGroup.vuesrc/shadcn/components/ui/button-group/ButtonGroupText.vuesrc/shadcn/components/ui/input-group/InputGroupText.vuesrc/shadcn/components/ui/item/ItemFooter.vuesrc/shadcn/components/ui/input-group/InputGroupInput.vuesrc/shadcn/components/ui/empty/EmptyTitle.vuesrc/renderer/src/components/settings/ShortcutSettings.vuesrc/shadcn/components/ui/empty/index.tssrc/shadcn/components/ui/field/FieldError.vuesrc/shadcn/components/ui/kbd/index.tssrc/shadcn/components/ui/empty/Empty.vuesrc/shadcn/components/ui/field/FieldLabel.vuesrc/shadcn/components/ui/item/Item.vuesrc/shadcn/components/ui/item/ItemContent.vuesrc/shadcn/components/ui/item/index.tssrc/shadcn/components/ui/field/FieldDescription.vuesrc/shadcn/components/ui/field/index.tssrc/shadcn/components/ui/item/ItemTitle.vuesrc/renderer/src/components/prompt-input/PromptInput.vuesrc/shadcn/components/ui/button-group/index.tssrc/renderer/src/components/prompt-input/ModelChooser.vuesrc/shadcn/components/ui/empty/EmptyHeader.vuesrc/renderer/src/views/playground/demos/InputGroupDemo.vuesrc/main/presenter/configPresenter/modelConfig.tssrc/shadcn/components/ui/field/FieldSeparator.vuesrc/shadcn/components/ui/field/FieldContent.vuesrc/shadcn/components/ui/button-group/ButtonGroupSeparator.vuesrc/main/presenter/configPresenter/index.tssrc/renderer/src/views/playground/demos/ButtonGroupDemo.vuesrc/renderer/src/components/ChatView.vuesrc/shadcn/components/ui/spinner/Spinner.vuesrc/shadcn/components/ui/field/FieldLegend.vuesrc/shadcn/components/ui/input-group/InputGroupAddon.vuesrc/renderer/src/views/playground/demos/FieldDemo.vuesrc/shadcn/components/ui/item/ItemHeader.vuesrc/shadcn/components/ui/item/ItemSeparator.vuesrc/shadcn/components/ui/kbd/Kbd.vuesrc/shadcn/components/ui/item/ItemDescription.vuesrc/shadcn/components/ui/input-group/InputGroupButton.vuesrc/renderer/src/views/playground/demos/SpinnerDemo.vuesrc/shadcn/components/ui/kbd/KbdGroup.vuesrc/renderer/src/views/playground/demos/EmptyDemo.vuesrc/shadcn/components/ui/button/index.tssrc/shadcn/components/ui/item/ItemGroup.vuesrc/shadcn/components/ui/input-group/index.tssrc/shadcn/components/ui/item/ItemActions.vuesrc/shadcn/components/ui/field/Field.vuesrc/shadcn/components/ui/empty/EmptyMedia.vuesrc/renderer/src/views/ChatTabView.vuesrc/renderer/src/views/playground/demos/ItemDemo.vuesrc/renderer/src/views/playground/demos/KbdDemo.vuesrc/shadcn/components/ui/input-group/InputGroup.vuesrc/renderer/src/views/PlaygroundTabView.vuesrc/shadcn/components/ui/empty/EmptyContent.vuetest/main/presenter/providerDbModelConfig.test.tssrc/shadcn/components/ui/empty/EmptyDescription.vuesrc/renderer/src/components/TitleView.vuesrc/shadcn/components/ui/field/FieldTitle.vue
**/*.{ts,tsx,vue}
📄 CodeRabbit inference engine (CLAUDE.md)
Enable and adhere to strict TypeScript typing (avoid implicit any, prefer precise types)
Files:
src/shadcn/components/ui/spinner/index.tssrc/shadcn/components/ui/field/FieldSet.vuesrc/shadcn/components/ui/input-group/InputGroupTextarea.vuesrc/shadcn/components/ui/separator/Separator.vuesrc/shared/types/model-db.tssrc/shadcn/components/ui/item/ItemMedia.vuesrc/renderer/src/components/message/MessageList.vuesrc/shadcn/components/ui/field/FieldGroup.vuesrc/shadcn/components/ui/button-group/ButtonGroup.vuesrc/shadcn/components/ui/button-group/ButtonGroupText.vuesrc/shadcn/components/ui/input-group/InputGroupText.vuesrc/shadcn/components/ui/item/ItemFooter.vuesrc/shadcn/components/ui/input-group/InputGroupInput.vuesrc/shadcn/components/ui/empty/EmptyTitle.vuesrc/renderer/src/components/settings/ShortcutSettings.vuesrc/shadcn/components/ui/empty/index.tssrc/shadcn/components/ui/field/FieldError.vuesrc/shadcn/components/ui/kbd/index.tssrc/shadcn/components/ui/empty/Empty.vuesrc/shadcn/components/ui/field/FieldLabel.vuesrc/shadcn/components/ui/item/Item.vuesrc/shadcn/components/ui/item/ItemContent.vuesrc/shadcn/components/ui/item/index.tssrc/shadcn/components/ui/field/FieldDescription.vuesrc/shadcn/components/ui/field/index.tssrc/shadcn/components/ui/item/ItemTitle.vuesrc/renderer/src/components/prompt-input/PromptInput.vuesrc/shadcn/components/ui/button-group/index.tssrc/renderer/src/components/prompt-input/ModelChooser.vuesrc/shadcn/components/ui/empty/EmptyHeader.vuesrc/renderer/src/views/playground/demos/InputGroupDemo.vuesrc/main/presenter/configPresenter/modelConfig.tssrc/shadcn/components/ui/field/FieldSeparator.vuesrc/shadcn/components/ui/field/FieldContent.vuesrc/shadcn/components/ui/button-group/ButtonGroupSeparator.vuesrc/main/presenter/configPresenter/index.tssrc/renderer/src/views/playground/demos/ButtonGroupDemo.vuesrc/renderer/src/components/ChatView.vuesrc/shadcn/components/ui/spinner/Spinner.vuesrc/shadcn/components/ui/field/FieldLegend.vuesrc/shadcn/components/ui/input-group/InputGroupAddon.vuesrc/renderer/src/views/playground/demos/FieldDemo.vuesrc/shadcn/components/ui/item/ItemHeader.vuesrc/shadcn/components/ui/item/ItemSeparator.vuesrc/shadcn/components/ui/kbd/Kbd.vuesrc/shadcn/components/ui/item/ItemDescription.vuesrc/shadcn/components/ui/input-group/InputGroupButton.vuesrc/renderer/src/views/playground/demos/SpinnerDemo.vuesrc/shadcn/components/ui/kbd/KbdGroup.vuesrc/renderer/src/views/playground/demos/EmptyDemo.vuesrc/shadcn/components/ui/button/index.tssrc/shadcn/components/ui/item/ItemGroup.vuesrc/shadcn/components/ui/input-group/index.tssrc/shadcn/components/ui/item/ItemActions.vuesrc/shadcn/components/ui/field/Field.vuesrc/shadcn/components/ui/empty/EmptyMedia.vuesrc/renderer/src/views/ChatTabView.vuesrc/renderer/src/views/playground/demos/ItemDemo.vuesrc/renderer/src/views/playground/demos/KbdDemo.vuesrc/shadcn/components/ui/input-group/InputGroup.vuesrc/renderer/src/views/PlaygroundTabView.vuesrc/shadcn/components/ui/empty/EmptyContent.vuetest/main/presenter/providerDbModelConfig.test.tssrc/shadcn/components/ui/empty/EmptyDescription.vuesrc/renderer/src/components/TitleView.vuesrc/shadcn/components/ui/field/FieldTitle.vue
**/*.{js,jsx,ts,tsx,vue}
📄 CodeRabbit inference engine (AGENTS.md)
Apply Prettier formatting: single quotes, no semicolons, max width 100
Files:
src/shadcn/components/ui/spinner/index.tssrc/shadcn/components/ui/field/FieldSet.vuescripts/update-shadcn.jssrc/shadcn/components/ui/input-group/InputGroupTextarea.vuesrc/shadcn/components/ui/separator/Separator.vuesrc/shared/types/model-db.tssrc/shadcn/components/ui/item/ItemMedia.vuesrc/renderer/src/components/message/MessageList.vuesrc/shadcn/components/ui/field/FieldGroup.vuesrc/shadcn/components/ui/button-group/ButtonGroup.vuesrc/shadcn/components/ui/button-group/ButtonGroupText.vuesrc/shadcn/components/ui/input-group/InputGroupText.vuesrc/shadcn/components/ui/item/ItemFooter.vuesrc/shadcn/components/ui/input-group/InputGroupInput.vuesrc/shadcn/components/ui/empty/EmptyTitle.vuesrc/renderer/src/components/settings/ShortcutSettings.vuesrc/shadcn/components/ui/empty/index.tssrc/shadcn/components/ui/field/FieldError.vuesrc/shadcn/components/ui/kbd/index.tssrc/shadcn/components/ui/empty/Empty.vuesrc/shadcn/components/ui/field/FieldLabel.vuesrc/shadcn/components/ui/item/Item.vuesrc/shadcn/components/ui/item/ItemContent.vuesrc/shadcn/components/ui/item/index.tssrc/shadcn/components/ui/field/FieldDescription.vuesrc/shadcn/components/ui/field/index.tssrc/shadcn/components/ui/item/ItemTitle.vuesrc/renderer/src/components/prompt-input/PromptInput.vuesrc/shadcn/components/ui/button-group/index.tssrc/renderer/src/components/prompt-input/ModelChooser.vuesrc/shadcn/components/ui/empty/EmptyHeader.vuesrc/renderer/src/views/playground/demos/InputGroupDemo.vuesrc/main/presenter/configPresenter/modelConfig.tssrc/shadcn/components/ui/field/FieldSeparator.vuesrc/shadcn/components/ui/field/FieldContent.vuesrc/shadcn/components/ui/button-group/ButtonGroupSeparator.vuesrc/main/presenter/configPresenter/index.tssrc/renderer/src/views/playground/demos/ButtonGroupDemo.vuesrc/renderer/src/components/ChatView.vuesrc/shadcn/components/ui/spinner/Spinner.vuesrc/shadcn/components/ui/field/FieldLegend.vuesrc/shadcn/components/ui/input-group/InputGroupAddon.vuesrc/renderer/src/views/playground/demos/FieldDemo.vuesrc/shadcn/components/ui/item/ItemHeader.vuesrc/shadcn/components/ui/item/ItemSeparator.vuesrc/shadcn/components/ui/kbd/Kbd.vuesrc/shadcn/components/ui/item/ItemDescription.vuesrc/shadcn/components/ui/input-group/InputGroupButton.vuesrc/renderer/src/views/playground/demos/SpinnerDemo.vuesrc/shadcn/components/ui/kbd/KbdGroup.vuesrc/renderer/src/views/playground/demos/EmptyDemo.vuesrc/shadcn/components/ui/button/index.tssrc/shadcn/components/ui/item/ItemGroup.vuesrc/shadcn/components/ui/input-group/index.tssrc/shadcn/components/ui/item/ItemActions.vuesrc/shadcn/components/ui/field/Field.vuesrc/shadcn/components/ui/empty/EmptyMedia.vuesrc/renderer/src/views/ChatTabView.vuesrc/renderer/src/views/playground/demos/ItemDemo.vuesrc/renderer/src/views/playground/demos/KbdDemo.vuesrc/shadcn/components/ui/input-group/InputGroup.vuesrc/renderer/src/views/PlaygroundTabView.vuesrc/shadcn/components/ui/empty/EmptyContent.vuetest/main/presenter/providerDbModelConfig.test.tssrc/shadcn/components/ui/empty/EmptyDescription.vuesrc/renderer/src/components/TitleView.vuesrc/shadcn/components/ui/field/FieldTitle.vue
scripts/**
📄 CodeRabbit inference engine (AGENTS.md)
Place build/signing/installer/runtime and commit-related scripts in scripts/
Files:
scripts/update-shadcn.jsscripts/fetch-provider-db.mjs
src/shared/**/*.{ts,tsx,d.ts}
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
共享类型定义放在
shared目录
Files:
src/shared/types/model-db.ts
src/shared/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
Place shared types, utilities, constants, and IPC contract definitions under src/shared/
Files:
src/shared/types/model-db.ts
src/shared/**
📄 CodeRabbit inference engine (AGENTS.md)
Store shared TypeScript types/utilities in src/shared/
Files:
src/shared/types/model-db.ts
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/message/MessageList.vuesrc/renderer/src/components/settings/ShortcutSettings.vuesrc/renderer/src/components/prompt-input/PromptInput.vuesrc/renderer/src/components/prompt-input/ModelChooser.vuesrc/renderer/src/views/playground/demos/InputGroupDemo.vuesrc/renderer/src/views/playground/demos/ButtonGroupDemo.vuesrc/renderer/src/components/ChatView.vuesrc/renderer/src/views/playground/demos/FieldDemo.vuesrc/renderer/src/views/playground/demos/SpinnerDemo.vuesrc/renderer/src/views/playground/demos/EmptyDemo.vuesrc/renderer/src/views/ChatTabView.vuesrc/renderer/src/views/playground/demos/ItemDemo.vuesrc/renderer/src/views/playground/demos/KbdDemo.vuesrc/renderer/src/views/PlaygroundTabView.vuesrc/renderer/src/components/TitleView.vue
src/renderer/**/*.{vue,ts,js,tsx,jsx}
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
渲染进程代码放在
src/renderer
Files:
src/renderer/src/components/message/MessageList.vuesrc/renderer/src/components/settings/ShortcutSettings.vuesrc/renderer/src/components/prompt-input/PromptInput.vuesrc/renderer/src/components/prompt-input/ModelChooser.vuesrc/renderer/src/views/playground/demos/InputGroupDemo.vuesrc/renderer/src/views/playground/demos/ButtonGroupDemo.vuesrc/renderer/src/components/ChatView.vuesrc/renderer/src/views/playground/demos/FieldDemo.vuesrc/renderer/src/views/playground/demos/SpinnerDemo.vuesrc/renderer/src/views/playground/demos/EmptyDemo.vuesrc/renderer/src/views/ChatTabView.vuesrc/renderer/src/views/playground/demos/ItemDemo.vuesrc/renderer/src/views/playground/demos/KbdDemo.vuesrc/renderer/src/views/PlaygroundTabView.vuesrc/renderer/src/components/TitleView.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/message/MessageList.vuesrc/renderer/src/components/settings/ShortcutSettings.vuesrc/renderer/src/components/prompt-input/PromptInput.vuesrc/renderer/src/components/prompt-input/ModelChooser.vuesrc/renderer/src/views/playground/demos/InputGroupDemo.vuesrc/renderer/src/views/playground/demos/ButtonGroupDemo.vuesrc/renderer/src/components/ChatView.vuesrc/renderer/src/views/playground/demos/FieldDemo.vuesrc/renderer/src/views/playground/demos/SpinnerDemo.vuesrc/renderer/src/views/playground/demos/EmptyDemo.vuesrc/renderer/src/views/ChatTabView.vuesrc/renderer/src/views/playground/demos/ItemDemo.vuesrc/renderer/src/views/playground/demos/KbdDemo.vuesrc/renderer/src/views/PlaygroundTabView.vuesrc/renderer/src/components/TitleView.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/message/MessageList.vuesrc/renderer/src/components/settings/ShortcutSettings.vuesrc/renderer/src/components/prompt-input/PromptInput.vuesrc/renderer/src/components/prompt-input/ModelChooser.vuesrc/renderer/src/views/playground/demos/InputGroupDemo.vuesrc/renderer/src/views/playground/demos/ButtonGroupDemo.vuesrc/renderer/src/components/ChatView.vuesrc/renderer/src/views/playground/demos/FieldDemo.vuesrc/renderer/src/views/playground/demos/SpinnerDemo.vuesrc/renderer/src/views/playground/demos/EmptyDemo.vuesrc/renderer/src/views/ChatTabView.vuesrc/renderer/src/views/playground/demos/ItemDemo.vuesrc/renderer/src/views/playground/demos/KbdDemo.vuesrc/renderer/src/views/PlaygroundTabView.vuesrc/renderer/src/components/TitleView.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/message/MessageList.vuesrc/renderer/src/components/settings/ShortcutSettings.vuesrc/renderer/src/components/prompt-input/PromptInput.vuesrc/renderer/src/components/prompt-input/ModelChooser.vuesrc/renderer/src/views/playground/demos/InputGroupDemo.vuesrc/renderer/src/views/playground/demos/ButtonGroupDemo.vuesrc/renderer/src/components/ChatView.vuesrc/renderer/src/views/playground/demos/FieldDemo.vuesrc/renderer/src/views/playground/demos/SpinnerDemo.vuesrc/renderer/src/views/playground/demos/EmptyDemo.vuesrc/renderer/src/views/ChatTabView.vuesrc/renderer/src/views/playground/demos/ItemDemo.vuesrc/renderer/src/views/playground/demos/KbdDemo.vuesrc/renderer/src/views/PlaygroundTabView.vuesrc/renderer/src/components/TitleView.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/message/MessageList.vuesrc/renderer/src/components/settings/ShortcutSettings.vuesrc/renderer/src/components/prompt-input/PromptInput.vuesrc/renderer/src/components/prompt-input/ModelChooser.vuesrc/renderer/src/views/playground/demos/InputGroupDemo.vuesrc/renderer/src/views/playground/demos/ButtonGroupDemo.vuesrc/renderer/src/components/ChatView.vuesrc/renderer/src/views/playground/demos/FieldDemo.vuesrc/renderer/src/views/playground/demos/SpinnerDemo.vuesrc/renderer/src/views/playground/demos/EmptyDemo.vuesrc/renderer/src/views/ChatTabView.vuesrc/renderer/src/views/playground/demos/ItemDemo.vuesrc/renderer/src/views/playground/demos/KbdDemo.vuesrc/renderer/src/views/PlaygroundTabView.vuesrc/renderer/src/components/TitleView.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/message/MessageList.vuesrc/renderer/src/components/settings/ShortcutSettings.vuesrc/renderer/src/components/prompt-input/PromptInput.vuesrc/renderer/src/components/prompt-input/ModelChooser.vuesrc/renderer/src/views/playground/demos/InputGroupDemo.vuesrc/renderer/src/views/playground/demos/ButtonGroupDemo.vuesrc/renderer/src/components/ChatView.vuesrc/renderer/src/views/playground/demos/FieldDemo.vuesrc/renderer/src/views/playground/demos/SpinnerDemo.vuesrc/renderer/src/views/playground/demos/EmptyDemo.vuesrc/renderer/src/views/ChatTabView.vuesrc/renderer/src/views/playground/demos/ItemDemo.vuesrc/renderer/src/views/playground/demos/KbdDemo.vuesrc/renderer/src/views/PlaygroundTabView.vuesrc/renderer/src/components/TitleView.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/message/MessageList.vuesrc/renderer/src/components/settings/ShortcutSettings.vuesrc/renderer/src/components/prompt-input/PromptInput.vuesrc/renderer/src/components/prompt-input/ModelChooser.vuesrc/renderer/src/views/playground/demos/InputGroupDemo.vuesrc/renderer/src/views/playground/demos/ButtonGroupDemo.vuesrc/renderer/src/components/ChatView.vuesrc/renderer/src/views/playground/demos/FieldDemo.vuesrc/renderer/src/views/playground/demos/SpinnerDemo.vuesrc/renderer/src/views/playground/demos/EmptyDemo.vuesrc/renderer/src/views/ChatTabView.vuesrc/renderer/src/views/playground/demos/ItemDemo.vuesrc/renderer/src/views/playground/demos/KbdDemo.vuesrc/renderer/src/views/PlaygroundTabView.vuesrc/renderer/src/components/TitleView.vue
src/renderer/src/components/**/*
📄 CodeRabbit inference engine (CLAUDE.md)
Organize UI components by feature within src/renderer/src/
Files:
src/renderer/src/components/message/MessageList.vuesrc/renderer/src/components/settings/ShortcutSettings.vuesrc/renderer/src/components/prompt-input/PromptInput.vuesrc/renderer/src/components/prompt-input/ModelChooser.vuesrc/renderer/src/components/ChatView.vuesrc/renderer/src/components/TitleView.vue
src/renderer/src/**
📄 CodeRabbit inference engine (AGENTS.md)
Put application code for the Vue app under src/renderer/src (components, stores, views, i18n, lib)
Files:
src/renderer/src/components/message/MessageList.vuesrc/renderer/src/components/settings/ShortcutSettings.vuesrc/renderer/src/components/prompt-input/PromptInput.vuesrc/renderer/src/components/prompt-input/ModelChooser.vuesrc/renderer/src/views/playground/demos/InputGroupDemo.vuesrc/renderer/src/views/playground/demos/ButtonGroupDemo.vuesrc/renderer/src/components/ChatView.vuesrc/renderer/src/views/playground/demos/FieldDemo.vuesrc/renderer/src/views/playground/demos/SpinnerDemo.vuesrc/renderer/src/views/playground/demos/EmptyDemo.vuesrc/renderer/src/views/ChatTabView.vuesrc/renderer/src/views/playground/demos/ItemDemo.vuesrc/renderer/src/views/playground/demos/KbdDemo.vuesrc/renderer/src/views/PlaygroundTabView.vuesrc/renderer/src/components/TitleView.vue
src/renderer/src/**/*.{vue,ts}
📄 CodeRabbit inference engine (AGENTS.md)
All user-facing strings in the renderer must use vue-i18n keys defined in src/renderer/src/i18n
Files:
src/renderer/src/components/message/MessageList.vuesrc/renderer/src/components/settings/ShortcutSettings.vuesrc/renderer/src/components/prompt-input/PromptInput.vuesrc/renderer/src/components/prompt-input/ModelChooser.vuesrc/renderer/src/views/playground/demos/InputGroupDemo.vuesrc/renderer/src/views/playground/demos/ButtonGroupDemo.vuesrc/renderer/src/components/ChatView.vuesrc/renderer/src/views/playground/demos/FieldDemo.vuesrc/renderer/src/views/playground/demos/SpinnerDemo.vuesrc/renderer/src/views/playground/demos/EmptyDemo.vuesrc/renderer/src/views/ChatTabView.vuesrc/renderer/src/views/playground/demos/ItemDemo.vuesrc/renderer/src/views/playground/demos/KbdDemo.vuesrc/renderer/src/views/PlaygroundTabView.vuesrc/renderer/src/components/TitleView.vue
src/renderer/**/*.vue
📄 CodeRabbit inference engine (AGENTS.md)
Name Vue components in PascalCase (e.g., ChatInput.vue)
Files:
src/renderer/src/components/message/MessageList.vuesrc/renderer/src/components/settings/ShortcutSettings.vuesrc/renderer/src/components/prompt-input/PromptInput.vuesrc/renderer/src/components/prompt-input/ModelChooser.vuesrc/renderer/src/views/playground/demos/InputGroupDemo.vuesrc/renderer/src/views/playground/demos/ButtonGroupDemo.vuesrc/renderer/src/components/ChatView.vuesrc/renderer/src/views/playground/demos/FieldDemo.vuesrc/renderer/src/views/playground/demos/SpinnerDemo.vuesrc/renderer/src/views/playground/demos/EmptyDemo.vuesrc/renderer/src/views/ChatTabView.vuesrc/renderer/src/views/playground/demos/ItemDemo.vuesrc/renderer/src/views/playground/demos/KbdDemo.vuesrc/renderer/src/views/PlaygroundTabView.vuesrc/renderer/src/components/TitleView.vue
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/main/presenter/configPresenter/modelConfig.tssrc/main/presenter/configPresenter/index.ts
src/main/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/electron-best-practices.mdc)
Use Electron's built-in APIs for file system and native dialogs
Files:
src/main/presenter/configPresenter/modelConfig.tssrc/main/presenter/configPresenter/index.ts
src/main/**/*.{ts,js,tsx,jsx}
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
主进程代码放在
src/main
Files:
src/main/presenter/configPresenter/modelConfig.tssrc/main/presenter/configPresenter/index.ts
src/main/**
📄 CodeRabbit inference engine (AGENTS.md)
Place all Electron main-process code under src/main/
Files:
src/main/presenter/configPresenter/modelConfig.tssrc/main/presenter/configPresenter/index.ts
src/main/presenter/**
📄 CodeRabbit inference engine (AGENTS.md)
src/main/presenter/**: Organize main-process presenters under src/main/presenter/ (Window/Tab/Thread/Mcp/Config/LLMProvider)
Follow the Presenter pattern for main-process modules
Files:
src/main/presenter/configPresenter/modelConfig.tssrc/main/presenter/configPresenter/index.ts
test/**/*
📄 CodeRabbit inference engine (CLAUDE.md)
Place unit and integration tests under the test/ directory mirroring project structure
Files:
test/main/presenter/providerDbModelConfig.test.ts
test/{main,renderer}/**
📄 CodeRabbit inference engine (AGENTS.md)
Mirror source structure in tests under test/main/** and test/renderer/** (with setup files)
Files:
test/main/presenter/providerDbModelConfig.test.ts
test/{main,renderer}/**/*.{test,spec}.ts
📄 CodeRabbit inference engine (AGENTS.md)
test/{main,renderer}/**/*.{test,spec}.ts: Name test files with *.test.ts or *.spec.ts
Write tests with Vitest (jsdom) and Vue Test Utils
Files:
test/main/presenter/providerDbModelConfig.test.ts
🧠 Learnings (5)
📚 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/main/presenter/configPresenter/providers.ts : Add provider configuration entries in src/main/presenter/configPresenter/providers.ts
Applied to files:
src/main/presenter/configPresenter/modelConfig.ts
📚 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} : Use the Icon component with lucide icons.
Applied to files:
src/shadcn/components/ui/spinner/Spinner.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} : Use <script setup> syntax for concise component definitions.
Applied to files:
src/renderer/src/views/ChatTabView.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} : Use Vue 3 with TypeScript, leveraging defineComponent and PropType.
Applied to files:
src/renderer/src/views/ChatTabView.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/{composables,utils}/**/*.ts : Use VueUse for common composables and utility functions.
Applied to files:
src/renderer/src/views/ChatTabView.vue
🧬 Code graph analysis (1)
src/shadcn/components/ui/input-group/index.ts (1)
src/shadcn/components/ui/button/index.ts (1)
ButtonVariants(39-39)
🔇 Additional comments (31)
src/shadcn/components/ui/empty/EmptyContent.vue (1)
1-20: LGTM!The EmptyContent component follows standard shadcn-vue patterns with proper prop definition, class composition using
cn, and slot-based content projection. The implementation is clean and consistent.src/shadcn/components/ui/separator/Separator.vue (1)
22-27: LGTM!The formatting change from template literal to regular
:classbinding is purely cosmetic with no functional impact. Both approaches are valid.src/main/presenter/configPresenter/index.ts (1)
656-657: LGTM! Provider model schema update correctly implemented.The changes properly derive
reasoningandenableSearchcapabilities from nested provider model structures (reasoning?.supportedandsearch?.supported), aligning with the structured schema updates across the PR.src/shadcn/components/ui/spinner/index.ts (1)
1-1: LGTM!Standard component re-export pattern consistent with other shadcn-vue components.
scripts/update-shadcn.js (1)
52-59: LGTM!The addition of new shadcn components (
button-group,empty,field,input-group,item,kbd,spinner) to the installation list is consistent with the component additions across the PR.src/shadcn/components/ui/field/FieldSet.vue (1)
1-21: LGTM!The component properly uses a semantic
<fieldset>element and implements conditional gap styling using modern CSS:has()selectors. The structure and TypeScript typing are correct.src/shadcn/components/ui/item/ItemFooter.vue (1)
1-17: LGTM!The component structure is clean and follows Vue 3 Composition API best practices. TypeScript typing and class composition are correctly implemented.
src/shadcn/components/ui/item/ItemActions.vue (1)
1-17: LGTM!The component structure is clean and follows Vue 3 Composition API best practices. TypeScript typing and class composition are correctly implemented.
src/shadcn/components/ui/field/FieldLabel.vue (1)
1-23: LGTM!The component follows proper Vue 3 Composition API patterns, uses TypeScript correctly, and implements a clean wrapper around the Label component with proper class composition.
src/shadcn/components/ui/input-group/InputGroupInput.vue (1)
1-19: LGTM!The component is well-structured, uses TypeScript properly, and provides a clean input wrapper for input-group composition with appropriate styling.
src/renderer/src/views/ChatTabView.vue (1)
41-41: LGTM! Clean simplification of title logic.The removal of
activeModeldependencies and the simplified title update mechanism driven byactiveThreadchanges is a good refactor. The watch logic correctly handles both thread changes and thread list updates.Also applies to: 96-96, 124-141
src/shadcn/components/ui/item/ItemDescription.vue (1)
1-21: LGTM!The component provides a clean description wrapper with proper typography styling, including link styles. The implementation follows Vue 3 best practices.
src/shadcn/components/ui/field/FieldDescription.vue (1)
1-22: LGTM!The component is well-implemented with field-specific styling and contextual layout adjustments. The use of advanced CSS selectors for field-oriented layouts is appropriate.
src/shadcn/components/ui/button-group/ButtonGroup.vue (1)
1-22: LGTM!The component correctly implements a button group with proper accessibility attributes (
role="group"), orientation support, and variant-based styling. The implementation follows Vue 3 best practices.src/shadcn/components/ui/item/ItemGroup.vue (1)
1-18: LGTM!The component provides a clean item group container with proper accessibility (
role="list") and flexbox layout. The implementation is straightforward and correct.src/shadcn/components/ui/item/ItemTitle.vue (1)
1-17: LGTM!The component follows Vue 3 Composition API best practices with proper TypeScript typing and class composition via the
cnutility.src/shadcn/components/ui/item/ItemHeader.vue (1)
1-17: LGTM!The component is well-structured and follows the same proven pattern as the other Item family components.
src/shadcn/components/ui/input-group/InputGroupButton.vue (1)
1-21: LGTM!The component correctly delegates to the Button component with appropriate defaults for input group context (
xssize andghostvariant).scripts/fetch-provider-db.mjs (1)
60-90: LGTM!The expanded reasoning and search schema handling correctly:
- Maintains backward compatibility with boolean reasoning
- Validates the new structured reasoning object with nested budget
- Adds comprehensive search object parsing
- Applies proper type and bounds checks before inclusion
The defensive validation ensures only well-formed data enters the sanitized output.
src/shadcn/components/ui/item/ItemContent.vue (1)
1-17: LGTM!The component follows the established Item family pattern, and the sibling selector styling is appropriate for controlling layout when multiple ItemContent components appear adjacent to each other.
src/shadcn/components/ui/spinner/Spinner.vue (1)
3-3: Remove this suggestion: direct lucide-vue-next imports are acceptable in the shadcn component library.Likely an incorrect or invalid review comment.
src/shadcn/components/ui/input-group/index.ts (1)
1-59: LGTM!The CVA variant definitions are well-structured with appropriate defaults. The type exports and interface definitions follow TypeScript best practices. The alignment and size variants provide good flexibility for the input-group components.
src/shadcn/components/ui/item/ItemSeparator.vue (1)
13-17: Verify hardcoded horizontal orientation.The
orientationprop is hardcoded to"horizontal". If ItemSeparator will only be used horizontally, this is fine. However, if vertical separators might be needed in the future, consider exposing orientation as a prop with a default value instead.If vertical separators are needed, consider:
-const props = defineProps< +const props = withDefaults(defineProps< SeparatorProps & { class?: HTMLAttributes["class"] } ->() +>(), { + orientation: "horizontal" +})<Separator data-slot="item-separator" - orientation="horizontal" + :orientation="props.orientation" :class="cn('my-0', props.class)" />src/shadcn/components/ui/field/FieldSeparator.vue (1)
1-29: LGTM!The component implements a clean separator with optional label overlay. The conditional rendering of the content span based on
$slots.default, along with the absolute positioning of the separator, creates the expected visual effect of text interrupting a line.src/main/presenter/configPresenter/modelConfig.ts (1)
296-301: LGTM!The updated derivation logic correctly sources defaults from nested provider metadata:
Boolean()coercion appropriately converts potentially undefined values to booleans- Optional chaining safely accesses nested properties
thinkingBudgetexplicitly falls back toundefinedsearchStrategyternary correctly defaults to'turbo'for all non-'max'values including undefinedThe changes align with the broader PR goal of supporting structured reasoning and search schemas in provider models.
src/shadcn/components/ui/input-group/InputGroupAddon.vue (1)
1-36: LGTM with minor refinement opportunity.The component implements proper click handling to focus the input when clicking the addon (unless clicking a button). The use of
withDefaultsfor the align prop, the role="group" attribute, and the CVA-based class composition all follow best practices.src/shadcn/components/ui/item/Item.vue (1)
1-27: LGTM!The component correctly integrates with the Primitive wrapper, properly composes variant classes, and follows Vue 3 Composition API best practices. The prop types are well-defined and the template structure is clean.
src/shared/types/model-db.ts (1)
6-23: LGTM! Schemas are well-structured.The new
ReasoningSchemaandSearchSchemaare correctly defined with optional fields and proper Zod type validation. The nested structure forreasoning.budget.defaultprovides good flexibility for future extensions.test/main/presenter/providerDbModelConfig.test.ts (2)
69-70: LGTM! Test data correctly reflects the new schema.The mock provider DB now uses the structured
reasoningandsearchobjects, aligning with the updatedModelSchemaand providing comprehensive test coverage for the new fields.
94-97: LGTM! Assertions validate new model capabilities.The test correctly verifies that the derived config values (
thinkingBudget,enableSearch,forcedSearch,searchStrategy) match the provider DB metadata, ensuring the presenter layer properly maps the new fields.src/shadcn/components/ui/input-group/InputGroupTextarea.vue (1)
12-18: Forward attributes and events to Textarea.The component should forward all unhandled attributes and events to the underlying Textarea element for proper usage in parent components (e.g., placeholder, rows, v-model, @input, etc.).
Apply this diff to forward attributes:
<Textarea data-slot="input-group-control" + v-bind="$attrs" :class="cn( 'flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 dark:bg-transparent', props.class, )" />Also add
inheritAttrs: falseto the script setup to prevent double attribute binding:<script setup lang="ts"> +import { useAttrs } from 'vue' import type { HTMLAttributes } from "vue" import { cn } from '@shadcn/lib/utils' import { Textarea } from '@shadcn/components/ui/textarea' +defineOptions({ + inheritAttrs: false +}) + const props = defineProps<{ class?: HTMLAttributes["class"] }>() </script>Likely an incorrect or invalid review comment.
| const handleModelUpdate = (model: MODEL_META) => { | ||
| chatStore.updateChatConfig({ | ||
| modelId: model.id, | ||
| providerId: model.providerId | ||
| }) | ||
| configPresenter.setSetting('preferredModel', { | ||
| modelId: model.id, | ||
| providerId: model.providerId | ||
| }) | ||
| modelSelectOpen.value = false | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Restore providerId when switching models
ModelChooser emits (model, providerId), but this handler ignores the second argument and instead reads model.providerId. RENDERER_MODEL_META does not guarantee a providerId field, so updateChatConfig ends up writing providerId: undefined, breaking subsequent sends. Please accept the emitted provider id explicitly.
-const handleModelUpdate = (model: MODEL_META) => {
- chatStore.updateChatConfig({
- modelId: model.id,
- providerId: model.providerId
- })
+const handleModelUpdate = (model: RENDERER_MODEL_META, providerId: string) => {
+ const nextProviderId = providerId || (model as { providerId?: string }).providerId || ''
+
+ chatStore.updateChatConfig({
+ modelId: model.id,
+ providerId: nextProviderId
+ })
- configPresenter.setSetting('preferredModel', {
- modelId: model.id,
- providerId: model.providerId
- })
+ configPresenter.setSetting('preferredModel', {
+ modelId: model.id,
+ providerId: nextProviderId
+ })📝 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.
| const handleModelUpdate = (model: MODEL_META) => { | |
| chatStore.updateChatConfig({ | |
| modelId: model.id, | |
| providerId: model.providerId | |
| }) | |
| configPresenter.setSetting('preferredModel', { | |
| modelId: model.id, | |
| providerId: model.providerId | |
| }) | |
| modelSelectOpen.value = false | |
| } | |
| const handleModelUpdate = (model: RENDERER_MODEL_META, providerId: string) => { | |
| const nextProviderId = providerId || (model as { providerId?: string }).providerId || '' | |
| chatStore.updateChatConfig({ | |
| modelId: model.id, | |
| providerId: nextProviderId | |
| }) | |
| configPresenter.setSetting('preferredModel', { | |
| modelId: model.id, | |
| providerId: nextProviderId | |
| }) | |
| modelSelectOpen.value = false | |
| } |
🤖 Prompt for AI Agents
In src/renderer/src/components/prompt-input/PromptInput.vue around lines 603 to
615, the handleModelUpdate callback currently ignores the second emitted
argument and reads model.providerId which can be undefined; change the function
signature to accept the emitted providerId (e.g. (model, providerId)) and use
that providerId when calling chatStore.updateChatConfig and
configPresenter.setSetting('preferredModel') (fall back to model.providerId only
if the emitted providerId is undefined), then keep existing UI behavior
(modelSelectOpen.value = false).
| stopRateLimitPolling() | ||
| window.electron.ipcRenderer.removeAllListeners(RATE_LIMIT_EVENTS.CONFIG_UPDATED) | ||
| window.electron.ipcRenderer.removeAllListeners(RATE_LIMIT_EVENTS.REQUEST_EXECUTED) | ||
| window.electron.ipcRenderer.removeAllListeners(RATE_LIMIT_EVENTS.REQUEST_QUEUED) |
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.
Avoid unregistering other listeners
removeAllListeners nukes every handler for these IPC channels, so when this component unmounts it also disconnects listeners registered by other views. Use removeListener/off with the specific handleRateLimitEvent callback instead.
-window.electron.ipcRenderer.removeAllListeners(RATE_LIMIT_EVENTS.CONFIG_UPDATED)
-window.electron.ipcRenderer.removeAllListeners(RATE_LIMIT_EVENTS.REQUEST_EXECUTED)
-window.electron.ipcRenderer.removeAllListeners(RATE_LIMIT_EVENTS.REQUEST_QUEUED)
+window.electron.ipcRenderer.removeListener(
+ RATE_LIMIT_EVENTS.CONFIG_UPDATED,
+ handleRateLimitEvent
+)
+window.electron.ipcRenderer.removeListener(
+ RATE_LIMIT_EVENTS.REQUEST_EXECUTED,
+ handleRateLimitEvent
+)
+window.electron.ipcRenderer.removeListener(
+ RATE_LIMIT_EVENTS.REQUEST_QUEUED,
+ handleRateLimitEvent
+)📝 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.
| stopRateLimitPolling() | |
| window.electron.ipcRenderer.removeAllListeners(RATE_LIMIT_EVENTS.CONFIG_UPDATED) | |
| window.electron.ipcRenderer.removeAllListeners(RATE_LIMIT_EVENTS.REQUEST_EXECUTED) | |
| window.electron.ipcRenderer.removeAllListeners(RATE_LIMIT_EVENTS.REQUEST_QUEUED) | |
| stopRateLimitPolling() | |
| window.electron.ipcRenderer.removeListener( | |
| RATE_LIMIT_EVENTS.CONFIG_UPDATED, | |
| handleRateLimitEvent | |
| ) | |
| window.electron.ipcRenderer.removeListener( | |
| RATE_LIMIT_EVENTS.REQUEST_EXECUTED, | |
| handleRateLimitEvent | |
| ) | |
| window.electron.ipcRenderer.removeListener( | |
| RATE_LIMIT_EVENTS.REQUEST_QUEUED, | |
| handleRateLimitEvent | |
| ) |
🤖 Prompt for AI Agents
In src/renderer/src/components/prompt-input/PromptInput.vue around lines 1428 to
1431, the cleanup currently calls removeAllListeners which unregisters other
components' handlers; replace those removeAllListeners calls with removeListener
(or off) passing the exact RATE_LIMIT_EVENTS.* channel and the original
handleRateLimitEvent callback used when registering so only this component's
handler is removed, ensuring the same function reference is passed and leaving
other listeners intact; keep stopRateLimitPolling() as-is.
| <Kbd v-else class="text-muted-foreground">...</Kbd> | ||
| </template> | ||
| <template v-else-if="shortcut.key.length"> | ||
| <Kbd v-for="(key, idx) in shortcut.key" :key="idx"> | ||
| {{ key }} | ||
| </span> | ||
| </span> | ||
| <Icon | ||
| v-if="recordingShortcutId !== shortcut.id" | ||
| icon="lucide:pencil" | ||
| class="w-3.5 h-3.5 text-muted-foreground group-hover:opacity-0 transition-opacity" | ||
| /> | ||
| </Button> | ||
|
|
||
| <!-- 清理图标 - 只在hover时显示且不是禁用状态 --> | ||
| <Button | ||
| v-if="!shortcut.disabled && recordingShortcutId !== shortcut.id" | ||
| variant="ghost" | ||
| size="sm" | ||
| class="absolute right-1 top-1/2 -translate-y-1/2 h-8 w-8 p-0 opacity-0 group-hover:opacity-100 transition-opacity z-10 hover:bg-destructive/10" | ||
| @click.stop="clearShortcut(shortcut.id)" | ||
| :title="t('settings.shortcuts.clearShortcut')" | ||
| </Kbd> | ||
| </template> | ||
| <Kbd v-else class="text-muted-foreground">—</Kbd> | ||
| </KbdGroup> |
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.
Localize the recording placeholders.
The placeholder keycaps (... on Line 48 and — on Line 55) are user-visible strings. Per renderer guidelines these must come from vue-i18n keys rather than hard-coded literals. Please move them into the i18n resources and reference them via t(...).
As per coding guidelines
🤖 Prompt for AI Agents
In src/renderer/src/components/settings/ShortcutSettings.vue around lines 48 to
56 the placeholder keycap literals ("..." and "—") are hard-coded and must be
localized; replace those literals with calls to the i18n translator (e.g.
t('shortcuts.recordingPlaceholderEllipsis') and
t('shortcuts.recordingPlaceholderDash')) and ensure the component has access to
t (via useI18n or this.$t depending on script style), then add matching keys to
the renderer locale resource files for all supported languages with the
appropriate translations.
| <EmptyTitle>No conversations yet</EmptyTitle> | ||
| <EmptyDescription> | ||
| Start a new thread to keep track of customer questions and follow-ups. | ||
| </EmptyDescription> | ||
| </EmptyHeader> | ||
| <EmptyContent class="flex flex-col sm:flex-row gap-2"> | ||
| <Button>New conversation</Button> | ||
| <Button variant="outline">Import history</Button> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace all hardcoded strings with i18n keys.
Lines 7-14 contain multiple hardcoded English strings in a renderer component. According to the coding guidelines, all user-facing strings in src/renderer/src/**/* must use i18n keys via vue-i18n.
As per coding guidelines
Apply this diff to use i18n:
- <EmptyTitle>No conversations yet</EmptyTitle>
+ <EmptyTitle>{{ $t('playground.empty.noConversations') }}</EmptyTitle>
<EmptyDescription>
- Start a new thread to keep track of customer questions and follow-ups.
+ {{ $t('playground.empty.startThreadDescription') }}
</EmptyDescription>
</EmptyHeader>
<EmptyContent class="flex flex-col sm:flex-row gap-2">
- <Button>New conversation</Button>
- <Button variant="outline">Import history</Button>
+ <Button>{{ $t('playground.empty.newConversation') }}</Button>
+ <Button variant="outline">{{ $t('playground.empty.importHistory') }}</Button>
</EmptyContent>Then add the corresponding keys to your i18n locale files (e.g., src/renderer/src/i18n/en.json):
{
"playground": {
"empty": {
"noConversations": "No conversations yet",
"startThreadDescription": "Start a new thread to keep track of customer questions and follow-ups.",
"newConversation": "New conversation",
"importHistory": "Import history"
}
}
}🤖 Prompt for AI Agents
In src/renderer/src/views/playground/demos/EmptyDemo.vue around lines 7 to 14,
replace the hardcoded English user-facing strings in EmptyTitle,
EmptyDescription and the two Button labels with vue-i18n translation keys (e.g.
use this.$t or $t calls like playground.empty.noConversations,
playground.empty.startThreadDescription, playground.empty.newConversation,
playground.empty.importHistory) and update any necessary component props to
accept translated text; then add the corresponding keys to the renderer locale
file src/renderer/src/i18n/en.json under playground.empty with the four provided
entries so the UI displays localized text.
| <template> | ||
| <FieldSet class="max-w-xl space-y-6"> | ||
| <FieldLegend class="text-base font-semibold">Notification preferences</FieldLegend> | ||
| <FieldDescription class="text-sm text-muted-foreground"> | ||
| Arrange label, helper text, and controls with a11y-friendly field primitives. | ||
| </FieldDescription> | ||
|
|
||
| <FieldGroup> | ||
| <Field orientation="responsive"> | ||
| <FieldLabel for="digest" class="font-medium">Weekly product digest</FieldLabel> | ||
| <FieldContent> | ||
| <Switch id="digest" /> | ||
| <FieldDescription class="text-sm text-muted-foreground"> | ||
| Receive a summary of key updates every Monday morning. | ||
| </FieldDescription> | ||
| </FieldContent> | ||
| </Field> | ||
|
|
||
| <Field orientation="responsive"> | ||
| <FieldLabel for="channel" class="font-medium">Default channel</FieldLabel> | ||
| <FieldContent class="gap-2"> | ||
| <Select> | ||
| <SelectTrigger id="channel" class="w-full sm:w-48"> | ||
| <SelectValue placeholder="Choose channel" /> | ||
| </SelectTrigger> | ||
| <SelectContent> | ||
| <SelectItem value="email">Email</SelectItem> | ||
| <SelectItem value="slack">Slack</SelectItem> | ||
| <SelectItem value="teams">Teams</SelectItem> | ||
| </SelectContent> | ||
| </Select> | ||
| <FieldDescription class="text-sm text-muted-foreground"> | ||
| Where notifications should arrive by default. | ||
| </FieldDescription> | ||
| </FieldContent> | ||
| </Field> | ||
|
|
||
| <Field orientation="responsive" data-invalid="true"> | ||
| <FieldLabel for="quota" class="font-medium">Usage quota</FieldLabel> | ||
| <FieldContent> | ||
| <Input id="quota" type="number" min="0" placeholder="25" aria-invalid="true" /> | ||
| <FieldDescription class="text-sm text-muted-foreground"> | ||
| The maximum number of alerts your team can trigger each day. | ||
| </FieldDescription> | ||
| <FieldError :errors="[{ message: 'Provide a value between 10 and 50.' }]" /> | ||
| </FieldContent> | ||
| </Field> | ||
| </FieldGroup> | ||
| </FieldSet> | ||
| </template> |
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.
Use i18n keys for all user-facing strings.
This demo contains multiple hardcoded English strings (e.g., "Notification preferences", "Weekly product digest", "Choose channel", etc.) which violate the coding guideline requiring all user-facing strings in src/renderer/src/**/* to use i18n keys via vue-i18n.
Even for playground demos, following i18n best practices ensures consistency and demonstrates proper usage to other developers.
As per coding guidelines: "All user-facing strings must use i18n keys (avoid hardcoded user-visible text in code)" and "Use the 'vue-i18n' framework for all internationalization in the renderer."
| :class="cn( | ||
| 'text-muted-foreground [&>a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4', | ||
| $attrs.class ?? '', | ||
| )" |
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.
Use props.class instead of $attrs.class.
For consistency with EmptyContent.vue and proper prop handling, use props.class in the class composition.
Apply this diff:
:class="cn(
'text-muted-foreground [&>a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4',
- $attrs.class ?? '',
+ props.class,
)"📝 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.
| :class="cn( | |
| 'text-muted-foreground [&>a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4', | |
| $attrs.class ?? '', | |
| )" | |
| :class="cn( | |
| 'text-muted-foreground [&>a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4', | |
| props.class, | |
| )" |
🤖 Prompt for AI Agents
In src/shadcn/components/ui/empty/EmptyDescription.vue around lines 13 to 16,
the class composition currently uses $attrs.class which should be props.class
for consistency with EmptyContent.vue and proper prop handling; update the cn
call to use props.class ?? '' instead of $attrs.class ?? '', ensuring the
component uses the passed prop rather than attribute lookup.
| defineProps<{ | ||
| class?: HTMLAttributes["class"] | ||
| }>() | ||
| </script> | ||
|
|
||
| <template> | ||
| <div | ||
| data-slot="empty-header" | ||
| :class="cn( | ||
| 'flex max-w-sm flex-col items-center gap-2 text-center', | ||
| $attrs.class ?? '', | ||
| )" |
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.
Critical: Fix prop access bug.
The component declares class as a prop but then accesses $attrs.class in the template. Once a prop is declared via defineProps, it's removed from $attrs, so $attrs.class will always be undefined.
Apply this diff to fix the prop access:
-defineProps<{
+const props = defineProps<{
class?: HTMLAttributes["class"]
}>() <div
data-slot="empty-header"
- :class="cn(
- 'flex max-w-sm flex-col items-center gap-2 text-center',
- $attrs.class ?? '',
- )"
+ :class="cn('flex max-w-sm flex-col items-center gap-2 text-center', props.class)"
>
<slot />
</div>This aligns the component with the pattern used by sibling components (ItemTitle, ItemHeader, ItemContent).
🤖 Prompt for AI Agents
In src/shadcn/components/ui/empty/EmptyHeader.vue around lines 5 to 16, the
component declares a `class` prop via defineProps but then reads `$attrs.class`
(which will be undefined once the prop is declared); change the script to assign
the props object (e.g. const props = defineProps<{ class?:
HTMLAttributes["class"] }>() ) and update the template binding to use
props.class (replace `$attrs.class ?? ''` with `props.class ?? ''`) so the
declared prop is read correctly.
| data-slot="empty-icon" | ||
| :data-variant="variant" | ||
| :class="cn(emptyMediaVariants({ variant }), props.class)" | ||
| > |
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.
Hook the variant prop into the template.
variant is never defined in scope, so the incoming prop is dropped. Bind through props.variant when setting the data attribute and calling emptyMediaVariants.
Apply this diff:
- :data-variant="variant"
- :class="cn(emptyMediaVariants({ variant }), props.class)"
+ :data-variant="props.variant"
+ :class="cn(emptyMediaVariants({ variant: props.variant }), props.class)"📝 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.
| data-slot="empty-icon" | |
| :data-variant="variant" | |
| :class="cn(emptyMediaVariants({ variant }), props.class)" | |
| > | |
| data-slot="empty-icon" | |
| :data-variant="props.variant" | |
| :class="cn(emptyMediaVariants({ variant: props.variant }), props.class)" | |
| > |
🤖 Prompt for AI Agents
In src/shadcn/components/ui/empty/EmptyMedia.vue around lines 15 to 18, the
template references an undefined variable `variant`, so the incoming prop is
ignored; update the bindings to use `props.variant` instead of `variant` for
both the :data-variant attribute and the call to emptyMediaVariants (i.e., pass
props.variant into emptyMediaVariants), or alternatively destructure variant
from props in setup and use that variable; ensure the template consistently
reads the prop so the variant is applied.
| data-slot="empty-icon" | ||
| :data-variant="variant" | ||
| :class="cn(emptyMediaVariants({ variant }), props.class)" | ||
| > |
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.
Wire the variant prop correctly.
variant resolves to undefined because it is never bound in script setup. Use props.variant when forwarding the prop to the DOM and to emptyMediaVariants.
Apply this diff:
- :data-variant="variant"
- :class="cn(emptyMediaVariants({ variant }), props.class)"
+ :data-variant="props.variant"
+ :class="cn(emptyMediaVariants({ variant: props.variant }), props.class)"📝 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.
| data-slot="empty-icon" | |
| :data-variant="variant" | |
| :class="cn(emptyMediaVariants({ variant }), props.class)" | |
| > | |
| data-slot="empty-icon" | |
| :data-variant="props.variant" | |
| :class="cn(emptyMediaVariants({ variant: props.variant }), props.class)" | |
| > |
🤖 Prompt for AI Agents
In src/shadcn/components/ui/empty/EmptyTitle.vue around lines 15 to 18, the
template uses the unbound identifier "variant" which is undefined; change the
bindings to reference the prop (props.variant) when forwarding to the DOM and
when calling emptyMediaVariants (e.g., :data-variant="props.variant" and pass {
variant: props.variant } into emptyMediaVariants) so the component uses the
actual prop value.
| const content = computed(() => { | ||
| if (!props.errors || props.errors.length === 0) | ||
| return null | ||
| if (props.errors.length === 1 && props.errors[0]?.message) { | ||
| return props.errors[0].message | ||
| } | ||
| return props.errors.some(e => e?.message) | ||
| ? props.errors | ||
| : null | ||
| }) | ||
| </script> | ||
|
|
||
| <template> | ||
| <div | ||
| v-if="$slots.default || content" | ||
| role="alert" | ||
| data-slot="field-error" | ||
| :class="cn('text-destructive text-sm font-normal', props.class)" | ||
| > | ||
| <slot v-if="$slots.default" /> | ||
|
|
||
| <template v-else-if="typeof content === 'string'"> | ||
| {{ content }} | ||
| </template> | ||
|
|
||
| <ul v-else-if="Array.isArray(content)" class="ml-4 flex list-disc flex-col gap-1"> | ||
| <li v-for="(error, index) in content" :key="index"> | ||
| {{ error?.message }} | ||
| </li> | ||
| </ul> |
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.
Filter out empty error entries before rendering
When errors contains items without a message (common with form libs that keep placeholders), the <ul> renders blank bullets. Filter and normalize the messages so only meaningful text is displayed.
const content = computed(() => {
- if (!props.errors || props.errors.length === 0)
- return null
-
- if (props.errors.length === 1 && props.errors[0]?.message) {
- return props.errors[0].message
- }
-
- return props.errors.some(e => e?.message)
- ? props.errors
- : null
+ const messages = props.errors
+ ?.map(error => error?.message?.trim())
+ .filter((message): message is string => Boolean(message?.length))
+
+ if (!messages || messages.length === 0)
+ return null
+
+ return messages.length === 1 ? messages[0] : messages
})- <ul v-else-if="Array.isArray(content)" class="ml-4 flex list-disc flex-col gap-1">
- <li v-for="(error, index) in content" :key="index">
- {{ error?.message }}
+ <ul v-else-if="Array.isArray(content)" class="ml-4 flex list-disc flex-col gap-1">
+ <li v-for="(message, index) in content" :key="index">
+ {{ message }}
</li>
</ul>📝 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.
| const content = computed(() => { | |
| if (!props.errors || props.errors.length === 0) | |
| return null | |
| if (props.errors.length === 1 && props.errors[0]?.message) { | |
| return props.errors[0].message | |
| } | |
| return props.errors.some(e => e?.message) | |
| ? props.errors | |
| : null | |
| }) | |
| </script> | |
| <template> | |
| <div | |
| v-if="$slots.default || content" | |
| role="alert" | |
| data-slot="field-error" | |
| :class="cn('text-destructive text-sm font-normal', props.class)" | |
| > | |
| <slot v-if="$slots.default" /> | |
| <template v-else-if="typeof content === 'string'"> | |
| {{ content }} | |
| </template> | |
| <ul v-else-if="Array.isArray(content)" class="ml-4 flex list-disc flex-col gap-1"> | |
| <li v-for="(error, index) in content" :key="index"> | |
| {{ error?.message }} | |
| </li> | |
| </ul> | |
| const content = computed(() => { | |
| const messages = props.errors | |
| ?.map(error => error?.message?.trim()) | |
| .filter((message): message is string => Boolean(message?.length)) | |
| if (!messages || messages.length === 0) | |
| return null | |
| return messages.length === 1 ? messages[0] : messages | |
| }) | |
| </script> | |
| <template> | |
| <div | |
| v-if="$slots.default || content" | |
| role="alert" | |
| data-slot="field-error" | |
| :class="cn('text-destructive text-sm font-normal', props.class)" | |
| > | |
| <slot v-if="$slots.default" /> | |
| <template v-else-if="typeof content === 'string'"> | |
| {{ content }} | |
| </template> | |
| <ul v-else-if="Array.isArray(content)" class="ml-4 flex list-disc flex-col gap-1"> | |
| <li v-for="(message, index) in content" :key="index"> | |
| {{ message }} | |
| </li> | |
| </ul> |
🤖 Prompt for AI Agents
In src/shadcn/components/ui/field/FieldError.vue around lines 11 to 42, the
computed content can include error objects without a message which renders blank
list bullets; change the computed to filter out falsy or empty messages and
normalize content so it returns either null, a single string, or an array of
message strings. Specifically, when props.errors exists, filter props.errors for
entries with a non-empty message, if none return null, if one return that
message string, otherwise return the array of message strings; keep the template
logic but ensure Array.isArray(content) yields an array of strings (not objects)
so the <ul> only renders meaningful text.
Summary by CodeRabbit
New Features
Refactor
Chores