@springbrand/message-panel 0.1.3-alpha.34 → 0.1.3-alpha.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cloud-os/README.md +2 -1
- package/cloud-os/assets/thinking-star.svg +9 -0
- package/cloud-os/chat/cloud-os-chat-messages.tsx +262 -77
- package/cloud-os/chat/code-runner.tsx +106 -0
- package/cloud-os/chat/range.ts +8 -0
- package/cloud-os/chat/rich-blocks.tsx +161 -260
- package/cloud-os/chat/surfaces.tsx +90 -0
- package/cloud-os/chat/tool-call.tsx +94 -0
- package/cloud-os/chat/tool-presentation.ts +3 -11
- package/cloud-os/chat/tool-rows.tsx +145 -141
- package/cloud-os/chat/tool-timeline.tsx +123 -0
- package/cloud-os/chat/transcript-model.ts +92 -15
- package/cloud-os/chat/web-search.tsx +135 -0
- package/cloud-os/composer/cloud-os-chat-input.tsx +1 -1
- package/cloud-os/index.ts +10 -0
- package/cloud-os/primitives/collapsible.tsx +21 -0
- package/cloud-os/styles/cloud-os.css +18 -0
- package/package.json +3 -1
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ComponentProps } from "react";
|
|
4
|
+
import { useLayoutEffect, useRef, useState } from "react";
|
|
5
|
+
import { cn } from "../internal/cn";
|
|
6
|
+
|
|
7
|
+
export const paper = "bg-background border border-border/60 dark:bg-popover";
|
|
8
|
+
|
|
9
|
+
export const field = "bg-foreground/[0.04] dark:bg-foreground/[0.06]";
|
|
10
|
+
|
|
11
|
+
export const ghostButton =
|
|
12
|
+
"flex items-center justify-center rounded-full text-foreground/45 outline-none transition-[background-color,color,scale] duration-150 hover:bg-foreground/[0.06] hover:text-foreground/90 active:scale-[0.96] focus-visible:ring-1 focus-visible:ring-foreground/20 motion-reduce:transition-none dark:hover:bg-foreground/[0.09]";
|
|
13
|
+
|
|
14
|
+
export const labelSwap =
|
|
15
|
+
"col-start-1 row-start-1 flex w-max items-center gap-1.5 leading-none transition-[opacity,filter] duration-300 ease-[cubic-bezier(0.23,1,0.32,1)] motion-reduce:transition-none";
|
|
16
|
+
|
|
17
|
+
export const labelSwapIn = "opacity-100 blur-none";
|
|
18
|
+
|
|
19
|
+
export const labelSwapOut =
|
|
20
|
+
"pointer-events-none select-none opacity-0 blur-[2px]";
|
|
21
|
+
|
|
22
|
+
export const collapsePanel =
|
|
23
|
+
"h-(--collapsible-panel-height) overflow-hidden transition-[height] duration-200 ease-[cubic-bezier(0.32,0.72,0,1)] data-[ending-style]:h-0 data-[starting-style]:h-0 motion-reduce:transition-none";
|
|
24
|
+
|
|
25
|
+
export const mono = "font-mono text-[11px] tracking-tight";
|
|
26
|
+
|
|
27
|
+
export const codeScroll = "max-h-48 overflow-auto";
|
|
28
|
+
|
|
29
|
+
export const codeSurface = "w-max min-w-full";
|
|
30
|
+
|
|
31
|
+
export function ShimmerLabel({
|
|
32
|
+
active = true,
|
|
33
|
+
className,
|
|
34
|
+
...props
|
|
35
|
+
}: ComponentProps<"span"> & { active?: boolean }) {
|
|
36
|
+
return (
|
|
37
|
+
<span
|
|
38
|
+
className={cn(active && "shimmer motion-reduce:animate-none", className)}
|
|
39
|
+
{...props}
|
|
40
|
+
/>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function SwapLabel({
|
|
45
|
+
active,
|
|
46
|
+
children,
|
|
47
|
+
className,
|
|
48
|
+
}: {
|
|
49
|
+
active: 0 | 1;
|
|
50
|
+
children: [React.ReactNode, React.ReactNode];
|
|
51
|
+
className?: string;
|
|
52
|
+
}) {
|
|
53
|
+
const layers = [useRef<HTMLSpanElement>(null), useRef<HTMLSpanElement>(null)];
|
|
54
|
+
const [width, setWidth] = useState<number | null>(null);
|
|
55
|
+
|
|
56
|
+
useLayoutEffect(() => {
|
|
57
|
+
const target = layers[active]?.current;
|
|
58
|
+
if (!target) return undefined;
|
|
59
|
+
const measure = () =>
|
|
60
|
+
setWidth(Math.ceil(target.getBoundingClientRect().width));
|
|
61
|
+
measure();
|
|
62
|
+
const observer = new ResizeObserver(measure);
|
|
63
|
+
observer.observe(target);
|
|
64
|
+
return () => observer.disconnect();
|
|
65
|
+
}, [active]);
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<span
|
|
69
|
+
style={width === null ? undefined : { width }}
|
|
70
|
+
className={cn(
|
|
71
|
+
"grid overflow-x-clip transition-[width] duration-300 ease-[cubic-bezier(0.23,1,0.32,1)] motion-reduce:transition-none",
|
|
72
|
+
className,
|
|
73
|
+
)}
|
|
74
|
+
>
|
|
75
|
+
{children.map((layer, index) => (
|
|
76
|
+
<span
|
|
77
|
+
key={index}
|
|
78
|
+
ref={layers[index]}
|
|
79
|
+
aria-hidden={active !== index}
|
|
80
|
+
className={cn(
|
|
81
|
+
labelSwap,
|
|
82
|
+
active === index ? labelSwapIn : labelSwapOut,
|
|
83
|
+
)}
|
|
84
|
+
>
|
|
85
|
+
{layer}
|
|
86
|
+
</span>
|
|
87
|
+
))}
|
|
88
|
+
</span>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { CheckIcon, ChevronRightIcon } from "lucide-react";
|
|
4
|
+
import {
|
|
5
|
+
Collapsible,
|
|
6
|
+
CollapsibleContent,
|
|
7
|
+
CollapsibleTrigger,
|
|
8
|
+
} from "../primitives/collapsible";
|
|
9
|
+
import { cn } from "../internal/cn";
|
|
10
|
+
import {
|
|
11
|
+
collapsePanel,
|
|
12
|
+
field,
|
|
13
|
+
mono,
|
|
14
|
+
ShimmerLabel,
|
|
15
|
+
SwapLabel,
|
|
16
|
+
} from "./surfaces";
|
|
17
|
+
|
|
18
|
+
export interface ToolCallProps {
|
|
19
|
+
label: string;
|
|
20
|
+
activeLabel: string;
|
|
21
|
+
query: string;
|
|
22
|
+
result: string;
|
|
23
|
+
running: boolean;
|
|
24
|
+
open: boolean;
|
|
25
|
+
onOpenChange: (open: boolean) => void;
|
|
26
|
+
className?: string;
|
|
27
|
+
children?: React.ReactNode;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function ToolCall({
|
|
31
|
+
label,
|
|
32
|
+
activeLabel,
|
|
33
|
+
query,
|
|
34
|
+
result,
|
|
35
|
+
running,
|
|
36
|
+
open,
|
|
37
|
+
onOpenChange,
|
|
38
|
+
className,
|
|
39
|
+
children,
|
|
40
|
+
}: ToolCallProps) {
|
|
41
|
+
return (
|
|
42
|
+
<Collapsible
|
|
43
|
+
data-slot="tool-call"
|
|
44
|
+
open={open}
|
|
45
|
+
onOpenChange={onOpenChange}
|
|
46
|
+
className={cn("w-full max-w-sm", className)}
|
|
47
|
+
>
|
|
48
|
+
<CollapsibleTrigger className="group/trigger text-foreground/55 hover:text-foreground/90 flex items-center gap-2 rounded-md py-1 text-[13.5px] transition-colors outline-none">
|
|
49
|
+
<ChevronRightIcon className="size-3.5 shrink-0 opacity-60 transition-transform duration-200 ease-[cubic-bezier(0.32,0.72,0,1)] group-data-open/trigger:rotate-90 group-data-panel-open/trigger:rotate-90 motion-reduce:transition-none" />
|
|
50
|
+
<SwapLabel active={running ? 0 : 1} className="text-start">
|
|
51
|
+
<ShimmerLabel
|
|
52
|
+
active={running}
|
|
53
|
+
className="relative inline-block leading-none"
|
|
54
|
+
>
|
|
55
|
+
{activeLabel}
|
|
56
|
+
</ShimmerLabel>
|
|
57
|
+
<>{label}</>
|
|
58
|
+
</SwapLabel>
|
|
59
|
+
{query && (
|
|
60
|
+
<span
|
|
61
|
+
className={cn(
|
|
62
|
+
mono,
|
|
63
|
+
"bg-foreground/[0.06] text-foreground/70 rounded-md px-1.5 py-0.5",
|
|
64
|
+
)}
|
|
65
|
+
>
|
|
66
|
+
{query}
|
|
67
|
+
</span>
|
|
68
|
+
)}
|
|
69
|
+
<span className="ms-auto flex w-4 items-center justify-end">
|
|
70
|
+
{!running && (
|
|
71
|
+
<CheckIcon className="fade-in zoom-in-90 animate-in size-3.5 text-emerald-500 duration-200" />
|
|
72
|
+
)}
|
|
73
|
+
</span>
|
|
74
|
+
</CollapsibleTrigger>
|
|
75
|
+
<CollapsibleContent className={cn(collapsePanel, "outline-none")}>
|
|
76
|
+
{children ? (
|
|
77
|
+
<div className="mt-2">{children}</div>
|
|
78
|
+
) : (
|
|
79
|
+
<div className={cn(field, "mt-2 overflow-hidden rounded-2xl text-xs")}>
|
|
80
|
+
<div className="px-3.5 py-2.5">
|
|
81
|
+
<p className={cn(mono, "text-foreground/35 mb-1")}>Result</p>
|
|
82
|
+
<p
|
|
83
|
+
className="text-foreground/90 overflow-auto leading-4"
|
|
84
|
+
style={{ maxHeight: "5rem" }}
|
|
85
|
+
>
|
|
86
|
+
{result}
|
|
87
|
+
</p>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
)}
|
|
91
|
+
</CollapsibleContent>
|
|
92
|
+
</Collapsible>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
@@ -213,7 +213,7 @@ export function canonicalToolKind(name: string): CloudOsToolKind {
|
|
|
213
213
|
return "web-fetch";
|
|
214
214
|
}
|
|
215
215
|
if (
|
|
216
|
-
["javascript", "js_exec", "node_repl", "execute_javascript", "execute_code"].includes(
|
|
216
|
+
["javascript", "js_exec", "node_repl", "execute", "execute_javascript", "execute_code"].includes(
|
|
217
217
|
normalized,
|
|
218
218
|
)
|
|
219
219
|
) {
|
|
@@ -329,16 +329,8 @@ export function getToolCallSummary(call: CloudOsToolCall): {
|
|
|
329
329
|
target: url ? hostnameOf(url) : undefined,
|
|
330
330
|
};
|
|
331
331
|
}
|
|
332
|
-
case "javascript":
|
|
333
|
-
|
|
334
|
-
.split("\n")
|
|
335
|
-
.map((line) => line.trim())
|
|
336
|
-
.find((line) => line.length > 0);
|
|
337
|
-
return {
|
|
338
|
-
verb: running ? "Running code" : "Ran code",
|
|
339
|
-
target: firstLine ? truncate(firstLine, 60) : undefined,
|
|
340
|
-
};
|
|
341
|
-
}
|
|
332
|
+
case "javascript":
|
|
333
|
+
return { verb: running ? "Running code" : "Ran code" };
|
|
342
334
|
case "tasks":
|
|
343
335
|
return { verb: running ? "Updating plan" : "Updated plan" };
|
|
344
336
|
case "agent": {
|
|
@@ -1,15 +1,29 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { memo, useEffect, useId, useState } from "react";
|
|
2
|
+
import { CodeRunner } from "./code-runner";
|
|
3
3
|
import { MarkdownMessage } from "./markdown-message";
|
|
4
|
+
import { ToolCall } from "./tool-call";
|
|
5
|
+
import { ToolTimeline } from "./tool-timeline";
|
|
6
|
+
import { WebSearchToolCall } from "./web-search";
|
|
4
7
|
import {
|
|
5
8
|
getToolCallSummary,
|
|
6
|
-
getToolIcon,
|
|
7
9
|
normalizeOutputText,
|
|
10
|
+
safeStringify,
|
|
8
11
|
type CloudOsToolCall,
|
|
9
12
|
type PhosphorIcon,
|
|
10
13
|
type ToolCallGroup,
|
|
11
14
|
} from "./tool-presentation";
|
|
12
15
|
|
|
16
|
+
const DISCLOSURE_TITLE_BASE =
|
|
17
|
+
"flex min-w-0 cursor-pointer gap-2 rounded-xl text-left text-[14px] leading-[1.4] text-kumo-inactive transition-colors duration-150 ease-out hover:text-kumo-subtle focus-visible:text-kumo-subtle focus-visible:outline-none active:scale-[0.995]";
|
|
18
|
+
|
|
19
|
+
export const DISCLOSURE_TITLE_WITH_ICON =
|
|
20
|
+
`${DISCLOSURE_TITLE_BASE} items-start py-1`;
|
|
21
|
+
|
|
22
|
+
export const DISCLOSURE_ICON =
|
|
23
|
+
"mt-0.5 flex size-4 shrink-0 items-center justify-center";
|
|
24
|
+
|
|
25
|
+
const thinkingStar = new URL("../assets/thinking-star.svg", import.meta.url).href;
|
|
26
|
+
|
|
13
27
|
/**
|
|
14
28
|
* 工作行(work rows): WorkIcon / ToolCallDetails / NestedToolCallRow /
|
|
15
29
|
* ToolGroupRow / ThinkingTraceRow。渲染 UIMessage 归一化后的 CloudOsToolCall。
|
|
@@ -34,28 +48,18 @@ function displayOutput(value: unknown, fallback: string): string {
|
|
|
34
48
|
return displayValue(record);
|
|
35
49
|
}
|
|
36
50
|
|
|
37
|
-
function
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
aria-label="Copy output"
|
|
50
|
-
>
|
|
51
|
-
<Copy size={16} />
|
|
52
|
-
</button>
|
|
53
|
-
</div>
|
|
54
|
-
<pre className="mt-2 max-h-56 overflow-auto border-t border-kumo-line/70 pt-3 font-sans text-[14px] font-normal leading-5 whitespace-pre-wrap">
|
|
55
|
-
{value}
|
|
56
|
-
</pre>
|
|
57
|
-
</div>
|
|
58
|
-
);
|
|
51
|
+
function toolCallView(tc: CloudOsToolCall) {
|
|
52
|
+
const settled = getToolCallSummary({ ...tc, running: false });
|
|
53
|
+
const active = getToolCallSummary({ ...tc, running: true });
|
|
54
|
+
return {
|
|
55
|
+
label: settled.verb,
|
|
56
|
+
activeLabel: active.verb,
|
|
57
|
+
query: settled.target ?? "",
|
|
58
|
+
result:
|
|
59
|
+
tc.errorText ||
|
|
60
|
+
displayOutput(tc.output, tc.outputText) ||
|
|
61
|
+
(tc.running ? "Running…" : "No additional data"),
|
|
62
|
+
};
|
|
59
63
|
}
|
|
60
64
|
|
|
61
65
|
export const ToolCallDetails = memo(function ToolCallDetails({
|
|
@@ -63,21 +67,13 @@ export const ToolCallDetails = memo(function ToolCallDetails({
|
|
|
63
67
|
}: {
|
|
64
68
|
toolCall: CloudOsToolCall;
|
|
65
69
|
}) {
|
|
66
|
-
const output = displayOutput(tc.output, tc.outputText);
|
|
67
70
|
return (
|
|
68
|
-
<
|
|
69
|
-
{tc
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
{output && <OutputPre value={output} />}
|
|
75
|
-
{!output && !tc.errorText && (
|
|
76
|
-
<p className="m-0 text-[12px] leading-4 text-kumo-inactive">
|
|
77
|
-
No additional data
|
|
78
|
-
</p>
|
|
79
|
-
)}
|
|
80
|
-
</div>
|
|
71
|
+
<ToolCall
|
|
72
|
+
{...toolCallView(tc)}
|
|
73
|
+
running={tc.running}
|
|
74
|
+
open
|
|
75
|
+
onOpenChange={() => undefined}
|
|
76
|
+
/>
|
|
81
77
|
);
|
|
82
78
|
});
|
|
83
79
|
|
|
@@ -90,42 +86,49 @@ export const NestedToolCallRow = memo(function NestedToolCallRow({
|
|
|
90
86
|
open: boolean;
|
|
91
87
|
onToggle: (key: string) => void;
|
|
92
88
|
}) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const Icon = getToolIcon(tc.kind);
|
|
89
|
+
if (tc.kind === "web-search" && !tc.failed) {
|
|
90
|
+
return <WebSearchToolCall toolCall={tc} />;
|
|
91
|
+
}
|
|
97
92
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
93
|
+
if (tc.kind === "javascript") {
|
|
94
|
+
const code = typeof tc.input.code === "string"
|
|
95
|
+
? tc.input.code
|
|
96
|
+
: safeStringify(tc.input);
|
|
97
|
+
const output =
|
|
98
|
+
tc.errorText || displayOutput(tc.output, tc.outputText);
|
|
99
|
+
const key = `call-${tc.toolCallId}`;
|
|
100
|
+
return (
|
|
101
|
+
<ToolCall
|
|
102
|
+
{...toolCallView(tc)}
|
|
103
|
+
running={tc.running}
|
|
104
|
+
open={open}
|
|
105
|
+
onOpenChange={(next) => {
|
|
106
|
+
if (next !== open) onToggle(key);
|
|
107
|
+
}}
|
|
108
|
+
className="max-w-none"
|
|
105
109
|
>
|
|
106
|
-
<
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
{
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
</div>
|
|
110
|
+
<CodeRunner
|
|
111
|
+
language="javascript"
|
|
112
|
+
code={code}
|
|
113
|
+
state={tc.running ? "running" : tc.failed ? "error" : "ok"}
|
|
114
|
+
output={output ? output.split(/\r?\n/) : []}
|
|
115
|
+
className="max-w-none"
|
|
116
|
+
/>
|
|
117
|
+
</ToolCall>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const key = `call-${tc.toolCallId}`;
|
|
122
|
+
return (
|
|
123
|
+
<ToolCall
|
|
124
|
+
{...toolCallView(tc)}
|
|
125
|
+
running={tc.running}
|
|
126
|
+
open={open}
|
|
127
|
+
onOpenChange={(next) => {
|
|
128
|
+
if (next !== open) onToggle(key);
|
|
129
|
+
}}
|
|
130
|
+
className="max-w-none"
|
|
131
|
+
/>
|
|
129
132
|
);
|
|
130
133
|
});
|
|
131
134
|
|
|
@@ -149,21 +152,14 @@ export const ThinkingTraceRow = memo(function ThinkingTraceRow({
|
|
|
149
152
|
<button
|
|
150
153
|
type="button"
|
|
151
154
|
onClick={() => setOpen((current) => !current)}
|
|
152
|
-
className=
|
|
155
|
+
className={`${DISCLOSURE_TITLE_WITH_ICON} group w-fit max-w-[80%]`}
|
|
153
156
|
aria-expanded={open}
|
|
154
157
|
aria-controls={contentId}
|
|
155
158
|
>
|
|
156
|
-
<span className=
|
|
157
|
-
<
|
|
158
|
-
size={14}
|
|
159
|
-
className="transition-opacity group-hover:opacity-0 group-focus-visible:opacity-0"
|
|
160
|
-
/>
|
|
161
|
-
<CaretDown
|
|
162
|
-
size={14}
|
|
163
|
-
className={`absolute opacity-0 transition-[opacity,transform] group-hover:opacity-100 group-focus-visible:opacity-100 ${open ? "" : "-rotate-90"}`}
|
|
164
|
-
/>
|
|
159
|
+
<span className={DISCLOSURE_ICON} aria-hidden="true">
|
|
160
|
+
<img src={thinkingStar} alt="" className="size-[12.2369px]" />
|
|
165
161
|
</span>
|
|
166
|
-
<span className="flex-shrink-0
|
|
162
|
+
<span className="flex-shrink-0">Think</span>
|
|
167
163
|
<span aria-hidden="true">·</span>
|
|
168
164
|
<span
|
|
169
165
|
className="min-w-0 flex-1 truncate"
|
|
@@ -175,7 +171,7 @@ export const ThinkingTraceRow = memo(function ThinkingTraceRow({
|
|
|
175
171
|
{open && (
|
|
176
172
|
<div
|
|
177
173
|
id={contentId}
|
|
178
|
-
className="cos-markdown w-full
|
|
174
|
+
className="cos-markdown w-full px-4 text-[14px] leading-[1.4] text-kumo-default/70"
|
|
179
175
|
>
|
|
180
176
|
<MarkdownMessage message={reasoning} />
|
|
181
177
|
</div>
|
|
@@ -184,6 +180,20 @@ export const ThinkingTraceRow = memo(function ThinkingTraceRow({
|
|
|
184
180
|
);
|
|
185
181
|
});
|
|
186
182
|
|
|
183
|
+
function useElapsedSeconds(running: boolean) {
|
|
184
|
+
const [elapsed, setElapsed] = useState(0);
|
|
185
|
+
useEffect(() => {
|
|
186
|
+
if (!running) return undefined;
|
|
187
|
+
const startedAt = Date.now() - elapsed * 1000;
|
|
188
|
+
const timer = window.setInterval(
|
|
189
|
+
() => setElapsed(Math.floor((Date.now() - startedAt) / 1000)),
|
|
190
|
+
1000,
|
|
191
|
+
);
|
|
192
|
+
return () => window.clearInterval(timer);
|
|
193
|
+
}, [running]);
|
|
194
|
+
return elapsed;
|
|
195
|
+
}
|
|
196
|
+
|
|
187
197
|
export const ToolGroupRow = memo(function ToolGroupRow({
|
|
188
198
|
group,
|
|
189
199
|
open,
|
|
@@ -195,63 +205,57 @@ export const ToolGroupRow = memo(function ToolGroupRow({
|
|
|
195
205
|
expandedKeys: ReadonlySet<string>;
|
|
196
206
|
onToggle: (key: string) => void;
|
|
197
207
|
}) {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
{
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
</span>
|
|
220
|
-
)}
|
|
221
|
-
<CaretRight
|
|
222
|
-
size={13}
|
|
223
|
-
weight="bold"
|
|
224
|
-
className={`flex-shrink-0 text-kumo-inactive transition-transform duration-150 ease-out ${open ? "rotate-90" : ""}`}
|
|
225
|
-
/>
|
|
226
|
-
</span>
|
|
227
|
-
{group.detailLines.length > 1 && (
|
|
228
|
-
<span className="mt-1 block truncate font-mono text-[12px] leading-4 text-kumo-inactive">
|
|
229
|
-
{group.detailLines.join(" · ")}
|
|
230
|
-
</span>
|
|
231
|
-
)}
|
|
232
|
-
</span>
|
|
233
|
-
</button>
|
|
234
|
-
{open &&
|
|
235
|
-
(group.calls.length === 1 ? (
|
|
236
|
-
<div className="ml-8 mt-1 space-y-3">
|
|
237
|
-
<ToolCallDetails toolCall={group.calls[0]} />
|
|
238
|
-
</div>
|
|
239
|
-
) : (
|
|
240
|
-
<div className="ml-8 mt-1 space-y-1">
|
|
241
|
-
{group.calls.map((toolCall) => {
|
|
242
|
-
const key = `call-${toolCall.toolCallId}`;
|
|
243
|
-
return (
|
|
244
|
-
<NestedToolCallRow
|
|
245
|
-
key={toolCall.toolCallId}
|
|
246
|
-
toolCall={toolCall}
|
|
247
|
-
open={expandedKeys.has(key)}
|
|
248
|
-
onToggle={onToggle}
|
|
249
|
-
/>
|
|
250
|
-
);
|
|
251
|
-
})}
|
|
252
|
-
</div>
|
|
208
|
+
const elapsed = useElapsedSeconds(group.hasRunning && group.calls.length > 1);
|
|
209
|
+
const onlyCall = group.calls.length === 1 ? group.calls[0] : undefined;
|
|
210
|
+
if (onlyCall) {
|
|
211
|
+
return (
|
|
212
|
+
<NestedToolCallRow
|
|
213
|
+
toolCall={onlyCall}
|
|
214
|
+
open={expandedKeys.has(`call-${onlyCall.toolCallId}`)}
|
|
215
|
+
onToggle={onToggle}
|
|
216
|
+
/>
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
if (group.calls.every((call) => call.kind === "web-search")) {
|
|
220
|
+
return (
|
|
221
|
+
<div className="flex w-full max-w-sm flex-col gap-4">
|
|
222
|
+
{group.calls.map((toolCall) => (
|
|
223
|
+
<NestedToolCallRow
|
|
224
|
+
key={toolCall.toolCallId}
|
|
225
|
+
toolCall={toolCall}
|
|
226
|
+
open={expandedKeys.has(`call-${toolCall.toolCallId}`)}
|
|
227
|
+
onToggle={onToggle}
|
|
228
|
+
/>
|
|
253
229
|
))}
|
|
254
|
-
|
|
230
|
+
</div>
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
return (
|
|
234
|
+
<ToolTimeline
|
|
235
|
+
steps={[]}
|
|
236
|
+
visibleSteps={0}
|
|
237
|
+
streaming={group.hasRunning}
|
|
238
|
+
open={open}
|
|
239
|
+
onOpenChange={(next) => {
|
|
240
|
+
if (next !== open) onToggle(group.key);
|
|
241
|
+
}}
|
|
242
|
+
restingLabel={group.label}
|
|
243
|
+
activeLabel={`Working for ${elapsed}s`}
|
|
244
|
+
stats={[]}
|
|
245
|
+
className="max-w-none"
|
|
246
|
+
>
|
|
247
|
+
{group.calls.map((toolCall) => {
|
|
248
|
+
const key = `call-${toolCall.toolCallId}`;
|
|
249
|
+
return (
|
|
250
|
+
<NestedToolCallRow
|
|
251
|
+
key={toolCall.toolCallId}
|
|
252
|
+
toolCall={toolCall}
|
|
253
|
+
open={expandedKeys.has(key)}
|
|
254
|
+
onToggle={onToggle}
|
|
255
|
+
/>
|
|
256
|
+
);
|
|
257
|
+
})}
|
|
258
|
+
</ToolTimeline>
|
|
255
259
|
);
|
|
256
260
|
});
|
|
257
261
|
|