diff --git a/src/renderer/components/quick-prompt/ModelSelector.tsx b/src/renderer/components/quick-prompt/ModelSelector.tsx index 684fc47..9011c30 100644 --- a/src/renderer/components/quick-prompt/ModelSelector.tsx +++ b/src/renderer/components/quick-prompt/ModelSelector.tsx @@ -1,7 +1,9 @@ import { useEffect, useRef, useMemo } from 'react'; -import { Check, Crown, Zap, Brain } from 'lucide-react'; +import { Check, Brain } from 'lucide-react'; -import type { ModelDefinition } from '@shared/domain/models'; +import { ProviderIcon } from '@renderer/components/ProviderIcons'; +import type { ModelDefinition, ModelProvider } from '@shared/domain/models'; +import { providerMeta } from '@shared/domain/models'; import type { ReasoningEffort } from '@shared/domain/workflow'; interface ModelSelectorProps { @@ -13,12 +15,6 @@ interface ModelSelectorProps { onClose: () => void; } -const tierMeta: Record = { - premium: { label: 'Premium', icon: Crown, color: 'text-amber-400', bg: 'bg-amber-400/10' }, - standard: { label: 'Standard', icon: Zap, color: 'text-[var(--color-text-accent)]', bg: 'bg-[var(--color-accent-muted)]' }, - fast: { label: 'Fast', icon: Zap, color: 'text-emerald-400', bg: 'bg-emerald-400/10' }, -}; - const reasoningLevels: { value: ReasoningEffort; label: string; description: string }[] = [ { value: 'low', label: 'Low', description: 'Fast, concise' }, { value: 'medium', label: 'Med', description: 'Balanced' }, @@ -26,8 +22,8 @@ const reasoningLevels: { value: ReasoningEffort; label: string; description: str { value: 'xhigh', label: 'Max', description: 'Exhaustive' }, ]; -interface ModelGroup { - tier: string; +interface ProviderGroup { + provider: ModelProvider | 'other'; label: string; models: ModelDefinition[]; } @@ -66,25 +62,38 @@ export function ModelSelector({ const selectedModel = models.find((m) => m.id === selectedModelId); const supportedEfforts = selectedModel?.supportedReasoningEfforts; - // Group models by tier - const groups = useMemo((): ModelGroup[] => { - const tierOrder = ['premium', 'standard', 'fast', 'other']; + // Group models by provider + const groups = useMemo((): ProviderGroup[] => { + const providerOrder = providerMeta.map((p) => p.id); + const providerLabels = new Map(providerMeta.map((p) => [p.id, p.label])); const grouped = new Map(); for (const model of models) { - const tier = model.tier ?? 'other'; - const list = grouped.get(tier) ?? []; + const key = model.provider ?? 'other'; + const list = grouped.get(key) ?? []; list.push(model); - grouped.set(tier, list); + grouped.set(key, list); } - return tierOrder - .filter((t) => grouped.has(t)) - .map((tier) => ({ - tier, - label: tier === 'other' ? 'Other' : tierMeta[tier]?.label ?? tier, - models: grouped.get(tier)!, - })); + const result: ProviderGroup[] = []; + for (const providerId of providerOrder) { + const providerModels = grouped.get(providerId); + if (providerModels) { + result.push({ + provider: providerId, + label: providerLabels.get(providerId) ?? providerId, + models: providerModels, + }); + } + } + + // Any models without a known provider + const other = grouped.get('other'); + if (other) { + result.push({ provider: 'other', label: 'Other', models: other }); + } + + return result; }, [models]); return ( @@ -94,54 +103,63 @@ export function ModelSelector({ role="listbox" aria-label="Select model" > - {/* Model list — grouped by tier */} + {/* Model list — grouped by provider */}
- {groups.map((group, gi) => { - const meta = tierMeta[group.tier]; - const TierIcon = meta?.icon; + {groups.map((group, gi) => ( +
+ {gi > 0 &&
} - return ( -
- {gi > 0 &&
} - - {/* Group header */} -
- {TierIcon && } - - {group.label} - -
- - {/* Models in this group */} - {group.models.map((model) => { - const isSelected = model.id === selectedModelId; - - return ( - - ); - })} + {/* Provider header */} +
+ {group.provider !== 'other' && ( + + )} + + {group.label} +
- ); - })} + + {/* Models in this provider group */} + {group.models.map((model) => { + const isSelected = model.id === selectedModelId; + const tierLabel = model.tier === 'premium' ? 'PRO' : model.tier === 'fast' ? 'FAST' : undefined; + const tierColor = model.tier === 'premium' + ? 'text-amber-400 bg-amber-400/10' + : model.tier === 'fast' + ? 'text-emerald-400 bg-emerald-400/10' + : ''; + + return ( + + ); + })} +
+ ))}
{/* Reasoning effort — only shown when selected model supports it */} diff --git a/src/renderer/quickprompt.html b/src/renderer/quickprompt.html index 2a208f7..3c456ec 100644 --- a/src/renderer/quickprompt.html +++ b/src/renderer/quickprompt.html @@ -1,5 +1,5 @@ - + in quickprompt.html. */ -body[data-quickprompt] { +body[data-quickprompt], +body[data-quickprompt] #root { background: transparent !important; min-height: 0 !important; } -body[data-quickprompt] #root { - min-height: 0 !important; +html:has(body[data-quickprompt]) { + background: transparent !important; } .qp-container { @@ -723,10 +724,8 @@ body[data-quickprompt] #root { .qp-panel { background: var(--color-surface-1); - box-shadow: - 0 24px 80px rgba(0, 0, 0, 0.6), - 0 8px 24px rgba(0, 0, 0, 0.4), - 0 0 1px rgba(255, 255, 255, 0.08) inset; + /* No box-shadow — on Windows transparent windows, shadows paint visibly + on the transparent canvas and create a visible dark rectangle. */ transition: border-color 0.3s ease; } @@ -737,27 +736,15 @@ body[data-quickprompt] #root { .qp-border-complete { border: 1px solid var(--color-border-glow); - box-shadow: - 0 24px 80px rgba(0, 0, 0, 0.55), - 0 0 20px rgba(36, 92, 249, 0.06), - 0 0 1px rgba(255, 255, 255, 0.06) inset; } /* Animated breathing border during streaming */ @keyframes qp-border-breathe { 0%, 100% { border-color: rgba(36, 92, 249, 0.35); - box-shadow: - 0 24px 80px rgba(0, 0, 0, 0.55), - 0 0 20px rgba(36, 92, 249, 0.08), - 0 0 1px rgba(255, 255, 255, 0.06) inset; } 50% { border-color: rgba(138, 41, 230, 0.4); - box-shadow: - 0 24px 80px rgba(0, 0, 0, 0.55), - 0 0 32px rgba(138, 41, 230, 0.1), - 0 0 1px rgba(255, 255, 255, 0.06) inset; } }