working switch

This commit is contained in:
Thomas G. Lopes 2025-06-14 18:45:05 +01:00
parent 24f0ae3219
commit e8d25a8f11
7 changed files with 69 additions and 17 deletions

View file

@ -17,9 +17,10 @@ export default defineSchema({
provider: providerValidator,
/** Different providers may use different ids for the same model */
model_id: v.string(),
pinned: v.union(v.number(), v.null())
pinned: v.union(v.number(), v.null()),
})
.index('by_user', ['user_id'])
.index('by_model_provider', ['model_id', 'provider'])
.index('by_provider_user', ['provider', 'user_id']),
.index('by_provider_user', ['provider', 'user_id'])
.index('by_model_provider_user', ['model_id', 'provider', 'user_id']),
});

View file

@ -2,6 +2,7 @@ import { query, mutation } from './_generated/server';
import { v } from 'convex/values';
import { providerValidator } from './schema';
import * as array from '../../utils/array';
import * as object from '../../utils/object';
export const get_enabled = query({
args: {
@ -17,6 +18,24 @@ export const get_enabled = query({
},
});
export const is_enabled = query({
args: {
user_id: v.string(),
provider: providerValidator,
model_id: v.string(),
},
handler: async (ctx, args) => {
const model = await ctx.db
.query('user_enabled_models')
.withIndex('by_model_provider_user', (q) =>
q.eq('model_id', args.model_id).eq('provider', args.provider).eq('user_id', args.user_id)
)
.first();
return !!model;
},
});
export const set = mutation({
args: {
provider: providerValidator,
@ -38,7 +57,7 @@ export const set = mutation({
await ctx.db.delete(existing._id);
} else {
await ctx.db.insert('user_enabled_models', {
...{ ...args, enabled: undefined },
...object.pick(args, ['provider', 'model_id', 'user_id']),
pinned: null,
});
}

View file

@ -1,10 +1,19 @@
<script lang="ts">
import { cn } from '$lib/utils/utils';
import { Toggle, type ToggleProps } from 'melt/builders';
import { type ComponentProps } from 'melt';
let { class: className, ...rest }: ToggleProps & { class?: string } = $props();
let {
class: className,
value = $bindable(false),
...rest
}: ComponentProps<ToggleProps> & { class?: string } = $props();
const toggle = new Toggle(rest);
const toggle = new Toggle({
value: () => value ?? false,
onValueChange: (v) => (value = v),
...rest,
});
</script>
<button

View file

@ -2,3 +2,16 @@
export function keys<T extends object>(obj: T): Array<keyof T> {
return Object.keys(obj) as Array<keyof T>;
}
export function omit<T extends object, K extends keyof T>(obj: T, keys: K[]): Omit<T, K> {
return Object.fromEntries(
Object.entries(obj).filter(([key]) => !keys.includes(key as K))
) as Omit<T, K>;
}
export function pick<T extends object, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
return Object.fromEntries(Object.entries(obj).filter(([key]) => keys.includes(key as K))) as Pick<
T,
K
>;
}

View file

@ -28,7 +28,6 @@
let loading = $state(false);
const toasts = new LocalToasts({ id });
$inspect(toasts.toasts);
async function submit(e: SubmitEvent) {
loading = true;

View file

@ -1,7 +1,7 @@
<script lang="ts">
import { Provider } from '$lib/types.js';
import { useQuery } from 'convex-svelte';
import Model from './model.svelte';
import ModelCard from './model-card.svelte';
import { session } from '$lib/state/session.svelte';
import { api } from '$lib/backend/convex/_generated/api';
@ -10,6 +10,11 @@
const enabledModels = useQuery(api.user_enabled_models.get_enabled, {
user_id: session.current?.user.id ?? '',
});
$inspect(
enabledModels.data,
!!enabledModels.data?.[`${Provider.OpenRouter}:${data.openRouterModels[0].id}`]
);
</script>
<svelte:head>
@ -24,6 +29,6 @@
<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} />
<ModelCard provider={Provider.OpenRouter} {model} {enabled} />
{/each}
</div>

View file

@ -5,6 +5,7 @@
import { useConvexClient } from 'convex-svelte';
import { api } from '$lib/backend/convex/_generated/api';
import { session } from '$lib/state/session.svelte.js';
import { ResultAsync } from 'neverthrow';
type Model = {
id: string;
@ -35,15 +36,21 @@
let showMore = $state(false);
async function toggleEnabled(enabled: boolean) {
async function toggleEnabled(v: boolean) {
enabled = v; // Optimistic!
if (!session.current?.user.id) return;
await client.mutation(api.user_enabled_models.set, {
provider,
user_id: session.current.user.id,
model_id: model.id,
enabled,
});
const res = await ResultAsync.fromPromise(
client.mutation(api.user_enabled_models.set, {
provider,
user_id: session.current.user.id,
model_id: model.id,
enabled: v,
}),
(e) => e
);
if (res.isErr()) enabled = !v; // Should have been a realist :(
}
</script>
@ -52,7 +59,7 @@
<div class="flex items-center justify-between">
<Card.Title>{model.name}</Card.Title>
<!-- TODO: make this actually work -->
<Switch value={enabled} onValueChange={toggleEnabled} />
<Switch bind:value={() => enabled, toggleEnabled} />
</div>
<Card.Description
>{showMore ? fullDescription : (shortDescription ?? fullDescription)}</Card.Description
@ -67,5 +74,4 @@
</button>
{/if}
</Card.Header>
<Card.Content></Card.Content>
</Card.Root>