@springbrand/message-panel 0.1.3-alpha.1 → 0.1.3-alpha.2

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.
@@ -0,0 +1,216 @@
1
+ import { CaretRight } from "@phosphor-icons/react";
2
+ import { memo } from "react";
3
+ import { MarkdownMessage } from "./markdown-message";
4
+ import {
5
+ getToolCallSummary,
6
+ getToolIcon,
7
+ normalizeOutputText,
8
+ safeStringify,
9
+ type CloudOsToolCall,
10
+ type PhosphorIcon,
11
+ type ToolCallGroup,
12
+ } from "./tool-presentation";
13
+
14
+ /**
15
+ * 工作行(work rows)。整块从 cloudflare-os-main 的 ChatInterface.tsx 拷来:
16
+ * WorkIcon / ToolCallDetails / NestedToolCallRow / ToolGroupRow / ThinkingTraceRow。
17
+ * className 一个字没改;改的只有取数——从 gadgets 的 AiToolCall 换成
18
+ * UIMessage 归一化后的 CloudOsToolCall。
19
+ */
20
+
21
+ export function WorkIcon({ Icon }: { Icon: PhosphorIcon }) {
22
+ return <Icon size={15} className="text-kumo-inactive" />;
23
+ }
24
+
25
+ function OutputPre({ label, value }: { label?: string; value: string }) {
26
+ if (!value) return null;
27
+ return (
28
+ <>
29
+ {label && (
30
+ <span className="font-mono text-[11px] leading-4 text-kumo-inactive uppercase tracking-[0.08em]">
31
+ {label}
32
+ </span>
33
+ )}
34
+ <pre className="max-h-56 overflow-auto rounded-xl border border-kumo-line/70 bg-kumo-base p-3 font-mono text-[12px] leading-[18px] text-kumo-subtle whitespace-pre-wrap">
35
+ {value}
36
+ </pre>
37
+ </>
38
+ );
39
+ }
40
+
41
+ export const ToolCallDetails = memo(function ToolCallDetails({
42
+ toolCall: tc,
43
+ }: {
44
+ toolCall: CloudOsToolCall;
45
+ }) {
46
+ const code =
47
+ typeof tc.input.code === "string"
48
+ ? tc.input.code
49
+ : typeof tc.input.command === "string"
50
+ ? tc.input.command
51
+ : "";
52
+ const inputText =
53
+ Object.keys(tc.input).length > 0 ? safeStringify(tc.input) : "";
54
+ return (
55
+ <div className="space-y-2">
56
+ {tc.errorText && (
57
+ <pre className="rounded-xl border border-kumo-danger/20 bg-kumo-danger-tint/40 p-3 font-mono text-[12px] leading-[18px] text-kumo-danger whitespace-pre-wrap">
58
+ {tc.errorText}
59
+ </pre>
60
+ )}
61
+ {code ? (
62
+ <>
63
+ <OutputPre label="Code" value={code} />
64
+ <OutputPre label="Output" value={tc.outputText} />
65
+ </>
66
+ ) : (
67
+ <>
68
+ <OutputPre value={inputText} />
69
+ {tc.outputText && <OutputPre label="Output" value={tc.outputText} />}
70
+ </>
71
+ )}
72
+ {!code && !inputText && !tc.outputText && !tc.errorText && (
73
+ <p className="m-0 text-[12px] leading-4 text-kumo-inactive">
74
+ No additional data
75
+ </p>
76
+ )}
77
+ </div>
78
+ );
79
+ });
80
+
81
+ export const NestedToolCallRow = memo(function NestedToolCallRow({
82
+ toolCall: tc,
83
+ open,
84
+ onToggle,
85
+ }: {
86
+ toolCall: CloudOsToolCall;
87
+ open: boolean;
88
+ onToggle: (key: string) => void;
89
+ }) {
90
+ const key = `call-${tc.toolCallId}`;
91
+ const summary = getToolCallSummary(tc);
92
+ const label = `${summary.verb}${summary.target ? ` ${summary.target}` : ""}`;
93
+ const Icon = getToolIcon(tc.kind);
94
+
95
+ return (
96
+ <div className="group/nested">
97
+ <button
98
+ type="button"
99
+ onClick={() => onToggle(key)}
100
+ className="flex w-full cursor-pointer items-center gap-3 rounded-xl px-1.5 py-1 text-left text-kumo-subtle transition-colors duration-150 ease-out hover:text-kumo-default focus-visible:text-kumo-default focus-visible:outline-none active:scale-[0.995]"
101
+ aria-expanded={open}
102
+ >
103
+ <span className="flex h-5 w-5 flex-shrink-0 items-center justify-center">
104
+ <WorkIcon Icon={Icon} />
105
+ </span>
106
+ <span className="flex min-w-0 flex-1 items-center gap-2 text-[14px] leading-5 tracking-[-0.25px]">
107
+ <span className="min-w-0 truncate">{label}</span>
108
+ {tc.failed && (
109
+ <span className="flex-shrink-0 rounded-full bg-kumo-danger-tint px-2 py-0.5 text-[10px] font-semibold uppercase tracking-[0.04em] text-kumo-danger">
110
+ Error
111
+ </span>
112
+ )}
113
+ <CaretRight
114
+ size={13}
115
+ weight="bold"
116
+ className={`flex-shrink-0 text-kumo-inactive transition-transform duration-150 ease-out ${open ? "rotate-90" : ""}`}
117
+ />
118
+ </span>
119
+ </button>
120
+ {open && (
121
+ <div className="themed-surface-inset ml-8 mt-1 space-y-3 rounded-2xl border border-kumo-line/70 bg-kumo-elevated/45 p-3">
122
+ <ToolCallDetails toolCall={tc} />
123
+ </div>
124
+ )}
125
+ </div>
126
+ );
127
+ });
128
+
129
+ export const ThinkingTraceRow = memo(function ThinkingTraceRow({
130
+ reasoning,
131
+ }: {
132
+ reasoning: string;
133
+ }) {
134
+ return (
135
+ <div className="min-w-0 py-1 text-kumo-subtle">
136
+ <div className="cos-markdown min-w-0 text-[13px] leading-[19px]">
137
+ <MarkdownMessage message={reasoning} />
138
+ </div>
139
+ </div>
140
+ );
141
+ });
142
+
143
+ export const ToolGroupRow = memo(function ToolGroupRow({
144
+ group,
145
+ open,
146
+ expandedKeys,
147
+ onToggle,
148
+ }: {
149
+ group: ToolCallGroup;
150
+ open: boolean;
151
+ expandedKeys: ReadonlySet<string>;
152
+ onToggle: (key: string) => void;
153
+ }) {
154
+ return (
155
+ <div className="group -ml-0.5">
156
+ <button
157
+ type="button"
158
+ onClick={() => onToggle(group.key)}
159
+ className="flex w-full cursor-pointer items-center gap-3 rounded-xl px-1.5 py-1 text-left text-kumo-subtle transition-colors duration-150 ease-out hover:text-kumo-default focus-visible:text-kumo-default focus-visible:outline-none active:scale-[0.995]"
160
+ aria-expanded={open}
161
+ >
162
+ <span className="flex h-5 w-5 flex-shrink-0 items-center justify-center">
163
+ <WorkIcon Icon={group.Icon} />
164
+ </span>
165
+ <span className="min-w-0 flex-1">
166
+ <span className="flex min-w-0 items-center gap-2 text-[14px] leading-5 tracking-[-0.25px]">
167
+ <span
168
+ className={`min-w-0 truncate ${group.hasRunning ? "cos-thinking-shimmer" : ""}`}
169
+ >
170
+ {group.label}
171
+ </span>
172
+ {group.hasError && (
173
+ <span className="flex-shrink-0 rounded-full bg-kumo-danger-tint px-2 py-0.5 text-[10px] font-semibold uppercase tracking-[0.04em] text-kumo-danger">
174
+ Error
175
+ </span>
176
+ )}
177
+ <CaretRight
178
+ size={13}
179
+ weight="bold"
180
+ className={`flex-shrink-0 text-kumo-inactive transition-transform duration-150 ease-out ${open ? "rotate-90" : ""}`}
181
+ />
182
+ </span>
183
+ {group.detailLines.length > 1 && (
184
+ <span className="mt-1 block truncate font-mono text-[12px] leading-4 text-kumo-inactive">
185
+ {group.detailLines.join(" · ")}
186
+ </span>
187
+ )}
188
+ </span>
189
+ </button>
190
+ {open &&
191
+ (group.calls.length === 1 ? (
192
+ <div className="themed-surface-inset ml-8 mt-1 space-y-3 rounded-2xl border border-kumo-line/70 bg-kumo-elevated/45 p-3">
193
+ <ToolCallDetails toolCall={group.calls[0]} />
194
+ </div>
195
+ ) : (
196
+ <div className="ml-8 mt-1 space-y-1">
197
+ {group.calls.map((toolCall) => {
198
+ const key = `call-${toolCall.toolCallId}`;
199
+ return (
200
+ <NestedToolCallRow
201
+ key={toolCall.toolCallId}
202
+ toolCall={toolCall}
203
+ open={expandedKeys.has(key)}
204
+ onToggle={onToggle}
205
+ />
206
+ );
207
+ })}
208
+ </div>
209
+ ))}
210
+ </div>
211
+ );
212
+ });
213
+
214
+ export function toolOutputSummary(call: CloudOsToolCall): string {
215
+ return normalizeOutputText(call.output);
216
+ }