search functionality

This commit is contained in:
Aidan Bleser 2025-06-16 06:42:56 -05:00
parent d5dbab44fa
commit 1aaae9b8f8
11 changed files with 326 additions and 275 deletions

View file

@ -1,6 +1,6 @@
@import 'tailwindcss';
@custom-variant dark (&:where(.dark, .dark *));
@custom-variant dark (&:is(.dark *));
:root {
--background: oklch(0.9754 0.0084 325.6414);

View file

@ -93,4 +93,3 @@ export function clearQueryCache(): void {
}
export { globalCache as queryCache };

View file

@ -13,11 +13,7 @@ export class SessionStorageCache<T = unknown> {
private debounceMs: number;
private pendingWrites = new Set<string>();
constructor(
storageKey = 'query-cache',
maxSizeBytes = 1024 * 1024,
debounceMs = 300
) {
constructor(storageKey = 'query-cache', maxSizeBytes = 1024 * 1024, debounceMs = 300) {
this.storageKey = storageKey;
this.debounceMs = debounceMs;
this.memoryCache = new LRUCache<string, CacheEntry<T>>(maxSizeBytes);

View file

@ -0,0 +1 @@
export { default as Search } from './search.svelte';

View file

@ -0,0 +1,13 @@
<script lang="ts">
import type { HTMLInputAttributes } from 'svelte/elements';
import Search from '~icons/lucide/search';
let { value = $bindable(''), ...rest }: HTMLInputAttributes = $props();
</script>
<div
class="border-input focus-within:ring-ring ring-offset-background relative flex h-9 items-center rounded-md border p-2 text-base ring-offset-2 focus-within:ring-2 md:text-sm"
>
<Search class="text-muted-foreground size-4" />
<input {...rest} bind:value type="text" class="flex-1 bg-transparent px-2 outline-none" />
</div>

View file

@ -1,4 +1,4 @@
import { redirect } from "@sveltejs/kit";
import { redirect } from '@sveltejs/kit';
export async function load() {
// temporary redirect to /chat

View file

@ -2,10 +2,14 @@
import { api } from '$lib/backend/convex/_generated/api';
import { useCachedQuery } from '$lib/cache/cached-query.svelte';
import { Button } from '$lib/components/ui/button';
import { Search } from '$lib/components/ui/search';
import { session } from '$lib/state/session.svelte';
import { Provider } from '$lib/types.js';
import { cn } from '$lib/utils/utils';
import ModelCard from './model-card.svelte';
import { Toggle } from 'melt/builders';
import XIcon from '~icons/lucide/x';
import PlusIcon from '~icons/lucide/plus';
let { data } = $props();
@ -21,6 +25,23 @@
const hasOpenRouterKey = $derived(
openRouterKeyQuery.data !== undefined && openRouterKeyQuery.data !== ''
);
let search = $state('');
const openRouterModels = $derived(
data.openRouterModels.filter((model) => {
if (search !== '' && !hasOpenRouterKey) return false;
if (!openRouterToggle.value) return false;
return model.name.toLowerCase().includes(search.toLowerCase());
})
);
const openRouterToggle = new Toggle({
value: true,
// TODO: enable this if and when when we use multiple providers
disabled: true,
});
</script>
<svelte:head>
@ -32,7 +53,23 @@
Choose which models appear in your model selector. This won't affect existing conversations.
</h2>
<div class="mt-8 flex flex-col gap-4">
<div class="mt-4 flex flex-col gap-2">
<Search bind:value={search} placeholder="Search models" />
<div class="flex place-items-center gap-2">
<button
{...openRouterToggle.trigger}
aria-label="OpenRouter"
class="group text-primary-foreground bg-primary aria-[pressed=false]:border-border border-primary aria-[pressed=false]:bg-background flex place-items-center gap-1 rounded-full border px-2 py-1 text-xs transition-all disabled:cursor-not-allowed disabled:opacity-50"
>
OpenRouter
<XIcon class="inline size-3 group-aria-[pressed=false]:hidden" />
<PlusIcon class="inline size-3 group-aria-[pressed=true]:hidden" />
</button>
</div>
</div>
{#if openRouterModels.length > 0}
<div class="mt-4 flex flex-col gap-4">
<div>
<h3 class="text-lg font-bold">OpenRouter</h3>
<p class="text-muted-foreground text-sm">Easy access to over 400 models.</p>
@ -43,9 +80,15 @@
'pointer-events-none max-h-96 mask-b-from-0% mask-b-to-80%': !hasOpenRouterKey,
})}
>
{#each data.openRouterModels as model (model.id)}
{@const enabled = enabledModels.data?.[`${Provider.OpenRouter}:${model.id}`] !== undefined}
<ModelCard provider={Provider.OpenRouter} {model} {enabled} disabled={!hasOpenRouterKey} />
{#each openRouterModels as model (model.id)}
{@const enabled =
enabledModels.data?.[`${Provider.OpenRouter}:${model.id}`] !== undefined}
<ModelCard
provider={Provider.OpenRouter}
{model}
{enabled}
disabled={!hasOpenRouterKey}
/>
{/each}
</div>
{#if !hasOpenRouterKey}
@ -56,4 +99,5 @@
</div>
{/if}
</div>
</div>
</div>
{/if}

View file

@ -10,9 +10,7 @@
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler",
"types": [
"unplugin-icons/types/svelte"
]
"types": ["unplugin-icons/types/svelte"]
}
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files