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