Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/main/presenter/windowPresenter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,7 @@ export class WindowPresenter implements IWindowPresenter {
titleBarStyle: 'hiddenInset', // macOS 风格标题栏
transparent: process.platform === 'darwin', // macOS 标题栏透明
vibrancy: process.platform === 'darwin' ? 'hud' : undefined, // macOS 磨砂效果
backgroundMaterial: process.platform === 'win32' ? 'mica' : undefined, // Windows 11 材质效果
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 English for comments.

The comment contains Chinese text, which violates the coding guidelines.

As per coding guidelines

Apply this diff to use English:

-      backgroundMaterial: process.platform === 'win32' ? 'mica' : undefined, // Windows 11 材质效果
+      backgroundMaterial: process.platform === 'win32' ? 'mica' : undefined, // Windows 11 mica material effect
📝 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
backgroundMaterial: process.platform === 'win32' ? 'mica' : undefined, // Windows 11 材质效果
backgroundMaterial: process.platform === 'win32' ? 'mica' : undefined, // Windows 11 mica material effect
🤖 Prompt for AI Agents
In src/main/presenter/windowPresenter/index.ts around line 657, the inline
comment contains Chinese text; replace it with an English comment that conveys
the same meaning (e.g., "Windows 11 material effect" or "Windows 11 acrylic/mica
material effect") so the codebase follows the English-only comment guideline;
update only the comment text and preserve the code behavior.

backgroundColor: '#00ffffff', // 透明背景色
maximizable: true, // 允许最大化
frame: process.platform === 'darwin', // macOS 无边框
Expand Down
6 changes: 3 additions & 3 deletions src/renderer/shell/App.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<div class="w-screen h-screen">
<div class="w-screen h-screen" :class="isWinMacOS ? '' : 'bg-background'">
<AppBar />
<main class="content-container">
<!-- WebContentsView will be rendered here by the main process -->
Expand All @@ -11,12 +11,12 @@
import AppBar from './components/AppBar.vue'
import { ref, onMounted } from 'vue'
import { usePresenter } from '@/composables/usePresenter'
const isMacOS = ref(false)
const isWinMacOS = ref(false)
const devicePresenter = usePresenter('devicePresenter')
// Shell component setup
onMounted(() => {
devicePresenter.getDeviceInfo().then((deviceInfo) => {
isMacOS.value = deviceInfo.platform === 'darwin'
isWinMacOS.value = deviceInfo.platform === 'darwin' || deviceInfo.platform === 'win32'
})
})
</script>
Expand Down
11 changes: 7 additions & 4 deletions src/renderer/shell/components/AppBar.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
<template>
<div
class="flex flex-row h-9 min-h-9 bg-window-background border border-b-0 border-window-inner-border box-border rounded-t-[10px] relative overflow-hidden"
:class="[!isFullscreened && isMacOS ? '' : ' rounded-t-none']"
class="flex flex-row h-9 min-h-9 border border-b-0 border-window-inner-border box-border rounded-t-[10px] relative overflow-hidden"
:class="[
!isFullscreened && isMacOS ? '' : ' rounded-t-none',
isMacOS ? 'bg-window-background' : 'bg-window-background/10'
]"
Comment on lines +3 to +7
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Verify Windows background styling consistency.

The AppBar applies bg-window-background only when isMacOS is true, and bg-window-background/10 otherwise. However, src/renderer/shell/App.vue treats both Windows and macOS the same (via isWinMacOS).

Since Windows now uses the 'mica' material (per windowPresenter/index.ts line 657), verify whether AppBar should also apply bg-window-background (not /10) for Windows to ensure visual consistency with the mica effect.

Run the following script to check how Windows is handled in both files:


🏁 Script executed:

#!/bin/bash
# Check OS detection patterns in App.vue and AppBar.vue

echo "=== App.vue OS detection ==="
rg -n "isWinMacOS|darwin|win32" src/renderer/shell/App.vue

echo ""
echo "=== AppBar.vue OS detection ==="
rg -n "isMacOS|darwin|win32" src/renderer/shell/components/AppBar.vue

echo ""
echo "=== Background styling usage ==="
rg -n "bg-window-background" src/renderer/shell/

Length of output: 895


Use full background on Windows in AppBar.vue
Replace the isMacOS check with a combined darwin || win32 condition (e.g. isWinMacOS) so Windows also applies bg-window-background instead of bg-window-background/10.

🤖 Prompt for AI Agents
In src/renderer/shell/components/AppBar.vue around lines 3 to 7, the template
uses only isMacOS to decide the background class so Windows still gets
bg-window-background/10; add a combined platform check (e.g. isWinMacOS =
isMacOS || isWin32 or check process.platform === 'darwin' || process.platform
=== 'win32' where platform flags are defined) and replace the two isMacOS checks
with this new isWinMacOS so both macOS and Windows use bg-window-background
while other platforms keep bg-window-background/10; ensure the new flag is
defined/imported in the component script and used in the same two class
locations.

:dir="langStore.dir"
>
<div class="absolute bottom-0 left-0 w-full h-[1px] bg-border"></div>
<div class="absolute bottom-0 left-0 w-full h-[1px] bg-border z-10"></div>
<div
class="h-full shrink-0 w-0 flex-1 flex select-none text-center text-sm font-medium flex-row items-center justify-start window-drag-region"
>
Expand Down Expand Up @@ -102,7 +105,7 @@
</Button>
<Button
v-if="!isMacOS"
class="window-no-drag-region shrink-0 w-12 bg-transparent shadow-none rounded-none hover:bg-card/80 text-xs font-medium text-foreground flex items-center justify-center transition-all duration-200 group"
class="window-no-drag-region shrink-0 w-12 bg-transparent shadow-none rounded-none hover:bg-red-700/80 hover:text-white text-xs font-medium text-foreground flex items-center justify-center transition-all duration-200 group"
@click="closeWindow"
>
<CloseIcon class="h-3! w-3!" />
Expand Down
6 changes: 3 additions & 3 deletions src/renderer/src/assets/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@
/* light */
/* color */
--bg-accent: hsl(0 0 15% / 0.05);
--bg-window-background: hsl(0 0 100% / 0.1);
--bg-window-background: hsl(0 0 100% / 0.5);
--bg-background-70: hsl(0 0 100% / 0.7);
--bg-card: hsl(0 0 100%);
--bg-primary: hsl(210 100% 43%);
Expand Down Expand Up @@ -178,7 +178,7 @@
/* dark */
/* color */
--bg-accent: hsl(0 0 100% / 0.05);
--bg-window-background: hsl(0 0 0% / 1);
--bg-window-background: hsl(0 0 10% / 0.5);
--bg-background-70: hsl(0 0 15% / 0.7);

--bg-muted: hsl(0 0 100% / 0.03);
Expand Down Expand Up @@ -254,7 +254,7 @@
--bg-primary: hsl(210 100% 43%);
--bg-primary-70: hsl(210 100% 43% / 0.7);
--bg-primary-700: hsl(210 100% 37%);
--bg-window-background: hsl(0 0 10% / 0.7);
--bg-window-background: hsl(0 0 10% / 0.5);
--border-window-outside: hsl(0 0 0% / 0.4);
--shadow-card: hsl(0 0 0% / 0.4);
--syntax-bold: var(--color-blue-500);
Expand Down
15 changes: 0 additions & 15 deletions src/renderer/src/components/NewThread.vue
Original file line number Diff line number Diff line change
@@ -1,16 +1,5 @@
<template>
<div class="h-full w-full flex flex-col items-center justify-start">
<div class="w-full p-2 flex flex-row gap-2 items-center">
<Button
class="w-7 h-7 rounded-md"
size="icon"
variant="outline"
@click="onSidebarButtonClick"
>
<Icon v-if="chatStore.isSidebarOpen" icon="lucide:panel-left-close" class="w-4 h-4" />
<Icon v-else icon="lucide:panel-left-open" class="w-4 h-4" />
</Button>
</div>
<div class="h-0 w-full grow flex flex-col items-center justify-center">
<img src="@/assets/logo-dark.png" class="w-24 h-24" loading="lazy" />
<h1 class="text-2xl font-bold px-8 pt-4">{{ t('newThread.greeting') }}</h1>
Expand Down Expand Up @@ -328,10 +317,6 @@ const handleMouseLeave = () => {
isHovering.value = false
}

const onSidebarButtonClick = () => {
chatStore.isSidebarOpen = !chatStore.isSidebarOpen
}

const handleModelUpdate = (model: MODEL_META, providerId: string) => {
activeModel.value = {
name: model.name,
Expand Down
15 changes: 5 additions & 10 deletions src/renderer/src/components/mcpToolsList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,8 @@ onMounted(async () => {
id="mcp-btn"
variant="outline"
:class="[
'flex border border-input rounded-lg shadow-sm items-center gap-1.5 h-7 text-xs px-1.5 w-auto',
mcpEnabled
? 'dark:!bg-primary bg-primary border-primary text-primary-foreground hover:bg-primary/90 hover:text-primary-foreground'
: ''
'flex text-accent-foreground rounded-lg shadow-sm items-center gap-1.5 h-7 text-xs px-1.5 w-auto',
mcpEnabled ? 'text-primary' : ''
]"
size="icon"
>
Expand All @@ -114,12 +112,9 @@ onMounted(async () => {
/>
<Icon v-else icon="lucide:hammer" class="w-4 h-4" />

<span
v-if="hasTools && !isLoading && !isError"
:class="{ 'text-muted-foreground': !mcpEnabled, 'text-white': mcpEnabled }"
class="text-sm"
>{{ getTotalEnabledToolCount() }}</span
>
<span v-if="hasTools && !isLoading && !isError" class="text-sm">{{
getTotalEnabledToolCount()
}}</span>
</Button>
</TooltipTrigger>
<TooltipContent>
Expand Down
6 changes: 3 additions & 3 deletions src/renderer/src/components/message/MessageItemUser.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<div
v-show="!message.content.continue"
:data-message-id="message.id"
class="flex flex-row-reverse group py-4 pl-11 gap-2 user-message-item"
class="flex flex-row-reverse group pl-11 gap-2 user-message-item"
>
<!-- 头像 -->
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

Replace Chinese comments with English.

Comments must be in English per coding guidelines.

As per coding guidelines.

Apply this diff to translate the comments:

-    <!-- 头像 -->
+    <!-- Avatar -->
-      <!-- 消息内容 -->
+      <!-- Message content -->
-  // 处理 mention 点击事件,可以根据需要实现具体逻辑
+  // Handle mention click event, implement specific logic as needed

Also applies to: 20-20, 201-201

🤖 Prompt for AI Agents
In src/renderer/src/components/message/MessageItemUser.vue around lines 7, 20
and 201, several inline comments are written in Chinese; replace each Chinese
comment with an equivalent English comment per coding guidelines (e.g.,
translate "头像" to "Avatar" and other Chinese phrases to clear English
descriptions) ensuring wording remains concise and context-appropriate and
preserving comment placement and intent.

<div class="w-5 h-5 bg-muted rounded-md overflow-hidden">
Expand All @@ -18,7 +18,7 @@
:timestamp="message.timestamp"
/>
<!-- 消息内容 -->
<div class="text-sm bg-[#EFF6FF] dark:bg-muted rounded-lg p-2 border flex flex-col gap-1.5">
<div class="text-sm bg-muted dark:bg-muted rounded-lg p-2 border flex flex-col gap-1.5">
<div v-show="message.content.files.length > 0" class="flex flex-wrap gap-1.5">
<FileItem
v-for="file in message.content.files"
Expand All @@ -35,7 +35,7 @@
<textarea
ref="editTextarea"
v-model="editedText"
class="text-sm bg-[#EFF6FF] dark:bg-muted rounded-lg p-2 border flex flex-col gap-1.5 resize-none overflow-y-auto overscroll-contain min-w-[40vw] w-full"
class="text-sm bg-muted dark:bg-muted rounded-lg p-2 border flex flex-col gap-1.5 resize-none overflow-y-auto overscroll-contain min-w-[40vw] w-full"
:style="{
width: originalContentWidth + 20 + 'px',
maxHeight: editMaxHeight ? editMaxHeight + 'px' : undefined
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/components/message/MessageList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
>
<div
ref="messageList"
class="w-full break-all transition-opacity duration-300"
class="w-full break-all transition-opacity duration-300 pt-4"
:class="{ 'opacity-0': !visible }"
>
<div
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/components/message/MessageToolbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<template v-if="!isCapturingImage">
<TooltipProvider>
<div
class="w-full h-8 text-xs text-muted-foreground items-center justify-between flex flex-row opacity-0 group-hover:opacity-100 transition-opacity"
class="w-full h-7 text-xs text-muted-foreground items-center justify-between flex flex-row opacity-0 group-hover:opacity-100 transition-opacity"
:class="[isAssistant ? '' : 'flex-row-reverse']"
>
<span v-show="!loading" class="flex flex-row gap-3">
Expand Down
101 changes: 52 additions & 49 deletions src/renderer/src/components/prompt-input/ModelChooser.vue
Original file line number Diff line number Diff line change
@@ -1,65 +1,68 @@
<template>
<div
class="flex w-full flex-col gap-3 rounded-2xl border border-border/60 bg-card/95 p-4 shadow-[0_26px_60px_-32px_rgba(12,18,36,0.85)] backdrop-blur-lg dark:border-white/10 dark:bg-[#0b111b]/95"
<Card
class="w-full border-border/60 bg-card/95 shadow-sm backdrop-blur supports-[backdrop-filter]:bg-card/80"
:dir="langStore.dir"
>
<Input
v-model="keyword"
:placeholder="t('model.search.placeholder')"
class="h-9 w-full rounded-lg border border-border/50 bg-white/60 px-3 text-xs font-medium text-foreground/80 placeholder:text-muted-foreground/70 focus-visible:border-primary focus-visible:bg-white focus-visible:ring-0 dark:border-white/10 dark:bg-white/[0.08] dark:text-white/80 dark:placeholder:text-white/50 dark:focus-visible:bg-white/[0.12]"
/>
<ScrollArea class="h-72 pr-2">
<div class="flex flex-col gap-4">
<div v-for="provider in filteredProviders" :key="provider.id" class="flex flex-col gap-2">
<p
class="px-1 text-[10px] font-semibold uppercase tracking-[0.14em] text-muted-foreground/70 dark:text-white/60"
>
{{ provider.name }}
</p>
<div class="flex flex-col gap-1.5" role="listbox" aria-orientation="vertical">
<button
v-for="model in provider.models"
:key="`${provider.id}-${model.id}`"
type="button"
class="relative flex w-full items-center gap-3 overflow-hidden rounded-xl border border-border/60 bg-white/70 px-4 py-2 text-left text-xs font-semibold text-foreground/80 shadow-[0_1px_0_rgba(9,13,24,0.08)] transition-all duration-200 hover:-translate-y-0.5 hover:border-border/80 hover:bg-white/90 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/50 dark:border-white/12 dark:bg-white/[0.06] dark:text-white/80 dark:shadow-[0_1px_0_rgba(12,18,36,0.4)] dark:hover:border-white/30 dark:hover:bg-white/12 before:absolute before:inset-y-2 before:left-1 before:w-px before:rounded-full before:bg-primary before:opacity-0 before:transition-opacity before:duration-200"
:class="[
isSelected(provider.id, model.id)
? 'border-primary/80 bg-primary/12 text-foreground shadow-[0_16px_34px_-22px_rgba(37,99,235,0.55)] ring-1 ring-primary/40 before:opacity-100 dark:border-[#3c82ff]/70 dark:bg-[#162237]/90 dark:text-white dark:ring-[#3c82ff]/45 dark:before:bg-[#3c82ff]'
: 'dark:before:bg-white/20'
]"
role="option"
:aria-selected="isSelected(provider.id, model.id)"
:data-selected="isSelected(provider.id, model.id)"
@click="handleModelSelect(provider.id, model)"
<CardContent class="flex flex-col gap-4 p-4">
<Input
v-model="keyword"
:placeholder="t('model.search.placeholder')"
class="h-9 w-full text-sm"
/>
<ScrollArea class="h-72 pr-2">
<div class="flex flex-col gap-5">
<div v-for="provider in filteredProviders" :key="provider.id" class="flex flex-col gap-2">
<Badge
variant="outline"
class="w-fit uppercase tracking-[0.18em] text-[10px] font-semibold text-muted-foreground"
>
<div
class="flex h-7 w-7 items-center justify-center rounded-lg border border-border/60 bg-white/80 text-[11px] font-semibold uppercase text-muted-foreground/80 transition-all duration-200 group-data-[selected=true]:border-transparent group-data-[selected=true]:bg-primary group-data-[selected=true]:text-white/90 dark:border-white/15 dark:bg-white/[0.08] dark:group-data-[selected=true]:bg-[#3c82ff]"
{{ provider.name }}
</Badge>
<div class="flex flex-col gap-1.5" role="listbox" aria-orientation="vertical">
<Button
v-for="model in provider.models"
:key="`${provider.id}-${model.id}`"
type="button"
variant="outline"
class="group w-full justify-start gap-3 rounded-lg px-3 py-2 text-left text-sm font-medium transition data-[selected=true]:border-primary data-[selected=true]:bg-primary/10 data-[selected=true]:text-foreground/90 dark:data-[selected=true]:bg-primary/15"
role="option"
:aria-selected="isSelected(provider.id, model.id)"
:data-selected="isSelected(provider.id, model.id)"
@click="handleModelSelect(provider.id, model)"
>
<ModelIcon
class="h-4 w-4 shrink-0 opacity-70 transition duration-200 group-hover:opacity-100 group-data-[selected=true]:opacity-100"
:model-id="provider.id"
:is-dark="themeStore.isDark"
<div
class="flex h-7 w-7 items-center justify-center rounded-md border border-border bg-muted/40 text-[11px] font-semibold uppercase text-muted-foreground transition group-data-[selected=true]:border-primary group-data-[selected=true]:bg-primary/20 group-data-[selected=true]:text-primary"
>
<ModelIcon
class="h-4 w-4 shrink-0 opacity-80 transition group-hover:opacity-100 group-data-[selected=true]:opacity-100"
:model-id="provider.id"
:is-dark="themeStore.isDark"
/>
</div>
<span class="flex-1 truncate">
{{ model.name }}
</span>
<Icon
v-if="isSelected(provider.id, model.id)"
icon="lucide:check"
class="h-4 w-4 shrink-0 text-primary dark:text-primary/80"
aria-hidden="true"
/>
</div>
<span class="flex-1 truncate text-[13px] font-medium">
{{ model.name }}
</span>
<Icon
icon="lucide:check"
class="h-3.5 w-3.5 shrink-0 text-primary opacity-0 transition-opacity duration-200 group-data-[selected=true]:opacity-100 dark:text-[#9db8ff]"
aria-hidden="true"
/>
</button>
</Button>
</div>
</div>
</div>
</div>
</ScrollArea>
</div>
</ScrollArea>
Comment on lines +12 to +55
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

Add empty state handling for search results.

When the search keyword doesn't match any models, filteredProviders will be empty and the user sees a blank ScrollArea with no feedback. Consider adding an empty state message to improve UX.

Add an empty state inside the ScrollArea:

       <ScrollArea class="h-72 pr-2">
-        <div class="flex flex-col gap-5">
+        <div v-if="filteredProviders.length > 0" class="flex flex-col gap-5">
           <div v-for="provider in filteredProviders" :key="provider.id" class="flex flex-col gap-2">
             <!-- existing content -->
           </div>
         </div>
+        <div v-else class="flex h-full items-center justify-center text-sm text-muted-foreground">
+          {{ t('model.search.noResults') }}
+        </div>
       </ScrollArea>

Note: You'll need to add the corresponding i18n key model.search.noResults to your translation files.

📝 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
<ScrollArea class="h-72 pr-2">
<div class="flex flex-col gap-5">
<div v-for="provider in filteredProviders" :key="provider.id" class="flex flex-col gap-2">
<Badge
variant="outline"
class="w-fit uppercase tracking-[0.18em] text-[10px] font-semibold text-muted-foreground"
>
<div
class="flex h-7 w-7 items-center justify-center rounded-lg border border-border/60 bg-white/80 text-[11px] font-semibold uppercase text-muted-foreground/80 transition-all duration-200 group-data-[selected=true]:border-transparent group-data-[selected=true]:bg-primary group-data-[selected=true]:text-white/90 dark:border-white/15 dark:bg-white/[0.08] dark:group-data-[selected=true]:bg-[#3c82ff]"
{{ provider.name }}
</Badge>
<div class="flex flex-col gap-1.5" role="listbox" aria-orientation="vertical">
<Button
v-for="model in provider.models"
:key="`${provider.id}-${model.id}`"
type="button"
variant="outline"
class="group w-full justify-start gap-3 rounded-lg px-3 py-2 text-left text-sm font-medium transition data-[selected=true]:border-primary data-[selected=true]:bg-primary/10 data-[selected=true]:text-foreground/90 dark:data-[selected=true]:bg-primary/15"
role="option"
:aria-selected="isSelected(provider.id, model.id)"
:data-selected="isSelected(provider.id, model.id)"
@click="handleModelSelect(provider.id, model)"
>
<ModelIcon
class="h-4 w-4 shrink-0 opacity-70 transition duration-200 group-hover:opacity-100 group-data-[selected=true]:opacity-100"
:model-id="provider.id"
:is-dark="themeStore.isDark"
<div
class="flex h-7 w-7 items-center justify-center rounded-md border border-border bg-muted/40 text-[11px] font-semibold uppercase text-muted-foreground transition group-data-[selected=true]:border-primary group-data-[selected=true]:bg-primary/20 group-data-[selected=true]:text-primary"
>
<ModelIcon
class="h-4 w-4 shrink-0 opacity-80 transition group-hover:opacity-100 group-data-[selected=true]:opacity-100"
:model-id="provider.id"
:is-dark="themeStore.isDark"
/>
</div>
<span class="flex-1 truncate">
{{ model.name }}
</span>
<Icon
v-if="isSelected(provider.id, model.id)"
icon="lucide:check"
class="h-4 w-4 shrink-0 text-primary dark:text-primary/80"
aria-hidden="true"
/>
</div>
<span class="flex-1 truncate text-[13px] font-medium">
{{ model.name }}
</span>
<Icon
icon="lucide:check"
class="h-3.5 w-3.5 shrink-0 text-primary opacity-0 transition-opacity duration-200 group-data-[selected=true]:opacity-100 dark:text-[#9db8ff]"
aria-hidden="true"
/>
</button>
</Button>
</div>
</div>
</div>
</div>
</ScrollArea>
</div>
</ScrollArea>
<ScrollArea class="h-72 pr-2">
<div v-if="filteredProviders.length > 0" class="flex flex-col gap-5">
<div v-for="provider in filteredProviders" :key="provider.id" class="flex flex-col gap-2">
<Badge
variant="outline"
class="w-fit uppercase tracking-[0.18em] text-[10px] font-semibold text-muted-foreground"
>
{{ provider.name }}
</Badge>
<div class="flex flex-col gap-1.5" role="listbox" aria-orientation="vertical">
<Button
v-for="model in provider.models"
:key="`${provider.id}-${model.id}`"
type="button"
variant="outline"
class="group w-full justify-start gap-3 rounded-lg px-3 py-2 text-left text-sm font-medium transition data-[selected=true]:border-primary data-[selected=true]:bg-primary/10 data-[selected=true]:text-foreground/90 dark:data-[selected=true]:bg-primary/15"
role="option"
:aria-selected="isSelected(provider.id, model.id)"
:data-selected="isSelected(provider.id, model.id)"
@click="handleModelSelect(provider.id, model)"
>
<div
class="flex h-7 w-7 items-center justify-center rounded-md border border-border bg-muted/40 text-[11px] font-semibold uppercase text-muted-foreground transition group-data-[selected=true]:border-primary group-data-[selected=true]:bg-primary/20 group-data-[selected=true]:text-primary"
>
<ModelIcon
class="h-4 w-4 shrink-0 opacity-80 transition group-hover:opacity-100 group-data-[selected=true]:opacity-100"
:model-id="provider.id"
:is-dark="themeStore.isDark"
/>
</div>
<span class="flex-1 truncate">
{{ model.name }}
</span>
<Icon
v-if="isSelected(provider.id, model.id)"
icon="lucide:check"
class="h-4 w-4 shrink-0 text-primary dark:text-primary/80"
aria-hidden="true"
/>
</Button>
</div>
</div>
</div>
<div v-else class="flex h-full items-center justify-center text-sm text-muted-foreground">
{{ t('model.search.noResults') }}
</div>
</ScrollArea>
🤖 Prompt for AI Agents
In src/renderer/src/components/prompt-input/ModelChooser.vue around lines 12 to
55, the ScrollArea shows nothing when filteredProviders is empty; add an
empty-state block rendered when filteredProviders.length === 0 that displays a
localized message (use i18n key model.search.noResults) inside the ScrollArea
(matching the component styling and spacing), ensure it is
keyboard/screen-reader friendly (e.g. role="status" or aria-live="polite"), and
update translation files with the new model.search.noResults entry.

</CardContent>
</Card>
</template>

<script setup lang="ts">
import { computed, ref, type PropType } from 'vue'
import { useI18n } from 'vue-i18n'
import { Badge } from '@shadcn/components/ui/badge'
import { Button } from '@shadcn/components/ui/button'
import { Card, CardContent } from '@shadcn/components/ui/card'
import { Input } from '@shadcn/components/ui/input'
import { ScrollArea } from '@shadcn/components/ui/scroll-area'
import { useChatStore } from '@/stores/chat'
Expand Down
Loading