]> git.djapps.eu Git - pkg/ggml/sources/llama.cpp/blob
c43495b6c7d32a8cbb6f230292ec165da930d69d
[pkg/ggml/sources/llama.cpp] /
1 <script lang="ts" generics="TMeta">
2 // Generic chrome shell shared by every per-tool block under
3 // `ChatMessageToolCall/`. Owns:
4 // - the collapsible wrapper (defaults to CollapsibleContentBlock;
5 // `exec_shell_command` swaps in CollapsibleTerminalBlock via the
6 // `wrapper` prop);
7 // - the icon, spinner state, and MCP favicon fallback chain;
8 // - the status subtitle pill.
9 // Components supply only their `meta`, a title snippet, and a body
10 // snippet - everything around them is this single source of truth.
11
12 import { Loader2, Wrench } from '@lucide/svelte';
13 import { CollapsibleContentBlock } from '$lib/components/app';
14 import { ICON_CLASS_DEFAULT, ICON_CLASS_SPIN } from '$lib/constants';
15 import { AgenticSectionType } from '$lib/enums';
16 import { mcpStore } from '$lib/stores/mcp.svelte';
17 import { type AgenticSection, type BuiltinToolUiEntry, getBuiltinToolUi } from '$lib/utils';
18 import type { Component, Snippet } from 'svelte';
19
20 type ToolCallBlockMetaWithError = TMeta & { errorMessage?: string };
21
22 interface ToolCallCtx {
23 isStreaming: boolean;
24 isPending: boolean;
25 isStreamingCall: boolean;
26 isCodeStreaming: boolean;
27 }
28
29 interface Props {
30 section: AgenticSection;
31 open: boolean;
32 isStreaming: boolean;
33 /**
34 * The per-tool meta, including any `errorMessage` field that the
35 * shared chrome uses to compute the status pill subtitle.
36 */
37 meta: ToolCallBlockMetaWithError | null | undefined;
38 /**
39 * True while the tool's process is actively producing output
40 * chunks after its args finished streaming (used by
41 * `exec_shell_command`'s stdout feed).
42 */
43 extraLiveStreaming?: boolean;
44 /**
45 * Swap the title-row icon for a spinning `Loader2` while the
46 * spinner is showing. Only meaningful for tools where "live"
47 * is interesting (e.g. exec_shell_command showing the in-flight
48 * process). Other tools leave it off and render the spinner
49 * inline within the body.
50 */
51 spinIconWhenActive?: boolean;
52 /**
53 * Wrapper component that renders the title row and the body
54 * children. Defaults to CollapsibleContentBlock;
55 * `exec_shell_command` uses CollapsibleTerminalBlock for its
56 * terminal-style frame.
57 */
58 wrapper?: typeof CollapsibleContentBlock;
59 title?: string;
60 titleSnippet?: Snippet;
61 onToggle?: () => void;
62 children: Snippet<[TMeta | null | undefined, ToolCallCtx]>;
63 }
64
65 let {
66 children,
67 extraLiveStreaming = false,
68 isStreaming,
69 meta,
70 onToggle,
71 open,
72 section,
73 spinIconWhenActive = false,
74 title,
75 titleSnippet,
76 wrapper: Wrapper = CollapsibleContentBlock
77 }: Props = $props();
78
79 const isPending = $derived(section.type === AgenticSectionType.TOOL_CALL_PENDING);
80 const isStreamingCall = $derived(section.type === AgenticSectionType.TOOL_CALL_STREAMING);
81 const showSpinner = $derived(isPending || (isStreamingCall && isStreaming) || extraLiveStreaming);
82 const isCodeStreaming = $derived(isStreaming && (isPending || isStreamingCall));
83
84 const toolUi: BuiltinToolUiEntry | null = $derived(getBuiltinToolUi(section.toolName));
85 const toolIcon: Component = $derived(
86 spinIconWhenActive && showSpinner ? Loader2 : (toolUi?.icon ?? Wrench)
87 );
88 const toolIconClass = $derived(
89 spinIconWhenActive && showSpinner ? ICON_CLASS_SPIN : ICON_CLASS_DEFAULT
90 );
91 // Drop the MCP favicon while the spinner is on so the title row
92 // signals "in flight" without being overwritten by server branding.
93 const mcpServerFavicon = $derived(
94 showSpinner ? null : mcpStore.getServerFaviconForTool(section.toolName)
95 );
96 const iconUrl = $derived(
97 showSpinner || (toolUi?.icon ?? null) || !mcpServerFavicon ? null : mcpServerFavicon
98 );
99
100 // No subtitle while the call is in flight - the spinner already
101 // signals activity; only terminal states get a pill.
102 function subtitleFor(errorMessage?: string): string | undefined {
103 if (showSpinner) return undefined;
104
105 if (errorMessage) return 'failed';
106
107 if (isStreamingCall && !isStreaming) return 'incomplete';
108
109 return undefined;
110 }
111
112 const subtitle = $derived(subtitleFor(meta?.errorMessage));
113 </script>
114
115 <Wrapper
116 {open}
117 class="my-2"
118 icon={toolIcon}
119 iconClass={toolIconClass}
120 {iconUrl}
121 {title}
122 {titleSnippet}
123 {subtitle}
124 {onToggle}
125 >
126 {@render children(meta, {
127 isCodeStreaming,
128 isPending,
129 isStreaming,
130 isStreamingCall
131 })}
132 </Wrapper>