Thomas G. Lopes
commited on
Commit
·
84f775e
1
Parent(s):
651ed79
ask before moving checkpoints
Browse files- src/app.css +11 -0
- src/lib/components/dialog.svelte +66 -0
- src/lib/components/inference-playground/custom-provider-select.svelte +2 -2
- src/lib/components/inference-playground/model-selector-modal.svelte +4 -4
- src/lib/components/inference-playground/project-select.svelte +61 -7
- src/lib/components/inference-playground/provider-select.svelte +2 -2
- src/lib/components/tooltip.svelte +1 -1
- src/lib/state/session.svelte.ts +5 -3
src/app.css
CHANGED
@@ -93,3 +93,14 @@ body {
|
|
93 |
body.dark {
|
94 |
color-scheme: dark;
|
95 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
93 |
body.dark {
|
94 |
color-scheme: dark;
|
95 |
}
|
96 |
+
|
97 |
+
@layer base {
|
98 |
+
:focus-visible {
|
99 |
+
outline: 3px solid var(--color-blue-400);
|
100 |
+
outline-offset: 2px;
|
101 |
+
|
102 |
+
@variant dark {
|
103 |
+
outline-color: var(--color-blue-500);
|
104 |
+
}
|
105 |
+
}
|
106 |
+
}
|
src/lib/components/dialog.svelte
ADDED
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import { clickOutside } from "$lib/actions/click-outside.js";
|
3 |
+
import type { Snippet } from "svelte";
|
4 |
+
import type { EventHandler } from "svelte/elements";
|
5 |
+
import IconCross from "~icons/carbon/close";
|
6 |
+
|
7 |
+
interface Props {
|
8 |
+
title: string | Snippet;
|
9 |
+
children: Snippet;
|
10 |
+
footer?: Snippet;
|
11 |
+
onClose?: () => void;
|
12 |
+
open: boolean;
|
13 |
+
onSubmit?: EventHandler<SubmitEvent>;
|
14 |
+
}
|
15 |
+
|
16 |
+
let { children, onClose, open, title, footer, onSubmit }: Props = $props();
|
17 |
+
|
18 |
+
let dialog: HTMLDialogElement | undefined = $state();
|
19 |
+
|
20 |
+
$effect(() => {
|
21 |
+
if (open) {
|
22 |
+
dialog?.showModal();
|
23 |
+
} else {
|
24 |
+
dialog?.close();
|
25 |
+
}
|
26 |
+
});
|
27 |
+
</script>
|
28 |
+
|
29 |
+
<dialog bind:this={dialog} onclose={onClose}>
|
30 |
+
{#if open}
|
31 |
+
<form class="fixed inset-0 z-50 flex items-center justify-center overflow-hidden bg-black/85" onsubmit={onSubmit}>
|
32 |
+
<div class="relative w-xl rounded-xl bg-white shadow-sm dark:bg-gray-900" use:clickOutside={() => onClose?.()}>
|
33 |
+
<div class="flex items-center justify-between rounded-t border-b p-4 md:px-5 md:py-4 dark:border-gray-800">
|
34 |
+
{#if typeof title === "string"}
|
35 |
+
<h3 class="flex items-center gap-2.5 text-lg font-semibold text-gray-900 dark:text-white">
|
36 |
+
{title}
|
37 |
+
</h3>
|
38 |
+
{:else}
|
39 |
+
{@render title()}
|
40 |
+
{/if}
|
41 |
+
<button
|
42 |
+
type="button"
|
43 |
+
class="ms-auto inline-flex h-8 w-8 items-center justify-center rounded-lg bg-transparent text-sm text-gray-400 hover:bg-gray-200 hover:text-gray-900 dark:hover:bg-gray-600 dark:hover:text-white"
|
44 |
+
onclick={onClose}
|
45 |
+
>
|
46 |
+
<div class="text-xl">
|
47 |
+
<IconCross />
|
48 |
+
</div>
|
49 |
+
<span class="sr-only">Close modal</span>
|
50 |
+
</button>
|
51 |
+
</div>
|
52 |
+
<!-- Modal body -->
|
53 |
+
<div class="p-4 md:p-5">
|
54 |
+
{@render children()}
|
55 |
+
</div>
|
56 |
+
|
57 |
+
{#if footer}
|
58 |
+
<!-- Modal footer -->
|
59 |
+
<div class="flex rounded-b border-t border-gray-200 p-4 md:p-5 dark:border-gray-800">
|
60 |
+
{@render footer()}
|
61 |
+
</div>
|
62 |
+
{/if}
|
63 |
+
</div>
|
64 |
+
</form>
|
65 |
+
{/if}
|
66 |
+
</dialog>
|
src/lib/components/inference-playground/custom-provider-select.svelte
CHANGED
@@ -88,13 +88,13 @@
|
|
88 |
|
89 |
<div {...select.content} class="rounded-lg border bg-gray-100 outline-hidden dark:border-gray-700 dark:bg-gray-800">
|
90 |
{#each providers as p}
|
91 |
-
<
|
92 |
<div
|
93 |
class="flex items-center gap-2 rounded-md px-2 py-1.5 group-data-[highlighted]:bg-gray-200 dark:group-data-[highlighted]:bg-gray-700"
|
94 |
>
|
95 |
<IconProvider provider={p} />
|
96 |
{formatName(p)}
|
97 |
</div>
|
98 |
-
</
|
99 |
{/each}
|
100 |
</div>
|
|
|
88 |
|
89 |
<div {...select.content} class="rounded-lg border bg-gray-100 outline-hidden dark:border-gray-700 dark:bg-gray-800">
|
90 |
{#each providers as p}
|
91 |
+
<div {...select.getOption(p)} class="group block w-full p-1 text-sm dark:text-white">
|
92 |
<div
|
93 |
class="flex items-center gap-2 rounded-md px-2 py-1.5 group-data-[highlighted]:bg-gray-200 dark:group-data-[highlighted]:bg-gray-700"
|
94 |
>
|
95 |
<IconProvider provider={p} />
|
96 |
{formatName(p)}
|
97 |
</div>
|
98 |
+
</div>
|
99 |
{/each}
|
100 |
</div>
|
src/lib/components/inference-playground/model-selector-modal.svelte
CHANGED
@@ -93,7 +93,7 @@
|
|
93 |
>
|
94 |
{#snippet modelEntry(model: Model | CustomModel, trending?: boolean)}
|
95 |
{@const [nameSpace, modelName] = model.id.split("/")}
|
96 |
-
<
|
97 |
class="flex w-full cursor-pointer items-center px-2 py-1.5 text-sm
|
98 |
data-[highlighted]:bg-gray-100 data-[highlighted]:dark:bg-gray-800"
|
99 |
data-model
|
@@ -165,7 +165,7 @@
|
|
165 |
<span class="text-sm">Edit</span>
|
166 |
</Tooltip>
|
167 |
{/if}
|
168 |
-
</
|
169 |
{/snippet}
|
170 |
{#if trending.length > 0}
|
171 |
<div class="px-2 py-1.5 text-xs font-medium text-gray-500">Trending</div>
|
@@ -179,7 +179,7 @@
|
|
179 |
{@render modelEntry(model, false)}
|
180 |
{/each}
|
181 |
{/if}
|
182 |
-
<
|
183 |
class="flex w-full cursor-pointer items-center gap-2 px-2 py-1.5 text-sm text-gray-500 data-[highlighted]:bg-blue-500/15 data-[highlighted]:text-blue-600 dark:text-gray-400 dark:data-[highlighted]:text-blue-300"
|
184 |
{...combobox.getOption("__custom__", () => {
|
185 |
onClose?.();
|
@@ -192,7 +192,7 @@
|
|
192 |
>
|
193 |
<IconAdd class="rounded bg-blue-500/10 text-blue-600" />
|
194 |
Add a custom endpoint
|
195 |
-
</
|
196 |
{#if other.length > 0}
|
197 |
<div class="px-2 py-1.5 text-xs font-medium text-gray-500">Other models</div>
|
198 |
{#each other as model}
|
|
|
93 |
>
|
94 |
{#snippet modelEntry(model: Model | CustomModel, trending?: boolean)}
|
95 |
{@const [nameSpace, modelName] = model.id.split("/")}
|
96 |
+
<div
|
97 |
class="flex w-full cursor-pointer items-center px-2 py-1.5 text-sm
|
98 |
data-[highlighted]:bg-gray-100 data-[highlighted]:dark:bg-gray-800"
|
99 |
data-model
|
|
|
165 |
<span class="text-sm">Edit</span>
|
166 |
</Tooltip>
|
167 |
{/if}
|
168 |
+
</div>
|
169 |
{/snippet}
|
170 |
{#if trending.length > 0}
|
171 |
<div class="px-2 py-1.5 text-xs font-medium text-gray-500">Trending</div>
|
|
|
179 |
{@render modelEntry(model, false)}
|
180 |
{/each}
|
181 |
{/if}
|
182 |
+
<div
|
183 |
class="flex w-full cursor-pointer items-center gap-2 px-2 py-1.5 text-sm text-gray-500 data-[highlighted]:bg-blue-500/15 data-[highlighted]:text-blue-600 dark:text-gray-400 dark:data-[highlighted]:text-blue-300"
|
184 |
{...combobox.getOption("__custom__", () => {
|
185 |
onClose?.();
|
|
|
192 |
>
|
193 |
<IconAdd class="rounded bg-blue-500/10 text-blue-600" />
|
194 |
Add a custom endpoint
|
195 |
+
</div>
|
196 |
{#if other.length > 0}
|
197 |
<div class="px-2 py-1.5 text-xs font-medium text-gray-500">Other models</div>
|
198 |
{#each other as model}
|
src/lib/components/inference-playground/project-select.svelte
CHANGED
@@ -1,17 +1,20 @@
|
|
1 |
<script lang="ts">
|
|
|
|
|
2 |
import { session } from "$lib/state/session.svelte.js";
|
3 |
import { cn } from "$lib/utils/cn.js";
|
4 |
import { Select } from "melt/builders";
|
5 |
-
import
|
6 |
import IconCaret from "~icons/carbon/chevron-down";
|
7 |
import IconCross from "~icons/carbon/close";
|
8 |
import IconEdit from "~icons/carbon/edit";
|
|
|
9 |
import IconSave from "~icons/carbon/save";
|
10 |
import IconDelete from "~icons/carbon/trash-can";
|
|
|
11 |
import { prompt } from "../prompts.svelte";
|
12 |
import Tooltip from "../tooltip.svelte";
|
13 |
import CheckpointsMenu from "./checkpoints-menu.svelte";
|
14 |
-
import { checkpoints } from "$lib/state/checkpoints.svelte";
|
15 |
|
16 |
interface Props {
|
17 |
class?: string;
|
@@ -29,9 +32,34 @@
|
|
29 |
sameWidth: true,
|
30 |
});
|
31 |
|
32 |
-
|
33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
34 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
35 |
</script>
|
36 |
|
37 |
<div class={cn("flex w-full items-stretch gap-2 ", classNames)}>
|
@@ -57,11 +85,11 @@
|
|
57 |
{#if isDefault}
|
58 |
<Tooltip>
|
59 |
{#snippet trigger(tooltip)}
|
60 |
-
<button class="btn size-[32px] p-0" {...tooltip.trigger} onclick={
|
61 |
<IconSave />
|
62 |
</button>
|
63 |
{/snippet}
|
64 |
-
Save
|
65 |
</Tooltip>
|
66 |
{:else}
|
67 |
<Tooltip>
|
@@ -92,7 +120,7 @@
|
|
92 |
{name}
|
93 |
{#if hasCheckpoints}
|
94 |
<div
|
95 |
-
class="text-3xs grid aspect-square place-items-center rounded bg-yellow-
|
96 |
aria-label="Project has checkpoints"
|
97 |
>
|
98 |
<IconHistory />
|
@@ -125,3 +153,29 @@
|
|
125 |
</div>
|
126 |
{/each}
|
127 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
<script lang="ts">
|
2 |
+
import { autofocus } from "$lib/actions/autofocus.js";
|
3 |
+
import { checkpoints } from "$lib/state/checkpoints.svelte";
|
4 |
import { session } from "$lib/state/session.svelte.js";
|
5 |
import { cn } from "$lib/utils/cn.js";
|
6 |
import { Select } from "melt/builders";
|
7 |
+
import type { EventHandler } from "svelte/elements";
|
8 |
import IconCaret from "~icons/carbon/chevron-down";
|
9 |
import IconCross from "~icons/carbon/close";
|
10 |
import IconEdit from "~icons/carbon/edit";
|
11 |
+
import IconHistory from "~icons/carbon/recently-viewed";
|
12 |
import IconSave from "~icons/carbon/save";
|
13 |
import IconDelete from "~icons/carbon/trash-can";
|
14 |
+
import Dialog from "../dialog.svelte";
|
15 |
import { prompt } from "../prompts.svelte";
|
16 |
import Tooltip from "../tooltip.svelte";
|
17 |
import CheckpointsMenu from "./checkpoints-menu.svelte";
|
|
|
18 |
|
19 |
interface Props {
|
20 |
class?: string;
|
|
|
32 |
sameWidth: true,
|
33 |
});
|
34 |
|
35 |
+
type SaveDialogState = {
|
36 |
+
open: boolean;
|
37 |
+
moveCheckpoints: boolean;
|
38 |
+
name: string;
|
39 |
+
};
|
40 |
+
|
41 |
+
const defaultSdState: SaveDialogState = {
|
42 |
+
open: false,
|
43 |
+
moveCheckpoints: true,
|
44 |
+
name: "",
|
45 |
+
};
|
46 |
+
|
47 |
+
let sdState = $state(defaultSdState);
|
48 |
+
const projectPlaceholder = $derived(`Project #${session.$.projects.length}`);
|
49 |
+
|
50 |
+
function openSaveDialog() {
|
51 |
+
sdState = { ...defaultSdState, open: true };
|
52 |
}
|
53 |
+
|
54 |
+
const saveDialog = async function (e) {
|
55 |
+
e.preventDefault();
|
56 |
+
session.saveProject({
|
57 |
+
...sdState,
|
58 |
+
name: sdState.name || projectPlaceholder,
|
59 |
+
});
|
60 |
+
|
61 |
+
sdState = { ...defaultSdState };
|
62 |
+
} satisfies EventHandler<SubmitEvent>;
|
63 |
</script>
|
64 |
|
65 |
<div class={cn("flex w-full items-stretch gap-2 ", classNames)}>
|
|
|
85 |
{#if isDefault}
|
86 |
<Tooltip>
|
87 |
{#snippet trigger(tooltip)}
|
88 |
+
<button class="btn size-[32px] p-0" {...tooltip.trigger} onclick={openSaveDialog}>
|
89 |
<IconSave />
|
90 |
</button>
|
91 |
{/snippet}
|
92 |
+
Save as Project
|
93 |
</Tooltip>
|
94 |
{:else}
|
95 |
<Tooltip>
|
|
|
120 |
{name}
|
121 |
{#if hasCheckpoints}
|
122 |
<div
|
123 |
+
class="text-3xs grid aspect-square place-items-center rounded bg-yellow-300 p-0.5 text-yellow-700 dark:bg-yellow-400/25 dark:text-yellow-400"
|
124 |
aria-label="Project has checkpoints"
|
125 |
>
|
126 |
<IconHistory />
|
|
|
153 |
</div>
|
154 |
{/each}
|
155 |
</div>
|
156 |
+
|
157 |
+
<Dialog title="Set project name" open={sdState.open} onClose={() => (sdState.open = false)} onSubmit={saveDialog}>
|
158 |
+
<label class="flex flex-col gap-2 font-medium text-gray-900 dark:text-white">
|
159 |
+
<p>Project name</p>
|
160 |
+
<input
|
161 |
+
bind:value={sdState.name}
|
162 |
+
placeholder={projectPlaceholder}
|
163 |
+
use:autofocus
|
164 |
+
type="text"
|
165 |
+
class="block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400"
|
166 |
+
/>
|
167 |
+
</label>
|
168 |
+
<label class="mt-4 flex gap-2 font-medium text-gray-900 dark:text-white">
|
169 |
+
<input bind:checked={sdState.moveCheckpoints} type="checkbox" />
|
170 |
+
<p>Move checkpoints over</p>
|
171 |
+
</label>
|
172 |
+
|
173 |
+
{#snippet footer()}
|
174 |
+
<button
|
175 |
+
type="submit"
|
176 |
+
class="ml-auto rounded-lg bg-black px-5 py-2.5 text-sm font-medium text-white hover:bg-gray-900 focus:ring-4 focus:ring-gray-300 focus:outline-hidden dark:border-gray-700 dark:bg-gray-800 dark:hover:bg-gray-700 dark:focus:ring-gray-700"
|
177 |
+
>
|
178 |
+
Submit
|
179 |
+
</button>
|
180 |
+
{/snippet}
|
181 |
+
</Dialog>
|
src/lib/components/inference-playground/provider-select.svelte
CHANGED
@@ -96,14 +96,14 @@
|
|
96 |
|
97 |
<div {...select.content} class="rounded-lg border bg-gray-100 dark:border-gray-700 dark:bg-gray-800">
|
98 |
{#each conversation.model.inferenceProviderMapping as { provider, providerId } (provider + providerId)}
|
99 |
-
<
|
100 |
<div
|
101 |
class="flex items-center gap-2 rounded-md px-2 py-1.5 group-data-[highlighted]:bg-gray-200 dark:group-data-[highlighted]:bg-gray-700"
|
102 |
>
|
103 |
<IconProvider {provider} />
|
104 |
{formatName(provider)}
|
105 |
</div>
|
106 |
-
</
|
107 |
{/each}
|
108 |
</div>
|
109 |
</div>
|
|
|
96 |
|
97 |
<div {...select.content} class="rounded-lg border bg-gray-100 dark:border-gray-700 dark:bg-gray-800">
|
98 |
{#each conversation.model.inferenceProviderMapping as { provider, providerId } (provider + providerId)}
|
99 |
+
<div {...select.getOption(provider)} class="group block w-full p-1 text-sm dark:text-white">
|
100 |
<div
|
101 |
class="flex items-center gap-2 rounded-md px-2 py-1.5 group-data-[highlighted]:bg-gray-200 dark:group-data-[highlighted]:bg-gray-700"
|
102 |
>
|
103 |
<IconProvider {provider} />
|
104 |
{formatName(provider)}
|
105 |
</div>
|
106 |
+
</div>
|
107 |
{/each}
|
108 |
</div>
|
109 |
</div>
|
src/lib/components/tooltip.svelte
CHANGED
@@ -37,7 +37,7 @@
|
|
37 |
{@render trigger(tooltip)}
|
38 |
|
39 |
<div {...tooltip.content} class="rounded-xl bg-white p-0 shadow-xl dark:bg-gray-700">
|
40 |
-
<div {...tooltip.arrow} class="rounded-tl"></div>
|
41 |
<p class="px-4 py-1 text-gray-700 dark:text-white">{@render children()}</p>
|
42 |
</div>
|
43 |
|
|
|
37 |
{@render trigger(tooltip)}
|
38 |
|
39 |
<div {...tooltip.content} class="rounded-xl bg-white p-0 shadow-xl dark:bg-gray-700">
|
40 |
+
<div {...tooltip.arrow} class="size-2 rounded-tl"></div>
|
41 |
<p class="px-4 py-1 text-gray-700 dark:text-white">{@render children()}</p>
|
42 |
</div>
|
43 |
|
src/lib/state/session.svelte.ts
CHANGED
@@ -139,17 +139,19 @@ class SessionState {
|
|
139 |
if (typia.is<Session>(s)) this.$ = s;
|
140 |
}
|
141 |
|
142 |
-
saveProject = (name: string) => {
|
143 |
const defaultProject = this.$.projects.find(p => p.id === "default");
|
144 |
if (!defaultProject) return;
|
145 |
|
146 |
const project: Project = {
|
147 |
...defaultProject,
|
148 |
-
name,
|
149 |
id: crypto.randomUUID(),
|
150 |
};
|
151 |
|
152 |
-
|
|
|
|
|
153 |
|
154 |
defaultProject.conversations = [getDefaults().defaultConversation];
|
155 |
|
|
|
139 |
if (typia.is<Session>(s)) this.$ = s;
|
140 |
}
|
141 |
|
142 |
+
saveProject = (args: { name: string; moveCheckpoints?: boolean }) => {
|
143 |
const defaultProject = this.$.projects.find(p => p.id === "default");
|
144 |
if (!defaultProject) return;
|
145 |
|
146 |
const project: Project = {
|
147 |
...defaultProject,
|
148 |
+
name: args.name,
|
149 |
id: crypto.randomUUID(),
|
150 |
};
|
151 |
|
152 |
+
if (args.moveCheckpoints) {
|
153 |
+
checkpoints.migrate(defaultProject.id, project.id);
|
154 |
+
}
|
155 |
|
156 |
defaultProject.conversations = [getDefaults().defaultConversation];
|
157 |
|