enable/disable mutation
This commit is contained in:
parent
ef5a698c67
commit
0763dc465d
11 changed files with 178 additions and 23 deletions
|
|
@ -8,6 +8,7 @@
|
|||
"paths": {
|
||||
"*": "$lib/blocks",
|
||||
"utils": "$lib/utils",
|
||||
"ts": "$lib/utils",
|
||||
"ui": "$lib/components/ui",
|
||||
"actions": "$lib/actions",
|
||||
"hooks": "$lib/hooks"
|
||||
|
|
|
|||
|
|
@ -14,5 +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
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,14 +6,14 @@ export const providerValidator = v.union(...Object.values(Provider).map((p) => v
|
|||
|
||||
export default defineSchema({
|
||||
user_keys: defineTable({
|
||||
user_id: v.id('users'),
|
||||
user_id: v.string(),
|
||||
provider: providerValidator,
|
||||
key: v.string(),
|
||||
})
|
||||
.index('by_user', ['user_id'])
|
||||
.index('by_provider_user', ['provider', 'user_id']),
|
||||
user_enabled_models: defineTable({
|
||||
user_id: v.id('users'),
|
||||
user_id: v.string(),
|
||||
provider: providerValidator,
|
||||
/** Different providers may use different ids for the same model */
|
||||
model_id: v.string(),
|
||||
|
|
|
|||
|
|
@ -1,16 +1,19 @@
|
|||
import { mutation } from './_generated/server';
|
||||
import { query, mutation } from './_generated/server';
|
||||
import { v } from 'convex/values';
|
||||
import { providerValidator } from './schema';
|
||||
import * as array from '../../utils/array';
|
||||
|
||||
export const get = mutation({
|
||||
export const get_enabled = query({
|
||||
args: {
|
||||
user_id: v.id('users'),
|
||||
user_id: v.string(),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
return await ctx.db
|
||||
const models = await ctx.db
|
||||
.query('user_enabled_models')
|
||||
.withIndex('by_user', (q) => q.eq('user_id', args.user_id))
|
||||
.collect();
|
||||
|
||||
return array.toMap(models, (m) => [`${m.provider}:${m.model_id}`, m]);
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -18,7 +21,8 @@ export const set = mutation({
|
|||
args: {
|
||||
provider: providerValidator,
|
||||
model_id: v.string(),
|
||||
user_id: v.id('users'),
|
||||
user_id: v.string(),
|
||||
enabled: v.boolean(),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
const existing = await ctx.db
|
||||
|
|
@ -28,8 +32,15 @@ export const set = mutation({
|
|||
)
|
||||
.first();
|
||||
|
||||
if (existing) return;
|
||||
if (args.enabled && existing) return; // nothing to do here
|
||||
|
||||
await ctx.db.insert('user_enabled_models', { ...args, pinned: null });
|
||||
if (existing) {
|
||||
await ctx.db.delete(existing._id);
|
||||
} else {
|
||||
await ctx.db.insert('user_enabled_models', {
|
||||
...{ ...args, enabled: undefined },
|
||||
pinned: null,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { providerValidator } from './schema';
|
|||
|
||||
export const get = query({
|
||||
args: {
|
||||
user_id: v.id('users'),
|
||||
user_id: v.string(),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
const allKeys = await ctx.db
|
||||
|
|
@ -26,7 +26,7 @@ export const get = query({
|
|||
export const set = mutation({
|
||||
args: {
|
||||
provider: providerValidator,
|
||||
user_id: v.id('users'),
|
||||
user_id: v.string(),
|
||||
key: v.string(),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
|
|
|
|||
3
src/lib/components/ui/switch/index.ts
Normal file
3
src/lib/components/ui/switch/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import Switch from './switch.svelte';
|
||||
|
||||
export { Switch };
|
||||
24
src/lib/components/ui/switch/switch.svelte
Normal file
24
src/lib/components/ui/switch/switch.svelte
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<script lang="ts">
|
||||
import { cn } from '$lib/utils/utils';
|
||||
import { Toggle, type ToggleProps } from 'melt/builders';
|
||||
|
||||
let { class: className, ...rest }: ToggleProps & { class?: string } = $props();
|
||||
|
||||
const toggle = new Toggle(rest);
|
||||
</script>
|
||||
|
||||
<button
|
||||
{...toggle.trigger}
|
||||
class={cn(
|
||||
'bg-muted-foreground/20 relative h-5 w-10 rounded-full transition-all',
|
||||
{ 'bg-primary': toggle.value },
|
||||
className
|
||||
)}
|
||||
>
|
||||
<span
|
||||
class={cn('bg-background absolute top-0.5 left-0.5 h-4 w-4 rounded-full transition-all', {
|
||||
'bg-primary-foreground': toggle.value,
|
||||
})}
|
||||
style="transform: translateX({toggle.value ? '20px' : '0px'})"
|
||||
></span>
|
||||
</button>
|
||||
80
src/lib/utils/array.ts
Normal file
80
src/lib/utils/array.ts
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
Installed from @ieedan/std
|
||||
*/
|
||||
|
||||
/** Maps the provided map into an array using the provided mapping function.
|
||||
*
|
||||
* @param map Map to be entered into an array
|
||||
* @param fn A mapping function to transform each pair into an item
|
||||
* @returns
|
||||
*
|
||||
* ## Usage
|
||||
* ```ts
|
||||
* console.log(map); // Map(5) { 0 => 5, 1 => 4, 2 => 3, 3 => 2, 4 => 1 }
|
||||
*
|
||||
* const arr = fromMap(map, (_, value) => value);
|
||||
*
|
||||
* console.log(arr); // [5, 4, 3, 2, 1]
|
||||
* ```
|
||||
*/
|
||||
export function fromMap<K, V, T>(map: Map<K, V>, fn: (key: K, value: V) => T): T[] {
|
||||
const items: T[] = [];
|
||||
|
||||
for (const [key, value] of map) {
|
||||
items.push(fn(key, value));
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
/** Calculates the sum of all elements in the array based on the provided function.
|
||||
*
|
||||
* @param arr Array of items to be summed.
|
||||
* @param fn Summing function
|
||||
* @returns
|
||||
*
|
||||
* ## Usage
|
||||
*
|
||||
* ```ts
|
||||
* const total = sum([1, 2, 3, 4, 5], (num) => num);
|
||||
*
|
||||
* console.log(total); // 15
|
||||
* ```
|
||||
*/
|
||||
export function sum<T>(arr: T[], fn: (item: T) => number): number {
|
||||
let total = 0;
|
||||
|
||||
for (const item of arr) {
|
||||
total = total + fn(item);
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
/** Maps the provided array into a map
|
||||
*
|
||||
* @param arr Array of items to be entered into a map
|
||||
* @param fn A mapping function to transform each item into a key value pair
|
||||
* @returns
|
||||
*
|
||||
* ## Usage
|
||||
* ```ts
|
||||
* const map = toMap([5, 4, 3, 2, 1], (item, i) => [i, item]);
|
||||
*
|
||||
* console.log(map); // Map(5) { 0 => 5, 1 => 4, 2 => 3, 3 => 2, 4 => 1 }
|
||||
* ```
|
||||
*/
|
||||
export function toMap<T, V>(
|
||||
arr: T[],
|
||||
fn: (item: T, index: number) => [key: string, value: V]
|
||||
): Record<string, V> {
|
||||
const map: Record<string, V> = {};
|
||||
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
const [key, value] = fn(arr[i], i);
|
||||
|
||||
map[key] = value;
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
|
@ -39,7 +39,7 @@
|
|||
const key = formData.get('key');
|
||||
if (key === null || !session.current?.user.id) return;
|
||||
|
||||
const res = await client.mutation(api.user_keys.set, {
|
||||
await client.mutation(api.user_keys.set, {
|
||||
provider,
|
||||
user_id: session.current?.user.id ?? '',
|
||||
key: `${key}`,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
<script lang="ts">
|
||||
import { Provider } from '$lib/types.js';
|
||||
import { useConvexClient } from 'convex-svelte';
|
||||
import { useQuery } from 'convex-svelte';
|
||||
import Model from './model.svelte';
|
||||
import { session } from '$lib/state/session.svelte';
|
||||
import { api } from '$lib/backend/convex/_generated/api';
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
const client = useConvexClient();
|
||||
const enabledModels = useQuery(api.user_enabled_models.get_enabled, {
|
||||
user_id: session.current?.user.id ?? '',
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
|
@ -20,5 +24,6 @@
|
|||
</div>
|
||||
|
||||
{#each data.openRouterModels as model}
|
||||
<Model provider={Provider.OpenRouter} model={model} />
|
||||
{@const enabled = enabledModels.data?.[`${Provider.OpenRouter}:${model.id}`] !== undefined}
|
||||
<Model provider={Provider.OpenRouter} {model} {enabled} />
|
||||
{/each}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
<script lang="ts">
|
||||
import type { Provider } from '$lib/types';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Switch } from '$lib/components/ui/switch';
|
||||
import { useConvexClient } from 'convex-svelte';
|
||||
import { api } from '$lib/backend/convex/_generated/api';
|
||||
import { session } from '$lib/state/session.svelte.js';
|
||||
|
||||
type Model = {
|
||||
id: string;
|
||||
|
|
@ -11,12 +15,15 @@
|
|||
type Props = {
|
||||
provider: Provider;
|
||||
model: Model;
|
||||
enabled?: boolean;
|
||||
};
|
||||
|
||||
let { provider, model }: Props = $props();
|
||||
let { provider, model, enabled = false }: Props = $props();
|
||||
|
||||
const client = useConvexClient();
|
||||
|
||||
function getShortDescription(text: string) {
|
||||
// match any punctuation followed by a space or the end of the string
|
||||
// match any punctuation followed by a space or the end of the string
|
||||
const index = text.match(/[.!?](\s|$)/)?.index;
|
||||
|
||||
if (index === undefined) return { shortDescription: null, fullDescription: text };
|
||||
|
|
@ -27,23 +34,38 @@
|
|||
const { shortDescription, fullDescription } = $derived(getShortDescription(model.description));
|
||||
|
||||
let showMore = $state(false);
|
||||
|
||||
async function toggleEnabled(enabled: boolean) {
|
||||
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,
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<Card.Root>
|
||||
<Card.Header>
|
||||
<Card.Title>{model.name}</Card.Title>
|
||||
<Card.Description>{showMore ? fullDescription : shortDescription ?? fullDescription}</Card.Description>
|
||||
<div class="flex items-center justify-between">
|
||||
<Card.Title>{model.name}</Card.Title>
|
||||
<!-- TODO: make this actually work -->
|
||||
<Switch value={enabled} onValueChange={toggleEnabled} />
|
||||
</div>
|
||||
<Card.Description
|
||||
>{showMore ? fullDescription : (shortDescription ?? fullDescription)}</Card.Description
|
||||
>
|
||||
{#if shortDescription !== null}
|
||||
<button
|
||||
type="button"
|
||||
class="text-muted-foreground text-start w-fit text-xs"
|
||||
class="text-muted-foreground w-fit text-start text-xs"
|
||||
onclick={() => (showMore = !showMore)}
|
||||
>
|
||||
{showMore ? 'Show less' : 'Show more'}
|
||||
</button>
|
||||
{/if}
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
|
||||
</Card.Content>
|
||||
<Card.Content></Card.Content>
|
||||
</Card.Root>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue