summaryrefslogtreecommitdiffhomepage
path: root/src/features/chat/ui/ToolCard.svelte
diff options
context:
space:
mode:
Diffstat (limited to 'src/features/chat/ui/ToolCard.svelte')
-rw-r--r--src/features/chat/ui/ToolCard.svelte111
1 files changed, 111 insertions, 0 deletions
diff --git a/src/features/chat/ui/ToolCard.svelte b/src/features/chat/ui/ToolCard.svelte
new file mode 100644
index 0000000..8c6e325
--- /dev/null
+++ b/src/features/chat/ui/ToolCard.svelte
@@ -0,0 +1,111 @@
+<script lang="ts">
+ import type { ToolEntry } from "../index";
+ import { viewToolEntry } from "../logic/tool-summary";
+ import LazyCollapse from "./LazyCollapse.svelte";
+
+ let { entry }: { entry: ToolEntry } = $props();
+ const card = $derived(viewToolEntry(entry));
+ let showAll = $state(false);
+</script>
+
+<LazyCollapse label={`Toggle tool call ${card.name}`}>
+ {#snippet title()}
+ {#if card.status === "pending"}
+ <span class="loading loading-spinner loading-xs shrink-0" aria-label="Running"></span>
+ {:else if card.status === "error"}
+ <span class="shrink-0 text-error" aria-label="Failed">✗</span>
+ {:else}
+ <span class="shrink-0 text-success" aria-label="Succeeded">✓</span>
+ {/if}
+ <svg
+ class="h-4 w-4 shrink-0 opacity-60"
+ xmlns="http://www.w3.org/2000/svg"
+ viewBox="0 0 24 24"
+ fill="none"
+ stroke="currentColor"
+ stroke-width="2"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ aria-hidden="true"
+ >
+ <path
+ d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"
+ />
+ </svg>
+ <span class="max-w-[35%] shrink-0 truncate" title={card.name}>{card.name}</span>
+ <span class="min-w-0 flex-1 truncate opacity-70" title={card.callSummary}
+ >{card.callSummary}</span
+ >
+ {#each card.callBadges as badge}
+ <span class="shrink-0 text-xs opacity-70">{badge}</span>
+ {/each}
+ {#if card.resultSummary !== null}
+ <span
+ class="max-w-[40%] shrink-0 truncate text-xs"
+ class:text-error={card.status === "error"}
+ title={card.resultSummary}
+ >
+ → {card.resultSummary}
+ </span>
+ {/if}
+ {/snippet}
+ <div class="min-w-0 space-y-3 text-xs">
+ <section aria-label="Call">
+ <div class="mb-1 font-medium opacity-60">Call</div>
+ {#if card.callDetail.kind === "command"}
+ <pre class="overflow-x-auto">$ {card.callDetail.text}</pre>
+ {:else if card.callDetail.kind === "edit"}
+ <div class="mb-1 break-all font-mono">{card.callDetail.path}</div>
+ <div class="space-y-1">
+ <div class="opacity-60">− old</div>
+ <pre class="overflow-x-auto text-error">{card.callDetail.oldText}</pre>
+ <div class="opacity-60">+ new</div>
+ <pre class="overflow-x-auto text-success">{card.callDetail.newText}</pre>
+ </div>
+ {:else}
+ <dl class="space-y-1">
+ {#each card.callDetail.fields as field}
+ <div>
+ <dt class="opacity-60">{field.key}</dt>
+ <dd>
+ <pre
+ class="overflow-x-auto"
+ class:whitespace-pre-wrap={!field.multiline}>{field.value}</pre>
+ </dd>
+ </div>
+ {/each}
+ </dl>
+ {/if}
+ </section>
+ <section aria-label="Response">
+ <div class="mb-1 font-medium opacity-60">Response</div>
+ {#if card.output === null}
+ <span class="opacity-60">running…</span>
+ {:else}
+ <div class:text-error={card.output.isError}>
+ {#if card.output.kind === "full"}
+ <pre class="overflow-x-auto">{card.output.lines.join("\n") || "(no output)"}</pre>
+ {:else}
+ {#if showAll}
+ <pre class="overflow-x-auto">{card.output.all.join("\n")}</pre>
+ {:else}
+ <pre class="overflow-x-auto">{card.output.head.join("\n")}</pre>
+ {/if}
+ <button
+ class="btn btn-ghost btn-xs my-1"
+ aria-expanded={showAll}
+ onclick={() => {
+ showAll = !showAll;
+ }}
+ >
+ {showAll ? "Show fewer lines" : `⋯ ${card.output.hiddenCount} more lines`}
+ </button>
+ {#if !showAll}
+ <pre class="overflow-x-auto">{card.output.tail.join("\n")}</pre>
+ {/if}
+ {/if}
+ </div>
+ {/if}
+ </section>
+ </div>
+</LazyCollapse>