Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
Add a loading bar for tools (#1188)
Browse files* Add a loading bar for tools
* move css to right file
* lighter purple
and no rounding on the loading bar
* Update src/routes/+layout.server.ts
Co-authored-by: Mishig <[email protected]>
* Clear animation when done (#1191)
* Clear animation when done
* make the bar go to the end
---------
Co-authored-by: Nathan Sarrazin <[email protected]>
---------
Co-authored-by: Victor Mustar <[email protected]>
Co-authored-by: Mishig <[email protected]>
src/lib/components/chat/ChatMessage.svelte
CHANGED
@@ -16,30 +16,22 @@
|
|
16 |
import CarbonPen from "~icons/carbon/pen";
|
17 |
import CarbonChevronLeft from "~icons/carbon/chevron-left";
|
18 |
import CarbonChevronRight from "~icons/carbon/chevron-right";
|
19 |
-
import CarbonTools from "~icons/carbon/tools";
|
20 |
import { PUBLIC_SEP_TOKEN } from "$lib/constants/publicSepToken";
|
21 |
import type { Model } from "$lib/types/Model";
|
22 |
import UploadedFile from "./UploadedFile.svelte";
|
23 |
|
24 |
import OpenWebSearchResults from "../OpenWebSearchResults.svelte";
|
25 |
import {
|
26 |
-
MessageToolUpdateType,
|
27 |
MessageWebSearchUpdateType,
|
28 |
type MessageToolUpdate,
|
29 |
type MessageWebSearchSourcesUpdate,
|
30 |
type MessageWebSearchUpdate,
|
31 |
} from "$lib/types/MessageUpdate";
|
32 |
-
import {
|
33 |
-
isMessageToolCallUpdate,
|
34 |
-
isMessageToolResultUpdate,
|
35 |
-
isMessageToolErrorUpdate,
|
36 |
-
} from "$lib/utils/messageUpdates";
|
37 |
-
import type { ToolFront } from "$lib/types/Tool";
|
38 |
import { base } from "$app/paths";
|
39 |
import { useConvTreeStore } from "$lib/stores/convTree";
|
40 |
import { isReducedMotion } from "$lib/utils/isReduceMotion";
|
41 |
import Modal from "../Modal.svelte";
|
42 |
-
import
|
43 |
|
44 |
function sanitizeMd(md: string) {
|
45 |
let ret = md
|
@@ -221,8 +213,6 @@
|
|
221 |
$: if (message.children?.length === 0) $convTreeStore.leaf = message.id;
|
222 |
|
223 |
$: modalImageToShow = null as MessageFile | null;
|
224 |
-
|
225 |
-
const availableTools: ToolFront[] = $page.data.tools;
|
226 |
</script>
|
227 |
|
228 |
{#if modalImageToShow}
|
@@ -301,72 +291,7 @@
|
|
301 |
{#if toolUpdates}
|
302 |
{#each Object.values(toolUpdates) as tool}
|
303 |
{#if tool.length}
|
304 |
-
{
|
305 |
-
{@const toolError = tool.some(isMessageToolErrorUpdate)}
|
306 |
-
{@const toolDone = tool.some(isMessageToolResultUpdate)}
|
307 |
-
{#if toolName && toolName !== "websearch"}
|
308 |
-
<details
|
309 |
-
class="group/tool my-2.5 w-fit cursor-pointer rounded-lg border border-gray-200 bg-white pl-1 pr-2.5 text-sm shadow-sm transition-all open:mb-3
|
310 |
-
open:border-purple-500/10 open:bg-purple-600/5 open:shadow-sm dark:border-gray-800 dark:bg-gray-900 open:dark:border-purple-800/40 open:dark:bg-purple-800/10"
|
311 |
-
>
|
312 |
-
<summary
|
313 |
-
class="flex select-none list-none items-center gap-1.5 py-1 group-open/tool:text-purple-700 group-open/tool:dark:text-purple-300"
|
314 |
-
>
|
315 |
-
<div
|
316 |
-
class="relative grid size-[22px] place-items-center rounded bg-purple-600/10 dark:bg-purple-600/20"
|
317 |
-
>
|
318 |
-
<svg
|
319 |
-
class="absolute inset-0 text-purple-500/40 transition-opacity"
|
320 |
-
class:invisible={toolDone || toolError}
|
321 |
-
width="22"
|
322 |
-
height="22"
|
323 |
-
viewBox="0 0 38 38"
|
324 |
-
fill="none"
|
325 |
-
xmlns="http://www.w3.org/2000/svg"
|
326 |
-
>
|
327 |
-
<path
|
328 |
-
class="loading-path"
|
329 |
-
d="M8 2.5H30C30 2.5 35.5 2.5 35.5 8V30C35.5 30 35.5 35.5 30 35.5H8C8 35.5 2.5 35.5 2.5 30V8C2.5 8 2.5 2.5 8 2.5Z"
|
330 |
-
stroke="currentColor"
|
331 |
-
stroke-width="1"
|
332 |
-
stroke-linecap="round"
|
333 |
-
id="shape"
|
334 |
-
/>
|
335 |
-
</svg>
|
336 |
-
<CarbonTools class="text-xs text-purple-700 dark:text-purple-500" />
|
337 |
-
</div>
|
338 |
-
|
339 |
-
<span>
|
340 |
-
{toolError ? "Error calling" : toolDone ? "Called" : "Calling"} tool
|
341 |
-
<span class="font-semibold"
|
342 |
-
>{availableTools.find((el) => toolHasName(toolName, el))?.displayName}</span
|
343 |
-
>
|
344 |
-
</span>
|
345 |
-
</summary>
|
346 |
-
{#each tool as toolUpdate}
|
347 |
-
{#if toolUpdate.subtype === MessageToolUpdateType.Call}
|
348 |
-
<div class="mt-1 flex items-center gap-2 opacity-80">
|
349 |
-
<h3 class="text-sm">Parameters</h3>
|
350 |
-
<div class="h-px flex-1 bg-gradient-to-r from-gray-500/20" />
|
351 |
-
</div>
|
352 |
-
<ul class="py-1 text-sm">
|
353 |
-
{#each Object.entries(toolUpdate.call.parameters ?? {}) as [k, v]}
|
354 |
-
<li>
|
355 |
-
<span class="font-semibold">{k}</span>:
|
356 |
-
<span>{v}</span>
|
357 |
-
</li>
|
358 |
-
{/each}
|
359 |
-
</ul>
|
360 |
-
{:else if toolUpdate.subtype === MessageToolUpdateType.Error}
|
361 |
-
<div class="mt-1 flex items-center gap-2 opacity-80">
|
362 |
-
<h3 class="text-sm">Error</h3>
|
363 |
-
<div class="h-px flex-1 bg-gradient-to-r from-gray-500/20" />
|
364 |
-
</div>
|
365 |
-
<p class="text-sm">{toolUpdate.message}</p>
|
366 |
-
{/if}
|
367 |
-
{/each}
|
368 |
-
</details>
|
369 |
-
{/if}
|
370 |
{/if}
|
371 |
{/each}
|
372 |
{/if}
|
@@ -617,15 +542,6 @@
|
|
617 |
{/if}
|
618 |
|
619 |
<style>
|
620 |
-
details summary::-webkit-details-marker {
|
621 |
-
display: none;
|
622 |
-
}
|
623 |
-
|
624 |
-
.loading-path {
|
625 |
-
stroke-dasharray: 61.45;
|
626 |
-
animation: loading 2s linear infinite;
|
627 |
-
}
|
628 |
-
|
629 |
@keyframes loading {
|
630 |
to {
|
631 |
stroke-dashoffset: 122.9;
|
|
|
16 |
import CarbonPen from "~icons/carbon/pen";
|
17 |
import CarbonChevronLeft from "~icons/carbon/chevron-left";
|
18 |
import CarbonChevronRight from "~icons/carbon/chevron-right";
|
|
|
19 |
import { PUBLIC_SEP_TOKEN } from "$lib/constants/publicSepToken";
|
20 |
import type { Model } from "$lib/types/Model";
|
21 |
import UploadedFile from "./UploadedFile.svelte";
|
22 |
|
23 |
import OpenWebSearchResults from "../OpenWebSearchResults.svelte";
|
24 |
import {
|
|
|
25 |
MessageWebSearchUpdateType,
|
26 |
type MessageToolUpdate,
|
27 |
type MessageWebSearchSourcesUpdate,
|
28 |
type MessageWebSearchUpdate,
|
29 |
} from "$lib/types/MessageUpdate";
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
import { base } from "$app/paths";
|
31 |
import { useConvTreeStore } from "$lib/stores/convTree";
|
32 |
import { isReducedMotion } from "$lib/utils/isReduceMotion";
|
33 |
import Modal from "../Modal.svelte";
|
34 |
+
import ToolUpdate from "./ToolUpdate.svelte";
|
35 |
|
36 |
function sanitizeMd(md: string) {
|
37 |
let ret = md
|
|
|
213 |
$: if (message.children?.length === 0) $convTreeStore.leaf = message.id;
|
214 |
|
215 |
$: modalImageToShow = null as MessageFile | null;
|
|
|
|
|
216 |
</script>
|
217 |
|
218 |
{#if modalImageToShow}
|
|
|
291 |
{#if toolUpdates}
|
292 |
{#each Object.values(toolUpdates) as tool}
|
293 |
{#if tool.length}
|
294 |
+
<ToolUpdate {tool} {loading} />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
295 |
{/if}
|
296 |
{/each}
|
297 |
{/if}
|
|
|
542 |
{/if}
|
543 |
|
544 |
<style>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
545 |
@keyframes loading {
|
546 |
to {
|
547 |
stroke-dashoffset: 122.9;
|
src/lib/components/chat/ToolUpdate.svelte
ADDED
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import { MessageToolUpdateType, type MessageToolUpdate } from "$lib/types/MessageUpdate";
|
3 |
+
import {
|
4 |
+
isMessageToolCallUpdate,
|
5 |
+
isMessageToolErrorUpdate,
|
6 |
+
isMessageToolResultUpdate,
|
7 |
+
} from "$lib/utils/messageUpdates";
|
8 |
+
|
9 |
+
import CarbonTools from "~icons/carbon/tools";
|
10 |
+
import { toolHasName } from "$lib/utils/tools";
|
11 |
+
import type { ToolFront } from "$lib/types/Tool";
|
12 |
+
import { page } from "$app/stores";
|
13 |
+
import { onMount } from "svelte";
|
14 |
+
|
15 |
+
export let tool: MessageToolUpdate[];
|
16 |
+
export let loading: boolean = false;
|
17 |
+
|
18 |
+
const toolName = tool.find(isMessageToolCallUpdate)?.call.name;
|
19 |
+
const toolError = tool.some(isMessageToolErrorUpdate);
|
20 |
+
$: toolDone = tool.some(isMessageToolResultUpdate);
|
21 |
+
|
22 |
+
const availableTools: ToolFront[] = $page.data.tools;
|
23 |
+
|
24 |
+
let loadingBarEl: HTMLDivElement;
|
25 |
+
let animation: Animation | undefined = undefined;
|
26 |
+
|
27 |
+
let isShowingLoadingBar = false;
|
28 |
+
onMount(() => {
|
29 |
+
if (!toolDone && loading) {
|
30 |
+
loadingBarEl.classList.remove("hidden");
|
31 |
+
isShowingLoadingBar = true;
|
32 |
+
animation = loadingBarEl.animate([{ width: "0%" }, { width: "calc(100%+1rem)" }], {
|
33 |
+
duration: availableTools.find((tool) => tool.name === toolName)?.timeToUseMS,
|
34 |
+
fill: "forwards",
|
35 |
+
});
|
36 |
+
}
|
37 |
+
return () => animation?.cancel();
|
38 |
+
});
|
39 |
+
|
40 |
+
// go to 100% quickly if loading is done
|
41 |
+
$: (!loading || toolDone) &&
|
42 |
+
isShowingLoadingBar &&
|
43 |
+
(() => {
|
44 |
+
isShowingLoadingBar = false;
|
45 |
+
|
46 |
+
loadingBarEl.classList.remove("hidden");
|
47 |
+
|
48 |
+
animation?.cancel();
|
49 |
+
animation = loadingBarEl.animate(
|
50 |
+
[{ width: loadingBarEl.style.width }, { width: "calc(100%+1rem)" }],
|
51 |
+
{
|
52 |
+
duration: 300,
|
53 |
+
fill: "forwards",
|
54 |
+
}
|
55 |
+
);
|
56 |
+
|
57 |
+
setTimeout(() => {
|
58 |
+
loadingBarEl.classList.add("hidden");
|
59 |
+
}, 300);
|
60 |
+
})();
|
61 |
+
</script>
|
62 |
+
|
63 |
+
{#if toolName && toolName !== "websearch"}
|
64 |
+
<details
|
65 |
+
class="group/tool my-2.5 w-fit cursor-pointer rounded-lg border border-gray-200 bg-white pl-1 pr-2.5 text-sm shadow-sm transition-all open:mb-3
|
66 |
+
open:border-purple-500/10 open:bg-purple-600/5 open:shadow-sm dark:border-gray-800 dark:bg-gray-900 open:dark:border-purple-800/40 open:dark:bg-purple-800/10"
|
67 |
+
>
|
68 |
+
<summary
|
69 |
+
class="relative flex select-none list-none items-center gap-1.5 py-1 group-open/tool:text-purple-700 group-open/tool:dark:text-purple-300"
|
70 |
+
>
|
71 |
+
<div
|
72 |
+
bind:this={loadingBarEl}
|
73 |
+
class="absolute -m-1 hidden h-full w-[calc(100%+1rem)] rounded-lg bg-purple-500/5 transition-all dark:bg-purple-500/10"
|
74 |
+
/>
|
75 |
+
|
76 |
+
<div
|
77 |
+
class="relative grid size-[22px] place-items-center rounded bg-purple-600/10 dark:bg-purple-600/20"
|
78 |
+
>
|
79 |
+
<svg
|
80 |
+
class="absolute inset-0 text-purple-500/40 transition-opacity"
|
81 |
+
class:invisible={toolDone || toolError}
|
82 |
+
width="22"
|
83 |
+
height="22"
|
84 |
+
viewBox="0 0 38 38"
|
85 |
+
fill="none"
|
86 |
+
xmlns="http://www.w3.org/2000/svg"
|
87 |
+
>
|
88 |
+
<path
|
89 |
+
class="loading-path"
|
90 |
+
d="M8 2.5H30C30 2.5 35.5 2.5 35.5 8V30C35.5 30 35.5 35.5 30 35.5H8C8 35.5 2.5 35.5 2.5 30V8C2.5 8 2.5 2.5 8 2.5Z"
|
91 |
+
stroke="currentColor"
|
92 |
+
stroke-width="1"
|
93 |
+
stroke-linecap="round"
|
94 |
+
id="shape"
|
95 |
+
/>
|
96 |
+
</svg>
|
97 |
+
<CarbonTools class="text-xs text-purple-700 dark:text-purple-500" />
|
98 |
+
</div>
|
99 |
+
|
100 |
+
<span>
|
101 |
+
{toolError ? "Error calling" : toolDone ? "Called" : "Calling"} tool
|
102 |
+
<span class="font-semibold"
|
103 |
+
>{availableTools.find((el) => toolHasName(toolName, el))?.displayName}</span
|
104 |
+
>
|
105 |
+
</span>
|
106 |
+
</summary>
|
107 |
+
{#each tool as toolUpdate}
|
108 |
+
{#if toolUpdate.subtype === MessageToolUpdateType.Call}
|
109 |
+
<div class="mt-1 flex items-center gap-2 opacity-80">
|
110 |
+
<h3 class="text-sm">Parameters</h3>
|
111 |
+
<div class="h-px flex-1 bg-gradient-to-r from-gray-500/20" />
|
112 |
+
</div>
|
113 |
+
<ul class="py-1 text-sm">
|
114 |
+
{#each Object.entries(toolUpdate.call.parameters ?? {}) as [k, v]}
|
115 |
+
<li>
|
116 |
+
<span class="font-semibold">{k}</span>:
|
117 |
+
<span>{v}</span>
|
118 |
+
</li>
|
119 |
+
{/each}
|
120 |
+
</ul>
|
121 |
+
{:else if toolUpdate.subtype === MessageToolUpdateType.Error}
|
122 |
+
<div class="mt-1 flex items-center gap-2 opacity-80">
|
123 |
+
<h3 class="text-sm">Error</h3>
|
124 |
+
<div class="h-px flex-1 bg-gradient-to-r from-gray-500/20" />
|
125 |
+
</div>
|
126 |
+
<p class="text-sm">{toolUpdate.message}</p>
|
127 |
+
{/if}
|
128 |
+
{/each}
|
129 |
+
</details>
|
130 |
+
{/if}
|
131 |
+
|
132 |
+
<style>
|
133 |
+
details summary::-webkit-details-marker {
|
134 |
+
display: none;
|
135 |
+
}
|
136 |
+
|
137 |
+
.loading-path {
|
138 |
+
stroke-dasharray: 61.45;
|
139 |
+
animation: loading 2s linear infinite;
|
140 |
+
}
|
141 |
+
</style>
|
src/lib/types/Tool.ts
CHANGED
@@ -25,7 +25,7 @@ export interface Tool {
|
|
25 |
export type ToolFront = Pick<
|
26 |
Tool,
|
27 |
"name" | "displayName" | "description" | "isOnByDefault" | "isLocked"
|
28 |
-
|
29 |
|
30 |
export enum ToolResultStatus {
|
31 |
Success = "success",
|
|
|
25 |
export type ToolFront = Pick<
|
26 |
Tool,
|
27 |
"name" | "displayName" | "description" | "isOnByDefault" | "isLocked"
|
28 |
+
> & { timeToUseMS?: number };
|
29 |
|
30 |
export enum ToolResultStatus {
|
31 |
Success = "success",
|
src/routes/+layout.server.ts
CHANGED
@@ -9,6 +9,7 @@ import { env } from "$env/dynamic/private";
|
|
9 |
import { ObjectId } from "mongodb";
|
10 |
import type { ConvSidebar } from "$lib/types/ConvSidebar";
|
11 |
import { allTools } from "$lib/server/tools";
|
|
|
12 |
|
13 |
export const load: LayoutServerLoad = async ({ locals, depends }) => {
|
14 |
depends(UrlDependency.ConversationList);
|
@@ -105,6 +106,7 @@ export const load: LayoutServerLoad = async ({ locals, depends }) => {
|
|
105 |
}
|
106 |
}
|
107 |
|
|
|
108 |
return {
|
109 |
conversations: conversations.map((conv) => {
|
110 |
if (settings?.hideEmojiOnSidebar) {
|
@@ -172,6 +174,9 @@ export const load: LayoutServerLoad = async ({ locals, depends }) => {
|
|
172 |
description: tool.description,
|
173 |
isOnByDefault: tool.isOnByDefault,
|
174 |
isLocked: tool.isLocked,
|
|
|
|
|
|
|
175 |
})),
|
176 |
assistants: assistants
|
177 |
.filter((el) => userAssistantsSet.has(el._id.toString()))
|
|
|
9 |
import { ObjectId } from "mongodb";
|
10 |
import type { ConvSidebar } from "$lib/types/ConvSidebar";
|
11 |
import { allTools } from "$lib/server/tools";
|
12 |
+
import { MetricsServer } from "$lib/server/metrics";
|
13 |
|
14 |
export const load: LayoutServerLoad = async ({ locals, depends }) => {
|
15 |
depends(UrlDependency.ConversationList);
|
|
|
106 |
}
|
107 |
}
|
108 |
|
109 |
+
const toolUseDuration = (await MetricsServer.getMetrics().tool.toolUseDuration.get()).values;
|
110 |
return {
|
111 |
conversations: conversations.map((conv) => {
|
112 |
if (settings?.hideEmojiOnSidebar) {
|
|
|
174 |
description: tool.description,
|
175 |
isOnByDefault: tool.isOnByDefault,
|
176 |
isLocked: tool.isLocked,
|
177 |
+
timeToUseMS:
|
178 |
+
toolUseDuration.find((el) => el.labels.tool === tool.name && el.labels.quantile === 0.9)
|
179 |
+
?.value ?? 15_000,
|
180 |
})),
|
181 |
assistants: assistants
|
182 |
.filter((el) => userAssistantsSet.has(el._id.toString()))
|