Skip to content

Conversation

@zerob13
Copy link
Collaborator

@zerob13 zerob13 commented Oct 9, 2025

  • add new layout for chatview
  • add new prompt input

Summary by CodeRabbit

  • New Features

    • Introduced a powerful Prompt Input with rich text, drag-and-drop/paste file support, model picker, web search toggle, and rate-limit indicators.
    • Added a Model Chooser for quick model selection.
    • Expanded UI kit: Button Group, Empty states, Field primitives, Input Group, Item components, Kbd, and Spinner, plus new playground demos.
  • Refactor

    • Simplified the chat title bar and replaced the legacy chat input.
    • Adjusted chat and message list spacing for improved layout.
  • Chores

    • Updated UI dependencies and component installation scripts.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 9, 2025

Walkthrough

Updates 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

Cohort / File(s) Summary
Dependency bump
package.json
Upgrade dependency: reka-ui ^2.5.0 → ^2.5.1.
Provider DB schema & sanitization
scripts/fetch-provider-db.mjs, src/shared/types/model-db.ts, test/main/presenter/providerDbModelConfig.test.ts
Introduce structured reasoning (supported/budget) and search (supported/forced_search/search_strategy) schemas; update sanitization; extend tests to assert thinkingBudget, enableSearch, forcedSearch, searchStrategy.
Presenter mapping
src/main/presenter/configPresenter/index.ts, src/main/presenter/configPresenter/modelConfig.ts
Map reasoning via reasoning.supported; add enableSearch from search.supported; derive defaults from nested provider metadata (budget, forced_search, strategy).
Renderer input overhaul
src/renderer/src/components/ChatView.vue, src/renderer/src/components/prompt-input/PromptInput.vue, src/renderer/src/components/prompt-input/ModelChooser.vue
Replace ChatInput with new PromptInput; add ModelChooser; integrate TipTap editor, file paste/drag, model/search controls, rate-limit polling, and send flow; adjust ChatView container padding.
Title and layout simplification
src/renderer/src/components/TitleView.vue, src/renderer/src/components/message/MessageList.vue, src/renderer/src/views/ChatTabView.vue
Remove model selector/config and rate-limit UI from TitleView; simplify title handling; tweak message list classes; stop passing activeModel.
Settings shortcuts UI
src/renderer/src/components/settings/ShortcutSettings.vue
Redesign shortcut display/recording using Kbd/KbdGroup; new interaction flow and visuals; update helpers and messages.
Playground demos expansion
src/renderer/src/views/PlaygroundTabView.vue, src/renderer/src/views/playground/demos/*
Add demos for ButtonGroup, Empty, Field, InputGroup, Item, Kbd, Spinner; reorganize sections and imports.
shadcn additions: button-group
src/shadcn/components/ui/button-group/*
Add ButtonGroup, ButtonGroupSeparator, ButtonGroupText, variants/type exports.
shadcn additions: empty
src/shadcn/components/ui/empty/*
Add Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle plus variants/type exports.
shadcn additions: field
src/shadcn/components/ui/field/*
Add Field primitives (Field, Content, Description, Error, Group, Label, Legend, Separator, Set, Title) with variants/type exports.
shadcn additions: input-group
src/shadcn/components/ui/input-group/*
Add InputGroup and subcomponents (Addon, Button, Input, Text, Textarea) with variants/types.
shadcn additions: item
src/shadcn/components/ui/item/*
Add Item suite (Item, Actions, Content, Description, Footer, Group, Header, Media, Separator, Title) with variants/types.
shadcn additions: kbd
src/shadcn/components/ui/kbd/*
Add Kbd and KbdGroup; index re-exports.
shadcn additions: spinner
src/shadcn/components/ui/spinner/*
Add Spinner component and index export.
shadcn tweaks
src/shadcn/components/ui/button/index.ts, src/shadcn/components/ui/separator/Separator.vue
Button size variants add "icon-sm"/"icon-lg" and quote keys; Separator class string formatting only.
Tooling
scripts/update-shadcn.js
Extend component install list to include new shadcn components.

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
Loading
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

codex

Suggested reviewers

  • deepinfect

Poem

In wires I hop, with schemas new,
Reasoning sprouts, and searches too.
A prompt so plush, with files in tow,
I thump-send thoughts—away they go!
Shadcn meadows, buttons bloom—
Spinner twirls a humming tune.
Happy hops in coding’s room. 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly references the addition of a new prompt input component and layout changes, which aligns with the PR objectives to introduce a new chat view layout and prompt input. It clearly indicates a UI feature enhancement and matches the primary focus of the changeset. Although the “wip” suffix signals ongoing work, the main topics remain identifiable.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ui/chat-view-layout

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

yyhhyyyyyy and others added 3 commits October 9, 2025 19:10
* chore(shadcn): sync new ui components

* chore(playground): showcase new shadcn suites

* feat: replace kbg and kb for ShortcutSettings
@zerob13 zerob13 marked this pull request as ready for review October 9, 2025 15:06
@zerob13
Copy link
Collaborator Author

zerob13 commented Oct 9, 2025

@codex review

@zerob13 zerob13 closed this Oct 9, 2025
@zerob13 zerob13 reopened this Oct 9, 2025
@chatgpt-codex-connector
Copy link

Codex Review: Didn't find any major issues. Hooray!

ℹ️ About Codex in GitHub

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

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

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

Codex can also answer questions or update the PR. Try commenting

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 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 exposes clearContent. Define a restoreFocus() method and include it in defineExpose (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 in src/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 copy

All 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-slot attribute 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 variant prop has no default value. If this component is commonly used with one variant more than the other, consider setting a default using withDefaults to 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 variant prop lacks a default value. If one variant is more commonly used, consider setting it as the default using withDefaults for 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 orientation prop 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. Since currentTarget is 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?.parentElement exists, 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 getSearch helper accepts any string for search_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

📥 Commits

Reviewing files that changed from the base of the PR and between 422b931 and 6a2bb18.

📒 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.ts
  • scripts/update-shadcn.js
  • src/shared/types/model-db.ts
  • src/shadcn/components/ui/empty/index.ts
  • src/shadcn/components/ui/kbd/index.ts
  • src/shadcn/components/ui/item/index.ts
  • src/shadcn/components/ui/field/index.ts
  • src/shadcn/components/ui/button-group/index.ts
  • src/main/presenter/configPresenter/modelConfig.ts
  • src/main/presenter/configPresenter/index.ts
  • src/shadcn/components/ui/button/index.ts
  • src/shadcn/components/ui/input-group/index.ts
  • test/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.ts
  • src/shared/types/model-db.ts
  • src/shadcn/components/ui/empty/index.ts
  • src/shadcn/components/ui/kbd/index.ts
  • src/shadcn/components/ui/item/index.ts
  • src/shadcn/components/ui/field/index.ts
  • src/shadcn/components/ui/button-group/index.ts
  • src/main/presenter/configPresenter/modelConfig.ts
  • src/main/presenter/configPresenter/index.ts
  • src/shadcn/components/ui/button/index.ts
  • src/shadcn/components/ui/input-group/index.ts
  • test/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.ts
  • src/shadcn/components/ui/field/FieldSet.vue
  • scripts/update-shadcn.js
  • src/shadcn/components/ui/input-group/InputGroupTextarea.vue
  • src/shadcn/components/ui/separator/Separator.vue
  • src/shared/types/model-db.ts
  • src/shadcn/components/ui/item/ItemMedia.vue
  • src/renderer/src/components/message/MessageList.vue
  • src/shadcn/components/ui/field/FieldGroup.vue
  • src/shadcn/components/ui/button-group/ButtonGroup.vue
  • src/shadcn/components/ui/button-group/ButtonGroupText.vue
  • src/shadcn/components/ui/input-group/InputGroupText.vue
  • src/shadcn/components/ui/item/ItemFooter.vue
  • src/shadcn/components/ui/input-group/InputGroupInput.vue
  • src/shadcn/components/ui/empty/EmptyTitle.vue
  • src/renderer/src/components/settings/ShortcutSettings.vue
  • src/shadcn/components/ui/empty/index.ts
  • src/shadcn/components/ui/field/FieldError.vue
  • src/shadcn/components/ui/kbd/index.ts
  • src/shadcn/components/ui/empty/Empty.vue
  • src/shadcn/components/ui/field/FieldLabel.vue
  • src/shadcn/components/ui/item/Item.vue
  • src/shadcn/components/ui/item/ItemContent.vue
  • src/shadcn/components/ui/item/index.ts
  • src/shadcn/components/ui/field/FieldDescription.vue
  • src/shadcn/components/ui/field/index.ts
  • src/shadcn/components/ui/item/ItemTitle.vue
  • src/renderer/src/components/prompt-input/PromptInput.vue
  • src/shadcn/components/ui/button-group/index.ts
  • src/renderer/src/components/prompt-input/ModelChooser.vue
  • src/shadcn/components/ui/empty/EmptyHeader.vue
  • src/renderer/src/views/playground/demos/InputGroupDemo.vue
  • src/main/presenter/configPresenter/modelConfig.ts
  • src/shadcn/components/ui/field/FieldSeparator.vue
  • src/shadcn/components/ui/field/FieldContent.vue
  • src/shadcn/components/ui/button-group/ButtonGroupSeparator.vue
  • src/main/presenter/configPresenter/index.ts
  • src/renderer/src/views/playground/demos/ButtonGroupDemo.vue
  • src/renderer/src/components/ChatView.vue
  • src/shadcn/components/ui/spinner/Spinner.vue
  • src/shadcn/components/ui/field/FieldLegend.vue
  • src/shadcn/components/ui/input-group/InputGroupAddon.vue
  • src/renderer/src/views/playground/demos/FieldDemo.vue
  • src/shadcn/components/ui/item/ItemHeader.vue
  • src/shadcn/components/ui/item/ItemSeparator.vue
  • src/shadcn/components/ui/kbd/Kbd.vue
  • src/shadcn/components/ui/item/ItemDescription.vue
  • src/shadcn/components/ui/input-group/InputGroupButton.vue
  • src/renderer/src/views/playground/demos/SpinnerDemo.vue
  • src/shadcn/components/ui/kbd/KbdGroup.vue
  • src/renderer/src/views/playground/demos/EmptyDemo.vue
  • src/shadcn/components/ui/button/index.ts
  • src/shadcn/components/ui/item/ItemGroup.vue
  • src/shadcn/components/ui/input-group/index.ts
  • src/shadcn/components/ui/item/ItemActions.vue
  • src/shadcn/components/ui/field/Field.vue
  • src/shadcn/components/ui/empty/EmptyMedia.vue
  • src/renderer/src/views/ChatTabView.vue
  • src/renderer/src/views/playground/demos/ItemDemo.vue
  • src/renderer/src/views/playground/demos/KbdDemo.vue
  • src/shadcn/components/ui/input-group/InputGroup.vue
  • src/renderer/src/views/PlaygroundTabView.vue
  • src/shadcn/components/ui/empty/EmptyContent.vue
  • test/main/presenter/providerDbModelConfig.test.ts
  • src/shadcn/components/ui/empty/EmptyDescription.vue
  • src/renderer/src/components/TitleView.vue
  • src/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.ts
  • src/shadcn/components/ui/field/FieldSet.vue
  • src/shadcn/components/ui/input-group/InputGroupTextarea.vue
  • src/shadcn/components/ui/separator/Separator.vue
  • src/shared/types/model-db.ts
  • src/shadcn/components/ui/item/ItemMedia.vue
  • src/renderer/src/components/message/MessageList.vue
  • src/shadcn/components/ui/field/FieldGroup.vue
  • src/shadcn/components/ui/button-group/ButtonGroup.vue
  • src/shadcn/components/ui/button-group/ButtonGroupText.vue
  • src/shadcn/components/ui/input-group/InputGroupText.vue
  • src/shadcn/components/ui/item/ItemFooter.vue
  • src/shadcn/components/ui/input-group/InputGroupInput.vue
  • src/shadcn/components/ui/empty/EmptyTitle.vue
  • src/renderer/src/components/settings/ShortcutSettings.vue
  • src/shadcn/components/ui/empty/index.ts
  • src/shadcn/components/ui/field/FieldError.vue
  • src/shadcn/components/ui/kbd/index.ts
  • src/shadcn/components/ui/empty/Empty.vue
  • src/shadcn/components/ui/field/FieldLabel.vue
  • src/shadcn/components/ui/item/Item.vue
  • src/shadcn/components/ui/item/ItemContent.vue
  • src/shadcn/components/ui/item/index.ts
  • src/shadcn/components/ui/field/FieldDescription.vue
  • src/shadcn/components/ui/field/index.ts
  • src/shadcn/components/ui/item/ItemTitle.vue
  • src/renderer/src/components/prompt-input/PromptInput.vue
  • src/shadcn/components/ui/button-group/index.ts
  • src/renderer/src/components/prompt-input/ModelChooser.vue
  • src/shadcn/components/ui/empty/EmptyHeader.vue
  • src/renderer/src/views/playground/demos/InputGroupDemo.vue
  • src/main/presenter/configPresenter/modelConfig.ts
  • src/shadcn/components/ui/field/FieldSeparator.vue
  • src/shadcn/components/ui/field/FieldContent.vue
  • src/shadcn/components/ui/button-group/ButtonGroupSeparator.vue
  • src/main/presenter/configPresenter/index.ts
  • src/renderer/src/views/playground/demos/ButtonGroupDemo.vue
  • src/renderer/src/components/ChatView.vue
  • src/shadcn/components/ui/spinner/Spinner.vue
  • src/shadcn/components/ui/field/FieldLegend.vue
  • src/shadcn/components/ui/input-group/InputGroupAddon.vue
  • src/renderer/src/views/playground/demos/FieldDemo.vue
  • src/shadcn/components/ui/item/ItemHeader.vue
  • src/shadcn/components/ui/item/ItemSeparator.vue
  • src/shadcn/components/ui/kbd/Kbd.vue
  • src/shadcn/components/ui/item/ItemDescription.vue
  • src/shadcn/components/ui/input-group/InputGroupButton.vue
  • src/renderer/src/views/playground/demos/SpinnerDemo.vue
  • src/shadcn/components/ui/kbd/KbdGroup.vue
  • src/renderer/src/views/playground/demos/EmptyDemo.vue
  • src/shadcn/components/ui/button/index.ts
  • src/shadcn/components/ui/item/ItemGroup.vue
  • src/shadcn/components/ui/input-group/index.ts
  • src/shadcn/components/ui/item/ItemActions.vue
  • src/shadcn/components/ui/field/Field.vue
  • src/shadcn/components/ui/empty/EmptyMedia.vue
  • src/renderer/src/views/ChatTabView.vue
  • src/renderer/src/views/playground/demos/ItemDemo.vue
  • src/renderer/src/views/playground/demos/KbdDemo.vue
  • src/shadcn/components/ui/input-group/InputGroup.vue
  • src/renderer/src/views/PlaygroundTabView.vue
  • src/shadcn/components/ui/empty/EmptyContent.vue
  • test/main/presenter/providerDbModelConfig.test.ts
  • src/shadcn/components/ui/empty/EmptyDescription.vue
  • src/renderer/src/components/TitleView.vue
  • src/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.ts
  • src/shadcn/components/ui/field/FieldSet.vue
  • scripts/update-shadcn.js
  • src/shadcn/components/ui/input-group/InputGroupTextarea.vue
  • src/shadcn/components/ui/separator/Separator.vue
  • src/shared/types/model-db.ts
  • src/shadcn/components/ui/item/ItemMedia.vue
  • src/renderer/src/components/message/MessageList.vue
  • src/shadcn/components/ui/field/FieldGroup.vue
  • src/shadcn/components/ui/button-group/ButtonGroup.vue
  • src/shadcn/components/ui/button-group/ButtonGroupText.vue
  • src/shadcn/components/ui/input-group/InputGroupText.vue
  • src/shadcn/components/ui/item/ItemFooter.vue
  • src/shadcn/components/ui/input-group/InputGroupInput.vue
  • src/shadcn/components/ui/empty/EmptyTitle.vue
  • src/renderer/src/components/settings/ShortcutSettings.vue
  • src/shadcn/components/ui/empty/index.ts
  • src/shadcn/components/ui/field/FieldError.vue
  • src/shadcn/components/ui/kbd/index.ts
  • src/shadcn/components/ui/empty/Empty.vue
  • src/shadcn/components/ui/field/FieldLabel.vue
  • src/shadcn/components/ui/item/Item.vue
  • src/shadcn/components/ui/item/ItemContent.vue
  • src/shadcn/components/ui/item/index.ts
  • src/shadcn/components/ui/field/FieldDescription.vue
  • src/shadcn/components/ui/field/index.ts
  • src/shadcn/components/ui/item/ItemTitle.vue
  • src/renderer/src/components/prompt-input/PromptInput.vue
  • src/shadcn/components/ui/button-group/index.ts
  • src/renderer/src/components/prompt-input/ModelChooser.vue
  • src/shadcn/components/ui/empty/EmptyHeader.vue
  • src/renderer/src/views/playground/demos/InputGroupDemo.vue
  • src/main/presenter/configPresenter/modelConfig.ts
  • src/shadcn/components/ui/field/FieldSeparator.vue
  • src/shadcn/components/ui/field/FieldContent.vue
  • src/shadcn/components/ui/button-group/ButtonGroupSeparator.vue
  • src/main/presenter/configPresenter/index.ts
  • src/renderer/src/views/playground/demos/ButtonGroupDemo.vue
  • src/renderer/src/components/ChatView.vue
  • src/shadcn/components/ui/spinner/Spinner.vue
  • src/shadcn/components/ui/field/FieldLegend.vue
  • src/shadcn/components/ui/input-group/InputGroupAddon.vue
  • src/renderer/src/views/playground/demos/FieldDemo.vue
  • src/shadcn/components/ui/item/ItemHeader.vue
  • src/shadcn/components/ui/item/ItemSeparator.vue
  • src/shadcn/components/ui/kbd/Kbd.vue
  • src/shadcn/components/ui/item/ItemDescription.vue
  • src/shadcn/components/ui/input-group/InputGroupButton.vue
  • src/renderer/src/views/playground/demos/SpinnerDemo.vue
  • src/shadcn/components/ui/kbd/KbdGroup.vue
  • src/renderer/src/views/playground/demos/EmptyDemo.vue
  • src/shadcn/components/ui/button/index.ts
  • src/shadcn/components/ui/item/ItemGroup.vue
  • src/shadcn/components/ui/input-group/index.ts
  • src/shadcn/components/ui/item/ItemActions.vue
  • src/shadcn/components/ui/field/Field.vue
  • src/shadcn/components/ui/empty/EmptyMedia.vue
  • src/renderer/src/views/ChatTabView.vue
  • src/renderer/src/views/playground/demos/ItemDemo.vue
  • src/renderer/src/views/playground/demos/KbdDemo.vue
  • src/shadcn/components/ui/input-group/InputGroup.vue
  • src/renderer/src/views/PlaygroundTabView.vue
  • src/shadcn/components/ui/empty/EmptyContent.vue
  • test/main/presenter/providerDbModelConfig.test.ts
  • src/shadcn/components/ui/empty/EmptyDescription.vue
  • src/renderer/src/components/TitleView.vue
  • src/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.js
  • scripts/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.vue
  • src/renderer/src/components/settings/ShortcutSettings.vue
  • src/renderer/src/components/prompt-input/PromptInput.vue
  • src/renderer/src/components/prompt-input/ModelChooser.vue
  • src/renderer/src/views/playground/demos/InputGroupDemo.vue
  • src/renderer/src/views/playground/demos/ButtonGroupDemo.vue
  • src/renderer/src/components/ChatView.vue
  • src/renderer/src/views/playground/demos/FieldDemo.vue
  • src/renderer/src/views/playground/demos/SpinnerDemo.vue
  • src/renderer/src/views/playground/demos/EmptyDemo.vue
  • src/renderer/src/views/ChatTabView.vue
  • src/renderer/src/views/playground/demos/ItemDemo.vue
  • src/renderer/src/views/playground/demos/KbdDemo.vue
  • src/renderer/src/views/PlaygroundTabView.vue
  • src/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.vue
  • src/renderer/src/components/settings/ShortcutSettings.vue
  • src/renderer/src/components/prompt-input/PromptInput.vue
  • src/renderer/src/components/prompt-input/ModelChooser.vue
  • src/renderer/src/views/playground/demos/InputGroupDemo.vue
  • src/renderer/src/views/playground/demos/ButtonGroupDemo.vue
  • src/renderer/src/components/ChatView.vue
  • src/renderer/src/views/playground/demos/FieldDemo.vue
  • src/renderer/src/views/playground/demos/SpinnerDemo.vue
  • src/renderer/src/views/playground/demos/EmptyDemo.vue
  • src/renderer/src/views/ChatTabView.vue
  • src/renderer/src/views/playground/demos/ItemDemo.vue
  • src/renderer/src/views/playground/demos/KbdDemo.vue
  • src/renderer/src/views/PlaygroundTabView.vue
  • src/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.vue
  • src/renderer/src/components/settings/ShortcutSettings.vue
  • src/renderer/src/components/prompt-input/PromptInput.vue
  • src/renderer/src/components/prompt-input/ModelChooser.vue
  • src/renderer/src/views/playground/demos/InputGroupDemo.vue
  • src/renderer/src/views/playground/demos/ButtonGroupDemo.vue
  • src/renderer/src/components/ChatView.vue
  • src/renderer/src/views/playground/demos/FieldDemo.vue
  • src/renderer/src/views/playground/demos/SpinnerDemo.vue
  • src/renderer/src/views/playground/demos/EmptyDemo.vue
  • src/renderer/src/views/ChatTabView.vue
  • src/renderer/src/views/playground/demos/ItemDemo.vue
  • src/renderer/src/views/playground/demos/KbdDemo.vue
  • src/renderer/src/views/PlaygroundTabView.vue
  • src/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.vue
  • src/renderer/src/components/settings/ShortcutSettings.vue
  • src/renderer/src/components/prompt-input/PromptInput.vue
  • src/renderer/src/components/prompt-input/ModelChooser.vue
  • src/renderer/src/views/playground/demos/InputGroupDemo.vue
  • src/renderer/src/views/playground/demos/ButtonGroupDemo.vue
  • src/renderer/src/components/ChatView.vue
  • src/renderer/src/views/playground/demos/FieldDemo.vue
  • src/renderer/src/views/playground/demos/SpinnerDemo.vue
  • src/renderer/src/views/playground/demos/EmptyDemo.vue
  • src/renderer/src/views/ChatTabView.vue
  • src/renderer/src/views/playground/demos/ItemDemo.vue
  • src/renderer/src/views/playground/demos/KbdDemo.vue
  • src/renderer/src/views/PlaygroundTabView.vue
  • src/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.vue
  • src/renderer/src/components/settings/ShortcutSettings.vue
  • src/renderer/src/components/prompt-input/PromptInput.vue
  • src/renderer/src/components/prompt-input/ModelChooser.vue
  • src/renderer/src/views/playground/demos/InputGroupDemo.vue
  • src/renderer/src/views/playground/demos/ButtonGroupDemo.vue
  • src/renderer/src/components/ChatView.vue
  • src/renderer/src/views/playground/demos/FieldDemo.vue
  • src/renderer/src/views/playground/demos/SpinnerDemo.vue
  • src/renderer/src/views/playground/demos/EmptyDemo.vue
  • src/renderer/src/views/ChatTabView.vue
  • src/renderer/src/views/playground/demos/ItemDemo.vue
  • src/renderer/src/views/playground/demos/KbdDemo.vue
  • src/renderer/src/views/PlaygroundTabView.vue
  • src/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.vue
  • src/renderer/src/components/settings/ShortcutSettings.vue
  • src/renderer/src/components/prompt-input/PromptInput.vue
  • src/renderer/src/components/prompt-input/ModelChooser.vue
  • src/renderer/src/views/playground/demos/InputGroupDemo.vue
  • src/renderer/src/views/playground/demos/ButtonGroupDemo.vue
  • src/renderer/src/components/ChatView.vue
  • src/renderer/src/views/playground/demos/FieldDemo.vue
  • src/renderer/src/views/playground/demos/SpinnerDemo.vue
  • src/renderer/src/views/playground/demos/EmptyDemo.vue
  • src/renderer/src/views/ChatTabView.vue
  • src/renderer/src/views/playground/demos/ItemDemo.vue
  • src/renderer/src/views/playground/demos/KbdDemo.vue
  • src/renderer/src/views/PlaygroundTabView.vue
  • src/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.vue
  • src/renderer/src/components/settings/ShortcutSettings.vue
  • src/renderer/src/components/prompt-input/PromptInput.vue
  • src/renderer/src/components/prompt-input/ModelChooser.vue
  • src/renderer/src/views/playground/demos/InputGroupDemo.vue
  • src/renderer/src/views/playground/demos/ButtonGroupDemo.vue
  • src/renderer/src/components/ChatView.vue
  • src/renderer/src/views/playground/demos/FieldDemo.vue
  • src/renderer/src/views/playground/demos/SpinnerDemo.vue
  • src/renderer/src/views/playground/demos/EmptyDemo.vue
  • src/renderer/src/views/ChatTabView.vue
  • src/renderer/src/views/playground/demos/ItemDemo.vue
  • src/renderer/src/views/playground/demos/KbdDemo.vue
  • src/renderer/src/views/PlaygroundTabView.vue
  • src/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.vue
  • src/renderer/src/components/settings/ShortcutSettings.vue
  • src/renderer/src/components/prompt-input/PromptInput.vue
  • src/renderer/src/components/prompt-input/ModelChooser.vue
  • src/renderer/src/views/playground/demos/InputGroupDemo.vue
  • src/renderer/src/views/playground/demos/ButtonGroupDemo.vue
  • src/renderer/src/components/ChatView.vue
  • src/renderer/src/views/playground/demos/FieldDemo.vue
  • src/renderer/src/views/playground/demos/SpinnerDemo.vue
  • src/renderer/src/views/playground/demos/EmptyDemo.vue
  • src/renderer/src/views/ChatTabView.vue
  • src/renderer/src/views/playground/demos/ItemDemo.vue
  • src/renderer/src/views/playground/demos/KbdDemo.vue
  • src/renderer/src/views/PlaygroundTabView.vue
  • src/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.vue
  • src/renderer/src/components/settings/ShortcutSettings.vue
  • src/renderer/src/components/prompt-input/PromptInput.vue
  • src/renderer/src/components/prompt-input/ModelChooser.vue
  • src/renderer/src/components/ChatView.vue
  • src/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.vue
  • src/renderer/src/components/settings/ShortcutSettings.vue
  • src/renderer/src/components/prompt-input/PromptInput.vue
  • src/renderer/src/components/prompt-input/ModelChooser.vue
  • src/renderer/src/views/playground/demos/InputGroupDemo.vue
  • src/renderer/src/views/playground/demos/ButtonGroupDemo.vue
  • src/renderer/src/components/ChatView.vue
  • src/renderer/src/views/playground/demos/FieldDemo.vue
  • src/renderer/src/views/playground/demos/SpinnerDemo.vue
  • src/renderer/src/views/playground/demos/EmptyDemo.vue
  • src/renderer/src/views/ChatTabView.vue
  • src/renderer/src/views/playground/demos/ItemDemo.vue
  • src/renderer/src/views/playground/demos/KbdDemo.vue
  • src/renderer/src/views/PlaygroundTabView.vue
  • src/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.vue
  • src/renderer/src/components/settings/ShortcutSettings.vue
  • src/renderer/src/components/prompt-input/PromptInput.vue
  • src/renderer/src/components/prompt-input/ModelChooser.vue
  • src/renderer/src/views/playground/demos/InputGroupDemo.vue
  • src/renderer/src/views/playground/demos/ButtonGroupDemo.vue
  • src/renderer/src/components/ChatView.vue
  • src/renderer/src/views/playground/demos/FieldDemo.vue
  • src/renderer/src/views/playground/demos/SpinnerDemo.vue
  • src/renderer/src/views/playground/demos/EmptyDemo.vue
  • src/renderer/src/views/ChatTabView.vue
  • src/renderer/src/views/playground/demos/ItemDemo.vue
  • src/renderer/src/views/playground/demos/KbdDemo.vue
  • src/renderer/src/views/PlaygroundTabView.vue
  • src/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.vue
  • src/renderer/src/components/settings/ShortcutSettings.vue
  • src/renderer/src/components/prompt-input/PromptInput.vue
  • src/renderer/src/components/prompt-input/ModelChooser.vue
  • src/renderer/src/views/playground/demos/InputGroupDemo.vue
  • src/renderer/src/views/playground/demos/ButtonGroupDemo.vue
  • src/renderer/src/components/ChatView.vue
  • src/renderer/src/views/playground/demos/FieldDemo.vue
  • src/renderer/src/views/playground/demos/SpinnerDemo.vue
  • src/renderer/src/views/playground/demos/EmptyDemo.vue
  • src/renderer/src/views/ChatTabView.vue
  • src/renderer/src/views/playground/demos/ItemDemo.vue
  • src/renderer/src/views/playground/demos/KbdDemo.vue
  • src/renderer/src/views/PlaygroundTabView.vue
  • src/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.ts
  • src/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.ts
  • src/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.ts
  • src/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.ts
  • src/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.ts
  • src/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 :class binding 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 reasoning and enableSearch capabilities from nested provider model structures (reasoning?.supported and search?.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 activeModel dependencies and the simplified title update mechanism driven by activeThread changes 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 cn utility.

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 (xs size and ghost variant).

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 orientation prop 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
  • thinkingBudget explicitly falls back to undefined
  • searchStrategy ternary correctly defaults to 'turbo' for all non-'max' values including undefined

The 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 withDefaults for 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 ReasoningSchema and SearchSchema are correctly defined with optional fields and proper Zod type validation. The nested structure for reasoning.budget.default provides 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 reasoning and search objects, aligning with the updated ModelSchema and 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: false to 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.

Comment on lines +603 to +615
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
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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.

Suggested change
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).

Comment on lines +1428 to +1431
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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Suggested change
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.

Comment on lines +48 to +56
<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>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +7 to +14
<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>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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.

Comment on lines +1 to +50
<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>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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."

Comment on lines +13 to +16
:class="cn(
'text-muted-foreground [&>a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4',
$attrs.class ?? '',
)"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Suggested change
: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.

Comment on lines +5 to +16
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 ?? '',
)"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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.

Comment on lines +15 to +18
data-slot="empty-icon"
:data-variant="variant"
:class="cn(emptyMediaVariants({ variant }), props.class)"
>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Suggested change
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.

Comment on lines +15 to +18
data-slot="empty-icon"
:data-variant="variant"
:class="cn(emptyMediaVariants({ variant }), props.class)"
>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Suggested change
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.

Comment on lines +11 to +42
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>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Suggested change
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.

@zerob13 zerob13 merged commit edb3b3e into dev Oct 9, 2025
2 checks passed
@zerob13 zerob13 deleted the ui/chat-view-layout branch November 23, 2025 13:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants