generated from nhcarrigan/template
feat: restyle tool calls as collapsible blocks matching thinking block aesthetic
This commit is contained in:
@@ -7,6 +7,7 @@
|
|||||||
import Markdown from "./Markdown.svelte";
|
import Markdown from "./Markdown.svelte";
|
||||||
import HighlightedText from "./HighlightedText.svelte";
|
import HighlightedText from "./HighlightedText.svelte";
|
||||||
import ThinkingBlock from "./ThinkingBlock.svelte";
|
import ThinkingBlock from "./ThinkingBlock.svelte";
|
||||||
|
import ToolCallBlock from "./ToolCallBlock.svelte";
|
||||||
import { searchState, searchQuery } from "$lib/stores/search";
|
import { searchState, searchQuery } from "$lib/stores/search";
|
||||||
import { clipboardStore } from "$lib/stores/clipboard";
|
import { clipboardStore } from "$lib/stores/clipboard";
|
||||||
import { shouldHidePaths, maskPaths, showThinkingBlocks } from "$lib/stores/config";
|
import { shouldHidePaths, maskPaths, showThinkingBlocks } from "$lib/stores/config";
|
||||||
@@ -208,22 +209,6 @@
|
|||||||
if (!currentConversationId) return;
|
if (!currentConversationId) return;
|
||||||
await invoke("send_prompt", { conversationId: currentConversationId, message: "/compact" });
|
await invoke("send_prompt", { conversationId: currentConversationId, message: "/compact" });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collapsible tool lines
|
|
||||||
const TOOL_COLLAPSE_THRESHOLD = 60;
|
|
||||||
let expandedToolLines: Record<string, boolean> = {};
|
|
||||||
|
|
||||||
function isToolContentLong(content: string): boolean {
|
|
||||||
return content.length > TOOL_COLLAPSE_THRESHOLD;
|
|
||||||
}
|
|
||||||
|
|
||||||
function truncateToolContent(content: string): string {
|
|
||||||
return content.slice(0, TOOL_COLLAPSE_THRESHOLD) + "…";
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleToolLine(id: string) {
|
|
||||||
expandedToolLines = { ...expandedToolLines, [id]: !expandedToolLines[id] };
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -258,6 +243,18 @@
|
|||||||
{#if showThinking}
|
{#if showThinking}
|
||||||
<ThinkingBlock content={line.content} timestamp={line.timestamp} />
|
<ThinkingBlock content={line.content} timestamp={line.timestamp} />
|
||||||
{/if}
|
{/if}
|
||||||
|
{:else if line.type === "tool"}
|
||||||
|
<div
|
||||||
|
style={line.parentToolUseId
|
||||||
|
? "margin-left: 16px; padding-left: 8px; border-left: 2px solid var(--accent-primary);"
|
||||||
|
: ""}
|
||||||
|
>
|
||||||
|
<ToolCallBlock
|
||||||
|
toolName={line.toolName ?? null}
|
||||||
|
content={maskPaths(line.content, hidePaths)}
|
||||||
|
timestamp={line.timestamp}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div
|
<div
|
||||||
class="terminal-line mb-2 {getLineClass(line.type)} relative group"
|
class="terminal-line mb-2 {getLineClass(line.type)} relative group"
|
||||||
@@ -296,9 +293,6 @@
|
|||||||
{#if getLinePrefix(line.type)}
|
{#if getLinePrefix(line.type)}
|
||||||
<span class="terminal-prefix mr-2">{getLinePrefix(line.type)}</span>
|
<span class="terminal-prefix mr-2">{getLinePrefix(line.type)}</span>
|
||||||
{/if}
|
{/if}
|
||||||
{#if line.toolName}
|
|
||||||
<span class="terminal-tool-name mr-2">[{line.toolName}]</span>
|
|
||||||
{/if}
|
|
||||||
{#if line.type === "compact-prompt"}
|
{#if line.type === "compact-prompt"}
|
||||||
<button class="compact-action-btn" onclick={handleCompact}>
|
<button class="compact-action-btn" onclick={handleCompact}>
|
||||||
⚡ Compact Conversation
|
⚡ Compact Conversation
|
||||||
@@ -330,22 +324,6 @@
|
|||||||
<span class="copy-text">{copiedMessageId === line.id ? "Copied!" : "Copy"}</span>
|
<span class="copy-text">{copiedMessageId === line.id ? "Copied!" : "Copy"}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{:else if line.type === "tool" && isToolContentLong(maskPaths(line.content, hidePaths))}
|
|
||||||
<span class="tool-collapsible">
|
|
||||||
<HighlightedText
|
|
||||||
content={expandedToolLines[line.id]
|
|
||||||
? maskPaths(line.content, hidePaths)
|
|
||||||
: truncateToolContent(maskPaths(line.content, hidePaths))}
|
|
||||||
searchQuery={currentSearchQuery}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
class="tool-toggle-btn"
|
|
||||||
onclick={() => toggleToolLine(line.id)}
|
|
||||||
title={expandedToolLines[line.id] ? "Collapse" : "Expand to see full content"}
|
|
||||||
>
|
|
||||||
{expandedToolLines[line.id] ? "▲" : "▼"}
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
{:else}
|
{:else}
|
||||||
<HighlightedText
|
<HighlightedText
|
||||||
content={maskPaths(line.content, hidePaths)}
|
content={maskPaths(line.content, hidePaths)}
|
||||||
@@ -501,28 +479,4 @@
|
|||||||
.terminal-line {
|
.terminal-line {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tool-collapsible {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: baseline;
|
|
||||||
gap: 0.4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tool-toggle-btn {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
color: var(--text-tertiary, #6b7280);
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 0.7em;
|
|
||||||
padding: 0;
|
|
||||||
line-height: 1;
|
|
||||||
opacity: 0.7;
|
|
||||||
transition: opacity 0.15s ease;
|
|
||||||
font-family: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tool-toggle-btn:hover {
|
|
||||||
opacity: 1;
|
|
||||||
color: var(--terminal-tool, #c084fc);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
interface Props {
|
||||||
|
toolName: string | null;
|
||||||
|
content: string;
|
||||||
|
timestamp: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { toolName, content, timestamp }: Props = $props();
|
||||||
|
let isExpanded = $state(false);
|
||||||
|
|
||||||
|
function toggleExpanded() {
|
||||||
|
isExpanded = !isExpanded;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTime(date: Date): string {
|
||||||
|
return date.toLocaleTimeString("en-US", {
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="tool-block">
|
||||||
|
<button class="tool-header" onclick={toggleExpanded} type="button">
|
||||||
|
<span class="tool-timestamp">{formatTime(timestamp)}</span>
|
||||||
|
<svg
|
||||||
|
class="tool-icon"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
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>
|
||||||
|
{#if toolName}
|
||||||
|
<span class="tool-name">[{toolName}]</span>
|
||||||
|
{/if}
|
||||||
|
<span class="tool-label">{content}</span>
|
||||||
|
<svg
|
||||||
|
class="chevron"
|
||||||
|
class:expanded={isExpanded}
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="14"
|
||||||
|
height="14"
|
||||||
|
>
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{#if isExpanded}
|
||||||
|
<div class="tool-content">
|
||||||
|
{content}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.tool-block {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--terminal-tool, #c084fc);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-header:hover {
|
||||||
|
background: var(--bg-primary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-timestamp {
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
opacity: 0.7;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-name {
|
||||||
|
font-family: monospace;
|
||||||
|
font-weight: 600;
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: var(--terminal-tool-name, #ddd6fe);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-label {
|
||||||
|
flex: 1;
|
||||||
|
text-align: left;
|
||||||
|
font-style: italic;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chevron {
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: transform 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chevron.expanded {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-content {
|
||||||
|
padding: 0.75rem;
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
color: var(--terminal-tool, #c084fc);
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-style: italic;
|
||||||
|
line-height: 1.5;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user