diff --git a/src/routes/chat/+layout.svelte b/src/routes/chat/+layout.svelte index 601f395..a8fa570 100644 --- a/src/routes/chat/+layout.svelte +++ b/src/routes/chat/+layout.svelte @@ -802,7 +802,7 @@ {:else} {/if} - Attach image + Attach image {/if} diff --git a/src/routes/chat/model-picker.svelte b/src/routes/chat/model-picker.svelte index 4b8c2f1..5241f10 100644 --- a/src/routes/chat/model-picker.svelte +++ b/src/routes/chat/model-picker.svelte @@ -4,28 +4,26 @@ import { session } from '$lib/state/session.svelte'; import { settings } from '$lib/state/settings.svelte'; import { cn } from '$lib/utils/utils'; - import { Command, Popover } from 'bits-ui'; - import { Button } from '$lib/components/ui/button'; - import ChevronDownIcon from '~icons/lucide/chevron-down'; + import { Command } from 'bits-ui'; import CheckIcon from '~icons/lucide/check'; - + import SearchIcon from '~icons/lucide/search'; + import ChevronDownIcon from '~icons/lucide/chevron-down'; // Company icons from simple-icons - import OpenaiIcon from '~icons/simple-icons/openai'; import GoogleIcon from '~icons/simple-icons/google'; import MetaIcon from '~icons/simple-icons/meta'; import MicrosoftIcon from '~icons/simple-icons/microsoft'; + import OpenaiIcon from '~icons/simple-icons/openai'; import XIcon from '~icons/simple-icons/x'; - // Fallback to lucide icons for companies without simple-icons - import BrainIcon from '~icons/lucide/brain'; - import ZapIcon from '~icons/lucide/zap'; - import CpuIcon from '~icons/lucide/cpu'; import RobotIcon from '~icons/lucide/bot'; - + import BrainIcon from '~icons/lucide/brain'; + import CpuIcon from '~icons/lucide/cpu'; + import ZapIcon from '~icons/lucide/zap'; // Model-specific icons import LogosClaudeIcon from '~icons/logos/claude-icon'; - import MaterialIconThemeGeminiAi from '~icons/material-icon-theme/gemini-ai'; import LogosMistralAiIcon from '~icons/logos/mistral-ai-icon'; + import MaterialIconThemeGeminiAi from '~icons/material-icon-theme/gemini-ai'; + import { Popover } from 'melt/builders'; type Props = { class?: string; @@ -56,11 +54,11 @@ // Function to get model-specific icon function getModelIcon(modelId: string): typeof LogosClaudeIcon | null { const id = modelId.toLowerCase(); - + if (id.includes('claude') || id.includes('anthropic')) return LogosClaudeIcon; if (id.includes('gemini') || id.includes('gemma')) return MaterialIconThemeGeminiAi; if (id.includes('mistral') || id.includes('mixtral')) return LogosMistralAiIcon; - + return null; } @@ -141,8 +139,6 @@ return result; }); - let open = $state(false); - // Find current model details const currentModel = $derived(enabledArr.find((m) => m.model_id === settings.modelId)); const currentCompany = $derived( @@ -157,7 +153,34 @@ function selectModel(modelId: string) { settings.modelId = modelId; - open = false; + popover.open = false; + } + + let open = $state(true); + const popover = new Popover({ + open: () => open, + onOpenChange: (v) => { + console.log('📋 popover open:', v); + if (v === open) return; + open = v; + console.log('assigned', v); + if (v) return; + console.log('attempting to focus'); + document.getElementById(popover.trigger.id)?.focus(); + }, + floatingConfig: { + computePosition: { placement: 'top-start' }, + }, + }); + + // Name splitter. splits -,_,: + function splitName(name: string) { + return name.split(/[-_,:]/); + } + + function getModelTitle(modelId: string) { + const sn = splitName(modelId.replace(/^[^/]+\//, '')); + return sn[0] + (sn.length > 1 ? ' ' + sn.slice(1).join(' ') : ''); } @@ -167,80 +190,99 @@ {:else} - - -
- {#if companyIcons[currentCompany]} - {@const IconComponent = companyIcons[currentCompany]} - - {/if} - - {currentModel?.model_id.replace(/^[^/]+\//, '') || 'Select model'} - -
- -
+ - - +
+ + + + + + No models available. Enable some models in the account settings. + + {#each groupedModels as [company, models] (company)} + + + {company} + + + {#each models as model (model._id)} + {@const isSelected = settings.modelId === model.model_id} + {@const sn = splitName(model.model_id.replace(/^[^/]+\//, ''))} + selectModel(model.model_id)} + class={cn( + 'border-border flex h-40 w-32 flex-col items-center justify-center rounded-lg border p-2', + 'select-none', + 'data-selected:bg-accent/50 data-selected:text-accent-foreground', + isSelected && 'border-reflect border-none', + 'scroll-m-10' + )} + > + {#if getModelIcon(model.model_id)} + {@const ModelIcon = getModelIcon(model.model_id)} + + {:else if companyIcons[getCompanyFromModelId(model.model_id)]} + {@const CompanyIcon = companyIcons[getCompanyFromModelId(model.model_id)]} + + {/if} +

-

- {#if getModelIcon(model.model_id)} - {@const ModelIcon = getModelIcon(model.model_id)} - - {:else if companyIcons[getCompanyFromModelId(model.model_id)]} - {@const CompanyIcon = companyIcons[getCompanyFromModelId(model.model_id)]} - - {/if} - - {model.model_id.replace(/^[^/]+\//, '')} - - {#if settings.modelId === model.model_id} - - {/if} -
-
- {/each} -
-
- {/each} -
-
-
- - + {sn[0]} +

+

+ {sn.slice(1).join(' ')} +

+ + {/each} + + + {/each} + + + +
{/if}