summaryrefslogtreecommitdiffhomepage
path: root/packages/frontend/src/lib/components/ToolCallDisplay.svelte
blob: 805de6da43bf9f2971503bd28e85cd088e69a293 (plain)
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
<script lang="ts">
import type { ToolCallDisplay } from "../types.js";

const { toolCall }: { toolCall: ToolCallDisplay } = $props();

let isExpanded = $state(false);

function toggle() {
	isExpanded = !isExpanded;
}

interface ShellResult {
	stdout: string;
	stderr: string;
	exitCode: number;
}

function parseShellResult(result: string): ShellResult | null {
	try {
		const parsed = JSON.parse(result) as unknown;
		if (
			parsed !== null &&
			typeof parsed === "object" &&
			"stdout" in parsed &&
			"stderr" in parsed &&
			"exitCode" in parsed
		) {
			return {
				stdout: String((parsed as Record<string, unknown>).stdout ?? ""),
				stderr: String((parsed as Record<string, unknown>).stderr ?? ""),
				exitCode: Number((parsed as Record<string, unknown>).exitCode ?? 0),
			};
		}
		return null;
	} catch {
		return null;
	}
}

const isShell = $derived(toolCall.name === "run_shell");
const shellResult = $derived(
	isShell && toolCall.result !== undefined ? parseShellResult(toolCall.result) : null,
);
</script>

<div class="collapse collapse-arrow mb-2 p-1 opacity-60 {isExpanded ? 'collapse-open' : ''}">
	<button
		type="button"
		class="collapse-title flex items-center gap-2 text-sm italic cursor-pointer w-full text-left"
		onclick={toggle}
		aria-expanded={isExpanded}
	>
		<span class="badge badge-neutral badge-sm">tool</span>
		<span class="font-mono">{toolCall.name}</span>
		{#if toolCall.result !== undefined}
			{#if isShell && shellResult !== null}
				<span class="badge badge-sm ml-auto {shellResult.exitCode === 0 ? 'badge-success' : 'badge-error'}">
					exit {shellResult.exitCode}
				</span>
			{:else if toolCall.isError}
				<span class="badge badge-error badge-sm ml-auto">error</span>
			{:else}
				<span class="badge badge-success badge-sm ml-auto">done</span>
			{/if}
		{:else}
			<span class="badge badge-warning badge-sm ml-auto">pending</span>
		{/if}
	</button>

	<div class="collapse-content text-xs">
		<div class="mt-2">
			<p class="font-semibold text-base-content/70 mb-1">Arguments</p>
			<pre class="bg-base-300 rounded p-2 overflow-auto max-h-40 whitespace-pre-wrap break-all">{JSON.stringify(toolCall.arguments, null, 2)}</pre>
		</div>
		{#if isShell && toolCall.result !== undefined}
			{#if shellResult !== null}
				<div class="mt-2">
					<p class="font-semibold text-base-content/70 mb-1">stdout:</p>
					<pre class="bg-base-300 rounded p-2 overflow-auto max-h-40 whitespace-pre-wrap break-all font-mono">{shellResult.stdout || "(empty)"}</pre>
				</div>
				{#if shellResult.stderr}
					<div class="mt-2">
						<p class="font-semibold text-error/80 mb-1">stderr:</p>
						<pre class="bg-error/10 text-error rounded p-2 overflow-auto max-h-40 whitespace-pre-wrap break-all font-mono">{shellResult.stderr}</pre>
					</div>
				{/if}
				<div class="mt-2 flex items-center gap-2">
					<span class="font-semibold text-base-content/70">exit code:</span>
					<span class="badge badge-sm {shellResult.exitCode === 0 ? 'badge-success' : 'badge-error'}">{shellResult.exitCode}</span>
				</div>
			{:else}
				<div class="mt-2">
					<p class="font-semibold text-base-content/70 mb-1">Result</p>
					<pre class="rounded p-2 overflow-auto max-h-40 whitespace-pre-wrap break-all {toolCall.isError ? 'bg-error/20 text-error' : 'bg-base-300'}">{toolCall.result}</pre>
				</div>
			{/if}
		{:else if isShell && toolCall.shellOutput}
			{#if toolCall.shellOutput.stdout}
				<div class="mt-2">
					<p class="font-semibold text-base-content/70 mb-1">stdout</p>
					<pre class="bg-base-300 rounded p-2 overflow-auto max-h-40 whitespace-pre-wrap break-all text-xs">{toolCall.shellOutput.stdout}</pre>
				</div>
			{/if}
			{#if toolCall.shellOutput.stderr}
				<div class="mt-2">
					<p class="font-semibold text-error/70 mb-1">stderr</p>
					<pre class="bg-error/10 rounded p-2 overflow-auto max-h-40 whitespace-pre-wrap break-all text-xs text-error">{toolCall.shellOutput.stderr}</pre>
				</div>
			{/if}
			<span class="text-xs text-base-content/50 italic">Running...</span>
		{:else if toolCall.result !== undefined}
			<div class="mt-2">
				<p class="font-semibold text-base-content/70 mb-1">Result</p>
				<pre
					class="rounded p-2 overflow-auto max-h-40 whitespace-pre-wrap break-all {toolCall.isError
						? 'bg-error/20 text-error'
						: 'bg-base-300'}">{toolCall.result}</pre>
			</div>
		{/if}
	</div>
</div>