yay
This commit is contained in:
parent
fba573e1ef
commit
aff1901765
2 changed files with 130 additions and 88 deletions
|
|
@ -802,7 +802,7 @@
|
||||||
{:else}
|
{:else}
|
||||||
<ImageIcon class="!size-3" />
|
<ImageIcon class="!size-3" />
|
||||||
{/if}
|
{/if}
|
||||||
<span>Attach image</span>
|
<span class="whitespace-nowrap">Attach image</span>
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -4,28 +4,26 @@
|
||||||
import { session } from '$lib/state/session.svelte';
|
import { session } from '$lib/state/session.svelte';
|
||||||
import { settings } from '$lib/state/settings.svelte';
|
import { settings } from '$lib/state/settings.svelte';
|
||||||
import { cn } from '$lib/utils/utils';
|
import { cn } from '$lib/utils/utils';
|
||||||
import { Command, Popover } from 'bits-ui';
|
import { Command } from 'bits-ui';
|
||||||
import { Button } from '$lib/components/ui/button';
|
|
||||||
import ChevronDownIcon from '~icons/lucide/chevron-down';
|
|
||||||
import CheckIcon from '~icons/lucide/check';
|
import CheckIcon from '~icons/lucide/check';
|
||||||
|
import SearchIcon from '~icons/lucide/search';
|
||||||
|
import ChevronDownIcon from '~icons/lucide/chevron-down';
|
||||||
// Company icons from simple-icons
|
// Company icons from simple-icons
|
||||||
import OpenaiIcon from '~icons/simple-icons/openai';
|
|
||||||
import GoogleIcon from '~icons/simple-icons/google';
|
import GoogleIcon from '~icons/simple-icons/google';
|
||||||
import MetaIcon from '~icons/simple-icons/meta';
|
import MetaIcon from '~icons/simple-icons/meta';
|
||||||
import MicrosoftIcon from '~icons/simple-icons/microsoft';
|
import MicrosoftIcon from '~icons/simple-icons/microsoft';
|
||||||
|
import OpenaiIcon from '~icons/simple-icons/openai';
|
||||||
import XIcon from '~icons/simple-icons/x';
|
import XIcon from '~icons/simple-icons/x';
|
||||||
|
|
||||||
// Fallback to lucide icons for companies without simple-icons
|
// 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 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
|
// Model-specific icons
|
||||||
import LogosClaudeIcon from '~icons/logos/claude-icon';
|
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 LogosMistralAiIcon from '~icons/logos/mistral-ai-icon';
|
||||||
|
import MaterialIconThemeGeminiAi from '~icons/material-icon-theme/gemini-ai';
|
||||||
|
import { Popover } from 'melt/builders';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
class?: string;
|
class?: string;
|
||||||
|
|
@ -141,8 +139,6 @@
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
let open = $state(false);
|
|
||||||
|
|
||||||
// Find current model details
|
// Find current model details
|
||||||
const currentModel = $derived(enabledArr.find((m) => m.model_id === settings.modelId));
|
const currentModel = $derived(enabledArr.find((m) => m.model_id === settings.modelId));
|
||||||
const currentCompany = $derived(
|
const currentCompany = $derived(
|
||||||
|
|
@ -157,7 +153,34 @@
|
||||||
|
|
||||||
function selectModel(modelId: string) {
|
function selectModel(modelId: string) {
|
||||||
settings.modelId = modelId;
|
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(' ') : '');
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -167,9 +190,12 @@
|
||||||
<option value="">Loading models...</option>
|
<option value="">Loading models...</option>
|
||||||
</select>
|
</select>
|
||||||
{:else}
|
{:else}
|
||||||
<Popover.Root bind:open>
|
<button
|
||||||
<Popover.Trigger
|
{...popover.trigger}
|
||||||
class={cn('flex w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50', className)}
|
class={cn(
|
||||||
|
'border-input bg-background ring-offset-background placeholder:text-muted-foreground focus:ring-ring flex w-full items-center justify-between rounded-md border px-3 py-2 text-sm focus:ring-2 focus:ring-offset-2 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50',
|
||||||
|
className
|
||||||
|
)}
|
||||||
aria-expanded={open}
|
aria-expanded={open}
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
|
|
@ -177,48 +203,64 @@
|
||||||
{@const IconComponent = companyIcons[currentCompany]}
|
{@const IconComponent = companyIcons[currentCompany]}
|
||||||
<IconComponent class="size-4" />
|
<IconComponent class="size-4" />
|
||||||
{/if}
|
{/if}
|
||||||
<span class="truncate">
|
<span class="truncate capitalize">
|
||||||
{currentModel?.model_id.replace(/^[^/]+\//, '') || 'Select model'}
|
{currentModel ? getModelTitle(currentModel.model_id) : 'Select model'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<ChevronDownIcon class="size-4 opacity-50" />
|
<ChevronDownIcon class="size-4 opacity-50" />
|
||||||
</Popover.Trigger>
|
</button>
|
||||||
|
|
||||||
<Popover.Content
|
<div
|
||||||
class="z-50 mt-1 max-h-96 w-full min-w-80 overflow-hidden rounded-md border bg-white/95 p-0 shadow-lg backdrop-blur-sm dark:bg-gray-950/95"
|
{...popover.content}
|
||||||
|
class="border-border bg-popover mt-1 max-h-200 min-w-80 flex-col overflow-hidden rounded-xl border p-0 backdrop-blur-sm data-[open]:flex"
|
||||||
>
|
>
|
||||||
<Command.Root columns={3}>
|
<Command.Root class="flex h-full flex-col overflow-hidden" columns={4}>
|
||||||
|
<label class="group/label relative flex items-center gap-2 px-4 py-3 text-sm">
|
||||||
|
<SearchIcon class="text-muted-foreground" />
|
||||||
<Command.Input
|
<Command.Input
|
||||||
class="flex h-10 w-full rounded-md border-0 border-b bg-transparent px-3 py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50"
|
class="w-full outline-none"
|
||||||
placeholder="Search models..."
|
placeholder="Search models..."
|
||||||
|
{@attach (node) => {
|
||||||
|
if (popover.open) {
|
||||||
|
node.focus();
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
node.value = '';
|
||||||
|
};
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<Command.List class="max-h-80 overflow-y-auto">
|
<div
|
||||||
|
class="border-border/50 group-focus-within/label:border-foreground/30 absolute inset-x-2 bottom-0 h-1 border-b"
|
||||||
|
aria-hidden="true"
|
||||||
|
></div>
|
||||||
|
</label>
|
||||||
|
<Command.List class="overflow-y-auto">
|
||||||
<Command.Viewport>
|
<Command.Viewport>
|
||||||
<Command.Empty>
|
<Command.Empty class="text-muted-foreground p-4 text-sm">
|
||||||
No models available. Enable some models in the account settings.
|
No models available. Enable some models in the account settings.
|
||||||
</Command.Empty>
|
</Command.Empty>
|
||||||
{#each groupedModels as [company, models]}
|
{#each groupedModels as [company, models] (company)}
|
||||||
<Command.Group class="space-y-2">
|
<Command.Group class="space-y-2">
|
||||||
<Command.GroupHeading
|
<Command.GroupHeading
|
||||||
class="text-muted-foreground flex items-center gap-2 border-b border-gray-200 pb-1 pt-3 px-3 text-xs font-semibold tracking-wide uppercase dark:border-gray-700"
|
class="text-heading/75 flex items-center gap-2 px-3 pt-3 pb-1 text-xs font-semibold tracking-wide capitalize"
|
||||||
>
|
>
|
||||||
{#if companyIcons[company]}
|
{company}
|
||||||
{@const IconComponent = companyIcons[company]}
|
|
||||||
<IconComponent class="size-4" />
|
|
||||||
{/if}
|
|
||||||
{company} ({models.length})
|
|
||||||
</Command.GroupHeading>
|
</Command.GroupHeading>
|
||||||
<Command.GroupItems class="grid grid-cols-3 gap-3 px-3 pb-3">
|
<Command.GroupItems class="grid grid-cols-4 gap-3 px-3 pb-3">
|
||||||
{#each models as model (model._id)}
|
{#each models as model (model._id)}
|
||||||
|
{@const isSelected = settings.modelId === model.model_id}
|
||||||
|
{@const sn = splitName(model.model_id.replace(/^[^/]+\//, ''))}
|
||||||
<Command.Item
|
<Command.Item
|
||||||
value={model.model_id}
|
value={model.model_id}
|
||||||
onSelect={() => selectModel(model.model_id)}
|
onSelect={() => selectModel(model.model_id)}
|
||||||
class={cn(
|
class={cn(
|
||||||
'data-selected:bg-accent data-selected:text-accent-foreground relative flex cursor-pointer select-none flex-col items-center justify-center rounded-lg border border-gray-200 bg-white px-3 py-4 text-sm outline-none transition-all hover:border-gray-300 hover:shadow-sm dark:border-gray-700 dark:bg-gray-800 dark:hover:border-gray-600',
|
'border-border flex h-40 w-32 flex-col items-center justify-center rounded-lg border p-2',
|
||||||
settings.modelId === model.model_id && 'bg-primary border-primary text-primary-foreground shadow-md'
|
'select-none',
|
||||||
|
'data-selected:bg-accent/50 data-selected:text-accent-foreground',
|
||||||
|
isSelected && 'border-reflect border-none',
|
||||||
|
'scroll-m-10'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div class="flex min-h-16 flex-col items-center justify-center gap-2">
|
|
||||||
{#if getModelIcon(model.model_id)}
|
{#if getModelIcon(model.model_id)}
|
||||||
{@const ModelIcon = getModelIcon(model.model_id)}
|
{@const ModelIcon = getModelIcon(model.model_id)}
|
||||||
<ModelIcon class="size-6 shrink-0" />
|
<ModelIcon class="size-6 shrink-0" />
|
||||||
|
|
@ -226,13 +268,14 @@
|
||||||
{@const CompanyIcon = companyIcons[getCompanyFromModelId(model.model_id)]}
|
{@const CompanyIcon = companyIcons[getCompanyFromModelId(model.model_id)]}
|
||||||
<CompanyIcon class="size-6 shrink-0" />
|
<CompanyIcon class="size-6 shrink-0" />
|
||||||
{/if}
|
{/if}
|
||||||
<span class="text-center text-xs font-medium leading-tight">
|
<p
|
||||||
{model.model_id.replace(/^[^/]+\//, '')}
|
class="font-fake-proxima mt-2 text-center leading-tight font-bold capitalize"
|
||||||
</span>
|
>
|
||||||
{#if settings.modelId === model.model_id}
|
{sn[0]}
|
||||||
<CheckIcon class="absolute right-1 top-1 size-3 shrink-0" />
|
</p>
|
||||||
{/if}
|
<p class="mt-0 text-center text-xs leading-tight font-medium">
|
||||||
</div>
|
{sn.slice(1).join(' ')}
|
||||||
|
</p>
|
||||||
</Command.Item>
|
</Command.Item>
|
||||||
{/each}
|
{/each}
|
||||||
</Command.GroupItems>
|
</Command.GroupItems>
|
||||||
|
|
@ -241,6 +284,5 @@
|
||||||
</Command.Viewport>
|
</Command.Viewport>
|
||||||
</Command.List>
|
</Command.List>
|
||||||
</Command.Root>
|
</Command.Root>
|
||||||
</Popover.Content>
|
</div>
|
||||||
</Popover.Root>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue