Thomas G. Lopes commited on
Commit
d7cd63b
·
unverified ·
1 Parent(s): 143859e

Adjustments (#86)

Browse files
src/lib/components/dialog.svelte CHANGED
@@ -11,9 +11,10 @@
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
 
@@ -30,7 +31,7 @@
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
33
- class="relative w-xl rounded-xl bg-white shadow-sm dark:bg-gray-900"
34
  {@attach clickOutside(() => onClose?.())}
35
  >
36
  <div class="flex items-center justify-between rounded-t border-b p-4 md:px-5 md:py-4 dark:border-gray-800">
 
11
  onClose?: () => void;
12
  open: boolean;
13
  onSubmit?: EventHandler<SubmitEvent>;
14
+ class?: string;
15
  }
16
 
17
+ let { children, onClose, open, title, footer, onSubmit, class: classes }: Props = $props();
18
 
19
  let dialog: HTMLDialogElement | undefined = $state();
20
 
 
31
  {#if open}
32
  <form class="fixed inset-0 z-50 flex items-center justify-center overflow-hidden bg-black/85" onsubmit={onSubmit}>
33
  <div
34
+ class={["relative w-xl rounded-xl bg-white shadow-sm dark:bg-gray-900", classes]}
35
  {@attach clickOutside(() => onClose?.())}
36
  >
37
  <div class="flex items-center justify-between rounded-t border-b p-4 md:px-5 md:py-4 dark:border-gray-800">
src/lib/components/inference-playground/checkpoints-menu.svelte CHANGED
@@ -147,7 +147,12 @@
147
  >
148
  <p class="text-2xs pl-1.5 font-mono font-medium text-gray-500 uppercase">
149
  temp: {conversation.config.temperature}
150
- | max tokens: {conversation.config.max_tokens}
 
 
 
 
 
151
  </p>
152
  {#each iterate(sliced) as [msg, isLast]}
153
  <div class="flex flex-col gap-1 p-2">
 
147
  >
148
  <p class="text-2xs pl-1.5 font-mono font-medium text-gray-500 uppercase">
149
  temp: {conversation.config.temperature}
150
+ {#if conversation.config.max_tokens}
151
+ | max tokens: {conversation.config.max_tokens}
152
+ {/if}
153
+ {#if conversation.structuredOutput?.enabled}
154
+ | structured output
155
+ {/if}
156
  </p>
157
  {#each iterate(sliced) as [msg, isLast]}
158
  <div class="flex flex-col gap-1 p-2">
src/lib/components/inference-playground/custom-provider-select.svelte CHANGED
@@ -30,7 +30,7 @@
30
  },
31
  });
32
 
33
- const nameMap: Record<InferenceProvider, string> = {
34
  "sambanova": "SambaNova",
35
  "fal-ai": "fal",
36
  "cerebras": "Cerebras",
 
30
  },
31
  });
32
 
