diff --git a/packages/web-shell/client/components/messages/SettingsMessage.module.css b/packages/web-shell/client/components/messages/SettingsMessage.module.css deleted file mode 100644 index acbd5fcf25..0000000000 --- a/packages/web-shell/client/components/messages/SettingsMessage.module.css +++ /dev/null @@ -1,295 +0,0 @@ -.panel { - display: flex; - flex-direction: column; - gap: 10px; - margin: 0 0 12px; - padding: 10px 14px; - max-width: min(var(--chat-regular-content-width, 1000px), calc(100vw - 64px)); - border: 1px solid var(--border); - border-radius: 4px; - background: var(--background); - color: var(--foreground); - font-family: var(--font-mono); - font-size: 13px; -} - -.embeddedPanel { - margin: 0; - padding: 0; - max-width: none; - border: 0; - border-radius: 0; - background: transparent; - font-size: 12px; -} - -.header { - display: flex; - align-items: baseline; - gap: 8px; -} - -.title { - font-weight: 700; -} - -.secondary, -.hint, -.empty, -.busy { - color: var(--muted-foreground); -} - -.hint { - padding: 8px; - border-radius: 6px; - background: var(--background); -} - -.scopeTabs { - display: inline-flex; - align-self: flex-start; - gap: 4px; - padding: 3px; - border-radius: 8px; - background: var(--background); -} - -.scopeTab { - min-height: 26px; - padding: 4px 10px; - border: 0; - border-radius: 6px; - background: transparent; - color: var(--muted-foreground); - cursor: pointer; - font: inherit; - font-size: 12px; -} - -.scopeTab:hover, -.scopeTabActive { - background: var(--secondary); - color: var(--foreground); -} - -.settingsPage { - display: grid; - grid-template-columns: 160px minmax(0, 1fr); - gap: 12px; - min-height: 420px; -} - -.sidebar { - display: flex; - flex-direction: column; - gap: 3px; - min-width: 0; - padding-right: 8px; - border-right: 1px solid var(--border); -} - -.categoryButton { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - width: 100%; - min-height: 32px; - padding: 5px 8px; - border: 0; - border-radius: 6px; - background: transparent; - color: var(--muted-foreground); - cursor: pointer; - font: inherit; - text-align: left; -} - -.categoryButton:hover, -.categoryButtonActive { - background: var(--secondary); - color: var(--foreground); -} - -.categoryCount { - flex: 0 0 auto; - color: var(--muted-foreground); - font-size: 11px; -} - -.content { - display: flex; - min-width: 0; - flex-direction: column; - gap: 6px; -} - -.card { - display: grid; - grid-template-columns: minmax(0, 1fr) minmax(170px, max-content); - gap: 16px; - align-items: center; - padding: 8px; - border-radius: 8px; - background: var(--background); -} - -.card:hover { - background: var(--secondary); -} - -.cardBody { - min-width: 0; -} - -.cardTitle { - display: flex; - align-items: center; - gap: 6px; - color: var(--foreground); - font-size: 12px; - font-weight: 500; -} - -.cardDescription, -.scopeHint { - margin-top: 3px; - color: var(--muted-foreground); - font-size: 12px; - line-height: 1.45; - overflow-wrap: anywhere; -} - -.scopeBadge { - flex: 0 0 auto; - padding: 1px 5px; - border-radius: 999px; - background: var(--secondary); - color: var(--muted-foreground); - font-size: 11px; - font-weight: 400; -} - -.cardControl { - display: flex; - justify-content: flex-end; - min-width: 0; -} - -.select, -.actionButton, -.editInput { - min-height: 28px; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--background); - color: var(--foreground); - font: inherit; - font-size: 12px; -} - -.select { - max-width: 260px; - padding: 4px 28px 4px 8px; -} - -.actionButton { - max-width: 260px; - padding: 4px 9px; - cursor: pointer; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.actionButton:hover { - background: var(--secondary); -} - -.actionButton:disabled { - cursor: not-allowed; - opacity: 0.65; -} - -.actionButton:disabled:hover { - background: var(--background); -} - -.editor { - display: flex; - gap: 6px; - max-width: 320px; -} - -.editInput { - min-width: 120px; - padding: 4px 8px; - outline: none; -} - -.switch { - position: relative; - width: 38px; - height: 22px; - border: 1px solid var(--border); - border-radius: 999px; - background: var(--secondary); - cursor: pointer; -} - -.switchOn { - background: var(--agent-blue-500); - border-color: var(--agent-blue-500); -} - -.switchKnob { - position: absolute; - top: 3px; - left: 3px; - width: 14px; - height: 14px; - border-radius: 50%; - background: var(--background); - transition: transform 120ms ease; -} - -.switchOn .switchKnob { - transform: translateX(16px); -} - -.footer { - padding-top: 8px; - border-top: 1px solid var(--border); - color: var(--muted-foreground); -} - -@media (max-width: 720px) { - .settingsPage { - grid-template-columns: 1fr; - min-height: 0; - } - - .sidebar { - flex-direction: row; - overflow-x: auto; - padding-right: 0; - padding-bottom: 8px; - border-right: 0; - border-bottom: 1px solid var(--border); - } - - .categoryButton { - width: auto; - flex: 0 0 auto; - } - - .card { - grid-template-columns: 1fr; - gap: 8px; - } - - .cardControl { - justify-content: flex-start; - } -} diff --git a/packages/web-shell/client/components/messages/SettingsMessage.tsx b/packages/web-shell/client/components/messages/SettingsMessage.tsx index 6438ce9271..01b2df2b94 100644 --- a/packages/web-shell/client/components/messages/SettingsMessage.tsx +++ b/packages/web-shell/client/components/messages/SettingsMessage.tsx @@ -1,4 +1,21 @@ -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { + useCallback, + useEffect, + useMemo, + useState, + type ReactNode, +} from 'react'; +import { + BotIcon, + DatabaseIcon, + FlaskConicalIcon, + PaletteIcon, + ServerIcon, + Settings2Icon, + ShieldIcon, + SlidersHorizontalIcon, + WrenchIcon, +} from 'lucide-react'; import type { DaemonSettingDescriptor, DaemonSettingUpdateResult, @@ -17,10 +34,41 @@ import { THEME_SETTING_KEY, LANGUAGE_SETTING_KEY, themeSettingToWebShellTheme, + useTheme, webShellThemeToSettingValue, type WebShellTheme, } from '../../themeContext'; -import styles from './SettingsMessage.module.css'; +import { Alert, AlertDescription } from '../ui/alert'; +import { Badge } from '../ui/badge'; +import { Button } from '../ui/button'; +import { Card, CardContent, CardHeader, CardTitle } from '../ui/card'; +import { + Empty, + EmptyDescription, + EmptyHeader, + EmptyMedia, + EmptyTitle, +} from '../ui/empty'; +import { + Field, + FieldContent, + FieldDescription, + FieldGroup, + FieldTitle, +} from '../ui/field'; +import { Input } from '../ui/input'; +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectTrigger, + SelectValue, +} from '../ui/select'; +import { Separator } from '../ui/separator'; +import { Spinner } from '../ui/spinner'; +import { Switch } from '../ui/switch'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs'; type ChatWidthMode = '1000' | 'wide'; @@ -137,7 +185,7 @@ function formatValue( : String(effective); } const s = String(effective); - return s.length > 24 ? s.slice(0, 21) + '...' : s; + return s.length > 24 ? `${s.slice(0, 21)}…` : s; } function scopeHasValue( @@ -186,10 +234,7 @@ interface SettingsPageCategory { items: SettingsPageItem[]; } -function groupByCategory( - settings: DaemonSettingDescriptor[], - t: Translator, -): CategoryGroup[] { +function groupByCategory(settings: DaemonSettingDescriptor[]): CategoryGroup[] { const map = new Map(); for (const s of settings) { let group = map.get(s.category); @@ -200,11 +245,126 @@ function groupByCategory( group.push(s); } return Array.from(map.entries()).map(([category, items]) => ({ - category: formatSettingCategory(category, t), + category, items, })); } +function CategoryIcon({ category }: { category: string }) { + const normalized = category.toLowerCase(); + const Icon = normalized.includes('ui') + ? PaletteIcon + : normalized.includes('tool') + ? WrenchIcon + : normalized.includes('context') + ? DatabaseIcon + : normalized.includes('privacy') + ? ShieldIcon + : normalized.includes('model') + ? BotIcon + : normalized.includes('daemon') + ? ServerIcon + : normalized.includes('advanced') + ? SlidersHorizontalIcon + : normalized.includes('experimental') + ? FlaskConicalIcon + : Settings2Icon; + return