Thomas G. Lopes
commited on
Commit
·
b2170a7
1
Parent(s):
64cfbce
query param handling
Browse files- src/lib/components/InferencePlayground/InferencePlayground.svelte +24 -49
- src/lib/components/InferencePlayground/InferencePlaygroundConversation.svelte +23 -11
- src/lib/components/InferencePlayground/InferencePlaygroundModelSelector.svelte +1 -1
- src/lib/components/InferencePlayground/InferencePlaygroundProjectSelect.svelte +3 -3
- src/lib/stores/models.ts +1 -1
- src/lib/stores/session.ts +54 -21
src/lib/components/InferencePlayground/InferencePlayground.svelte
CHANGED
@@ -8,30 +8,26 @@
|
|
8 |
} from "./inferencePlaygroundUtils";
|
9 |
|
10 |
import { models } from "$lib/stores/models";
|
11 |
-
import {
|
12 |
import { token } from "$lib/stores/token";
|
13 |
import { isMac } from "$lib/utils/platform";
|
14 |
import { HfInference } from "@huggingface/inference";
|
15 |
import { onDestroy } from "svelte";
|
|
|
16 |
import IconCode from "~icons/carbon/code";
|
17 |
import IconCompare from "~icons/carbon/compare";
|
18 |
-
import IconDelete from "~icons/carbon/trash-can";
|
19 |
import IconInfo from "~icons/carbon/information";
|
20 |
-
import IconThrashcan from "~icons/carbon/trash-can";
|
21 |
import PlaygroundConversation from "./InferencePlaygroundConversation.svelte";
|
22 |
import PlaygroundConversationHeader from "./InferencePlaygroundConversationHeader.svelte";
|
23 |
import GenerationConfig from "./InferencePlaygroundGenerationConfig.svelte";
|
24 |
import HFTokenModal from "./InferencePlaygroundHFTokenModal.svelte";
|
25 |
import ModelSelector from "./InferencePlaygroundModelSelector.svelte";
|
26 |
import ModelSelectorModal from "./InferencePlaygroundModelSelectorModal.svelte";
|
27 |
-
import IconExternal from "~icons/carbon/arrow-up-right";
|
28 |
import InferencePlaygroundProjectSelect from "./InferencePlaygroundProjectSelect.svelte";
|
29 |
|
30 |
const startMessageUser: ConversationMessage = { role: "user", content: "" };
|
31 |
|
32 |
-
$: project = getActiveProject($session);
|
33 |
-
project = getActiveProject($session); // needed, otherwise its undefined on startup (not sure why).
|
34 |
-
|
35 |
let viewCode = false;
|
36 |
let viewSettings = false;
|
37 |
let loading = false;
|
@@ -43,34 +39,15 @@
|
|
43 |
latency: number;
|
44 |
generatedTokensCount: number;
|
45 |
}
|
46 |
-
let generationStats = project.conversations.map(_ => ({ latency: 0, generatedTokensCount: 0 })) as
|
47 |
| [GenerationStatistics]
|
48 |
| [GenerationStatistics, GenerationStatistics];
|
49 |
|
50 |
-
$: systemPromptSupported = project.conversations.some(conversation => isSystemPromptSupported(conversation.model));
|
51 |
-
$: compareActive = project.conversations.length === 2;
|
52 |
-
|
53 |
-
function addMessage(conversationIdx: number) {
|
54 |
-
const conversation = project.conversations[conversationIdx];
|
55 |
-
if (!conversation) return;
|
56 |
-
const msgs = conversation.messages.slice();
|
57 |
-
conversation.messages = [
|
58 |
-
...msgs,
|
59 |
-
{
|
60 |
-
role: msgs.at(-1)?.role === "user" ? "assistant" : "user",
|
61 |
-
content: "",
|
62 |
-
},
|
63 |
-
];
|
64 |
-
$session = $session;
|
65 |
-
}
|
66 |
-
|
67 |
-
function deleteMessage(conversationIdx: number, idx: number) {
|
68 |
-
project.conversations[conversationIdx]?.messages.splice(idx, 1)[0];
|
69 |
-
$session = $session;
|
70 |
-
}
|
71 |
|
72 |
function reset() {
|
73 |
-
project.conversations.map(conversation => {
|
74 |
conversation.systemMessage.content = "";
|
75 |
conversation.messages = [{ ...startMessageUser }];
|
76 |
});
|
@@ -140,10 +117,10 @@
|
|
140 |
return;
|
141 |
}
|
142 |
|
143 |
-
for (const [idx, conversation] of project.conversations.entries()) {
|
144 |
if (conversation.messages.at(-1)?.role === "assistant") {
|
145 |
let prefix = "";
|
146 |
-
if (project.conversations.length === 2) {
|
147 |
prefix = `Error on ${idx === 0 ? "left" : "right"} conversation. `;
|
148 |
}
|
149 |
return alert(`${prefix}Messages must alternate between user/assistant roles.`);
|
@@ -154,10 +131,10 @@
|
|
154 |
loading = true;
|
155 |
|
156 |
try {
|
157 |
-
const promises = project.conversations.map((conversation, idx) => runInference(conversation, idx));
|
158 |
await Promise.all(promises);
|
159 |
} catch (error) {
|
160 |
-
for (const conversation of project.conversations) {
|
161 |
if (conversation.messages.at(-1)?.role === "assistant" && !conversation.messages.at(-1)?.content?.trim()) {
|
162 |
conversation.messages.pop();
|
163 |
conversation.messages = [...conversation.messages];
|
@@ -201,16 +178,16 @@
|
|
201 |
|
202 |
function addCompareModel(modelId: ModelWithTokenizer["id"]) {
|
203 |
const model = $models.find(m => m.id === modelId);
|
204 |
-
if (!model || project.conversations.length === 2) {
|
205 |
return;
|
206 |
}
|
207 |
-
const newConversation = { ...JSON.parse(JSON.stringify(project.conversations[0])), model };
|
208 |
-
project.conversations = [
|
209 |
generationStats = [generationStats[0], { latency: 0, generatedTokensCount: 0 }];
|
210 |
}
|
211 |
|
212 |
function removeCompareModal(conversationIdx: number) {
|
213 |
-
project.conversations.splice(conversationIdx, 1)[0];
|
214 |
$session = $session;
|
215 |
generationStats.splice(conversationIdx, 1)[0];
|
216 |
generationStats = generationStats;
|
@@ -253,9 +230,9 @@
|
|
253 |
placeholder={systemPromptSupported
|
254 |
? "Enter a custom prompt"
|
255 |
: "System prompt is not supported with the chosen model."}
|
256 |
-
value={systemPromptSupported ? project.conversations[0].systemMessage.content : ""}
|
257 |
on:input={e => {
|
258 |
-
for (const conversation of project.conversations) {
|
259 |
conversation.systemMessage.content = e.currentTarget.value;
|
260 |
}
|
261 |
$session = $session;
|
@@ -268,7 +245,7 @@
|
|
268 |
<div
|
269 |
class="flex h-[calc(100dvh-5rem-120px)] divide-x divide-gray-200 overflow-x-auto overflow-y-hidden *:w-full max-sm:w-dvw md:h-[calc(100dvh-5rem)] md:pt-3 dark:divide-gray-800"
|
270 |
>
|
271 |
-
{#each project.conversations as conversation, conversationIdx}
|
272 |
<div class="max-sm:min-w-full">
|
273 |
{#if compareActive}
|
274 |
<PlaygroundConversationHeader
|
@@ -279,11 +256,9 @@
|
|
279 |
{/if}
|
280 |
<PlaygroundConversation
|
281 |
{loading}
|
282 |
-
|
283 |
{viewCode}
|
284 |
{compareActive}
|
285 |
-
on:addMessage={() => addMessage(conversationIdx)}
|
286 |
-
on:deleteMessage={e => deleteMessage(conversationIdx, e.detail)}
|
287 |
on:closeCode={() => (viewCode = false)}
|
288 |
/>
|
289 |
</div>
|
@@ -332,7 +307,7 @@
|
|
332 |
{#if loading}
|
333 |
<div class="flex flex-none items-center gap-[3px]">
|
334 |
<span class="mr-2">
|
335 |
-
{#if project.conversations[0].streaming || project.conversations[1]?.streaming}
|
336 |
Stop
|
337 |
{:else}
|
338 |
Cancel
|
@@ -367,7 +342,7 @@
|
|
367 |
class="flex flex-1 flex-col gap-6 overflow-y-hidden rounded-xl border border-gray-200/80 bg-white bg-linear-to-b from-white via-white p-3 shadow-xs dark:border-white/5 dark:bg-gray-900 dark:from-gray-800/40 dark:via-gray-800/40"
|
368 |
>
|
369 |
<div class="flex flex-col gap-2">
|
370 |
-
<ModelSelector bind:conversation={project.conversations[0]} />
|
371 |
<div class="flex items-center gap-2 self-end px-2 text-xs whitespace-nowrap">
|
372 |
<button
|
373 |
class="flex items-center gap-0.5 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"
|
@@ -377,7 +352,7 @@
|
|
377 |
Compare
|
378 |
</button>
|
379 |
<a
|
380 |
-
href="https://huggingface.co/{project.conversations[0].model.id}?inference_provider={project
|
381 |
.conversations[0].provider}"
|
382 |
target="_blank"
|
383 |
class="flex items-center gap-0.5 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"
|
@@ -388,7 +363,7 @@
|
|
388 |
</div>
|
389 |
</div>
|
390 |
|
391 |
-
<GenerationConfig bind:conversation={project.conversations[0]} />
|
392 |
{#if $token.value}
|
393 |
<button
|
394 |
on:click={token.reset}
|
@@ -447,7 +422,7 @@
|
|
447 |
|
448 |
{#if selectCompareModelOpen}
|
449 |
<ModelSelectorModal
|
450 |
-
conversation={project.conversations[0]}
|
451 |
on:modelSelected={e => addCompareModel(e.detail)}
|
452 |
on:close={() => (selectCompareModelOpen = false)}
|
453 |
/>
|
|
|
8 |
} from "./inferencePlaygroundUtils";
|
9 |
|
10 |
import { models } from "$lib/stores/models";
|
11 |
+
import { project, session } from "$lib/stores/session";
|
12 |
import { token } from "$lib/stores/token";
|
13 |
import { isMac } from "$lib/utils/platform";
|
14 |
import { HfInference } from "@huggingface/inference";
|
15 |
import { onDestroy } from "svelte";
|
16 |
+
import IconExternal from "~icons/carbon/arrow-up-right";
|
17 |
import IconCode from "~icons/carbon/code";
|
18 |
import IconCompare from "~icons/carbon/compare";
|
|
|
19 |
import IconInfo from "~icons/carbon/information";
|
20 |
+
import { default as IconDelete, default as IconThrashcan } from "~icons/carbon/trash-can";
|
21 |
import PlaygroundConversation from "./InferencePlaygroundConversation.svelte";
|
22 |
import PlaygroundConversationHeader from "./InferencePlaygroundConversationHeader.svelte";
|
23 |
import GenerationConfig from "./InferencePlaygroundGenerationConfig.svelte";
|
24 |
import HFTokenModal from "./InferencePlaygroundHFTokenModal.svelte";
|
25 |
import ModelSelector from "./InferencePlaygroundModelSelector.svelte";
|
26 |
import ModelSelectorModal from "./InferencePlaygroundModelSelectorModal.svelte";
|
|
|
27 |
import InferencePlaygroundProjectSelect from "./InferencePlaygroundProjectSelect.svelte";
|
28 |
|
29 |
const startMessageUser: ConversationMessage = { role: "user", content: "" };
|
30 |
|
|
|
|
|
|
|
31 |
let viewCode = false;
|
32 |
let viewSettings = false;
|
33 |
let loading = false;
|
|
|
39 |
latency: number;
|
40 |
generatedTokensCount: number;
|
41 |
}
|
42 |
+
let generationStats = $project.conversations.map(_ => ({ latency: 0, generatedTokensCount: 0 })) as
|
43 |
| [GenerationStatistics]
|
44 |
| [GenerationStatistics, GenerationStatistics];
|
45 |
|
46 |
+
$: systemPromptSupported = $project.conversations.some(conversation => isSystemPromptSupported(conversation.model));
|
47 |
+
$: compareActive = $project.conversations.length === 2;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
48 |
|
49 |
function reset() {
|
50 |
+
$project.conversations.map(conversation => {
|
51 |
conversation.systemMessage.content = "";
|
52 |
conversation.messages = [{ ...startMessageUser }];
|
53 |
});
|
|
|
117 |
return;
|
118 |
}
|
119 |
|
120 |
+
for (const [idx, conversation] of $project.conversations.entries()) {
|
121 |
if (conversation.messages.at(-1)?.role === "assistant") {
|
122 |
let prefix = "";
|
123 |
+
if ($project.conversations.length === 2) {
|
124 |
prefix = `Error on ${idx === 0 ? "left" : "right"} conversation. `;
|
125 |
}
|
126 |
return alert(`${prefix}Messages must alternate between user/assistant roles.`);
|
|
|
131 |
loading = true;
|
132 |
|
133 |
try {
|
134 |
+
const promises = $project.conversations.map((conversation, idx) => runInference(conversation, idx));
|
135 |
await Promise.all(promises);
|
136 |
} catch (error) {
|
137 |
+
for (const conversation of $project.conversations) {
|
138 |
if (conversation.messages.at(-1)?.role === "assistant" && !conversation.messages.at(-1)?.content?.trim()) {
|
139 |
conversation.messages.pop();
|
140 |
conversation.messages = [...conversation.messages];
|
|
|
178 |
|
179 |
function addCompareModel(modelId: ModelWithTokenizer["id"]) {
|
180 |
const model = $models.find(m => m.id === modelId);
|
181 |
+
if (!model || $project.conversations.length === 2) {
|
182 |
return;
|
183 |
}
|
184 |
+
const newConversation = { ...JSON.parse(JSON.stringify($project.conversations[0])), model };
|
185 |
+
$project.conversations = [...$project.conversations, newConversation];
|
186 |
generationStats = [generationStats[0], { latency: 0, generatedTokensCount: 0 }];
|
187 |
}
|
188 |
|
189 |
function removeCompareModal(conversationIdx: number) {
|
190 |
+
$project.conversations.splice(conversationIdx, 1)[0];
|
191 |
$session = $session;
|
192 |
generationStats.splice(conversationIdx, 1)[0];
|
193 |
generationStats = generationStats;
|
|
|
230 |
placeholder={systemPromptSupported
|
231 |
? "Enter a custom prompt"
|
232 |
: "System prompt is not supported with the chosen model."}
|
233 |
+
value={systemPromptSupported ? $project.conversations[0].systemMessage.content : ""}
|
234 |
on:input={e => {
|
235 |
+
for (const conversation of $project.conversations) {
|
236 |
conversation.systemMessage.content = e.currentTarget.value;
|
237 |
}
|
238 |
$session = $session;
|
|
|
245 |
<div
|
246 |
class="flex h-[calc(100dvh-5rem-120px)] divide-x divide-gray-200 overflow-x-auto overflow-y-hidden *:w-full max-sm:w-dvw md:h-[calc(100dvh-5rem)] md:pt-3 dark:divide-gray-800"
|
247 |
>
|
248 |
+
{#each $project.conversations as conversation, conversationIdx}
|
249 |
<div class="max-sm:min-w-full">
|
250 |
{#if compareActive}
|
251 |
<PlaygroundConversationHeader
|
|
|
256 |
{/if}
|
257 |
<PlaygroundConversation
|
258 |
{loading}
|
259 |
+
bind:conversation
|
260 |
{viewCode}
|
261 |
{compareActive}
|
|
|
|
|
262 |
on:closeCode={() => (viewCode = false)}
|
263 |
/>
|
264 |
</div>
|
|
|
307 |
{#if loading}
|
308 |
<div class="flex flex-none items-center gap-[3px]">
|
309 |
<span class="mr-2">
|
310 |
+
{#if $project.conversations[0].streaming || $project.conversations[1]?.streaming}
|
311 |
Stop
|
312 |
{:else}
|
313 |
Cancel
|
|
|
342 |
class="flex flex-1 flex-col gap-6 overflow-y-hidden rounded-xl border border-gray-200/80 bg-white bg-linear-to-b from-white via-white p-3 shadow-xs dark:border-white/5 dark:bg-gray-900 dark:from-gray-800/40 dark:via-gray-800/40"
|
343 |
>
|
344 |
<div class="flex flex-col gap-2">
|
345 |
+
<ModelSelector bind:conversation={$project.conversations[0]} />
|
346 |
<div class="flex items-center gap-2 self-end px-2 text-xs whitespace-nowrap">
|
347 |
<button
|
348 |
class="flex items-center gap-0.5 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"
|
|
|
352 |
Compare
|
353 |
</button>
|
354 |
<a
|
355 |
+
href="https://huggingface.co/{$project.conversations[0].model.id}?inference_provider={$project
|
356 |
.conversations[0].provider}"
|
357 |
target="_blank"
|
358 |
class="flex items-center gap-0.5 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"
|
|
|
363 |
</div>
|
364 |
</div>
|
365 |
|
366 |
+
<GenerationConfig bind:conversation={$project.conversations[0]} />
|
367 |
{#if $token.value}
|
368 |
<button
|
369 |
on:click={token.reset}
|
|
|
422 |
|
423 |
{#if selectCompareModelOpen}
|
424 |
<ModelSelectorModal
|
425 |
+
conversation={$project.conversations[0]}
|
426 |
on:modelSelected={e => addCompareModel(e.detail)}
|
427 |
on:close={() => (selectCompareModelOpen = false)}
|
428 |
/>
|
src/lib/components/InferencePlayground/InferencePlaygroundConversation.svelte
CHANGED
@@ -1,11 +1,11 @@
|
|
1 |
<script lang="ts">
|
2 |
import type { Conversation } from "$lib/types";
|
3 |
|
4 |
-
import {
|
5 |
|
|
|
6 |
import CodeSnippets from "./InferencePlaygroundCodeSnippets.svelte";
|
7 |
import Message from "./InferencePlaygroundMessage.svelte";
|
8 |
-
import IconPlus from "~icons/carbon/add";
|
9 |
|
10 |
export let conversation: Conversation;
|
11 |
export let loading: boolean;
|
@@ -16,11 +16,6 @@
|
|
16 |
let isProgrammaticScroll = true;
|
17 |
let conversationLength = conversation.messages.length;
|
18 |
|
19 |
-
const dispatch = createEventDispatcher<{
|
20 |
-
addMessage: void;
|
21 |
-
deleteMessage: number;
|
22 |
-
}>();
|
23 |
-
|
24 |
let messageContainer: HTMLDivElement | null = null;
|
25 |
|
26 |
async function resizeMessageTextAreas() {
|
@@ -60,6 +55,23 @@
|
|
60 |
}
|
61 |
|
62 |
$: viewCode, resizeMessageTextAreas();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
63 |
</script>
|
64 |
|
65 |
<svelte:window on:resize={resizeMessageTextAreas} />
|
@@ -82,24 +94,24 @@
|
|
82 |
{#each conversation.messages as message, messageIdx}
|
83 |
<Message
|
84 |
class="border-b"
|
85 |
-
|
86 |
{loading}
|
87 |
on:input={resizeMessageTextAreas}
|
88 |
-
on:delete={() =>
|
89 |
autofocus={!loading && messageIdx === conversation.messages.length - 1}
|
90 |
/>
|
91 |
{/each}
|
92 |
|
93 |
<button
|
94 |
class="flex px-3.5 py-6 hover:bg-gray-50 md:px-6 dark:hover:bg-gray-800/50"
|
95 |
-
on:click={
|
96 |
disabled={loading}
|
97 |
>
|
98 |
<div class="flex items-center gap-2 p-0! text-sm font-semibold">
|
99 |
<div class="text-lg">
|
100 |
<IconPlus />
|
101 |
</div>
|
102 |
-
|
103 |
</div>
|
104 |
</button>
|
105 |
{:else}
|
|
|
1 |
<script lang="ts">
|
2 |
import type { Conversation } from "$lib/types";
|
3 |
|
4 |
+
import { tick } from "svelte";
|
5 |
|
6 |
+
import IconPlus from "~icons/carbon/add";
|
7 |
import CodeSnippets from "./InferencePlaygroundCodeSnippets.svelte";
|
8 |
import Message from "./InferencePlaygroundMessage.svelte";
|
|
|
9 |
|
10 |
export let conversation: Conversation;
|
11 |
export let loading: boolean;
|
|
|
16 |
let isProgrammaticScroll = true;
|
17 |
let conversationLength = conversation.messages.length;
|
18 |
|
|
|
|
|
|
|
|
|
|
|
19 |
let messageContainer: HTMLDivElement | null = null;
|
20 |
|
21 |
async function resizeMessageTextAreas() {
|
|
|
55 |
}
|
56 |
|
57 |
$: viewCode, resizeMessageTextAreas();
|
58 |
+
|
59 |
+
function addMessage() {
|
60 |
+
const msgs = conversation.messages.slice();
|
61 |
+
conversation.messages = [
|
62 |
+
...msgs,
|
63 |
+
{
|
64 |
+
role: msgs.at(-1)?.role === "user" ? "assistant" : "user",
|
65 |
+
content: "",
|
66 |
+
},
|
67 |
+
];
|
68 |
+
conversation = conversation;
|
69 |
+
}
|
70 |
+
|
71 |
+
function deleteMessage(idx: number) {
|
72 |
+
conversation.messages.splice(idx, 1);
|
73 |
+
conversation = conversation;
|
74 |
+
}
|
75 |
</script>
|
76 |
|
77 |
<svelte:window on:resize={resizeMessageTextAreas} />
|
|
|
94 |
{#each conversation.messages as message, messageIdx}
|
95 |
<Message
|
96 |
class="border-b"
|
97 |
+
bind:message
|
98 |
{loading}
|
99 |
on:input={resizeMessageTextAreas}
|
100 |
+
on:delete={() => deleteMessage(messageIdx)}
|
101 |
autofocus={!loading && messageIdx === conversation.messages.length - 1}
|
102 |
/>
|
103 |
{/each}
|
104 |
|
105 |
<button
|
106 |
class="flex px-3.5 py-6 hover:bg-gray-50 md:px-6 dark:hover:bg-gray-800/50"
|
107 |
+
on:click={addMessage}
|
108 |
disabled={loading}
|
109 |
>
|
110 |
<div class="flex items-center gap-2 p-0! text-sm font-semibold">
|
111 |
<div class="text-lg">
|
112 |
<IconPlus />
|
113 |
</div>
|
114 |
+
Add message
|
115 |
</div>
|
116 |
</button>
|
117 |
{:else}
|
src/lib/components/InferencePlayground/InferencePlaygroundModelSelector.svelte
CHANGED
@@ -2,8 +2,8 @@
|
|
2 |
import type { Conversation, ModelWithTokenizer } from "$lib/types";
|
3 |
|
4 |
import { models } from "$lib/stores/models";
|
5 |
-
import Avatar from "../Avatar.svelte";
|
6 |
import IconCaret from "~icons/carbon/chevron-down";
|
|
|
7 |
import ModelSelectorModal from "./InferencePlaygroundModelSelectorModal.svelte";
|
8 |
import ProviderSelect from "./InferencePlaygroundProviderSelect.svelte";
|
9 |
import { defaultSystemMessage } from "./inferencePlaygroundUtils";
|
|
|
2 |
import type { Conversation, ModelWithTokenizer } from "$lib/types";
|
3 |
|
4 |
import { models } from "$lib/stores/models";
|
|
|
5 |
import IconCaret from "~icons/carbon/chevron-down";
|
6 |
+
import Avatar from "../Avatar.svelte";
|
7 |
import ModelSelectorModal from "./InferencePlaygroundModelSelectorModal.svelte";
|
8 |
import ProviderSelect from "./InferencePlaygroundProviderSelect.svelte";
|
9 |
import { defaultSystemMessage } from "./inferencePlaygroundUtils";
|
src/lib/components/InferencePlayground/InferencePlaygroundProjectSelect.svelte
CHANGED
@@ -3,15 +3,15 @@
|
|
3 |
import { cn } from "$lib/utils/cn";
|
4 |
import { createSelect, createSync } from "@melt-ui/svelte";
|
5 |
import IconCaret from "~icons/carbon/chevron-down";
|
6 |
-
import IconDelete from "~icons/carbon/trash-can";
|
7 |
import IconCross from "~icons/carbon/close";
|
8 |
-
import IconSave from "~icons/carbon/save";
|
9 |
import IconEdit from "~icons/carbon/edit";
|
|
|
|
|
10 |
|
11 |
let classNames: string = "";
|
12 |
export { classNames as class };
|
13 |
|
14 |
-
$: isDefault =
|
15 |
|
16 |
const {
|
17 |
elements: { trigger, menu, option },
|
|
|
3 |
import { cn } from "$lib/utils/cn";
|
4 |
import { createSelect, createSync } from "@melt-ui/svelte";
|
5 |
import IconCaret from "~icons/carbon/chevron-down";
|
|
|
6 |
import IconCross from "~icons/carbon/close";
|
|
|
7 |
import IconEdit from "~icons/carbon/edit";
|
8 |
+
import IconSave from "~icons/carbon/save";
|
9 |
+
import IconDelete from "~icons/carbon/trash-can";
|
10 |
|
11 |
let classNames: string = "";
|
12 |
export { classNames as class };
|
13 |
|
14 |
+
$: isDefault = $session.activeProjectId === "default";
|
15 |
|
16 |
const {
|
17 |
elements: { trigger, menu, option },
|
src/lib/stores/models.ts
CHANGED
@@ -3,6 +3,6 @@ import type { ModelWithTokenizer } from "$lib/types";
|
|
3 |
import { readable } from "svelte/store";
|
4 |
|
5 |
export const models = readable<ModelWithTokenizer[]>(undefined, set => {
|
6 |
-
const unsub = page.subscribe($p => set($p.data
|
7 |
return unsub;
|
8 |
});
|
|
|
3 |
import { readable } from "svelte/store";
|
4 |
|
5 |
export const models = readable<ModelWithTokenizer[]>(undefined, set => {
|
6 |
+
const unsub = page.subscribe($p => set($p.data?.models));
|
7 |
return unsub;
|
8 |
});
|
src/lib/stores/session.ts
CHANGED
@@ -42,12 +42,6 @@ function getDefaults() {
|
|
42 |
const featured = getTrending($models);
|
43 |
const defaultModel = featured[0] ?? $models[0] ?? emptyModel;
|
44 |
|
45 |
-
// Parse URL query parameters
|
46 |
-
const searchParams = new URLSearchParams(window.location.search);
|
47 |
-
const searchProviders = searchParams.getAll("provider");
|
48 |
-
const searchModelIds = searchParams.getAll("modelId");
|
49 |
-
const modelsFromSearch = searchModelIds.map(id => $models.find(model => model.id === id)).filter(Boolean);
|
50 |
-
|
51 |
const defaultConversation: Conversation = {
|
52 |
model: defaultModel,
|
53 |
config: { ...defaultGenerationConfig },
|
@@ -79,23 +73,37 @@ function createSessionStore() {
|
|
79 |
if (savedData) {
|
80 |
const parsed = safeParse(savedData);
|
81 |
const res = typia.validate<Session>(parsed);
|
82 |
-
if (res.success)
|
83 |
-
|
|
|
|
|
|
|
84 |
}
|
85 |
|
86 |
-
// Merge query params with savedSession
|
87 |
// Query params models and providers take precedence over savedSession's.
|
88 |
// In any case, we try to merge the two, and the amount of conversations
|
89 |
// is the maximum between the two.
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
99 |
|
100 |
set(savedSession);
|
101 |
});
|
@@ -139,7 +147,6 @@ function createSessionStore() {
|
|
139 |
});
|
140 |
};
|
141 |
|
142 |
-
// Override set method to use our custom update
|
143 |
const set: typeof store.set = (...args) => {
|
144 |
update(_ => args[0]);
|
145 |
};
|
@@ -195,7 +202,7 @@ function createSessionStore() {
|
|
195 |
}
|
196 |
|
197 |
const currProject = projects.find(p => p.id === s.activeProjectId);
|
198 |
-
const newSession = { ...s, projects, activeProjectId: currProject?.id ?? projects[0]?.id
|
199 |
return typia.is<Session>(newSession) ? newSession : s;
|
200 |
});
|
201 |
}
|
@@ -214,5 +221,31 @@ function createSessionStore() {
|
|
214 |
export const session = createSessionStore();
|
215 |
|
216 |
export function getActiveProject(s: Session) {
|
217 |
-
return s.projects.find(p => p.id === s.activeProjectId) ?? s.projects[0]
|
218 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
42 |
const featured = getTrending($models);
|
43 |
const defaultModel = featured[0] ?? $models[0] ?? emptyModel;
|
44 |
|
|
|
|
|
|
|
|
|
|
|
|
|
45 |
const defaultConversation: Conversation = {
|
46 |
model: defaultModel,
|
47 |
config: { ...defaultGenerationConfig },
|
|
|
73 |
if (savedData) {
|
74 |
const parsed = safeParse(savedData);
|
75 |
const res = typia.validate<Session>(parsed);
|
76 |
+
if (res.success) {
|
77 |
+
savedSession = parsed;
|
78 |
+
} else {
|
79 |
+
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(savedSession));
|
80 |
+
}
|
81 |
}
|
82 |
|
83 |
+
// Merge query params with savedSession's default project
|
84 |
// Query params models and providers take precedence over savedSession's.
|
85 |
// In any case, we try to merge the two, and the amount of conversations
|
86 |
// is the maximum between the two.
|
87 |
+
const dp = savedSession.projects.find(p => p.id === "default");
|
88 |
+
if (typia.is<DefaultProject>(dp)) {
|
89 |
+
const $models = get(models);
|
90 |
+
// Parse URL query parameters
|
91 |
+
const searchParams = new URLSearchParams(window.location.search);
|
92 |
+
const searchProviders = searchParams.getAll("provider");
|
93 |
+
const searchModelIds = searchParams.getAll("modelId");
|
94 |
+
const modelsFromSearch = searchModelIds.map(id => $models.find(model => model.id === id)).filter(Boolean);
|
95 |
+
if (modelsFromSearch.length > 0) savedSession.activeProjectId = "default";
|
96 |
+
|
97 |
+
const max = Math.max(dp.conversations.length, modelsFromSearch.length, searchProviders.length);
|
98 |
+
for (let i = 0; i < max; i++) {
|
99 |
+
const conversation = dp.conversations[i] ?? defaultConversation;
|
100 |
+
dp.conversations[i] = {
|
101 |
+
...conversation,
|
102 |
+
model: modelsFromSearch[i] ?? conversation.model,
|
103 |
+
provider: searchProviders[i] ?? conversation.provider,
|
104 |
+
};
|
105 |
+
}
|
106 |
+
}
|
107 |
|
108 |
set(savedSession);
|
109 |
});
|
|
|
147 |
});
|
148 |
};
|
149 |
|
|
|
150 |
const set: typeof store.set = (...args) => {
|
151 |
update(_ => args[0]);
|
152 |
};
|
|
|
202 |
}
|
203 |
|
204 |
const currProject = projects.find(p => p.id === s.activeProjectId);
|
205 |
+
const newSession = { ...s, projects, activeProjectId: currProject?.id ?? projects[0]?.id };
|
206 |
return typia.is<Session>(newSession) ? newSession : s;
|
207 |
});
|
208 |
}
|
|
|
221 |
export const session = createSessionStore();
|
222 |
|
223 |
export function getActiveProject(s: Session) {
|
224 |
+
return s.projects.find(p => p.id === s.activeProjectId) ?? s.projects[0];
|
225 |
}
|
226 |
+
|
227 |
+
function createProjectStore() {
|
228 |
+
const store = writable<Project>(undefined, set => {
|
229 |
+
return session.subscribe(s => {
|
230 |
+
set(getActiveProject(s));
|
231 |
+
});
|
232 |
+
});
|
233 |
+
|
234 |
+
const update: (typeof store)["update"] = cb => {
|
235 |
+
session.update(s => {
|
236 |
+
const project = getActiveProject(s);
|
237 |
+
const newProject = cb(project);
|
238 |
+
const projects = s.projects.map(p => (p.id === project.id ? newProject : p));
|
239 |
+
const newSession = { ...s, projects };
|
240 |
+
return typia.is<Session>(newSession) ? newSession : s;
|
241 |
+
});
|
242 |
+
};
|
243 |
+
|
244 |
+
const set: typeof store.set = (...args) => {
|
245 |
+
update(_ => args[0]);
|
246 |
+
};
|
247 |
+
|
248 |
+
return { ...store, update, set };
|
249 |
+
}
|
250 |
+
|
251 |
+
export const project = createProjectStore();
|