114 lines
2.9 KiB
Svelte
114 lines
2.9 KiB
Svelte
<script lang="ts">
|
|
import * as Card from '$lib/components/ui/card';
|
|
import { Label } from '$lib/components/ui/label';
|
|
import { Textarea } from '$lib/components/ui/textarea';
|
|
import { Button } from '$lib/components/ui/button';
|
|
import type { Doc } from '$lib/backend/convex/_generated/dataModel';
|
|
import { useConvexClient } from 'convex-svelte';
|
|
import { api } from '$lib/backend/convex/_generated/api';
|
|
import { session } from '$lib/state/session.svelte';
|
|
import { LocalToasts } from '$lib/builders/local-toasts.svelte';
|
|
import { ResultAsync } from 'neverthrow';
|
|
import TrashIcon from '~icons/lucide/trash';
|
|
|
|
type Props = {
|
|
rule: Doc<'user_rules'>;
|
|
};
|
|
|
|
const id = $props.id();
|
|
|
|
let { rule }: Props = $props();
|
|
|
|
const client = useConvexClient();
|
|
|
|
let updating = $state(false);
|
|
let deleting = $state(false);
|
|
|
|
const toasts = new LocalToasts({ id });
|
|
|
|
async function updateRule(e: SubmitEvent) {
|
|
e.preventDefault();
|
|
const formData = new FormData(e.target as HTMLFormElement);
|
|
const attach = formData.get('attach') as 'always' | 'manual';
|
|
const ruleText = formData.get('rule') as string;
|
|
|
|
if (ruleText === '' || !ruleText) return;
|
|
|
|
updating = true;
|
|
|
|
const res = await ResultAsync.fromPromise(
|
|
client.mutation(api.user_rules.update, {
|
|
ruleId: rule._id,
|
|
attach,
|
|
rule: ruleText,
|
|
sessionToken: session.current?.session.token ?? '',
|
|
}),
|
|
(e) => e
|
|
);
|
|
|
|
toasts.addToast({
|
|
data: {
|
|
content: res.isOk() ? 'Saved' : 'Failed to save',
|
|
variant: res.isOk() ? 'info' : 'danger',
|
|
},
|
|
});
|
|
|
|
updating = false;
|
|
}
|
|
|
|
async function deleteRule() {
|
|
deleting = true;
|
|
|
|
await client.mutation(api.user_rules.remove, {
|
|
ruleId: rule._id,
|
|
sessionToken: session.current?.session.token ?? '',
|
|
});
|
|
|
|
deleting = false;
|
|
}
|
|
</script>
|
|
|
|
<Card.Root>
|
|
<Card.Header>
|
|
<div class="flex items-center justify-between">
|
|
<Card.Title>{rule.name}</Card.Title>
|
|
<Button variant="destructive" size="icon" onclick={deleteRule} disabled={deleting}>
|
|
<TrashIcon class="size-4" />
|
|
</Button>
|
|
</div>
|
|
</Card.Header>
|
|
<Card.Content tag="form" onsubmit={updateRule}>
|
|
<div class="flex flex-col gap-2">
|
|
<Label for="attach">Rule Type</Label>
|
|
<select
|
|
id="attach"
|
|
name="attach"
|
|
value={rule.attach}
|
|
class="border-input bg-background h-9 w-fit rounded-md border px-2 pr-6 text-sm"
|
|
required
|
|
>
|
|
<option value="always">Always</option>
|
|
<option value="manual">Manual</option>
|
|
</select>
|
|
</div>
|
|
<div class="flex flex-col gap-2">
|
|
<Label for="rule">Instructions</Label>
|
|
<Textarea
|
|
id="rule"
|
|
value={rule.rule}
|
|
name="rule"
|
|
placeholder="How should the AI respond?"
|
|
required
|
|
/>
|
|
</div>
|
|
<div class="flex justify-end">
|
|
<Button loading={updating} {...toasts.trigger} type="submit">Save</Button>
|
|
</div>
|
|
</Card.Content>
|
|
</Card.Root>
|
|
|
|
{#each toasts.toasts as toast (toast)}
|
|
<div {...toast.attrs} class={toast.class}>
|
|
{toast.data.content}
|
|
</div>
|
|
{/each}
|