add more icons

This commit is contained in:
Thomas G. Lopes 2025-06-18 11:05:28 +01:00
parent ce53dae46e
commit ab14c5292c
7 changed files with 78 additions and 28 deletions

View file

@ -41,6 +41,7 @@ export default ts.config(
], ],
}, },
], ],
'svelte/require-each-key': 'off',
}, },
}, },
{ {

View file

@ -0,0 +1,27 @@
<script lang="ts">
import type { Props } from '.';
import { cn } from '$lib/utils/utils';
let { class: className, ...rest }: Props = $props();
</script>
<svg
class={cn('size-4 ', className)}
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
{...rest}
><title>Cohere</title><path
clip-rule="evenodd"
d="M8.128 14.099c.592 0 1.77-.033 3.398-.703 1.897-.781 5.672-2.2 8.395-3.656 1.905-1.018 2.74-2.366 2.74-4.18A4.56 4.56 0 0018.1 1H7.549A6.55 6.55 0 001 7.55c0 3.617 2.745 6.549 7.128 6.549z"
fill="#39594D"
fill-rule="evenodd"
/><path
clip-rule="evenodd"
d="M9.912 18.61a4.387 4.387 0 012.705-4.052l3.323-1.38c3.361-1.394 7.06 1.076 7.06 4.715a5.104 5.104 0 01-5.105 5.104l-3.597-.001a4.386 4.386 0 01-4.386-4.387z"
fill="#D18EE2"
fill-rule="evenodd"
/><path
d="M4.776 14.962A3.775 3.775 0 001 18.738v.489a3.776 3.776 0 007.551 0v-.49a3.775 3.775 0 00-3.775-3.775z"
fill="#FF7759"
/></svg
>

View file

@ -0,0 +1,17 @@
<script lang="ts">
import type { Props } from '.';
import { cn } from '$lib/utils/utils';
let { class: className, ...rest }: Props = $props();
</script>
<svg
class={cn('size-4 ', className)}
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
{...rest}
><title>DeepSeek</title><path
d="M23.748 4.482c-.254-.124-.364.113-.512.234-.051.039-.094.09-.137.136-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.156-.708-.311-.955-.65-.172-.241-.219-.51-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.093.172.187.129.323-.082.28-.18.552-.266.833-.055.179-.137.217-.329.14a5.526 5.526 0 01-1.736-1.18c-.857-.828-1.631-1.742-2.597-2.458a11.365 11.365 0 00-.689-.471c-.985-.957.13-1.743.388-1.836.27-.098.093-.432-.779-.428-.872.004-1.67.295-2.687.684a3.055 3.055 0 01-.465.137 9.597 9.597 0 00-2.883-.102c-1.885.21-3.39 1.102-4.497 2.623C.082 8.606-.231 10.684.152 12.85c.403 2.284 1.569 4.175 3.36 5.653 1.858 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.133-.284 4.994-1.86.47.234.962.327 1.78.397.63.059 1.236-.03 1.705-.128.735-.156.684-.837.419-.961-2.155-1.004-1.682-.595-2.113-.926 1.096-1.296 2.746-2.642 3.392-7.003.05-.347.007-.565 0-.845-.004-.17.035-.237.23-.256a4.173 4.173 0 001.545-.475c1.396-.763 1.96-2.015 2.093-3.517.02-.23-.004-.467-.247-.588zM11.581 18c-2.089-1.642-3.102-2.183-3.52-2.16-.392.024-.321.471-.235.763.09.288.207.486.371.739.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.167-1.361-.802-2.5-1.86-3.301-3.307-.774-1.393-1.224-2.887-1.298-4.482-.02-.386.093-.522.477-.592a4.696 4.696 0 011.529-.039c2.132.312 3.946 1.265 5.468 2.774.868.86 1.525 1.887 2.202 2.891.72 1.066 1.494 2.082 2.48 2.914.348.292.625.514.891.677-.802.09-2.14.11-3.054-.614zm1-6.44a.306.306 0 01.415-.287.302.302 0 01.2.288.306.306 0 01-.31.307.303.303 0 01-.304-.308zm3.11 1.596c-.2.081-.399.151-.59.16a1.245 1.245 0 01-.798-.254c-.274-.23-.47-.358-.552-.758a1.73 1.73 0 01.016-.588c.07-.327-.008-.537-.239-.727-.187-.156-.426-.199-.688-.199a.559.559 0 01-.254-.078c-.11-.054-.2-.19-.114-.358.028-.054.16-.186.192-.21.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.391.451.462.576.685.914.176.265.336.537.445.848.067.195-.019.354-.25.452z"
fill="#4D6BFE"
></path></svg
>

View file

@ -112,3 +112,7 @@ export function* iterate<T>(array: T[]): Generator<IterateReturn<T>> {
]; ];
} }
} }
export function last<T>(arr: T[]): T | undefined {
return arr[arr.length - 1];
}

View file

@ -79,7 +79,7 @@
{/if} {/if}
</p> </p>
<div class="mt-4 flex items-center gap-1"> <div class="mt-4 flex items-center gap-1">
{#each Object.entries(suggestionCategories) as [category, opts]} {#each Object.entries(suggestionCategories) as [category, opts] (category)}
<button <button
type="button" type="button"
class="data-[active=true]:bg-primary focus-visible:border-ring focus-visible:ring-ring/50 bg-muted relative inline-flex h-9 shrink-0 items-center justify-center gap-2 overflow-hidden rounded-full px-4 py-2 text-sm font-medium whitespace-nowrap outline-hidden transition-all select-none hover:cursor-pointer focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 has-[>svg]:px-3 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4" class="data-[active=true]:bg-primary focus-visible:border-ring focus-visible:ring-ring/50 bg-muted relative inline-flex h-9 shrink-0 items-center justify-center gap-2 overflow-hidden rounded-full px-4 py-2 text-sm font-medium whitespace-nowrap outline-hidden transition-all select-none hover:cursor-pointer focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 has-[>svg]:px-3 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
@ -100,7 +100,7 @@
<div class="mt-2 flex flex-col gap-2 p-2"> <div class="mt-2 flex flex-col gap-2 p-2">
{#if selectedCategory && suggestionCategories[selectedCategory]} {#if selectedCategory && suggestionCategories[selectedCategory]}
{#each suggestionCategories[selectedCategory]?.suggestions ?? [] as suggestion} {#each suggestionCategories[selectedCategory]?.suggestions ?? [] as suggestion (suggestion)}
<div class="border-border not-last:border-b not-last:pb-2"> <div class="border-border not-last:border-b not-last:pb-2">
<Button <Button
onclick={() => (prompt.current = suggestion)} onclick={() => (prompt.current = suggestion)}

View file

@ -4,8 +4,11 @@
import type { Id } from '$lib/backend/convex/_generated/dataModel'; import type { Id } from '$lib/backend/convex/_generated/dataModel';
import { useCachedQuery } from '$lib/cache/cached-query.svelte'; import { useCachedQuery } from '$lib/cache/cached-query.svelte';
import { session } from '$lib/state/session.svelte'; import { session } from '$lib/state/session.svelte';
import { watch } from 'runed';
import LoadingDots from './loading-dots.svelte'; import LoadingDots from './loading-dots.svelte';
import Message from './message.svelte'; import Message from './message.svelte';
import { last } from '$lib/utils/array';
import { settings } from '$lib/state/settings.svelte';
const messages = useCachedQuery(api.messages.getAllFromConversation, () => ({ const messages = useCachedQuery(api.messages.getAllFromConversation, () => ({
conversation_id: page.params.id ?? '', conversation_id: page.params.id ?? '',
@ -27,6 +30,24 @@
return lastMessage.content.length > 0; return lastMessage.content.length > 0;
}); });
let changedRoute = $state(false);
watch(
() => page.params.id,
() => {
changedRoute = true;
}
);
$effect(() => {
if (!changedRoute || !messages.data) return;
const lastMessage = last(messages.data)!;
if (lastMessage.model_id && lastMessage.model_id !== settings.modelId) {
settings.modelId = lastMessage.model_id;
}
changedRoute = false;
});
</script> </script>
<div class="flex h-full flex-1 flex-col py-4"> <div class="flex h-full flex-1 flex-col py-4">

View file

@ -13,13 +13,14 @@
import MicrosoftIcon from '~icons/simple-icons/microsoft'; import MicrosoftIcon from '~icons/simple-icons/microsoft';
import OpenaiIcon from '~icons/simple-icons/openai'; 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
import RobotIcon from '~icons/lucide/bot';
import BrainIcon from '~icons/lucide/brain'; import BrainIcon from '~icons/lucide/brain';
import CpuIcon from '~icons/lucide/cpu'; import CpuIcon from '~icons/lucide/cpu';
import ZapIcon from '~icons/lucide/zap'; import ZapIcon from '~icons/lucide/zap';
// Model-specific icons // Model-specific icons
import Cohere from '$lib/components/icons/cohere.svelte';
import Deepseek from '$lib/components/icons/deepseek.svelte';
import { Popover } from 'melt/builders'; import { Popover } from 'melt/builders';
import type { Component } from 'svelte';
import LogosClaudeIcon from '~icons/logos/claude-icon'; import LogosClaudeIcon from '~icons/logos/claude-icon';
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 MaterialIconThemeGeminiAi from '~icons/material-icon-theme/gemini-ai';
@ -37,7 +38,7 @@
const enabledArr = $derived(Object.values(enabledModelsQuery.data ?? {})); const enabledArr = $derived(Object.values(enabledModelsQuery.data ?? {}));
// Company icon mapping // Company icon mapping
const companyIcons: Record<string, typeof OpenaiIcon> = { const companyIcons: Record<string, Component> = {
openai: OpenaiIcon, openai: OpenaiIcon,
anthropic: BrainIcon, anthropic: BrainIcon,
google: GoogleIcon, google: GoogleIcon,
@ -46,11 +47,10 @@
'x-ai': XIcon, 'x-ai': XIcon,
microsoft: MicrosoftIcon, microsoft: MicrosoftIcon,
qwen: CpuIcon, qwen: CpuIcon,
deepseek: RobotIcon, deepseek: Deepseek,
cohere: CpuIcon, cohere: Cohere,
}; };
// Function to get model-specific icon
function getModelIcon(modelId: string): typeof LogosClaudeIcon | null { function getModelIcon(modelId: string): typeof LogosClaudeIcon | null {
const id = modelId.toLowerCase(); const id = modelId.toLowerCase();
@ -61,17 +61,13 @@
return null; return null;
} }
// Function to extract company from model ID
function getCompanyFromModelId(modelId: string): string { function getCompanyFromModelId(modelId: string): string {
const id = modelId.toLowerCase(); const id = modelId.toLowerCase();
// OpenAI models
if (id.includes('gpt') || id.includes('o1') || id.includes('openai')) return 'openai'; if (id.includes('gpt') || id.includes('o1') || id.includes('openai')) return 'openai';
// Anthropic models
if (id.includes('claude') || id.includes('anthropic')) return 'anthropic'; if (id.includes('claude') || id.includes('anthropic')) return 'anthropic';
// Google models
if ( if (
id.includes('gemini') || id.includes('gemini') ||
id.includes('gemma') || id.includes('gemma') ||
@ -80,25 +76,18 @@
) )
return 'google'; return 'google';
// Meta models
if (id.includes('llama') || id.includes('meta')) return 'meta'; if (id.includes('llama') || id.includes('meta')) return 'meta';
// Mistral models
if (id.includes('mistral') || id.includes('mixtral')) return 'mistral'; if (id.includes('mistral') || id.includes('mixtral')) return 'mistral';
// xAI models
if (id.includes('grok') || id.includes('x-ai')) return 'x-ai'; if (id.includes('grok') || id.includes('x-ai')) return 'x-ai';
// Microsoft models
if (id.includes('phi') || id.includes('microsoft')) return 'microsoft'; if (id.includes('phi') || id.includes('microsoft')) return 'microsoft';
// Qwen models
if (id.includes('qwen') || id.includes('alibaba')) return 'qwen'; if (id.includes('qwen') || id.includes('alibaba')) return 'qwen';
// DeepSeek models
if (id.includes('deepseek')) return 'deepseek'; if (id.includes('deepseek')) return 'deepseek';
// Cohere models
if (id.includes('command') || id.includes('cohere')) return 'cohere'; if (id.includes('command') || id.includes('cohere')) return 'cohere';
// Try to extract from model path (e.g., "anthropic/claude-3") // Try to extract from model path (e.g., "anthropic/claude-3")
@ -113,20 +102,16 @@
// Group models by company // Group models by company
const groupedModels = $derived.by(() => { const groupedModels = $derived.by(() => {
console.log('📊 enabledArr:', enabledArr);
const groups: Record<string, typeof enabledArr> = {}; const groups: Record<string, typeof enabledArr> = {};
enabledArr.forEach((model) => { enabledArr.forEach((model) => {
const company = getCompanyFromModelId(model.model_id); const company = getCompanyFromModelId(model.model_id);
console.log(`🏢 Model ${model.model_id} -> Company: ${company}`);
if (!groups[company]) { if (!groups[company]) {
groups[company] = []; groups[company] = [];
} }
groups[company].push(model); groups[company].push(model);
}); });
console.log('📋 Groups:', groups);
// Sort companies with known icons first // Sort companies with known icons first
const result = Object.entries(groups).sort(([a], [b]) => { const result = Object.entries(groups).sort(([a], [b]) => {
const aHasIcon = companyIcons[a] ? 0 : 1; const aHasIcon = companyIcons[a] ? 0 : 1;
@ -134,11 +119,9 @@
return aHasIcon - bHasIcon || a.localeCompare(b); return aHasIcon - bHasIcon || a.localeCompare(b);
}); });
console.log('🎯 Final grouped models:', result);
return result; return result;
}); });
// 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(
currentModel ? getCompanyFromModelId(currentModel.model_id) : 'other' currentModel ? getCompanyFromModelId(currentModel.model_id) : 'other'
@ -159,12 +142,9 @@
const popover = new Popover({ const popover = new Popover({
open: () => open, open: () => open,
onOpenChange: (v) => { onOpenChange: (v) => {
console.log('📋 popover open:', v);
if (v === open) return; if (v === open) return;
open = v; open = v;
console.log('assigned', v);
if (v) return; if (v) return;
console.log('attempting to focus');
document.getElementById(popover.trigger.id)?.focus(); document.getElementById(popover.trigger.id)?.focus();
}, },
floatingConfig: { floatingConfig: {