@springbrand/message-panel 0.3.0-alpha.8 → 0.3.0-alpha.9
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.
|
@@ -21,6 +21,7 @@ export interface ToolCallProps {
|
|
|
21
21
|
query: string;
|
|
22
22
|
result: string;
|
|
23
23
|
running: boolean;
|
|
24
|
+
iconUrl?: string;
|
|
24
25
|
open: boolean;
|
|
25
26
|
onOpenChange: (open: boolean) => void;
|
|
26
27
|
className?: string;
|
|
@@ -33,6 +34,7 @@ export function ToolCall({
|
|
|
33
34
|
query,
|
|
34
35
|
result,
|
|
35
36
|
running,
|
|
37
|
+
iconUrl,
|
|
36
38
|
open,
|
|
37
39
|
onOpenChange,
|
|
38
40
|
className,
|
|
@@ -47,6 +49,15 @@ export function ToolCall({
|
|
|
47
49
|
>
|
|
48
50
|
<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
51
|
<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" />
|
|
52
|
+
{iconUrl && (
|
|
53
|
+
<img
|
|
54
|
+
src={iconUrl}
|
|
55
|
+
alt=""
|
|
56
|
+
aria-hidden="true"
|
|
57
|
+
className="size-4 shrink-0 rounded-sm object-contain"
|
|
58
|
+
data-tool-icon=""
|
|
59
|
+
/>
|
|
60
|
+
)}
|
|
50
61
|
<SwapLabel active={running ? 0 : 1} className="text-start">
|
|
51
62
|
<ShimmerLabel
|
|
52
63
|
active={running}
|
|
@@ -64,6 +64,9 @@ export interface CloudOsToolCall {
|
|
|
64
64
|
input: UnknownRecord;
|
|
65
65
|
output: unknown;
|
|
66
66
|
outputText: string;
|
|
67
|
+
capabilityName?: string;
|
|
68
|
+
toolBrief?: string;
|
|
69
|
+
iconUrl?: string;
|
|
67
70
|
errorText: string;
|
|
68
71
|
/** UIMessage 的 part.state 原值。 */
|
|
69
72
|
state: string;
|
|
@@ -77,6 +80,7 @@ export interface CloudOsToolCall {
|
|
|
77
80
|
export interface ToolCallGroup {
|
|
78
81
|
key: string;
|
|
79
82
|
Icon: PhosphorIcon;
|
|
83
|
+
iconUrl?: string;
|
|
80
84
|
label: string;
|
|
81
85
|
detailLines: string[];
|
|
82
86
|
calls: CloudOsToolCall[];
|
|
@@ -380,6 +384,12 @@ export function getToolCallSummary(call: CloudOsToolCall): {
|
|
|
380
384
|
break;
|
|
381
385
|
}
|
|
382
386
|
const displayName = humanizeToolName(toolName);
|
|
387
|
+
if (call.capabilityName) {
|
|
388
|
+
const capabilityLabel = [call.capabilityName, call.toolBrief]
|
|
389
|
+
.filter(Boolean)
|
|
390
|
+
.join(" · ");
|
|
391
|
+
return { verb: running ? `Using ${capabilityLabel}` : `Used ${capabilityLabel}` };
|
|
392
|
+
}
|
|
383
393
|
return { verb: running ? `Using ${displayName}` : `Used ${displayName}` };
|
|
384
394
|
}
|
|
385
395
|
|
|
@@ -448,10 +458,12 @@ export function toCloudOsToolCall(
|
|
|
448
458
|
part: unknown,
|
|
449
459
|
fallbackId: string,
|
|
450
460
|
isActive: boolean,
|
|
461
|
+
liveMetadata?: Record<string, unknown>,
|
|
451
462
|
): CloudOsToolCall {
|
|
452
463
|
const record = recordOf(part);
|
|
453
464
|
const toolName = toolNameOfPart(part) ?? stringField(record, "name") ?? "tool";
|
|
454
465
|
const data = recordOf(record.data);
|
|
466
|
+
const liveMeta = recordOf(liveMetadata);
|
|
455
467
|
const input =
|
|
456
468
|
record.input != null
|
|
457
469
|
? recordOf(record.input)
|
|
@@ -459,6 +471,18 @@ export function toCloudOsToolCall(
|
|
|
459
471
|
? recordOf(data.input)
|
|
460
472
|
: data;
|
|
461
473
|
const output = record.output ?? data.output ?? record.result;
|
|
474
|
+
const outputRecord = recordOf(output);
|
|
475
|
+
const presentation = recordOf(outputRecord.presentation);
|
|
476
|
+
const toolMeta = recordOf(outputRecord.toolMeta);
|
|
477
|
+
const capabilityName =
|
|
478
|
+
stringField(presentation, "capabilityName") || stringField(toolMeta, "capabilityName") ||
|
|
479
|
+
stringField(liveMeta, "capabilityName") || undefined;
|
|
480
|
+
const toolBrief =
|
|
481
|
+
stringField(presentation, "toolBrief") || stringField(toolMeta, "toolBrief") ||
|
|
482
|
+
stringField(liveMeta, "toolBrief") || undefined;
|
|
483
|
+
const iconUrl =
|
|
484
|
+
stringField(presentation, "iconUrl") || stringField(toolMeta, "iconUrl") ||
|
|
485
|
+
stringField(liveMeta, "iconUrl") || undefined;
|
|
462
486
|
const errorText = stringField(record, "errorText", "error");
|
|
463
487
|
const state = stringField(record, "state", "status");
|
|
464
488
|
const failed =
|
|
@@ -475,9 +499,12 @@ export function toCloudOsToolCall(
|
|
|
475
499
|
input,
|
|
476
500
|
output,
|
|
477
501
|
outputText: normalizeOutputText(output),
|
|
502
|
+
capabilityName,
|
|
503
|
+
toolBrief,
|
|
504
|
+
iconUrl,
|
|
478
505
|
errorText,
|
|
479
506
|
state,
|
|
480
|
-
running: isActive && !failed && RUNNING_STATES.has(state),
|
|
507
|
+
running: isActive && !failed && (RUNNING_STATES.has(state) || record.preliminary === true),
|
|
481
508
|
failed,
|
|
482
509
|
awaitingApproval,
|
|
483
510
|
raw: part,
|
|
@@ -608,10 +635,16 @@ export function buildToolCallGroups(calls: CloudOsToolCall[]): ToolCallGroup[] {
|
|
|
608
635
|
labelParts.push(`${summary.verb}${summary.target ? ` ${summary.target}` : ""}`);
|
|
609
636
|
} else if (distinctKinds.length === 1) {
|
|
610
637
|
const summary = getToolCallSummary(calls[0]);
|
|
638
|
+
const sameCapability = calls.every(
|
|
639
|
+
(call) => call.capabilityName === calls[0].capabilityName &&
|
|
640
|
+
call.toolBrief === calls[0].toolBrief,
|
|
641
|
+
);
|
|
611
642
|
labelParts.push(
|
|
612
|
-
|
|
613
|
-
? `${summary.verb} ${
|
|
614
|
-
:
|
|
643
|
+
sameCapability && calls[0].capabilityName
|
|
644
|
+
? `${summary.verb} ${formatTimes(calls.length)}`
|
|
645
|
+
: detailLines.length === 1 && summary.target
|
|
646
|
+
? `${summary.verb} ${summary.target}`
|
|
647
|
+
: describeToolCallCount(calls[0].kind, calls[0].toolName, calls.length),
|
|
615
648
|
);
|
|
616
649
|
} else if (distinctKinds.length <= 3) {
|
|
617
650
|
labelParts.push(
|
|
@@ -629,6 +662,7 @@ export function buildToolCallGroups(calls: CloudOsToolCall[]): ToolCallGroup[] {
|
|
|
629
662
|
// 用第一条 work item 的 id 作 key:展开态在「流式 → 已落库」之间要活下来。
|
|
630
663
|
key: `group-${calls[0].toolCallId}`,
|
|
631
664
|
Icon: getToolIcon(calls[0].kind),
|
|
665
|
+
iconUrl: calls.find((call) => call.iconUrl)?.iconUrl,
|
|
632
666
|
label: labelParts
|
|
633
667
|
.map((part, index) => (index === 0 ? part : lowerFirst(part)))
|
|
634
668
|
.join(", "),
|
|
@@ -54,6 +54,7 @@ function toolCallView(tc: CloudOsToolCall) {
|
|
|
54
54
|
return {
|
|
55
55
|
label: settled.verb,
|
|
56
56
|
activeLabel: active.verb,
|
|
57
|
+
iconUrl: tc.iconUrl,
|
|
57
58
|
query: settled.target ?? "",
|
|
58
59
|
result:
|
|
59
60
|
tc.errorText ||
|
|
@@ -246,6 +247,7 @@ export const ToolGroupRow = memo(function ToolGroupRow({
|
|
|
246
247
|
restingLabel={group.label}
|
|
247
248
|
activeLabel={`Working for ${elapsed}s`}
|
|
248
249
|
stats={[]}
|
|
250
|
+
iconUrl={group.iconUrl}
|
|
249
251
|
className="max-w-none"
|
|
250
252
|
>
|
|
251
253
|
{group.calls.map((toolCall) => {
|
|
@@ -32,6 +32,7 @@ export interface ToolTimelineProps {
|
|
|
32
32
|
restingLabel: string;
|
|
33
33
|
activeLabel: string;
|
|
34
34
|
stats: TimelineStat[];
|
|
35
|
+
iconUrl?: string;
|
|
35
36
|
className?: string;
|
|
36
37
|
children?: ReactNode;
|
|
37
38
|
}
|
|
@@ -45,6 +46,7 @@ export function ToolTimeline({
|
|
|
45
46
|
restingLabel,
|
|
46
47
|
activeLabel,
|
|
47
48
|
stats,
|
|
49
|
+
iconUrl,
|
|
48
50
|
className,
|
|
49
51
|
children,
|
|
50
52
|
}: ToolTimelineProps) {
|
|
@@ -57,6 +59,15 @@ export function ToolTimeline({
|
|
|
57
59
|
>
|
|
58
60
|
<CollapsibleTrigger className="group/trigger text-foreground/55 hover:text-foreground/90 flex items-center gap-1.5 rounded-md py-1 text-[13.5px] transition-colors outline-none">
|
|
59
61
|
<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" />
|
|
62
|
+
{iconUrl && (
|
|
63
|
+
<img
|
|
64
|
+
src={iconUrl}
|
|
65
|
+
alt=""
|
|
66
|
+
aria-hidden="true"
|
|
67
|
+
className="size-4 shrink-0 rounded-sm object-contain"
|
|
68
|
+
data-tool-icon=""
|
|
69
|
+
/>
|
|
70
|
+
)}
|
|
60
71
|
<SwapLabel
|
|
61
72
|
active={streaming ? 0 : 1}
|
|
62
73
|
className="text-start tabular-nums"
|
|
@@ -431,6 +431,14 @@ function assistantBlocks(
|
|
|
431
431
|
approvalIdOf: (part: unknown) => string | undefined,
|
|
432
432
|
): AssistantBlock[] {
|
|
433
433
|
const blocks: AssistantBlock[] = [];
|
|
434
|
+
const actionToolMetaByCallId = new Map<string, UnknownRecord>();
|
|
435
|
+
for (const part of message.parts) {
|
|
436
|
+
if (dataKindOf(part) !== "action_tool_meta") continue;
|
|
437
|
+
const record = recordOf(part);
|
|
438
|
+
const data = dataOf(part);
|
|
439
|
+
const toolCallId = String(data.toolCallId ?? record.id ?? "");
|
|
440
|
+
if (toolCallId) actionToolMetaByCallId.set(toolCallId, data);
|
|
441
|
+
}
|
|
434
442
|
let pendingCalls: CloudOsToolCall[] = [];
|
|
435
443
|
let pendingActionCalls: CloudOsToolCall[] = [];
|
|
436
444
|
|
|
@@ -498,6 +506,7 @@ function assistantBlocks(
|
|
|
498
506
|
}
|
|
499
507
|
|
|
500
508
|
const dataKind = dataKindOf(part);
|
|
509
|
+
if (dataKind === "action_tool_meta") return;
|
|
501
510
|
if (dataKind === "error") {
|
|
502
511
|
const data = dataOf(part);
|
|
503
512
|
flush();
|
|
@@ -583,7 +592,13 @@ function assistantBlocks(
|
|
|
583
592
|
const toolName = toolNameOf(part);
|
|
584
593
|
if (toolName === null) return;
|
|
585
594
|
|
|
586
|
-
const
|
|
595
|
+
const toolCallId = String(record.toolCallId ?? record.id ?? "");
|
|
596
|
+
const call = toCloudOsToolCall(
|
|
597
|
+
part,
|
|
598
|
+
key,
|
|
599
|
+
isActive,
|
|
600
|
+
actionToolMetaByCallId.get(toolCallId),
|
|
601
|
+
);
|
|
587
602
|
const presentation = actionPresentationOf(call.output);
|
|
588
603
|
if (presentation) {
|
|
589
604
|
if (presentation.state === "running") {
|