Thomas G. Lopes commited on
Commit
84f775e
·
1 Parent(s): 651ed79

ask before moving checkpoints

Browse files
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
- <button {...select.getOption(p)} class="group block w-full p-1 text-sm dark:text-white" type="button">
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
- </button>
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
- <button
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
- </button>
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
- <button
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
- </button>
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 IconHistory from "~icons/carbon/recently-viewed";
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
- async function saveProject() {
33
- session.saveProject((await prompt("Set project name")) || "Project #" + (session.$.projects.length + 1));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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={saveProject}>
61
  <IconSave />
62
  </button>
63
  {/snippet}
64
- Save to Project
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-400/25 p-0.5 text-yellow-400"
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
- <button {...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
- </button>
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
- checkpoints.migrate(defaultProject.id, project.id);
 
 
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