33
+ const nameMap: Partial<Record<InferenceProvider, string>> = {
34
  "sambanova": "SambaNova",
35
  "fal-ai": "fal",
36
  "cerebras": "Cerebras",
src/lib/components/inference-playground/generation-config.svelte CHANGED
@@ -6,6 +6,7 @@
6
  import { GENERATION_CONFIG_KEYS, GENERATION_CONFIG_SETTINGS } from "./generation-config-settings.js";
7
  import StructuredOutputModal from "./structured-output-modal.svelte";
8
  import { maxAllowedTokens } from "./utils.svelte.js";
 
9
 
10
  interface Props {
11
  conversation: ConversationClass;
@@ -99,24 +100,28 @@
99
  ></div>
100
  </label>
101
 
102
- <label class="mt-2 flex cursor-pointer items-center justify-between" for="structured-output">
103
- <span class="text-sm font-medium text-gray-900 dark:text-gray-300">Structured Output</span>
104
- <div class="flex items-center gap-2">
105
- <input
106
- type="checkbox"
107
- bind:checked={
108
- () => conversation.data.structuredOutput?.enabled,
109
- v => conversation.update({ structuredOutput: { ...conversation.data.structuredOutput, enabled: v ?? false } })
110
- }
111
- class="peer sr-only"
112
- id="structured-output"
113
- />
114
- <button class="btn-mini" type="button" onclick={() => (editingStructuredOutput = true)}> edit </button>
115
- <div
116
- class="peer relative h-5 w-9 rounded-full bg-gray-200 peer-checked:bg-black peer-focus:outline-hidden after:absolute after:start-[2px] after:top-[2px] after:h-4 after:w-4 after:rounded-full after:border after:border-gray-300 after:bg-white after:transition-all after:content-[''] peer-checked:after:translate-x-full peer-checked:after:border-white dark:border-gray-600 dark:bg-gray-700 dark:peer-checked:bg-blue-600"
117
- ></div>
118
- </div>
119
- </label>
 
 
 
 
120
  </div>
121
 
122
  <StructuredOutputModal {conversation} bind:open={editingStructuredOutput} />
 
6
  import { GENERATION_CONFIG_KEYS, GENERATION_CONFIG_SETTINGS } from "./generation-config-settings.js";
7
  import StructuredOutputModal from "./structured-output-modal.svelte";
8
  import { maxAllowedTokens } from "./utils.svelte.js";
9
+ import { structuredForbiddenProviders } from "$lib/state/models.svelte.js";
10
 
11
  interface Props {
12
  conversation: ConversationClass;
 
100
  ></div>
101
  </label>
102
 
103
+ <!-- eslint-disable-next-line @typescript-eslint/no-explicit-any -->
104
+ {#if !structuredForbiddenProviders.includes(conversation.data.provider as any)}
105
+ <label class="mt-2 flex cursor-pointer items-center justify-between" for="structured-output">
106
+ <span class="text-sm font-medium text-gray-900 dark:text-gray-300">Structured Output</span>
107
+ <div class="flex items-center gap-2">
108
+ <input
109
+ type="checkbox"
110
+ bind:checked={
111
+ () => conversation.data.structuredOutput?.enabled,
112
+ v =>
113
+ conversation.update({ structuredOutput: { ...conversation.data.structuredOutput, enabled: v ?? false } })
114
+ }
115
+ class="peer sr-only"
116
+ id="structured-output"
117
+ />
118
+ <button class="btn-mini" type="button" onclick={() => (editingStructuredOutput = true)}> edit </button>
119
+ <div
120
+ class="peer relative h-5 w-9 rounded-full bg-gray-200 peer-checked:bg-black peer-focus:outline-hidden after:absolute after:start-[2px] after:top-[2px] after:h-4 after:w-4 after:rounded-full after:border after:border-gray-300 after:bg-white after:transition-all after:content-[''] peer-checked:after:translate-x-full peer-checked:after:border-white dark:border-gray-600 dark:bg-gray-700 dark:peer-checked:bg-blue-600"
121
+ ></div>
122
+ </div>
123
+ </label>
124
+ {/if}
125
  </div>
126
 
127
  <StructuredOutputModal {conversation} bind:open={editingStructuredOutput} />
src/lib/components/inference-playground/model-selector.svelte CHANGED
@@ -6,7 +6,6 @@
6
  import Avatar from "../avatar.svelte";
7
  import ModelSelectorModal from "./model-selector-modal.svelte";
8
  import ProviderSelect from "./provider-select.svelte";
9
- import { defaultSystemMessage } from "./utils.svelte.js";
10
 
11
  interface Props {
12
  conversation: ConversationClass;
@@ -24,7 +23,6 @@
24
  }
25
  conversation.update({
26
  modelId: model.id,
27
- systemMessage: { role: "system", content: defaultSystemMessage?.[modelId] ?? "" },
28
  provider: undefined,
29
  });
30
  }
 
6
  import Avatar from "../avatar.svelte";
7
  import ModelSelectorModal from "./model-selector-modal.svelte";
8
  import ProviderSelect from "./provider-select.svelte";
 
9
 
10
  interface Props {
11
  conversation: ConversationClass;
 
23
  }
24
  conversation.update({
25
  modelId: model.id,
 
26
  provider: undefined,
27
  });
28
  }
src/lib/components/inference-playground/playground.svelte CHANGED
@@ -105,11 +105,10 @@
105
  placeholder={systemPromptSupported
106
  ? "Enter a custom prompt"
107
  : "System prompt is not supported with the chosen model."}
108
- value={systemPromptSupported ? conversations.active[0]?.data.systemMessage.content : ""}
109
- oninput={e => {
110
- for (const c of conversations.active) {
111
- c.update({ ...c.data, systemMessage: { ...c.data.systemMessage, content: e.currentTarget.value } });
112
- }
113
  }}
114
  class="absolute inset-x-0 bottom-0 h-full resize-none bg-transparent px-3 pt-10 text-sm outline-hidden"
115
  ></textarea>
 
105
  placeholder={systemPromptSupported
106
  ? "Enter a custom prompt"
107
  : "System prompt is not supported with the chosen model."}
108
+ value={systemPromptSupported ? (projects.current?.systemMessage ?? "") : ""}
109
+ onchange={e => {
110
+ if (!projects.current) return;
111
+ projects.update({ ...projects.current, systemMessage: e.currentTarget.value });
 
112
  }}
113
  class="absolute inset-x-0 bottom-0 h-full resize-none bg-transparent px-3 pt-10 text-sm outline-hidden"
114
  ></textarea>
src/lib/components/inference-playground/schema-property.svelte ADDED
@@ -0,0 +1,309 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts" module>
2
+ const propertyTypes = ["string", "integer", "number", "boolean", "object", "enum", "array"] as const;
3
+ export type PropertyType = (typeof propertyTypes)[number];
4
+
5
+ export type PropertyDefinition = {
6
+ type: PropertyType;
7
+ description?: string;
8
+ enum?: string[];
9
+ properties?: { [key: string]: PropertyDefinition };
10
+ items?: PropertyDefinition;
11
+ };
12
+
13
+ // Example:
14
+ // {
15
+ // "type": "object",
16
+ // "properties": {
17
+ // "static": {
18
+ // "type": "string"
19
+ // },
20
+ // "array": {
21
+ // "type": "array",
22
+ // "items": {
23
+ // "type": "string"
24
+ // }
25
+ // },
26
+ // "enum": {
27
+ // "type": "string",
28
+ // "enum": [
29
+ // "V1",
30
+ // "V2",
31
+ // "V3"
32
+ // ]
33
+ // },
34
+ // "object": {
35
+ // "type": "object",
36
+ // "properties": {
37
+ // "key1": {
38
+ // "type": "string"
39
+ // },
40
+ // "key2": {
41
+ // "type": "string"
42
+ // }
43
+ // }
44
+ // }
45
+ // }
46
+ // }
47
+ </script>
48
+
49
+ <script lang="ts">
50
+ import { onchange } from "$lib/utils/template.js";
51
+ import IconX from "~icons/carbon/close";
52
+ import IconAdd from "~icons/carbon/add-large";
53
+ import SchemaProperty from "./schema-property.svelte";
54
+ import Tooltip from "../tooltip.svelte";
55
+
56
+ type Props = {
57
+ name: string;
58
+ definition: PropertyDefinition;
59
+ required?: boolean;
60
+ nesting?: number;
61
+ onDelete: () => void;
62
+ };
63
+
64
+ let { name = $bindable(), definition = $bindable(), onDelete, required = $bindable(), nesting = 0 }: Props = $props();
65
+
66
+ // If isArray, this will be the inner type of the array. Otherwise it will be the definition itself.
67
+ const innerDefinition = {
68
+ get $() {
69
+ if (definition.type === "array") {
70
+ return definition.items ?? { type: "string" };
71
+ }
72
+ return definition;
73
+ },
74
+ set $(v) {
75
+ if (isArray.current) {
76
+ definition = { ...definition, items: v };
77
+ return;
78
+ }
79
+
80
+ definition = v;
81
+ },
82
+ };
83
+
84
+ const type = {
85
+ get $() {
86
+ return "enum" in innerDefinition.$ ? "enum" : innerDefinition.$.type;
87
+ },
88
+ set $(v) {
89
+ delete definition.enum;
90
+ delete definition.properties;
91
+
92
+ const inner = { type: v === "enum" ? "string" : v } as PropertyDefinition;
93
+ if (v === "enum") inner.enum = [];
94
+
95
+ if (definition.type === "array") {
96
+ definition = { ...definition, items: inner };
97
+ }
98
+
99
+ definition = { ...definition, ...inner };
100
+ },
101
+ };
102
+
103
+ const isArray = {
104
+ get current() {
105
+ return definition.type === "array";
106
+ },
107
+ set current(v) {
108
+ delete definition.enum;
109
+ delete definition.properties;
110
+
111
+ if (v) {
112
+ definition = { ...definition, type: "array", items: { type: definition.type } };
113
+ } else {
114
+ const prevType = definition.items?.type ?? "string";
115
+ delete definition.items;
116
+ definition = { ...definition, type: prevType };
117
+ }
118
+ },
119
+ };
120
+
121
+ const nestingClasses = [
122
+ "border-gray-300 dark:border-gray-700",
123
+ "border-gray-300 dark:border-gray-600",
124
+ "border-gray-300 dark:border-gray-500",
125
+ "border-gray-300 dark:border-gray-600",
126
+ ];
127
+ </script>
128
+
129
+ <div
130
+ class={[
131
+ "relative space-y-2 border-l-2 bg-white py-2 pl-4 dark:bg-gray-900",
132
+ ...nestingClasses.map((c, i) => {
133
+ return nesting % nestingClasses.length === i && c;
134
+ }),
135
+ ]}
136
+ >
137
+ <div class="flex gap-2">
138
+ <div class="grow">
139
+ <label for="{name}-name" class="block text-xs font-medium text-gray-500 dark:text-gray-400"> Name </label>
140
+ <input
141
+ type="text"
142
+ id="{name}-name"
143
+ class="mt-1 w-full rounded-md border border-gray-300 bg-white px-2 py-1 text-sm text-gray-900 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:border-gray-700 dark:bg-gray-800 dark:text-white"
144
+ value={name}
145
+ {...onchange(v => (name = v))}
146
+ />
147
+ </div>
148
+
149
+ <div class="grow">
150
+ <label for="{name}-type" class="block text-xs font-medium text-gray-500 dark:text-gray-400"> Type </label>
151
+ <select
152
+ id="{name}-type"
153
+ class="mt-1 w-full rounded-md border border-gray-300 bg-white px-2 py-1.25 text-sm text-gray-900 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:border-gray-700 dark:bg-gray-800 dark:text-white"
154
+ bind:value={() => type.$, v => (type.$ = v)}
155
+ >
156
+ {#each propertyTypes.filter(t => t !== "array") as type}
157
+ <option value={type}>{type}</option>
158
+ {/each}
159
+ </select>
160
+ </div>
161
+ {#if type.$ === "object"}
162
+ <Tooltip>
163
+ {#snippet trigger(tooltip)}
164
+ <button
165
+ type="button"
166
+ class="btn-xs self-end rounded-md"
167
+ onclick={() => {
168
+ const prevProperties = innerDefinition.$.properties || {};
169
+ innerDefinition.$ = { ...innerDefinition.$, properties: { ...prevProperties, "": { type: "string" } } };
170
+ }}
171
+ aria-label="Add nested"
172
+ {...tooltip.trigger}
173
+ >
174
+ <IconAdd />
175
+ </button>
176
+ {/snippet}
177
+ Add nested property
178
+ </Tooltip>
179
+ {/if}
180
+ {#if type.$ === "enum"}
181
+ <Tooltip>
182
+ {#snippet trigger(tooltip)}
183
+ <button
184
+ type="button"
185
+ class="btn-xs self-end rounded-md"
186
+ onclick={() => {
187
+ const prevValues = innerDefinition.$.enum || [];
188
+ innerDefinition.$ = { ...innerDefinition.$, enum: [...prevValues, ""] };
189
+ }}
190
+ aria-label="Add enum value"
191
+ {...tooltip.trigger}
192
+ >
193
+ <IconAdd />
194
+ </button>
195
+ {/snippet}
196
+ Add enum value
197
+ </Tooltip>
198
+ {/if}
199
+ <button
200
+ type="button"
201
+ class="btn-xs self-end rounded-md text-red-500 hover:text-red-600 dark:text-red-400 dark:hover:text-red-500"
202
+ onclick={onDelete}
203
+ aria-label="delete"
204
+ >
205
+ <IconX />
206
+ </button>
207
+ </div>
208
+
209
+ {#if !nesting}
210
+ <div class="flex items-start">
211
+ <div class="flex h-5 items-center">
212
+ <input
213
+ id="required-{name}"
214
+ name="required-{name}"
215
+ type="checkbox"
216
+ class="h-4 w-4 rounded border border-gray-300 bg-white text-blue-600 focus:ring-blue-500 dark:border-gray-700 dark:bg-gray-800"
217
+ bind:checked={required}
218
+ />
219
+ </div>
220
+ <div class="ml-3 text-sm">
221
+ <label for="required-{name}" class="font-medium text-gray-700 dark:text-gray-300"> Required </label>
222
+ </div>
223
+ </div>
224
+ {/if}
225
+
226
+ <div class="flex items-start">
227
+ <div class="flex h-5 items-center">
228
+ <input
229
+ id="is-array-{name}"
230
+ name="is-array-{name}"
231
+ type="checkbox"
232
+ class="h-4 w-4 rounded border border-gray-300 bg-white text-blue-600 focus:ring-blue-500 dark:border-gray-700 dark:bg-gray-800"
233
+ bind:checked={isArray.current}
234
+ />
235
+ </div>
236
+ <div class="ml-3 text-sm">
237
+ <label for="is-array-{name}" class="font-medium text-gray-700 dark:text-gray-300"> Array </label>
238
+ </div>
239
+ </div>
240
+
241
+ {#if type.$ === "object"}
242
+ {#each Object.entries(innerDefinition.$.properties ?? {}) as [propertyName, propertyDefinition], index (index)}
243
+ <SchemaProperty
244
+ bind:name={
245
+ () => propertyName,
246
+ value => {
247
+ const nd = { ...innerDefinition.$ };
248
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
249
+ nd.properties![value] = innerDefinition.$.properties![propertyName] as any;
250
+ delete nd.properties![propertyName];
251
+ innerDefinition.$ = nd;
252
+ }
253
+ }
254
+ bind:definition={
255
+ () => propertyDefinition,
256
+ v => {
257
+ innerDefinition.$.properties![propertyName] = v;
258
+ innerDefinition.$ = innerDefinition.$;
259
+ }
260
+ }
261
+ onDelete={() => {
262
+ delete innerDefinition.$.properties![propertyName];
263
+ innerDefinition.$ = innerDefinition.$;
264
+ }}
265
+ nesting={nesting + 1}
266
+ />
267
+ {/each}
268
+ {/if}
269
+
270
+ {#if "enum" in innerDefinition.$}
271
+ <p class="text-xs font-medium text-gray-500 dark:text-gray-400">Values</p>
272
+ {#each innerDefinition.$.enum ?? [] as val, index (index)}
273
+ <div
274
+ class={[
275
+ "flex border-l-2 pl-2",
276
+ ...nestingClasses.map((c, i) => {
277
+ return (nesting + 1) % nestingClasses.length === i && c;
278
+ }),
279
+ ]}
280
+ >
281
+ <input
282
+ id="{name}-enum-{index}"
283
+ class="block w-full rounded-md border border-gray-300 bg-white px-2 py-1
284
+ text-sm text-gray-900 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:border-gray-700 dark:bg-gray-800 dark:text-white"
285
+ type="text"
286
+ value={val}
287
+ {...onchange(v => {
288
+ innerDefinition.$.enum = innerDefinition.$.enum ?? [];
289
+ innerDefinition.$.enum[index] = v;
290
+ innerDefinition.$ = innerDefinition.$;
291
+ })}
292
+ />
293
+ <button
294
+ type="button"
295
+ class="btn-xs ml-2 rounded-md text-red-500 hover:text-red-600 dark:text-red-400 dark:hover:text-red-500"
296
+ onclick={() => {
297
+ innerDefinition.$.enum = innerDefinition.$.enum ?? [];
298
+ innerDefinition.$.enum.splice(index, 1);
299
+ innerDefinition.$ = innerDefinition.$;
300
+ }}
301
+ >
302
+ <IconX />
303
+ </button>
304
+ </div>
305
+ {:else}
306
+ <p class="mt-2 text-xs italic text-gray-400">No enum values defined.</p>
307
+ {/each}
308
+ {/if}
309
+ </div>
src/lib/components/inference-playground/structured-output-modal.svelte CHANGED
@@ -1,16 +1,16 @@
1
  <script lang="ts">
 
2
  import { Synced } from "$lib/spells/synced.svelte";
3
  import { TextareaAutosize } from "$lib/spells/textarea-autosize.svelte";
4
  import type { ConversationClass } from "$lib/state/conversations.svelte.js";
5
  import { safeParse } from "$lib/utils/json.js";
6
- import { keys } from "$lib/utils/object.svelte";
7
  import { onchange, oninput } from "$lib/utils/template.js";
8
  import { RadioGroup } from "melt/builders";
9
  import { codeToHtml } from "shiki";
10
  import typia from "typia";
11
- import IconX from "~icons/carbon/close";
12
  import Dialog from "../dialog.svelte";
13
- import { isDark } from "$lib/spells/is-dark.svelte";
14
 
15
  interface Props {
16
  conversation: ConversationClass;
@@ -27,11 +27,9 @@
27
  });
28
 
29
  type Schema = {
30
- name?: string;
31
- description?: string;
32
  schema?: {
33
  type?: string;
34
- properties?: { [key: string]: { type: string; description?: string } };
35
  required?: string[];
36
  additionalProperties?: boolean;
37
  };
@@ -76,8 +74,6 @@
76
  keys(v.schema?.properties ?? {}).includes(name)
77
  );
78
  const validated: Schema = {
79
- name: v.name,
80
- description: v.description,
81
  schema: {
82
  ...v.schema,
83
  required,
@@ -110,7 +106,7 @@
110
  });
111
  </script>
112
 
113
- <Dialog title="Edit Structured Output" {open} onClose={() => (open = false)}>
114
  <div class="flex justify-end">
115
  <div
116
  class="flex items-center gap-0.5 rounded-md border border-gray-300 bg-white p-0.5 text-sm dark:border-gray-600 dark:bg-gray-800"
@@ -133,150 +129,55 @@
133
 
134
  {#if radioGroup.value === "form"}
135
  <div class="fade-y -mx-2 mt-2 -mb-4 max-h-200 space-y-4 overflow-auto px-2 py-4 text-left">
136
- <!-- Top-level properties -->
137
- <div>
138
- <label for="schema-name" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Name</label>
139
- <input
140
- type="text"
141
- id="schema-name"
142
- class="mt-1 block w-full rounded-md border border-gray-300 bg-white px-2 py-1 text-gray-900 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:border-gray-700 dark:bg-gray-800 dark:text-white"
143
- value={schemaObj.current.name}
144
- {...onchange(value => updateSchema({ name: value }))}
145
- />
146
- </div>
147
-
148
- <div>
149
- <label for="schema-description" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
150
- Description
151
- </label>
152
- <textarea
153
- id="schema-description"
154
- rows="3"
155
- class="mt-1 block w-full rounded-md border border-gray-300 bg-white px-2 py-1 text-gray-900 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:border-gray-700 dark:bg-gray-800 dark:text-white"
156
- value={schemaObj.current.description}
157
- {...onchange(value => updateSchema({ description: value }))}
158
- ></textarea>
159
- </div>
160
-
161
  <!-- Properties Section -->
162
  <div class="border-t border-gray-200 pt-4 dark:border-gray-700">
163
  <h3 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">Properties</h3>
164
  {#if schemaObj.current.schema?.properties}
165
  <div class="mt-3 space-y-3">
166
- {#each Object.entries(schemaObj.current.schema.properties) as [propertyName, propertyDefinition], index (index)}
167
- <div
168
- class="relative space-y-2 rounded-md border border-gray-300 bg-white p-3 dark:border-gray-700 dark:bg-gray-900"
169
- >
170
- <div>
171
- <label for="{propertyName}-name" class="block text-xs font-medium text-gray-500 dark:text-gray-400">
172
- Name
173
- </label>
174
- <input
175
- type="text"
176
- id="{propertyName}-name"
177
- class="mt-1 block w-full rounded-md border border-gray-300 bg-white px-2 py-1 text-sm text-gray-900 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:border-gray-700 dark:bg-gray-800 dark:text-white"
178
- value={propertyName}
179
- {...onchange(value => {
180
- const updatedProperties = { ...schemaObj.current.schema?.properties };
181
- if (!updatedProperties || !updatedProperties[propertyName]) return;
182
- updatedProperties[value] = updatedProperties[propertyName];
183
- delete updatedProperties[propertyName];
184
- updateSchemaNested({ properties: updatedProperties });
185
- })}
186
- />
187
- </div>
188
-
189
- <button
190
- type="button"
191
- class="absolute top-2 right-2 text-red-500 hover:text-red-600 dark:text-red-400 dark:hover:text-red-500"
192
- onclick={() => {
193
- const updatedProperties = { ...schemaObj.current.schema?.properties };
194
- if (!updatedProperties || !updatedProperties[propertyName]) return;
195
- delete updatedProperties[propertyName];
196
  updateSchemaNested({ properties: updatedProperties });
197
- }}
198
- aria-label="delete"
199
- >
200
- <IconX />
201
- </button>
202
-
203
- <div>
204
- <label for="{propertyName}-type" class="block text-xs font-medium text-gray-500 dark:text-gray-400">
205
- Type
206
- </label>
207
- <select
208
- id="{propertyName}-type"
209
- class="mt-1 block w-full rounded-md border border-gray-300 bg-white px-2 py-1 text-sm text-gray-900 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:border-gray-700 dark:bg-gray-800 dark:text-white"
210
- bind:value={
211
- () => propertyDefinition.type,
212
- value => {
213
- const updatedProperties = { ...schemaObj.current.schema?.properties };
214
- if (updatedProperties && updatedProperties[propertyName]) {
215
- updatedProperties[propertyName].type = value;
216
- updateSchemaNested({ properties: updatedProperties });
217
- }
218
  }
 
 
219
  }
220
- >
221
- <option value="string">string</option>
222
- <option value="integer">integer</option>
223
- <option value="number">number</option>
224
- <option value="boolean">boolean</option>
225
- <option value="array">array</option>
226
- <option value="object">object</option>
227
- <option value="enum">enum</option>
228
- <option value="null">null</option>
229
- </select>
230
- </div>
231
-
232
- <div>
233
- <label
234
- for="{propertyName}-description"
235
- class="block text-xs font-medium text-gray-500 dark:text-gray-400">Description</label
236
- >
237
- <input
238
- type="text"
239
- id="{propertyName}-description"
240
- class="mt-1 block w-full rounded-md border border-gray-300 bg-white px-2 py-1 text-sm text-gray-900 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:border-gray-700 dark:bg-gray-800 dark:text-white"
241
- value={propertyDefinition.description}
242
- {...onchange(value => {
243
- const updatedProperties = { ...schemaObj.current.schema?.properties };
244
- if (!updatedProperties || !updatedProperties[propertyName]) return;
245
- updatedProperties[propertyName].description = value;
246
- updateSchemaNested({ properties: updatedProperties });
247
- })}
248
- />
249
- </div>
250
-
251
- <div class="flex items-start">
252
- <div class="flex h-5 items-center">
253
- <input
254
- id="required-{propertyName}"
255
- aria-describedby="required-{propertyName}-description"
256
- name="required-{propertyName}"
257
- type="checkbox"
258
- class="h-4 w-4 rounded border border-gray-300 bg-white text-blue-600 focus:ring-blue-500 dark:border-gray-700 dark:bg-gray-800"
259
- checked={schemaObj.current.schema?.required?.includes(propertyName)}
260
- onchange={e => {
261
- let updatedRequired = [...(schemaObj.current.schema?.required || [])];
262
- if (e.currentTarget.checked) {
263
- if (!updatedRequired.includes(propertyName)) {
264
- updatedRequired.push(propertyName);
265
- }
266
- } else {
267
- updatedRequired = updatedRequired.filter(name => name !== propertyName);
268
- }
269
- updateSchemaNested({ required: updatedRequired });
270
- }}
271
- />
272
- </div>
273
- <div class="ml-3 text-sm">
274
- <label for="required-{propertyName}" class="font-medium text-gray-700 dark:text-gray-300">
275
- Required
276
- </label>
277
- </div>
278
- </div>
279
- </div>
280
  {:else}
281
  <p class="mt-3 text-sm text-gray-500">No properties defined yet.</p>
282
  {/each}
@@ -292,12 +193,12 @@
292
  const newPropertyName = `newProperty${Object.keys(schemaObj.current.schema?.properties || {}).length + 1}`;
293
  const updatedProperties = {
294
  ...(schemaObj.current.schema?.properties || {}),
295
- [newPropertyName]: { type: "string", description: "" },
296
  };
297
  updateSchemaNested({ properties: updatedProperties });
298
  }}
299
  >
300
- Add Property
301
  </button>
302
  </div>
303
 
 
1
  <script lang="ts">
2
+ import { isDark } from "$lib/spells/is-dark.svelte";
3
  import { Synced } from "$lib/spells/synced.svelte";
4
  import { TextareaAutosize } from "$lib/spells/textarea-autosize.svelte";
5
  import type { ConversationClass } from "$lib/state/conversations.svelte.js";
6
  import { safeParse } from "$lib/utils/json.js";
7
+ import { keys, renameKey } from "$lib/utils/object.svelte";
8
  import { onchange, oninput } from "$lib/utils/template.js";
9
  import { RadioGroup } from "melt/builders";
10
  import { codeToHtml } from "shiki";
11
  import typia from "typia";
 
12
  import Dialog from "../dialog.svelte";
13
+ import SchemaProperty, { type PropertyDefinition } from "./schema-property.svelte";
14
 
15
  interface Props {
16
  conversation: ConversationClass;
 
27
  });
28
 
29
  type Schema = {
 
 
30
  schema?: {
31
  type?: string;
32
+ properties?: { [key: string]: PropertyDefinition };
33
  required?: string[];
34
  additionalProperties?: boolean;
35
  };
 
74
  keys(v.schema?.properties ?? {}).includes(name)
75
  );
76
  const validated: Schema = {
 
 
77
  schema: {
78
  ...v.schema,
79
  required,
 
106
  });
107
  </script>
108
 
109
+ <Dialog class="!w-2xl max-w-[90vw]" title="Edit Structured Output" {open} onClose={() => (open = false)}>
110
  <div class="flex justify-end">
111
  <div
112
  class="flex items-center gap-0.5 rounded-md border border-gray-300 bg-white p-0.5 text-sm dark:border-gray-600 dark:bg-gray-800"
 
129
 
130
  {#if radioGroup.value === "form"}
131
  <div class="fade-y -mx-2 mt-2 -mb-4 max-h-200 space-y-4 overflow-auto px-2 py-4 text-left">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  <!-- Properties Section -->
133
  <div class="border-t border-gray-200 pt-4 dark:border-gray-700">
134
  <h3 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">Properties</h3>
135
  {#if schemaObj.current.schema?.properties}
136
  <div class="mt-3 space-y-3">
137
+ {#each Object.entries(schemaObj.current.schema.properties) as [propertyName, propertyDefinition]}
138
+ <SchemaProperty
139
+ bind:name={
140
+ () => propertyName,
141
+ value => {
142
+ const updatedProperties = renameKey(
143
+ schemaObj.current.schema?.properties ?? {},
144
+ propertyName,
145
+ value
146
+ );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  updateSchemaNested({ properties: updatedProperties });
148
+ }
149
+ }
150
+ bind:definition={
151
+ () => propertyDefinition,
152
+ v => {
153
+ const updatedProperties = { ...schemaObj.current.schema?.properties };
154
+ if (updatedProperties && updatedProperties[propertyName]) {
155
+ updatedProperties[propertyName] = v;
156
+ updateSchemaNested({ properties: updatedProperties });
157
+ }
158
+ }
159
+ }
160
+ bind:required={
161
+ () => schemaObj.current.schema?.required?.includes(propertyName) ?? false,
162
+ v => {
163
+ let updatedRequired = [...(schemaObj.current.schema?.required || [])];
164
+ if (v) {
165
+ if (!updatedRequired.includes(propertyName)) {
166
+ updatedRequired.push(propertyName);
 
 
167
  }
168
+ } else {
169
+ updatedRequired = updatedRequired.filter(name => name !== name);
170
  }
171
+ updateSchemaNested({ required: updatedRequired });
172
+ }
173
+ }
174
+ onDelete={() => {
175
+ const updatedProperties = { ...schemaObj.current.schema?.properties };
176
+ if (!updatedProperties || !updatedProperties[propertyName]) return;
177
+ delete updatedProperties[propertyName];
178
+ updateSchemaNested({ properties: updatedProperties });
179
+ }}
180
+ />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
  {:else}
182
  <p class="mt-3 text-sm text-gray-500">No properties defined yet.</p>
183
  {/each}
 
193
  const newPropertyName = `newProperty${Object.keys(schemaObj.current.schema?.properties || {}).length + 1}`;
194
  const updatedProperties = {
195
  ...(schemaObj.current.schema?.properties || {}),
196
+ [newPropertyName]: { type: "string" as const },
197
  };
198
  updateSchemaNested({ properties: updatedProperties });
199
  }}
200
  >
201
+ Add property
202
  </button>
203
  </div>
204
 
src/lib/components/inference-playground/utils.svelte.ts CHANGED
@@ -4,6 +4,7 @@ import { token } from "$lib/state/token.svelte";
4
  import {
5
  isCustomModel,
6
  isHFModel,
 
7
  type Conversation,
8
  type ConversationMessage,
9
  type CustomModel,
@@ -18,6 +19,8 @@ import { type ChatCompletionOutputMessage } from "@huggingface/tasks";
18
  import { AutoTokenizer, PreTrainedTokenizer } from "@huggingface/transformers";
19
  import OpenAI from "openai";
20
  import { images } from "$lib/state/images.svelte.js";
 
 
21
 
22
  type ChatCompletionInputMessageChunk =
23
  NonNullable<ChatCompletionInputMessage["content"]> extends string | (infer U)[] ? U : never;
@@ -84,14 +87,50 @@ async function getCompletionMetadata(
84
  ): Promise<CompletionMetadata> {
85
  const data = conversation instanceof ConversationClass ? conversation.data : conversation;
86
  const model = conversation.model;
87
- const { systemMessage } = data;
88
 
89
- const messages = [
90
- ...(isSystemPromptSupported(model) && systemMessage.content?.length ? [systemMessage] : []),
91
  ...data.messages,
92
  ];
93
  const parsed = await Promise.all(messages.map(parseMessage));
94
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  // Handle OpenAI-compatible models
96
  if (isCustomModel(model)) {
97
  const openai = new OpenAI({
@@ -104,22 +143,10 @@ async function getCompletionMetadata(
104
  });
105
 
106
  const args = {
107
- messages: parsed,
108
- ...data.config,
109
- model: model.id,
110
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
111
  } as any;
112
 
113
- if (data.structuredOutput?.enabled) {
114
- const json = safeParse(data.structuredOutput.schema ?? "");
115
- if (json) {
116
- args.response_format = {
117
- type: "json_schema",
118
- json_schema: json,
119
- };
120
- }
121
- }
122
-
123
  return {
124
  type: "openai",
125
  client: openai,
@@ -127,24 +154,12 @@ async function getCompletionMetadata(
127
  };
128
  }
129
  const args = {
130
- model: model.id,
131
- messages: parsed,
132
  provider: data.provider,
133
- ...data.config,
134
  // max_tokens: maxAllowedTokens(conversation) - currTokens,
135
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
136
  } as any;
137
 
138
- if (data.structuredOutput?.enabled) {
139
- const json = safeParse(data.structuredOutput.schema ?? "");
140
- if (json) {
141
- args.response_format = {
142
- type: "json_schema",
143
- json_schema: json,
144
- };
145
- }
146
- }
147
-
148
  // Handle HuggingFace models
149
  return {
150
  type: "huggingface",
 
4
  import {
5
  isCustomModel,
6
  isHFModel,
7
+ Provider,
8
  type Conversation,
9
  type ConversationMessage,
10
  type CustomModel,
 
19
  import { AutoTokenizer, PreTrainedTokenizer } from "@huggingface/transformers";
20
  import OpenAI from "openai";
21
  import { images } from "$lib/state/images.svelte.js";
22
+ import { projects } from "$lib/state/projects.svelte.js";
23
+ import { structuredForbiddenProviders } from "$lib/state/models.svelte.js";
24
 
25
  type ChatCompletionInputMessageChunk =
26
  NonNullable<ChatCompletionInputMessage["content"]> extends string | (infer U)[] ? U : never;
 
87
  ): Promise<CompletionMetadata> {
88
  const data = conversation instanceof ConversationClass ? conversation.data : conversation;
89
  const model = conversation.model;
90
+ const systemMessage = projects.current?.systemMessage;
91
 
92
+ const messages: ConversationMessage[] = [
93
+ ...(isSystemPromptSupported(model) && systemMessage?.length ? [{ role: "system", content: systemMessage }] : []),
94
  ...data.messages,
95
  ];
96
  const parsed = await Promise.all(messages.map(parseMessage));
97
 
98
+ const baseArgs = {
99
+ ...data.config,
100
+ messages: parsed,
101
+ model: model.id,
102
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
103
+ } as any;
104
+
105
+ const json = safeParse(data.structuredOutput?.schema ?? "");
106
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
107
+ if (json && data.structuredOutput?.enabled && !structuredForbiddenProviders.includes(data.provider as any)) {
108
+ switch (data.provider) {
109
+ case "cohere": {
110
+ baseArgs.response_format = {
111
+ type: "json_object",
112
+ ...json,
113
+ };
114
+ break;
115
+ }
116
+ case Provider.Nebius: {
117
+ baseArgs.response_format = {
118
+ type: "json_object",
119
+ json_schema: { ...json, name: "schema" },
120
+ };
121
+ break;
122
+ }
123
+ default: {
124
+ baseArgs.response_format = {
125
+ type: "json_object",
126
+ json_schema: json,
127
+ };
128
+
129
+ break;
130
+ }
131
+ }
132
+ }
133
+
134
  // Handle OpenAI-compatible models
135
  if (isCustomModel(model)) {
136
  const openai = new OpenAI({
 
143
  });
144
 
145
  const args = {
146
+ ...baseArgs,
 
 
147
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
148
  } as any;
149
 
 
 
 
 
 
 
 
 
 
 
150
  return {
151
  type: "openai",
152
  client: openai,
 
154
  };
155
  }
156
  const args = {
157
+ ...baseArgs,
 
158
  provider: data.provider,
 
159
  // max_tokens: maxAllowedTokens(conversation) - currTokens,
160
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
161
  } as any;
162
 
 
 
 
 
 
 
 
 
 
 
163
  // Handle HuggingFace models
164
  return {
165
  type: "huggingface",
src/lib/components/local-toasts.svelte CHANGED
@@ -1,9 +1,8 @@
1
  <script lang="ts">
2
- import { autoUpdate, computePosition } from "@floating-ui/dom";
3
  import { Toaster } from "melt/builders";
4
  import { type Snippet } from "svelte";
5
  import { type Attachment } from "svelte/attachments";
6
- import { fly } from "svelte/transition";
7
 
8
  interface Props {
9
  children: Snippet<[{ addToast: typeof toaster.addToast; trigger: typeof trigger }]>;
@@ -31,21 +30,96 @@
31
  export const addToast = toaster.addToast;
32
 
33
  const float: Attachment<HTMLElement> = function (node) {
 
 
34
  const triggerEl = document.querySelector(`[data-local-toast-trigger=${id}]`);
35
  if (!triggerEl) return;
36
 
37
  const compute = () =>
38
  computePosition(triggerEl, node, {
39
- placement: "top",
40
  strategy: "absolute",
41
- }).then(({ x, y }) => {
 
 
 
42
  Object.assign(node.style, {
43
- left: `${x}px`,
44
- top: `${y - 8}px`,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  });
46
  });
47
 
48
- return autoUpdate(triggerEl, node, compute);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  };
50
 
51
  const classMap: Record<ToastData["variant"], string> = {
@@ -62,8 +136,6 @@
62
  data-local-toast
63
  data-variant={toast.data.variant}
64
  class={[!toastSnippet && `${classMap[toast.data.variant]} rounded-full px-2 py-1 text-xs`]}
65
- in:fly={{ y: 10 }}
66
- out:fly={{ y: -4 }}
67
  {@attach float}
68
  >
69
  {#if toastSnippet}
 
1
  <script lang="ts">
2
+ import { autoUpdate, computePosition, flip, type Placement } from "@floating-ui/dom";
3
  import { Toaster } from "melt/builders";
4
  import { type Snippet } from "svelte";
5
  import { type Attachment } from "svelte/attachments";
 
6
 
7
  interface Props {
8
  children: Snippet<[{ addToast: typeof toaster.addToast; trigger: typeof trigger }]>;
 
30
  export const addToast = toaster.addToast;
31
 
32
  const float: Attachment<HTMLElement> = function (node) {
33
+ let placement: Placement = $state("top");
34
+
35
  const triggerEl = document.querySelector(`[data-local-toast-trigger=${id}]`);
36
  if (!triggerEl) return;
37
 
38
  const compute = () =>
39
  computePosition(triggerEl, node, {
 
40
  strategy: "absolute",
41
+ placement: "top",
42
+ middleware: [flip({ fallbackPlacements: ["left"] })],
43
+ }).then(({ x, y, placement: _placement }) => {
44
+ placement = _placement;
45
  Object.assign(node.style, {
46
+ left: placement === "top" ? `${x}px` : `${x - 4}px`,
47
+ top: placement === "top" ? `${y - 6}px` : `${y}px`,
48
+ });
49
+
50
+ // Animate
51
+ // Cancel any ongoing animations
52
+ node.getAnimations().forEach(anim => anim.cancel());
53
+
54
+ // Determine animation direction based on placement
55
+ let keyframes: Keyframe[] = [];
56
+ switch (placement) {
57
+ case "top":
58
+ keyframes = [
59
+ { opacity: 0, transform: "translateY(8px)", scale: "0.8" },
60
+ { opacity: 1, transform: "translateY(0)", scale: "1" },
61
+ ];
62
+ break;
63
+ case "left":
64
+ keyframes = [
65
+ { opacity: 0, transform: "translateX(8px)", scale: "0.8" },
66
+ { opacity: 1, transform: "translateX(0)", scale: "1" },
67
+ ];
68
+ break;
69
+ }
70
+
71
+ node.animate(keyframes, {
72
+ duration: 500,
73
+ easing: "cubic-bezier(0.22, 1, 0.36, 1)",
74
+ fill: "forwards",
75
  });
76
  });
77
 
78
+ const reference = node.cloneNode(true) as HTMLElement;
79
+ node.before(reference);
80
+ reference.style.visibility = "hidden";
81
+
82
+ const destroyers = [
83
+ autoUpdate(triggerEl, node, compute),
84
+ async () => {
85
+ // clone node
86
+ const cloned = node.cloneNode(true) as HTMLElement;
87
+ reference.before(cloned);
88
+ reference.remove();
89
+ cloned.getAnimations().forEach(anim => anim.cancel());
90
+
91
+ // Animate out
92
+ // Cancel any ongoing animations
93
+ cloned.getAnimations().forEach(anim => anim.cancel());
94
+
95
+ // Determine animation direction based on placement
96
+ let keyframes: Keyframe[] = [];
97
+ switch (placement) {
98
+ case "top":
99
+ keyframes = [
100
+ { opacity: 1, transform: "translateY(0)" },
101
+ { opacity: 0, transform: "translateY(-8px)" },
102
+ ];
103
+ break;
104
+ case "left":
105
+ keyframes = [
106
+ { opacity: 1, transform: "translateX(0)" },
107
+ { opacity: 0, transform: "translateX(-8px)" },
108
+ ];
109
+ break;
110
+ }
111
+
112
+ await cloned.animate(keyframes, {
113
+ duration: 400,
114
+ easing: "cubic-bezier(0.22, 1, 0.36, 1)",
115
+ fill: "forwards",
116
+ }).finished;
117
+
118
+ cloned.remove();
119
+ },
120
+ ];
121
+
122
+ return () => destroyers.forEach(d => d());
123
  };
124
 
125
  const classMap: Record<ToastData["variant"], string> = {
 
136
  data-local-toast
137
  data-variant={toast.data.variant}
138
  class={[!toastSnippet && `${classMap[toast.data.variant]} rounded-full px-2 py-1 text-xs`]}
 
 
139
  {@attach float}
140
  >
141
  {#if toastSnippet}
src/lib/components/share-modal.svelte CHANGED
@@ -153,7 +153,7 @@
153
  saving = false;
154
  return;
155
  }
156
- const projectId = await projects.create(`Saved - ${decoded.name}`);
157
  await Promise.allSettled(
158
  decoded.conversations.map(c => {
159
  conversations.create({
 
153
  saving = false;
154
  return;
155
  }
156
+ const projectId = await projects.create({ name: `Saved - ${decoded.name}` });
157
  await Promise.allSettled(
158
  decoded.conversations.map(c => {
159
  conversations.create({
src/lib/data/context_length.json CHANGED
@@ -101,19 +101,19 @@
101
  "stability-ai/sdxl": 0
102
  },
103
  "novita": {
104
- "deepseek/deepseek-prover-v2-671b": 160000,
105
- "qwen/qwen3-235b-a22b-fp8": 128000,
106
- "qwen/qwen3-30b-a3b-fp8": 128000,
107
- "qwen/qwen3-32b-fp8": 128000,
108
  "deepseek/deepseek-v3-0324": 128000,
 
 
 
109
  "qwen/qwen2.5-vl-72b-instruct": 96000,
110
  "deepseek/deepseek-v3-turbo": 64000,
111
- "deepseek/deepseek-r1-turbo": 64000,
112
  "meta-llama/llama-4-maverick-17b-128e-instruct-fp8": 1048576,
113
  "google/gemma-3-27b-it": 32000,
114
  "qwen/qwq-32b": 32768,
 
115
  "Sao10K/L3-8B-Stheno-v3.2": 8192,
116
  "gryphe/mythomax-l2-13b": 4096,
 
117
  "meta-llama/llama-4-scout-17b-16e-instruct": 131072,
118
  "deepseek/deepseek-r1-distill-llama-8b": 32000,
119
  "deepseek/deepseek_v3": 64000,
@@ -141,7 +141,7 @@
141
  "qwen/qwen3-1.7b-fp8": 32000,
142
  "qwen/qwen3-8b-fp8": 128000,
143
  "qwen/qwen3-4b-fp8": 128000,
144
- "qwen/qwen3-14b-fp8": 128000,
145
  "thudm/glm-4-9b-0414": 32000,
146
  "thudm/glm-z1-9b-0414": 32000,
147
  "thudm/glm-z1-32b-0414": 32000,
@@ -211,17 +211,18 @@
211
  "together": {
212
  "Qwen/QwQ-32B": 131072,
213
  "meta-llama/Llama-4-Scout-17B-16E-Instruct": 1048576,
 
214
  "meta-llama/Llama-Guard-4-12B": 1048576,
215
  "togethercomputer/m2-bert-80M-32k-retrieval": 32768,
216
- "google/gemma-2-9b-it": 8192,
217
  "cartesia/sonic": 0,
 
218
  "Qwen/Qwen2.5-7B-Instruct-Turbo": 32768,
219
  "deepseek-ai/DeepSeek-R1-Distill-Llama-70B-free": 8192,
220
  "meta-llama-llama-2-70b-hf": 4096,
 
221
  "BAAI/bge-base-en-v1.5": 512,
222
  "Gryphe/MythoMax-L2-13b": 4096,
223
- "deepseek-ai/DeepSeek-V3": 131072,
224
- "mistralai/Mistral-7B-Instruct-v0.1": 32768,
225
  "mistralai/Mixtral-8x7B-Instruct-v0.1": 32768,
226
  "google/gemma-2-27b-it": 8192,
227
  "Qwen/Qwen2-VL-72B-Instruct": 32768,
@@ -229,18 +230,21 @@
229
  "cartesia/sonic-2": 0,
230
  "togethercomputer/m2-bert-80M-8k-retrieval": 8192,
231
  "meta-llama/Llama-3.3-70B-Instruct-Turbo-Free": 131072,
 
232
  "scb10x/scb10x-llama3-1-typhoon2-70b-instruct": 8192,
233
  "togethercomputer/Refuel-Llm-V2-Small": 8192,
234
  "togethercomputer/MoA-1": 32768,
235
  "meta-llama/Meta-Llama-3-70B-Instruct-Turbo": 8192,
236
- "Qwen/Qwen3-235B-A22B-fp8-tput": 40960,
237
  "google/gemma-2b-it": 8192,
238
  "meta-llama/Llama-3.2-11B-Vision-Instruct-Turbo": 131072,
239
  "Gryphe/MythoMax-L2-13b-Lite": 4096,
 
240
  "scb10x/scb10x-llama3-1-typhoon2-8b-instruct": 8192,
241
  "meta-llama/Meta-Llama-Guard-3-8B": 8192,
242
- "intfloat/multilingual-e5-large-instruct": 514,
243
  "deepseek-ai/DeepSeek-R1": 163840,
 
 
244
  "arcee-ai/arcee-blitz": 32768,
245
  "arcee_ai/arcee-spotlight": 131072,
246
  "arcee-ai/caller": 32768,
@@ -248,20 +252,19 @@
248
  "arcee-ai/maestro-reasoning": 131072,
249
  "arcee-ai/virtuoso-large": 131072,
250
  "arcee-ai/virtuoso-medium-v2": 131072,
251
- "mistralai/Mistral-Small-24B-Instruct-2501": 32768,
252
  "meta-llama/Llama-3-8b-chat-hf": 8192,
 
 
 
253
  "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8": 1048576,
254
  "togethercomputer/MoA-1-Turbo": 32768,
255
  "meta-llama/Llama-3.3-70B-Instruct-Turbo": 131072,
256
- "Qwen/Qwen3-235B-A22B-fp8": 40960,
257
  "NousResearch/Nous-Hermes-2-Mixtral-8x7B-DPO": 32768,
258
  "deepseek-ai/DeepSeek-R1-Distill-Llama-70B": 131072,
259
  "deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B": 131072,
260
  "meta-llama/Meta-Llama-3-8B-Instruct-Lite": 8192,
261
- "meta-llama/Llama-3.2-90B-Vision-Instruct-Turbo": 131072,
262
  "meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo": 131072,
263
- "mistralai/Mixtral-8x7B-v0.1": 32768,
264
- "meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo": 131072,
265
  "mistralai/Mistral-7B-Instruct-v0.2": 32768,
266
  "deepseek-ai/DeepSeek-V3-p-dp": 131072,
267
  "deepseek-ai/DeepSeek-R1-Distill-Qwen-14B": 131072,
@@ -274,13 +277,9 @@
274
  "meta-llama/Llama-Vision-Free": 131072,
275
  "meta-llama/Llama-Guard-3-11B-Vision-Turbo": 131072,
276
  "meta-llama/Llama-3.2-3B-Instruct-Turbo": 131072,
277
- "meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo": 130815,
278
  "togethercomputer/Refuel-Llm-V2": 16384,
279
- "Alibaba-NLP/gte-modernbert-base": 8192,
280
  "Qwen/Qwen2.5-72B-Instruct-Turbo": 131072,
281
- "perplexity-ai/r1-1776": 163840,
282
- "meta-llama/Llama-2-70b-hf": 4096,
283
- "Qwen/Qwen2.5-VL-72B-Instruct": 32768
284
  },
285
  "fireworks-ai": {
286
  "accounts/fireworks/models/qwq-32b": 131072,
 
101
  "stability-ai/sdxl": 0
102
  },
103
  "novita": {
 
 
 
 
104
  "deepseek/deepseek-v3-0324": 128000,
105
+ "qwen/qwen3-235b-a22b-fp8": 40960,
106
+ "qwen/qwen3-30b-a3b-fp8": 40960,
107
+ "qwen/qwen3-32b-fp8": 40960,
108
  "qwen/qwen2.5-vl-72b-instruct": 96000,
109
  "deepseek/deepseek-v3-turbo": 64000,
 
110
  "meta-llama/llama-4-maverick-17b-128e-instruct-fp8": 1048576,
111
  "google/gemma-3-27b-it": 32000,
112
  "qwen/qwq-32b": 32768,
113
+ "deepseek/deepseek-r1-turbo": 64000,
114
  "Sao10K/L3-8B-Stheno-v3.2": 8192,
115
  "gryphe/mythomax-l2-13b": 4096,
116
+ "deepseek/deepseek-prover-v2-671b": 160000,
117
  "meta-llama/llama-4-scout-17b-16e-instruct": 131072,
118
  "deepseek/deepseek-r1-distill-llama-8b": 32000,
119
  "deepseek/deepseek_v3": 64000,
 
141
  "qwen/qwen3-1.7b-fp8": 32000,
142
  "qwen/qwen3-8b-fp8": 128000,
143
  "qwen/qwen3-4b-fp8": 128000,
144
+ "qwen/qwen3-14b-fp8": 40960,
145
  "thudm/glm-4-9b-0414": 32000,
146
  "thudm/glm-z1-9b-0414": 32000,
147
  "thudm/glm-z1-32b-0414": 32000,
 
211
  "together": {
212
  "Qwen/QwQ-32B": 131072,
213
  "meta-llama/Llama-4-Scout-17B-16E-Instruct": 1048576,
214
+ "mistralai/Mistral-7B-Instruct-v0.1": 32768,
215
  "meta-llama/Llama-Guard-4-12B": 1048576,
216
  "togethercomputer/m2-bert-80M-32k-retrieval": 32768,
 
217
  "cartesia/sonic": 0,
218
+ "meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo": 130815,
219
  "Qwen/Qwen2.5-7B-Instruct-Turbo": 32768,
220
  "deepseek-ai/DeepSeek-R1-Distill-Llama-70B-free": 8192,
221
  "meta-llama-llama-2-70b-hf": 4096,
222
+ "intfloat/multilingual-e5-large-instruct": 514,
223
  "BAAI/bge-base-en-v1.5": 512,
224
  "Gryphe/MythoMax-L2-13b": 4096,
225
+ "Alibaba-NLP/gte-modernbert-base": 8192,
 
226
  "mistralai/Mixtral-8x7B-Instruct-v0.1": 32768,
227
  "google/gemma-2-27b-it": 8192,
228
  "Qwen/Qwen2-VL-72B-Instruct": 32768,
 
230
  "cartesia/sonic-2": 0,
231
  "togethercomputer/m2-bert-80M-8k-retrieval": 8192,
232
  "meta-llama/Llama-3.3-70B-Instruct-Turbo-Free": 131072,
233
+ "deepseek-ai/DeepSeek-V3": 131072,
234
  "scb10x/scb10x-llama3-1-typhoon2-70b-instruct": 8192,
235
  "togethercomputer/Refuel-Llm-V2-Small": 8192,
236
  "togethercomputer/MoA-1": 32768,
237
  "meta-llama/Meta-Llama-3-70B-Instruct-Turbo": 8192,
 
238
  "google/gemma-2b-it": 8192,
239
  "meta-llama/Llama-3.2-11B-Vision-Instruct-Turbo": 131072,
240
  "Gryphe/MythoMax-L2-13b-Lite": 4096,
241
+ "Qwen/Qwen3-235B-A22B-fp8": 40960,
242
  "scb10x/scb10x-llama3-1-typhoon2-8b-instruct": 8192,
243
  "meta-llama/Meta-Llama-Guard-3-8B": 8192,
244
+ "marin-community/marin-8b-instruct": 131072,
245
  "deepseek-ai/DeepSeek-R1": 163840,
246
+ "meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo": 131072,
247
+ "Qwen/Qwen2.5-VL-72B-Instruct": 32768,
248
  "arcee-ai/arcee-blitz": 32768,
249
  "arcee_ai/arcee-spotlight": 131072,
250
  "arcee-ai/caller": 32768,
 
252
  "arcee-ai/maestro-reasoning": 131072,
253
  "arcee-ai/virtuoso-large": 131072,
254
  "arcee-ai/virtuoso-medium-v2": 131072,
255
+ "meta-llama/Llama-3.2-90B-Vision-Instruct-Turbo": 131072,
256
  "meta-llama/Llama-3-8b-chat-hf": 8192,
257
+ "mistralai/Mistral-Small-24B-Instruct-2501": 32768,
258
+ "Qwen/Qwen3-235B-A22B-fp8-tput": 40960,
259
+ "perplexity-ai/r1-1776": 163840,
260
  "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8": 1048576,
261
  "togethercomputer/MoA-1-Turbo": 32768,
262
  "meta-llama/Llama-3.3-70B-Instruct-Turbo": 131072,
 
263
  "NousResearch/Nous-Hermes-2-Mixtral-8x7B-DPO": 32768,
264
  "deepseek-ai/DeepSeek-R1-Distill-Llama-70B": 131072,
265
  "deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B": 131072,
266
  "meta-llama/Meta-Llama-3-8B-Instruct-Lite": 8192,
 
267
  "meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo": 131072,
 
 
268
  "mistralai/Mistral-7B-Instruct-v0.2": 32768,
269
  "deepseek-ai/DeepSeek-V3-p-dp": 131072,
270
  "deepseek-ai/DeepSeek-R1-Distill-Qwen-14B": 131072,
 
277
  "meta-llama/Llama-Vision-Free": 131072,
278
  "meta-llama/Llama-Guard-3-11B-Vision-Turbo": 131072,
279
  "meta-llama/Llama-3.2-3B-Instruct-Turbo": 131072,
 
280
  "togethercomputer/Refuel-Llm-V2": 16384,
 
281
  "Qwen/Qwen2.5-72B-Instruct-Turbo": 131072,
282
+ "meta-llama/Llama-2-70b-hf": 4096
 
 
283
  },
284
  "fireworks-ai": {
285
  "accounts/fireworks/models/qwq-32b": 131072,
src/lib/state/conversations.svelte.ts CHANGED
@@ -40,9 +40,6 @@ export class ConversationEntity {
40
  @Fields.json()
41
  messages!: ConversationMessage[];
42
 
43
- @Fields.json()
44
- systemMessage: ConversationMessage = { role: "system" };
45
-
46
  @Fields.boolean()
47
  streaming = false;
48
 
@@ -64,10 +61,6 @@ export type ConversationEntityMembers = MembersOnly<ConversationEntity>;
64
  const conversationsRepo = repo(ConversationEntity, idb);
65
 
66
  const startMessageUser: ConversationMessage = { role: "user", content: "" };
67
- const systemMessage: ConversationMessage = {
68
- role: "system",
69
- content: "",
70
- };
71
 
72
  export const emptyModel: Model = {
73
  _id: "",
@@ -89,7 +82,6 @@ function getDefaultConversation(projectId: string) {
89
  modelId: models.trending[0]?.id ?? models.remote[0]?.id ?? emptyModel.id,
90
  config: { ...defaultGenerationConfig },
91
  messages: [{ ...startMessageUser }],
92
- systemMessage,
93
  streaming: true,
94
  createdAt: new Date(),
95
  } satisfies Partial<ConversationEntityMembers>;
 
40
  @Fields.json()
41
  messages!: ConversationMessage[];
42
 
 
 
 
43
  @Fields.boolean()
44
  streaming = false;
45
 
 
61
  const conversationsRepo = repo(ConversationEntity, idb);
62
 
63
  const startMessageUser: ConversationMessage = { role: "user", content: "" };
 
 
 
 
64
 
65
  export const emptyModel: Model = {
66
  _id: "",
 
82
  modelId: models.trending[0]?.id ?? models.remote[0]?.id ?? emptyModel.id,
83
  config: { ...defaultGenerationConfig },
84
  messages: [{ ...startMessageUser }],
 
85
  streaming: true,
86
  createdAt: new Date(),
87
  } satisfies Partial<ConversationEntityMembers>;
src/lib/state/models.svelte.ts CHANGED
@@ -1,5 +1,5 @@
1
  import { page } from "$app/state";
2
- import { type CustomModel } from "$lib/types.js";
3
  import { edit, randomPick } from "$lib/utils/array.js";
4
  import { safeParse } from "$lib/utils/json.js";
5
  import typia from "typia";
@@ -10,6 +10,13 @@ const LOCAL_STORAGE_KEY = "hf_inference_playground_custom_models";
10
 
11
  const pageData = $derived(page.data as PageData);
12
 
 
 
 
 
 
 
 
13
  class Models {
14
  remote = $derived(pageData.models);
15
  trending = $derived(this.remote.toSorted((a, b) => b.trendingScore - a.trendingScore).slice(0, 5));
 
1
  import { page } from "$app/state";
2
+ import { Provider, type CustomModel } from "$lib/types.js";
3
  import { edit, randomPick } from "$lib/utils/array.js";
4
  import { safeParse } from "$lib/utils/json.js";
5
  import typia from "typia";
 
10
 
11
  const pageData = $derived(page.data as PageData);
12
 
13
+ export const structuredForbiddenProviders: Provider[] = [
14
+ Provider.Hyperbolic,
15
+ Provider.Nebius,
16
+ Provider.Novita,
17
+ Provider.Sambanova,
18
+ ];
19
+
20
  class Models {
21
  remote = $derived(pageData.models);
22
  trending = $derived(this.remote.toSorted((a, b) => b.trendingScore - a.trendingScore).slice(0, 5));
src/lib/state/projects.svelte.ts CHANGED
@@ -1,9 +1,9 @@
1
  import { idb } from "$lib/remult.js";
2
  import { dequal } from "dequal";
3
  import { Entity, Fields, repo, type MembersOnly } from "remult";
4
- import { conversations } from "./conversations.svelte";
5
  import { PersistedState } from "runed";
6
  import { checkpoints } from "./checkpoints.svelte";
 
7
 
8
  @Entity("project")
9
  export class ProjectEntity {
@@ -12,6 +12,9 @@ export class ProjectEntity {
12
 
13
  @Fields.string()
14
  name!: string;
 
 
 
15
  }
16
 
17
  export type ProjectEntityMembers = MembersOnly<ProjectEntity>;
@@ -45,17 +48,17 @@ class Projects {
45
  });
46
  }
47
 
48
- async create(name: string): Promise<string> {
49
- const { id } = await projectsRepo.save({ name });
50
- this.#projects[id] = { name, id };
51
- return id;
52
  }
53
 
54
  saveProject = async (args: { name: string; moveCheckpoints?: boolean }) => {
55
  const defaultProject = this.all.find(p => p.id === DEFAULT_PROJECT_ID);
56
  if (!defaultProject) return;
57
 
58
- const id = await this.create(args.name);
59
 
60
  if (args.moveCheckpoints) {
61
  checkpoints.migrate(defaultProject.id, id);
@@ -87,7 +90,7 @@ class Projects {
87
 
88
  async update(data: ProjectEntity) {
89
  if (!data.id) return;
90
- await projectsRepo.update(data.id, data);
91
  this.#projects[data.id] = { ...data };
92
  }
93
 
 
1
  import { idb } from "$lib/remult.js";
2
  import { dequal } from "dequal";
3
  import { Entity, Fields, repo, type MembersOnly } from "remult";
 
4
  import { PersistedState } from "runed";
5
  import { checkpoints } from "./checkpoints.svelte";
6
+ import { conversations } from "./conversations.svelte";
7
 
8
  @Entity("project")
9
  export class ProjectEntity {
 
12
 
13
  @Fields.string()
14
  name!: string;
15
+
16
+ @Fields.string()
17
+ systemMessage?: string;
18
  }
19
 
20
  export type ProjectEntityMembers = MembersOnly<ProjectEntity>;
 
48
  });
49
  }
50
 
51
+ async create(args: Omit<ProjectEntity, "id">): Promise<string> {
52
+ const p = await projectsRepo.save({ ...args });
53
+ this.#projects[p.id] = p;
54
+ return p.id;
55
  }
56
 
57
  saveProject = async (args: { name: string; moveCheckpoints?: boolean }) => {
58
  const defaultProject = this.all.find(p => p.id === DEFAULT_PROJECT_ID);
59
  if (!defaultProject) return;
60
 
61
+ const id = await this.create({ name: args.name, systemMessage: defaultProject.systemMessage });
62
 
63
  if (args.moveCheckpoints) {
64
  checkpoints.migrate(defaultProject.id, id);
 
90
 
91
  async update(data: ProjectEntity) {
92
  if (!data.id) return;
93
+ await projectsRepo.upsert({ where: { id: data.id }, set: data });
94
  this.#projects[data.id] = { ...data };
95
  }
96
 
src/lib/utils/object.svelte.ts CHANGED
@@ -84,3 +84,21 @@ export function deepMerge<T extends DeepMergeable, U extends DeepMergeable>(targ
84
 
85
  return result as T & U;
86
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
  return result as T & U;
86
  }
87
+
88
+ export function renameKey<T extends object>(
89
+ obj: T,
90
+ oldKey: keyof T,
91
+ newKey: string
92
+ ): { [K in keyof T as K extends typeof oldKey ? typeof newKey : K]: T[K] } {
93
+ const entries = Object.entries(obj);
94
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
95
+ const result: any = {};
96
+ for (const [key, value] of entries) {
97
+ if (key === oldKey) {
98
+ result[newKey] = value;
99
+ } else {
100
+ result[key] = value;
101
+ }
102
+ }
103
+ return result;
104
+ }