File size: 4,467 Bytes
2e21e16
 
 
 
 
 
 
 
 
 
 
 
 
f940e40
2e21e16
 
 
 
 
586e800
2e21e16
 
 
 
 
 
 
 
 
586e800
2e21e16
 
 
 
 
 
 
 
 
 
 
586e800
f940e40
 
2e21e16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
<script lang="ts">
	import { MessageToolUpdateType, type MessageToolUpdate } from "$lib/types/MessageUpdate";
	import {
		isMessageToolCallUpdate,
		isMessageToolErrorUpdate,
		isMessageToolResultUpdate,
	} from "$lib/utils/messageUpdates";

	import CarbonTools from "~icons/carbon/tools";
	import { toolHasName } from "$lib/utils/tools";
	import type { ToolFront } from "$lib/types/Tool";
	import { page } from "$app/stores";
	import { onMount } from "svelte";
	import { browser } from "$app/environment";

	export let tool: MessageToolUpdate[];
	export let loading: boolean = false;

	const toolName = tool.find(isMessageToolCallUpdate)?.call.name;
	$: toolError = tool.some(isMessageToolErrorUpdate);
	$: toolDone = tool.some(isMessageToolResultUpdate);

	const availableTools: ToolFront[] = $page.data.tools;

	let loadingBarEl: HTMLDivElement;
	let animation: Animation | undefined = undefined;

	let isShowingLoadingBar = false;
	onMount(() => {
		if (!toolError && !toolDone && loading && loadingBarEl) {
			loadingBarEl.classList.remove("hidden");
			isShowingLoadingBar = true;
			animation = loadingBarEl.animate([{ width: "0%" }, { width: "calc(100%+1rem)" }], {
				duration: availableTools.find((tool) => tool.name === toolName)?.timeToUseMS,
				fill: "forwards",
			});
		}
		return () => animation?.cancel();
	});

	// go to 100% quickly if loading is done
	$: (!loading || toolDone || toolError) &&
		browser &&
		loadingBarEl &&
		isShowingLoadingBar &&
		(() => {
			isShowingLoadingBar = false;

			loadingBarEl.classList.remove("hidden");

			animation?.cancel();
			animation = loadingBarEl.animate(
				[{ width: loadingBarEl.style.width }, { width: "calc(100%+1rem)" }],
				{
					duration: 300,
					fill: "forwards",
				}
			);

			setTimeout(() => {
				loadingBarEl.classList.add("hidden");
			}, 300);
		})();
</script>

{#if toolName && toolName !== "websearch"}
	<details
		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
        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"
	>
		<summary
			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"
		>
			<div
				bind:this={loadingBarEl}
				class="absolute -m-1 hidden h-full w-[calc(100%+1rem)] rounded-lg bg-purple-500/5 transition-all dark:bg-purple-500/10"
			/>

			<div
				class="relative grid size-[22px] place-items-center rounded bg-purple-600/10 dark:bg-purple-600/20"
			>
				<svg
					class="absolute inset-0 text-purple-500/40 transition-opacity"
					class:invisible={toolDone || toolError}
					width="22"
					height="22"
					viewBox="0 0 38 38"
					fill="none"
					xmlns="http://www.w3.org/2000/svg"
				>
					<path
						class="loading-path"
						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"
						stroke="currentColor"
						stroke-width="1"
						stroke-linecap="round"
						id="shape"
					/>
				</svg>
				<CarbonTools class="text-xs text-purple-700 dark:text-purple-500" />
			</div>

			<span>
				{toolError ? "Error calling" : toolDone ? "Called" : "Calling"} tool
				<span class="font-semibold"
					>{availableTools.find((el) => toolHasName(toolName, el))?.displayName}</span
				>
			</span>
		</summary>
		{#each tool as toolUpdate}
			{#if toolUpdate.subtype === MessageToolUpdateType.Call}
				<div class="mt-1 flex items-center gap-2 opacity-80">
					<h3 class="text-sm">Parameters</h3>
					<div class="h-px flex-1 bg-gradient-to-r from-gray-500/20" />
				</div>
				<ul class="py-1 text-sm">
					{#each Object.entries(toolUpdate.call.parameters ?? {}) as [k, v]}
						<li>
							<span class="font-semibold">{k}</span>:
							<span>{v}</span>
						</li>
					{/each}
				</ul>
			{:else if toolUpdate.subtype === MessageToolUpdateType.Error}
				<div class="mt-1 flex items-center gap-2 opacity-80">
					<h3 class="text-sm">Error</h3>
					<div class="h-px flex-1 bg-gradient-to-r from-gray-500/20" />
				</div>
				<p class="text-sm">{toolUpdate.message}</p>
			{/if}
		{/each}
	</details>
{/if}

<style>
	details summary::-webkit-details-marker {
		display: none;
	}

	.loading-path {
		stroke-dasharray: 61.45;
		animation: loading 2s linear infinite;
	}
</style>