@springbrand/message-panel 0.1.3-alpha.33 → 0.1.3-alpha.34
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/chat/cloud-os-chat-messages.tsx +44 -178
- package/cloud-os/chat/rich-blocks.tsx +276 -247
- package/cloud-os/chat/tool-rows.tsx +69 -306
- package/cloud-os/chat/transcript-model.ts +1 -108
- package/cloud-os/index.ts +0 -5
- package/cloud-os/styles/cloud-os.css +0 -12
- package/demo/chat-scenarios.ts +3 -9
- package/package.json +1 -1
- package/cloud-os/assets/thinking-spark.svg +0 -9
- package/cloud-os/chat/use-lifecycle-disclosure.ts +0 -65
|
@@ -34,11 +34,7 @@ import {
|
|
|
34
34
|
SuggestionsBlock,
|
|
35
35
|
type ApprovalDecision,
|
|
36
36
|
} from "./rich-blocks";
|
|
37
|
-
import {
|
|
38
|
-
ToolGroupRow,
|
|
39
|
-
WorkDescriptionRow,
|
|
40
|
-
WorkTraceDisclosure,
|
|
41
|
-
} from "./tool-rows";
|
|
37
|
+
import { ThinkingTraceRow, ToolGroupRow } from "./tool-rows";
|
|
42
38
|
import { CloudOsActivityIndicator } from "./activity-indicator";
|
|
43
39
|
import { ProductGeneration } from "./image-generation";
|
|
44
40
|
import { GenerationLoader } from "./loading-state";
|
|
@@ -52,14 +48,10 @@ import {
|
|
|
52
48
|
deriveTurnActivity,
|
|
53
49
|
formatClockTime,
|
|
54
50
|
formatFullTimestamp,
|
|
55
|
-
partitionAssistantBlocks,
|
|
56
51
|
rhythmTopClass,
|
|
57
52
|
type CloudOsEntry,
|
|
58
53
|
type CloudOsActionPresentation,
|
|
59
54
|
} from "./transcript-model";
|
|
60
|
-
import { useDisclosureExpandedKeys } from "./use-lifecycle-disclosure";
|
|
61
|
-
|
|
62
|
-
const NO_RUNNING_DISCLOSURES: ReadonlySet<string> = new Set();
|
|
63
55
|
|
|
64
56
|
export type CloudOsChatStatus = "ready" | "submitted" | "streaming" | "error";
|
|
65
57
|
|
|
@@ -353,6 +345,12 @@ export function CloudOsChatMessages({
|
|
|
353
345
|
const scrollRef = useRef<HTMLDivElement>(null);
|
|
354
346
|
const endRef = useRef<HTMLDivElement>(null);
|
|
355
347
|
const [showScrollButton, setShowScrollButton] = useState(false);
|
|
348
|
+
const [expandedToolCalls, setExpandedToolCalls] = useState<ReadonlySet<string>>(
|
|
349
|
+
() => new Set(),
|
|
350
|
+
);
|
|
351
|
+
const [expandedCompactions, setExpandedCompactions] = useState<
|
|
352
|
+
ReadonlySet<string>
|
|
353
|
+
>(() => new Set());
|
|
356
354
|
const { copied, copy } = useCopyAction();
|
|
357
355
|
const isActive =
|
|
358
356
|
turnActive ?? (status === "submitted" || status === "streaming");
|
|
@@ -367,74 +365,6 @@ export function CloudOsChatMessages({
|
|
|
367
365
|
}),
|
|
368
366
|
[approvalIdOf, isActive, messages, showThinkingTraces],
|
|
369
367
|
);
|
|
370
|
-
const workEntryKeys = useMemo(
|
|
371
|
-
() =>
|
|
372
|
-
new Set(
|
|
373
|
-
entries.flatMap((entry) =>
|
|
374
|
-
entry.type === "assistant" &&
|
|
375
|
-
partitionAssistantBlocks(entry.blocks).work.length > 0
|
|
376
|
-
? [entry.key]
|
|
377
|
-
: [],
|
|
378
|
-
),
|
|
379
|
-
),
|
|
380
|
-
[entries],
|
|
381
|
-
);
|
|
382
|
-
const runningWorkEntryKeys = useMemo(() => {
|
|
383
|
-
const keys = new Set<string>();
|
|
384
|
-
if (!isActive) return keys;
|
|
385
|
-
const entry = entries.findLast(
|
|
386
|
-
(candidate) => candidate.type === "assistant",
|
|
387
|
-
);
|
|
388
|
-
if (entry?.type !== "assistant") return keys;
|
|
389
|
-
const { work, content } = partitionAssistantBlocks(entry.blocks);
|
|
390
|
-
if (work.length > 0 && !content.some((block) => block.kind === "text")) {
|
|
391
|
-
keys.add(entry.key);
|
|
392
|
-
}
|
|
393
|
-
return keys;
|
|
394
|
-
}, [entries, isActive]);
|
|
395
|
-
const toolCallKeys = useMemo(
|
|
396
|
-
() =>
|
|
397
|
-
new Set(
|
|
398
|
-
entries.flatMap((entry) =>
|
|
399
|
-
entry.type === "assistant"
|
|
400
|
-
? entry.blocks.flatMap((block) =>
|
|
401
|
-
block.kind === "toolGroup" ? [block.group.key] : [],
|
|
402
|
-
)
|
|
403
|
-
: [],
|
|
404
|
-
),
|
|
405
|
-
),
|
|
406
|
-
[entries],
|
|
407
|
-
);
|
|
408
|
-
const runningToolCallKeys = useMemo(
|
|
409
|
-
() =>
|
|
410
|
-
new Set(
|
|
411
|
-
entries.flatMap((entry) =>
|
|
412
|
-
entry.type === "assistant" && runningWorkEntryKeys.has(entry.key)
|
|
413
|
-
? entry.blocks.flatMap((block) =>
|
|
414
|
-
block.kind === "toolGroup" && block.group.hasRunning
|
|
415
|
-
? [block.group.key]
|
|
416
|
-
: [],
|
|
417
|
-
)
|
|
418
|
-
: [],
|
|
419
|
-
),
|
|
420
|
-
),
|
|
421
|
-
[entries, runningWorkEntryKeys],
|
|
422
|
-
);
|
|
423
|
-
const compactionKeys = useMemo(
|
|
424
|
-
() =>
|
|
425
|
-
new Set(
|
|
426
|
-
entries.flatMap((entry) =>
|
|
427
|
-
entry.type === "compactionBoundary" ? [entry.key] : [],
|
|
428
|
-
),
|
|
429
|
-
),
|
|
430
|
-
[entries],
|
|
431
|
-
);
|
|
432
|
-
const [expandedToolCalls, toggleToolCall] =
|
|
433
|
-
useDisclosureExpandedKeys(toolCallKeys, runningToolCallKeys);
|
|
434
|
-
const [expandedWorkEntries, toggleWorkEntry] =
|
|
435
|
-
useDisclosureExpandedKeys(workEntryKeys, runningWorkEntryKeys);
|
|
436
|
-
const [expandedCompactions, toggleCompaction] =
|
|
437
|
-
useDisclosureExpandedKeys(compactionKeys, NO_RUNNING_DISCLOSURES);
|
|
438
368
|
|
|
439
369
|
const entryTopClasses = useMemo(
|
|
440
370
|
() =>
|
|
@@ -444,6 +374,24 @@ export function CloudOsChatMessages({
|
|
|
444
374
|
[entries],
|
|
445
375
|
);
|
|
446
376
|
|
|
377
|
+
const toggleToolCall = useCallback((key: string) => {
|
|
378
|
+
setExpandedToolCalls((current) => {
|
|
379
|
+
const next = new Set(current);
|
|
380
|
+
if (next.has(key)) next.delete(key);
|
|
381
|
+
else next.add(key);
|
|
382
|
+
return next;
|
|
383
|
+
});
|
|
384
|
+
}, []);
|
|
385
|
+
|
|
386
|
+
const toggleCompaction = useCallback((key: string) => {
|
|
387
|
+
setExpandedCompactions((current) => {
|
|
388
|
+
const next = new Set(current);
|
|
389
|
+
if (next.has(key)) next.delete(key);
|
|
390
|
+
else next.add(key);
|
|
391
|
+
return next;
|
|
392
|
+
});
|
|
393
|
+
}, []);
|
|
394
|
+
|
|
447
395
|
const syncScrollButton = useCallback(() => {
|
|
448
396
|
const element = scrollRef.current;
|
|
449
397
|
if (!element) return;
|
|
@@ -584,7 +532,7 @@ export function CloudOsChatMessages({
|
|
|
584
532
|
<span className="h-px flex-1 bg-kumo-line" aria-hidden="true" />
|
|
585
533
|
</div>
|
|
586
534
|
{expanded && (
|
|
587
|
-
<div className="cos-markdown mt-3 rounded-2xl border border-kumo-line/70 bg-kumo-elevated/45 p-3.5 text-[13px] leading-[19px]">
|
|
535
|
+
<div className="themed-surface-inset cos-markdown mt-3 rounded-2xl border border-kumo-line/70 bg-kumo-elevated/45 p-3.5 text-[13px] leading-[19px]">
|
|
588
536
|
<MarkdownMessage
|
|
589
537
|
message={entry.summary}
|
|
590
538
|
resolveUrl={resolveUrl}
|
|
@@ -597,21 +545,16 @@ export function CloudOsChatMessages({
|
|
|
597
545
|
);
|
|
598
546
|
}
|
|
599
547
|
|
|
600
|
-
const
|
|
601
|
-
partitionAssistantBlocks(entry.blocks);
|
|
602
|
-
const workGroups = workBlocks.flatMap((block) =>
|
|
603
|
-
block.kind === "toolGroup" ? [block.group] : [],
|
|
604
|
-
);
|
|
605
|
-
const hasNarrative = contentBlocks.some(
|
|
548
|
+
const hasNarrative = entry.blocks.some(
|
|
606
549
|
(block) => block.kind === "text",
|
|
607
550
|
);
|
|
608
|
-
const insufficientFailures =
|
|
551
|
+
const insufficientFailures = entry.blocks.filter(
|
|
609
552
|
(block) => block.kind === "actionPresentation" &&
|
|
610
553
|
block.presentation.state === "failed" &&
|
|
611
554
|
block.presentation.failureCode === "insufficient_credits",
|
|
612
555
|
);
|
|
613
556
|
const firstInsufficientFailure = insufficientFailures[0];
|
|
614
|
-
const hasCompletedAction =
|
|
557
|
+
const hasCompletedAction = entry.blocks.some(
|
|
615
558
|
(block) => block.kind === "actionPresentation" &&
|
|
616
559
|
block.presentation.state === "ready",
|
|
617
560
|
);
|
|
@@ -619,83 +562,17 @@ export function CloudOsChatMessages({
|
|
|
619
562
|
return (
|
|
620
563
|
<div
|
|
621
564
|
key={entry.key}
|
|
622
|
-
className={`${topClass} min-w-0 w-full max-w-[860px]`}
|
|
565
|
+
className={`${topClass} min-w-0 w-full max-w-[860px] space-y-2`}
|
|
623
566
|
>
|
|
624
|
-
<div className="group/agentMessage relative space-y-
|
|
625
|
-
{
|
|
626
|
-
<WorkTraceDisclosure
|
|
627
|
-
durationMs={entry.durationMs}
|
|
628
|
-
groups={workGroups}
|
|
629
|
-
completed={!runningWorkEntryKeys.has(entry.key)}
|
|
630
|
-
open={expandedWorkEntries.has(entry.key)}
|
|
631
|
-
onToggle={() => toggleWorkEntry(entry.key)}
|
|
632
|
-
>
|
|
633
|
-
{workBlocks.map((block) => {
|
|
634
|
-
switch (block.kind) {
|
|
635
|
-
case "reasoning":
|
|
636
|
-
case "text":
|
|
637
|
-
return (
|
|
638
|
-
<WorkDescriptionRow
|
|
639
|
-
key={block.key}
|
|
640
|
-
text={block.text}
|
|
641
|
-
running={runningWorkEntryKeys.has(entry.key)}
|
|
642
|
-
resolveUrl={resolveUrl}
|
|
643
|
-
/>
|
|
644
|
-
);
|
|
645
|
-
case "toolGroup":
|
|
646
|
-
return (
|
|
647
|
-
<ToolGroupRow
|
|
648
|
-
key={block.key}
|
|
649
|
-
group={block.group}
|
|
650
|
-
open={expandedToolCalls.has(block.group.key)}
|
|
651
|
-
expandedKeys={expandedToolCalls}
|
|
652
|
-
onToggle={toggleToolCall}
|
|
653
|
-
/>
|
|
654
|
-
);
|
|
655
|
-
case "plan":
|
|
656
|
-
return (
|
|
657
|
-
<PlanBlock
|
|
658
|
-
key={block.key}
|
|
659
|
-
steps={block.steps}
|
|
660
|
-
running={
|
|
661
|
-
runningWorkEntryKeys.has(entry.key) && block.running
|
|
662
|
-
}
|
|
663
|
-
showCollapsedSummary={false}
|
|
664
|
-
/>
|
|
665
|
-
);
|
|
666
|
-
case "parallel":
|
|
667
|
-
return (
|
|
668
|
-
<ParallelBlock
|
|
669
|
-
key={block.key}
|
|
670
|
-
label={block.label}
|
|
671
|
-
tools={block.tools}
|
|
672
|
-
running={runningWorkEntryKeys.has(entry.key)}
|
|
673
|
-
/>
|
|
674
|
-
);
|
|
675
|
-
case "subagents":
|
|
676
|
-
return (
|
|
677
|
-
<SubAgentsBlock
|
|
678
|
-
key={block.key}
|
|
679
|
-
agents={block.agents}
|
|
680
|
-
running={runningWorkEntryKeys.has(entry.key)}
|
|
681
|
-
/>
|
|
682
|
-
);
|
|
683
|
-
default:
|
|
684
|
-
return null;
|
|
685
|
-
}
|
|
686
|
-
})}
|
|
687
|
-
</WorkTraceDisclosure>
|
|
688
|
-
)}
|
|
689
|
-
|
|
690
|
-
{contentBlocks.map((block) => {
|
|
567
|
+
<div className="group/agentMessage relative space-y-1.5">
|
|
568
|
+
{entry.blocks.map((block) => {
|
|
691
569
|
switch (block.kind) {
|
|
692
570
|
case "reasoning":
|
|
693
571
|
return (
|
|
694
|
-
<
|
|
572
|
+
<ThinkingTraceRow
|
|
695
573
|
key={block.key}
|
|
696
|
-
|
|
574
|
+
reasoning={block.text}
|
|
697
575
|
running={block.running}
|
|
698
|
-
resolveUrl={resolveUrl}
|
|
699
576
|
/>
|
|
700
577
|
);
|
|
701
578
|
case "text": {
|
|
@@ -733,9 +610,7 @@ export function CloudOsChatMessages({
|
|
|
733
610
|
<PlanBlock
|
|
734
611
|
key={block.key}
|
|
735
612
|
steps={block.steps}
|
|
736
|
-
running={
|
|
737
|
-
runningWorkEntryKeys.has(entry.key) && block.running
|
|
738
|
-
}
|
|
613
|
+
running={block.running}
|
|
739
614
|
/>
|
|
740
615
|
);
|
|
741
616
|
case "askUser":
|
|
@@ -747,7 +622,13 @@ export function CloudOsChatMessages({
|
|
|
747
622
|
/>
|
|
748
623
|
);
|
|
749
624
|
case "suggestions":
|
|
750
|
-
return
|
|
625
|
+
return (
|
|
626
|
+
<SuggestionsBlock
|
|
627
|
+
key={block.key}
|
|
628
|
+
items={block.items}
|
|
629
|
+
onSuggestion={(text) => onSuggestion?.(text)}
|
|
630
|
+
/>
|
|
631
|
+
);
|
|
751
632
|
case "schedule":
|
|
752
633
|
return (
|
|
753
634
|
<ScheduleBlock
|
|
@@ -784,16 +665,11 @@ export function CloudOsChatMessages({
|
|
|
784
665
|
key={block.key}
|
|
785
666
|
label={block.label}
|
|
786
667
|
tools={block.tools}
|
|
787
|
-
running={runningWorkEntryKeys.has(entry.key)}
|
|
788
668
|
/>
|
|
789
669
|
);
|
|
790
670
|
case "subagents":
|
|
791
671
|
return (
|
|
792
|
-
<SubAgentsBlock
|
|
793
|
-
key={block.key}
|
|
794
|
-
agents={block.agents}
|
|
795
|
-
running={runningWorkEntryKeys.has(entry.key)}
|
|
796
|
-
/>
|
|
672
|
+
<SubAgentsBlock key={block.key} agents={block.agents} />
|
|
797
673
|
);
|
|
798
674
|
case "image":
|
|
799
675
|
{
|
|
@@ -874,7 +750,7 @@ export function CloudOsChatMessages({
|
|
|
874
750
|
)}
|
|
875
751
|
|
|
876
752
|
{hasNarrative && entry.timestamp !== undefined && (
|
|
877
|
-
<div className="mt-0.5 -ml-1 flex items-center gap-1">
|
|
753
|
+
<div className="mt-0.5 -ml-1 flex items-center gap-1 opacity-0 transition-opacity duration-150 ease-out group-hover/agentMessage:opacity-100 group-focus-within/agentMessage:opacity-100">
|
|
878
754
|
<Tooltip
|
|
879
755
|
content={copied === entry.copyText ? "Copied!" : "Copy message"}
|
|
880
756
|
asChild
|
|
@@ -899,16 +775,6 @@ export function CloudOsChatMessages({
|
|
|
899
775
|
</Tooltip>
|
|
900
776
|
</div>
|
|
901
777
|
)}
|
|
902
|
-
|
|
903
|
-
{contentBlocks.map((block) =>
|
|
904
|
-
block.kind === "suggestions" ? (
|
|
905
|
-
<SuggestionsBlock
|
|
906
|
-
key={block.key}
|
|
907
|
-
items={block.items}
|
|
908
|
-
onSuggestion={(text) => onSuggestion?.(text)}
|
|
909
|
-
/>
|
|
910
|
-
) : null
|
|
911
|
-
)}
|
|
912
778
|
</div>
|
|
913
779
|
</div>
|
|
914
780
|
);
|