refactor api keys logic

This commit is contained in:
Thomas G. Lopes 2025-06-14 17:52:54 +01:00
parent d1c95d0ee3
commit 07ec0ade92
10 changed files with 69 additions and 58 deletions

View file

@ -3,7 +3,8 @@ import type { Handle } from '@sveltejs/kit';
import { svelteKitHandler } from 'better-auth/svelte-kit';
export const handle: Handle = async ({ event, resolve }) => {
event.locals.auth = () => auth.api.getSession({ headers: event.request.headers });
// eslint-disable-next-line @typescript-eslint/no-explicit-any
event.locals.auth = () => auth.api.getSession({ headers: event.request.headers }) as any;
return svelteKitHandler({ event, resolve, auth });
};

View file

@ -14,14 +14,14 @@ export const auth = betterAuth({
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
},
},
databaseHooks: {
user: {
create: {
after: async ({ user }) => {
// TODO: automatically enable default models for the user
},
},
},
},
// databaseHooks: {
// user: {
// create: {
// after: async ({ user }) => {
// // TODO: automatically enable default models for the user
// },
// },
// },
// },
plugins: [],
});

View file

@ -3,7 +3,7 @@ import { Provider } from '../../types';
import { mutation, query } from './_generated/server';
import { providerValidator } from './schema';
export const get = query({
export const all = query({
args: {
user_id: v.string(),
},
@ -23,6 +23,23 @@ export const get = query({
},
});
export const get = query({
args: {
user_id: v.string(),
provider: providerValidator,
},
handler: async (ctx, args) => {
const key = await ctx.db
.query('user_keys')
.withIndex('by_provider_user', (q) =>
q.eq('provider', args.provider).eq('user_id', args.user_id)
)
.first();
return key?.key;
},
});
export const set = mutation({
args: {
provider: providerValidator,

View file

@ -40,7 +40,7 @@ export function getOpenRouterModels() {
(async () => {
const res = await fetch('https://openrouter.ai/api/v1/models');
const { data } = await res.json()
const { data } = await res.json();
return data as OpenRouterModel[];
})(),

View file

@ -6,3 +6,11 @@ export const Provider = {
} as const;
export type Provider = (typeof Provider)[keyof typeof Provider];
export type ProviderMeta = {
title: string;
link: string;
description: string;
models?: string[];
placeholder?: string;
};

View file

@ -66,7 +66,7 @@
</div>
</div>
</div>
<div class="space-y-8 pl-12 md:col-start-2">
<div class="pl-12 md:col-start-2">
<div
class="bg-card text-muted-foreground flex w-fit place-items-center gap-2 rounded-lg p-1 text-sm"
>
@ -80,7 +80,9 @@
</a>
{/each}
</div>
{@render children?.()}
<div class="pt-8">
{@render children?.()}
</div>
</div>
</div>
</div>

View file

@ -1,5 +1,5 @@
<script lang="ts">
import { Provider } from '$lib/types';
import { Provider, type ProviderMeta } from '$lib/types';
import { useQuery } from 'convex-svelte';
import { api } from '$lib/backend/convex/_generated/api';
import { session } from '$lib/state/session.svelte.js';
@ -7,14 +7,6 @@
const allProviders = Object.values(Provider);
type ProviderMeta = {
title: string;
link: string;
description: string;
models?: string[];
placeholder?: string;
};
const providersMeta: Record<Provider, ProviderMeta> = {
[Provider.OpenRouter]: {
title: 'OpenRouter',
@ -51,8 +43,6 @@
placeholder: 'sk-ant-...',
},
};
const keys = useQuery(api.user_keys.get, { user_id: session.current?.user.id ?? '' });
</script>
<svelte:head>
@ -67,9 +57,9 @@
</h2>
</div>
<div class="mt-8 flex flex-col gap-8">
<div class="mt-8 flex flex-col gap-4">
{#each allProviders as provider (provider)}
{@const meta = providersMeta[provider]}
<ProviderCard {provider} {meta} key={(async () => await keys.data?.[provider])()} />
<ProviderCard {provider} {meta} />
{/each}
</div>

View file

@ -4,26 +4,22 @@
import { Input } from '$lib/components/ui/input';
import { Button } from '$lib/components/ui/button';
import { Link } from '$lib/components/ui/link';
import { useConvexClient } from 'convex-svelte';
import { useConvexClient, useQuery } from 'convex-svelte';
import { api } from '$lib/backend/convex/_generated/api';
import { session } from '$lib/state/session.svelte.js';
import type { Provider } from '$lib/types';
type ProviderMeta = {
title: string;
link: string;
description: string;
models?: string[];
placeholder?: string;
};
import type { Provider, ProviderMeta } from '$lib/types';
type Props = {
provider: Provider;
meta: ProviderMeta;
key: Promise<string>;
};
let { provider, meta, key: keyPromise }: Props = $props();
let { provider, meta }: Props = $props();
const keyQuery = useQuery(api.user_keys.get, {
user_id: session.current?.user.id ?? '',
provider,
});
const client = useConvexClient();
@ -44,9 +40,8 @@
user_id: session.current?.user.id ?? '',
key: `${key}`,
});
// TODO: Setup toast notifications
} catch {
// TODO: Setup toast notifications
} finally {
loading = false;
}
@ -63,17 +58,17 @@
</Card.Header>
<Card.Content tag="form" onsubmit={submit}>
<div class="flex flex-col gap-1">
{#await keyPromise}
{#if keyQuery.isLoading}
<div class="bg-input h-9 animate-pulse rounded-md"></div>
{:then key}
{:else}
<Input
type="password"
placeholder={meta.placeholder ?? ''}
autocomplete="off"
name="key"
value={key}
value={keyQuery.data!}
/>
{/await}
{/if}
<span class="text-muted-foreground text-xs">
Get your API key from
<Link href={meta.link} target="_blank" class="text-blue-500">

View file

@ -1,9 +1,7 @@
import { getOpenRouterModels, type OpenRouterModel } from '$lib/backend/models/open-router';
export async function load() {
const [openRouterModels] = await Promise.all([getOpenRouterModels()]);
return {
openRouterModels: openRouterModels.unwrapOr([] as OpenRouterModel[]),
openRouterModels: (await getOpenRouterModels()).unwrapOr([] as OpenRouterModel[]),
};
}

View file

@ -16,14 +16,14 @@
<title>Models | Thom.chat</title>
</svelte:head>
<div>
<h1 class="text-2xl font-bold">Available Models</h1>
<h2 class="text-muted-foreground mt-2 text-sm">
Choose which models appear in your model selector. This won't affect existing conversations.
</h2>
</div>
<h1 class="text-2xl font-bold">Available Models</h1>
<h2 class="text-muted-foreground mt-2 text-sm">
Choose which models appear in your model selector. This won't affect existing conversations.
</h2>
{#each data.openRouterModels as model}
{@const enabled = enabledModels.data?.[`${Provider.OpenRouter}:${model.id}`] !== undefined}
<Model provider={Provider.OpenRouter} {model} {enabled} />
{/each}
<div class="mt-8 flex flex-col gap-4">
{#each data.openRouterModels as model (model.id)}
{@const enabled = enabledModels.data?.[`${Provider.OpenRouter}:${model.id}`] !== undefined}
<Model provider={Provider.OpenRouter} {model} {enabled} />
{/each}
</div>