@springbrand/message-panel 0.1.3-alpha.33 → 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 +290 -239
- package/cloud-os/chat/code-runner.tsx +106 -0
- package/cloud-os/chat/range.ts +8 -0
- package/cloud-os/chat/rich-blocks.tsx +186 -256
- 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 -378
- package/cloud-os/chat/tool-timeline.tsx +123 -0
- package/cloud-os/chat/transcript-model.ts +86 -116
- 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 -5
- package/cloud-os/primitives/collapsible.tsx +21 -0
- package/cloud-os/styles/cloud-os.css +18 -12
- package/demo/chat-scenarios.ts +3 -9
- package/package.json +3 -1
- package/cloud-os/assets/thinking-spark.svg +0 -9
- package/cloud-os/chat/use-lifecycle-disclosure.ts +0 -65
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { UIMessage } from "ai";
|
|
2
|
+
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
|
|
2
3
|
import {
|
|
3
4
|
ArrowDown,
|
|
4
5
|
Brain,
|
|
@@ -17,6 +18,11 @@ import {
|
|
|
17
18
|
type ReactNode,
|
|
18
19
|
} from "react";
|
|
19
20
|
import { Tooltip } from "../primitives/tooltip";
|
|
21
|
+
import {
|
|
22
|
+
Collapsible,
|
|
23
|
+
CollapsibleContent,
|
|
24
|
+
CollapsibleTrigger,
|
|
25
|
+
} from "../primitives/collapsible";
|
|
20
26
|
import { WorkshopIconButton } from "../primitives/workshop-controls";
|
|
21
27
|
import {
|
|
22
28
|
MarkdownMessage,
|
|
@@ -34,11 +40,8 @@ import {
|
|
|
34
40
|
SuggestionsBlock,
|
|
35
41
|
type ApprovalDecision,
|
|
36
42
|
} from "./rich-blocks";
|
|
37
|
-
import {
|
|
38
|
-
|
|
39
|
-
WorkDescriptionRow,
|
|
40
|
-
WorkTraceDisclosure,
|
|
41
|
-
} from "./tool-rows";
|
|
43
|
+
import { ThinkingTraceRow, ToolGroupRow } from "./tool-rows";
|
|
44
|
+
import { collapsePanel } from "./surfaces";
|
|
42
45
|
import { CloudOsActivityIndicator } from "./activity-indicator";
|
|
43
46
|
import { ProductGeneration } from "./image-generation";
|
|
44
47
|
import { GenerationLoader } from "./loading-state";
|
|
@@ -52,14 +55,11 @@ import {
|
|
|
52
55
|
deriveTurnActivity,
|
|
53
56
|
formatClockTime,
|
|
54
57
|
formatFullTimestamp,
|
|
55
|
-
partitionAssistantBlocks,
|
|
56
58
|
rhythmTopClass,
|
|
59
|
+
type AssistantBlock,
|
|
57
60
|
type CloudOsEntry,
|
|
58
61
|
type CloudOsActionPresentation,
|
|
59
62
|
} from "./transcript-model";
|
|
60
|
-
import { useDisclosureExpandedKeys } from "./use-lifecycle-disclosure";
|
|
61
|
-
|
|
62
|
-
const NO_RUNNING_DISCLOSURES: ReadonlySet<string> = new Set();
|
|
63
63
|
|
|
64
64
|
export type CloudOsChatStatus = "ready" | "submitted" | "streaming" | "error";
|
|
65
65
|
|
|
@@ -69,6 +69,8 @@ export interface CloudOsChatMessagesProps {
|
|
|
69
69
|
/** 权威 Turn 生命周期;提供时替代本地 status 推断。 */
|
|
70
70
|
turnActive?: boolean;
|
|
71
71
|
isRecovering?: boolean;
|
|
72
|
+
recoveryAttempt?: number;
|
|
73
|
+
recoveryMax?: number;
|
|
72
74
|
recoveryStatusLabel?: string;
|
|
73
75
|
awaitingApproval?: boolean;
|
|
74
76
|
/** 有 pending steer 时,活动占位说明「正在等当前步骤收尾」。 */
|
|
@@ -192,7 +194,10 @@ function UserBubble({
|
|
|
192
194
|
</div>
|
|
193
195
|
)}
|
|
194
196
|
{entry.text && (
|
|
195
|
-
<div
|
|
197
|
+
<div
|
|
198
|
+
data-user-message-bubble=""
|
|
199
|
+
className="themed-user-bubble-shadow cos-markdown cos-user-markdown w-fit max-w-[min(680px,78%)] rounded-[12px] border border-transparent bg-kumo-bubble-user px-4 py-2.5 text-[14px] leading-[22px] tracking-[-0.25px] text-kumo-default animate-in fade-in slide-in-from-bottom-1 duration-300 motion-reduce:animate-none"
|
|
200
|
+
>
|
|
196
201
|
<MarkdownMessage
|
|
197
202
|
message={entry.text}
|
|
198
203
|
resolveUrl={resolveUrl}
|
|
@@ -323,11 +328,49 @@ function ActionPresentationBlock({
|
|
|
323
328
|
);
|
|
324
329
|
}
|
|
325
330
|
|
|
331
|
+
function TurnWorkDisclosure({
|
|
332
|
+
running,
|
|
333
|
+
open,
|
|
334
|
+
onOpenChange,
|
|
335
|
+
children,
|
|
336
|
+
}: {
|
|
337
|
+
running: boolean;
|
|
338
|
+
open: boolean;
|
|
339
|
+
onOpenChange: (open: boolean) => void;
|
|
340
|
+
children: ReactNode;
|
|
341
|
+
}) {
|
|
342
|
+
return (
|
|
343
|
+
<Collapsible
|
|
344
|
+
data-slot="turn-work-disclosure"
|
|
345
|
+
open={open}
|
|
346
|
+
onOpenChange={onOpenChange}
|
|
347
|
+
>
|
|
348
|
+
<CollapsibleTrigger
|
|
349
|
+
aria-disabled={running}
|
|
350
|
+
className="group/trigger flex items-center gap-1.5 rounded-md py-1 text-[13.5px] text-kumo-inactive transition-colors outline-none hover:text-kumo-subtle"
|
|
351
|
+
>
|
|
352
|
+
<CaretRight
|
|
353
|
+
size={14}
|
|
354
|
+
className="shrink-0 transition-transform duration-200 group-data-open/trigger:rotate-90 group-data-panel-open/trigger:rotate-90 motion-reduce:transition-none"
|
|
355
|
+
/>
|
|
356
|
+
<span>{running ? "Working…" : "Work"}</span>
|
|
357
|
+
</CollapsibleTrigger>
|
|
358
|
+
<CollapsibleContent className={collapsePanel}>
|
|
359
|
+
<div className="cos-trace-chain relative space-y-4 pt-2">
|
|
360
|
+
{children}
|
|
361
|
+
</div>
|
|
362
|
+
</CollapsibleContent>
|
|
363
|
+
</Collapsible>
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
|
|
326
367
|
export function CloudOsChatMessages({
|
|
327
368
|
messages,
|
|
328
369
|
status,
|
|
329
370
|
turnActive,
|
|
330
371
|
isRecovering = false,
|
|
372
|
+
recoveryAttempt,
|
|
373
|
+
recoveryMax,
|
|
331
374
|
recoveryStatusLabel,
|
|
332
375
|
awaitingApproval = false,
|
|
333
376
|
hasPendingSteer = false,
|
|
@@ -353,6 +396,16 @@ export function CloudOsChatMessages({
|
|
|
353
396
|
const scrollRef = useRef<HTMLDivElement>(null);
|
|
354
397
|
const endRef = useRef<HTMLDivElement>(null);
|
|
355
398
|
const [showScrollButton, setShowScrollButton] = useState(false);
|
|
399
|
+
const reducedMotion = useReducedMotion();
|
|
400
|
+
const [expandedToolCalls, setExpandedToolCalls] = useState<ReadonlySet<string>>(
|
|
401
|
+
() => new Set(),
|
|
402
|
+
);
|
|
403
|
+
const [expandedTurnWork, setExpandedTurnWork] = useState<ReadonlySet<string>>(
|
|
404
|
+
() => new Set(),
|
|
405
|
+
);
|
|
406
|
+
const [expandedCompactions, setExpandedCompactions] = useState<
|
|
407
|
+
ReadonlySet<string>
|
|
408
|
+
>(() => new Set());
|
|
356
409
|
const { copied, copy } = useCopyAction();
|
|
357
410
|
const isActive =
|
|
358
411
|
turnActive ?? (status === "submitted" || status === "streaming");
|
|
@@ -367,74 +420,20 @@ export function CloudOsChatMessages({
|
|
|
367
420
|
}),
|
|
368
421
|
[approvalIdOf, isActive, messages, showThinkingTraces],
|
|
369
422
|
);
|
|
370
|
-
const
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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],
|
|
423
|
+
const latestUserIndex = entries.findLastIndex((entry) => entry.type === "user");
|
|
424
|
+
const activeTurnEntry = isActive
|
|
425
|
+
? entries.findLast(
|
|
426
|
+
(entry, index): entry is Extract<CloudOsEntry, { type: "assistant" }> =>
|
|
427
|
+
index > latestUserIndex && entry.type === "assistant",
|
|
428
|
+
)
|
|
429
|
+
: undefined;
|
|
430
|
+
const currentPlan = activeTurnEntry?.blocks.findLast(
|
|
431
|
+
(block): block is Extract<AssistantBlock, { kind: "plan" }> =>
|
|
432
|
+
block.kind === "plan",
|
|
422
433
|
);
|
|
423
|
-
const
|
|
424
|
-
() =>
|
|
425
|
-
|
|
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);
|
|
434
|
+
const completedPlanSteps = currentPlan?.steps.filter(
|
|
435
|
+
(step) => step.status === "done",
|
|
436
|
+
).length ?? 0;
|
|
438
437
|
|
|
439
438
|
const entryTopClasses = useMemo(
|
|
440
439
|
() =>
|
|
@@ -444,6 +443,33 @@ export function CloudOsChatMessages({
|
|
|
444
443
|
[entries],
|
|
445
444
|
);
|
|
446
445
|
|
|
446
|
+
const toggleToolCall = useCallback((key: string) => {
|
|
447
|
+
setExpandedToolCalls((current) => {
|
|
448
|
+
const next = new Set(current);
|
|
449
|
+
if (next.has(key)) next.delete(key);
|
|
450
|
+
else next.add(key);
|
|
451
|
+
return next;
|
|
452
|
+
});
|
|
453
|
+
}, []);
|
|
454
|
+
|
|
455
|
+
const setTurnWorkOpen = useCallback((key: string, open: boolean) => {
|
|
456
|
+
setExpandedTurnWork((current) => {
|
|
457
|
+
const next = new Set(current);
|
|
458
|
+
if (open) next.add(key);
|
|
459
|
+
else next.delete(key);
|
|
460
|
+
return next;
|
|
461
|
+
});
|
|
462
|
+
}, []);
|
|
463
|
+
|
|
464
|
+
const toggleCompaction = useCallback((key: string) => {
|
|
465
|
+
setExpandedCompactions((current) => {
|
|
466
|
+
const next = new Set(current);
|
|
467
|
+
if (next.has(key)) next.delete(key);
|
|
468
|
+
else next.add(key);
|
|
469
|
+
return next;
|
|
470
|
+
});
|
|
471
|
+
}, []);
|
|
472
|
+
|
|
447
473
|
const syncScrollButton = useCallback(() => {
|
|
448
474
|
const element = scrollRef.current;
|
|
449
475
|
if (!element) return;
|
|
@@ -461,6 +487,8 @@ export function CloudOsChatMessages({
|
|
|
461
487
|
const activity = deriveTurnActivity(entries, {
|
|
462
488
|
isActive,
|
|
463
489
|
isRecovering,
|
|
490
|
+
recoveryAttempt,
|
|
491
|
+
recoveryMax,
|
|
464
492
|
recoveryStatusLabel,
|
|
465
493
|
awaitingApproval,
|
|
466
494
|
hasPendingSteer,
|
|
@@ -477,14 +505,15 @@ export function CloudOsChatMessages({
|
|
|
477
505
|
data-status={status}
|
|
478
506
|
aria-busy={isActive || isRecovering}
|
|
479
507
|
>
|
|
480
|
-
<div
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
508
|
+
<div className="relative flex min-h-0 flex-1">
|
|
509
|
+
<div
|
|
510
|
+
ref={scrollRef}
|
|
511
|
+
onScroll={syncScrollButton}
|
|
512
|
+
tabIndex={0}
|
|
513
|
+
role="region"
|
|
514
|
+
aria-label="Chat messages"
|
|
515
|
+
className="cos-chat-panel flex-1 overflow-y-auto"
|
|
516
|
+
>
|
|
488
517
|
<div
|
|
489
518
|
className={`flex flex-col px-6 pb-8 pt-8 ${
|
|
490
519
|
constrainWidth ? "mx-auto w-full max-w-[920px]" : ""
|
|
@@ -501,7 +530,11 @@ export function CloudOsChatMessages({
|
|
|
501
530
|
|
|
502
531
|
if (entry.type === "user") {
|
|
503
532
|
return (
|
|
504
|
-
<div
|
|
533
|
+
<div
|
|
534
|
+
key={entry.key}
|
|
535
|
+
data-user-message-entry=""
|
|
536
|
+
className={topClass}
|
|
537
|
+
>
|
|
505
538
|
<UserBubble
|
|
506
539
|
entry={entry}
|
|
507
540
|
onCopy={copyText}
|
|
@@ -584,7 +617,7 @@ export function CloudOsChatMessages({
|
|
|
584
617
|
<span className="h-px flex-1 bg-kumo-line" aria-hidden="true" />
|
|
585
618
|
</div>
|
|
586
619
|
{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]">
|
|
620
|
+
<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
621
|
<MarkdownMessage
|
|
589
622
|
message={entry.summary}
|
|
590
623
|
resolveUrl={resolveUrl}
|
|
@@ -597,116 +630,87 @@ export function CloudOsChatMessages({
|
|
|
597
630
|
);
|
|
598
631
|
}
|
|
599
632
|
|
|
600
|
-
const
|
|
601
|
-
|
|
602
|
-
const
|
|
603
|
-
|
|
633
|
+
const entryIsActive =
|
|
634
|
+
isActive && entryIndex === entries.length - 1;
|
|
635
|
+
const finalTextBlocks = entryIsActive
|
|
636
|
+
? []
|
|
637
|
+
: entry.blocks.filter(
|
|
638
|
+
(block): block is Extract<AssistantBlock, { kind: "text" }> =>
|
|
639
|
+
block.kind === "text" && block.final,
|
|
640
|
+
);
|
|
641
|
+
const finalActionBlocks = entryIsActive
|
|
642
|
+
? []
|
|
643
|
+
: entry.blocks.filter(
|
|
644
|
+
(block): block is Extract<AssistantBlock, { kind: "actionPresentation" }> =>
|
|
645
|
+
block.kind === "actionPresentation" &&
|
|
646
|
+
block.presentation.state === "ready",
|
|
647
|
+
);
|
|
648
|
+
const finalMediaBlocks = entry.blocks.filter(
|
|
649
|
+
(block): block is Extract<AssistantBlock, { kind: "image" | "file" }> =>
|
|
650
|
+
block.kind === "image" || block.kind === "file",
|
|
604
651
|
);
|
|
605
|
-
const
|
|
606
|
-
(block) =>
|
|
652
|
+
const workBlocks = entry.blocks.filter(
|
|
653
|
+
(block) =>
|
|
654
|
+
block.kind !== "suggestions" &&
|
|
655
|
+
block.kind !== "image" &&
|
|
656
|
+
block.kind !== "file" &&
|
|
657
|
+
!(block.kind === "text" && block.final && !entryIsActive) &&
|
|
658
|
+
!(block.kind === "actionPresentation" && block.presentation.state === "ready"),
|
|
607
659
|
);
|
|
608
|
-
const
|
|
660
|
+
const hasNarrative = finalTextBlocks.length > 0;
|
|
661
|
+
const insufficientFailures = entry.blocks.filter(
|
|
609
662
|
(block) => block.kind === "actionPresentation" &&
|
|
610
663
|
block.presentation.state === "failed" &&
|
|
611
664
|
block.presentation.failureCode === "insufficient_credits",
|
|
612
665
|
);
|
|
613
666
|
const firstInsufficientFailure = insufficientFailures[0];
|
|
614
|
-
const hasCompletedAction =
|
|
667
|
+
const hasCompletedAction = entry.blocks.some(
|
|
615
668
|
(block) => block.kind === "actionPresentation" &&
|
|
616
669
|
block.presentation.state === "ready",
|
|
617
670
|
);
|
|
671
|
+
const followUpBlocks = entry.blocks.filter(
|
|
672
|
+
(block) => block.kind === "suggestions",
|
|
673
|
+
);
|
|
674
|
+
const footerTimestamp =
|
|
675
|
+
hasNarrative &&
|
|
676
|
+
entry.timestamp !== undefined &&
|
|
677
|
+
!entryIsActive &&
|
|
678
|
+
!entry.blocks.some(
|
|
679
|
+
(block) => block.kind === "reasoning" && block.running,
|
|
680
|
+
)
|
|
681
|
+
? entry.timestamp
|
|
682
|
+
: undefined;
|
|
618
683
|
|
|
619
684
|
return (
|
|
620
685
|
<div
|
|
621
686
|
key={entry.key}
|
|
622
|
-
className={`${topClass} min-w-0 w-full max-w-[860px]`}
|
|
687
|
+
className={`${topClass} group/agentMessage min-w-0 w-full max-w-[860px] space-y-2`}
|
|
623
688
|
>
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
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) => {
|
|
689
|
+
{workBlocks.length > 0 && (
|
|
690
|
+
<TurnWorkDisclosure
|
|
691
|
+
running={entryIsActive}
|
|
692
|
+
open={entryIsActive || expandedTurnWork.has(entry.key)}
|
|
693
|
+
onOpenChange={(open) => {
|
|
694
|
+
if (!entryIsActive) setTurnWorkOpen(entry.key, open);
|
|
695
|
+
}}
|
|
696
|
+
>
|
|
697
|
+
{workBlocks.map((block) => {
|
|
691
698
|
switch (block.kind) {
|
|
692
699
|
case "reasoning":
|
|
693
700
|
return (
|
|
694
|
-
<
|
|
701
|
+
<ThinkingTraceRow
|
|
695
702
|
key={block.key}
|
|
696
|
-
|
|
703
|
+
reasoning={block.text}
|
|
697
704
|
running={block.running}
|
|
698
|
-
resolveUrl={resolveUrl}
|
|
699
705
|
/>
|
|
700
706
|
);
|
|
701
707
|
case "text": {
|
|
702
708
|
const streaming =
|
|
703
|
-
|
|
704
|
-
entryIndex === entries.length - 1 &&
|
|
705
|
-
block === entry.blocks.at(-1);
|
|
709
|
+
entryIsActive && block === entry.blocks.at(-1);
|
|
706
710
|
return (
|
|
707
711
|
<div
|
|
708
712
|
key={block.key}
|
|
709
|
-
className="cos-markdown text-[14px] leading-[22px] tracking-[-0.25px] text-kumo-default"
|
|
713
|
+
className="cos-markdown px-1.5 text-[14px] leading-[22px] tracking-[-0.25px] text-kumo-default"
|
|
710
714
|
>
|
|
711
715
|
<MarkdownMessage
|
|
712
716
|
message={block.text}
|
|
@@ -729,13 +733,12 @@ export function CloudOsChatMessages({
|
|
|
729
733
|
/>
|
|
730
734
|
);
|
|
731
735
|
case "plan":
|
|
736
|
+
if (currentPlan?.key === block.key) return null;
|
|
732
737
|
return (
|
|
733
738
|
<PlanBlock
|
|
734
739
|
key={block.key}
|
|
735
740
|
steps={block.steps}
|
|
736
|
-
running={
|
|
737
|
-
runningWorkEntryKeys.has(entry.key) && block.running
|
|
738
|
-
}
|
|
741
|
+
running={block.running}
|
|
739
742
|
/>
|
|
740
743
|
);
|
|
741
744
|
case "askUser":
|
|
@@ -784,49 +787,11 @@ export function CloudOsChatMessages({
|
|
|
784
787
|
key={block.key}
|
|
785
788
|
label={block.label}
|
|
786
789
|
tools={block.tools}
|
|
787
|
-
running={runningWorkEntryKeys.has(entry.key)}
|
|
788
790
|
/>
|
|
789
791
|
);
|
|
790
792
|
case "subagents":
|
|
791
793
|
return (
|
|
792
|
-
<SubAgentsBlock
|
|
793
|
-
key={block.key}
|
|
794
|
-
agents={block.agents}
|
|
795
|
-
running={runningWorkEntryKeys.has(entry.key)}
|
|
796
|
-
/>
|
|
797
|
-
);
|
|
798
|
-
case "image":
|
|
799
|
-
{
|
|
800
|
-
const url = resolveUrl?.(block.src) ?? block.src;
|
|
801
|
-
const artifactId = resolveArtifactId?.(url);
|
|
802
|
-
return (
|
|
803
|
-
<Fragment key={block.key}>
|
|
804
|
-
{renderFileView(renderFile, {
|
|
805
|
-
file: {
|
|
806
|
-
url,
|
|
807
|
-
filename: block.alt,
|
|
808
|
-
mediaType: block.mediaType ?? "image/*",
|
|
809
|
-
},
|
|
810
|
-
placement: "assistant-message",
|
|
811
|
-
onOpen: artifactId && onOpenArtifact
|
|
812
|
-
? () => onOpenArtifact(artifactId)
|
|
813
|
-
: undefined,
|
|
814
|
-
})}
|
|
815
|
-
</Fragment>
|
|
816
|
-
);
|
|
817
|
-
}
|
|
818
|
-
case "file":
|
|
819
|
-
return (
|
|
820
|
-
<Fragment key={block.key}>
|
|
821
|
-
{renderFileView(renderFile, {
|
|
822
|
-
file: {
|
|
823
|
-
...block.file,
|
|
824
|
-
url:
|
|
825
|
-
resolveUrl?.(block.file.url) ?? block.file.url,
|
|
826
|
-
},
|
|
827
|
-
placement: "assistant-message",
|
|
828
|
-
})}
|
|
829
|
-
</Fragment>
|
|
794
|
+
<SubAgentsBlock key={block.key} agents={block.agents} />
|
|
830
795
|
);
|
|
831
796
|
case "actionPresentation":
|
|
832
797
|
if (
|
|
@@ -855,8 +820,67 @@ export function CloudOsChatMessages({
|
|
|
855
820
|
);
|
|
856
821
|
}
|
|
857
822
|
})}
|
|
823
|
+
</TurnWorkDisclosure>
|
|
824
|
+
)}
|
|
825
|
+
|
|
826
|
+
{(finalMediaBlocks.length > 0 || finalActionBlocks.length > 0 || finalTextBlocks.length > 0) && (
|
|
827
|
+
<div data-final-message="" className="space-y-4">
|
|
828
|
+
{finalMediaBlocks.map((block) => {
|
|
829
|
+
if (block.kind === "image") {
|
|
830
|
+
const url = resolveUrl?.(block.src) ?? block.src;
|
|
831
|
+
const artifactId = resolveArtifactId?.(url);
|
|
832
|
+
return (
|
|
833
|
+
<Fragment key={block.key}>
|
|
834
|
+
{renderFileView(renderFile, {
|
|
835
|
+
file: {
|
|
836
|
+
url,
|
|
837
|
+
filename: block.alt,
|
|
838
|
+
mediaType: block.mediaType ?? "image/*",
|
|
839
|
+
},
|
|
840
|
+
placement: "assistant-message",
|
|
841
|
+
onOpen: artifactId && onOpenArtifact
|
|
842
|
+
? () => onOpenArtifact(artifactId)
|
|
843
|
+
: undefined,
|
|
844
|
+
})}
|
|
845
|
+
</Fragment>
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
return (
|
|
849
|
+
<Fragment key={block.key}>
|
|
850
|
+
{renderFileView(renderFile, {
|
|
851
|
+
file: {
|
|
852
|
+
...block.file,
|
|
853
|
+
url: resolveUrl?.(block.file.url) ?? block.file.url,
|
|
854
|
+
},
|
|
855
|
+
placement: "assistant-message",
|
|
856
|
+
})}
|
|
857
|
+
</Fragment>
|
|
858
|
+
);
|
|
859
|
+
})}
|
|
860
|
+
{finalActionBlocks.map((block) => (
|
|
861
|
+
<ActionPresentationBlock
|
|
862
|
+
key={block.key}
|
|
863
|
+
presentation={block.presentation}
|
|
864
|
+
renderFile={renderFile}
|
|
865
|
+
resolveUrl={resolveUrl}
|
|
866
|
+
resolveArtifactId={resolveArtifactId}
|
|
867
|
+
onOpenArtifact={onOpenArtifact}
|
|
868
|
+
/>
|
|
869
|
+
))}
|
|
870
|
+
{finalTextBlocks.map((block) => (
|
|
871
|
+
<div key={block.key} className="cos-markdown px-1.5 text-[14px] leading-[22px] tracking-[-0.25px] text-kumo-default">
|
|
872
|
+
<MarkdownMessage
|
|
873
|
+
message={block.text}
|
|
874
|
+
resolveUrl={resolveUrl}
|
|
875
|
+
resolveArtifactId={resolveArtifactId}
|
|
876
|
+
onOpenArtifact={onOpenArtifact}
|
|
877
|
+
/>
|
|
878
|
+
</div>
|
|
879
|
+
))}
|
|
880
|
+
</div>
|
|
881
|
+
)}
|
|
858
882
|
|
|
859
|
-
|
|
883
|
+
{entry.terminal?.kind === "error" && (
|
|
860
884
|
<ErrorBlock
|
|
861
885
|
title={entry.terminal.title}
|
|
862
886
|
body={entry.terminal.body}
|
|
@@ -864,7 +888,7 @@ export function CloudOsChatMessages({
|
|
|
864
888
|
/>
|
|
865
889
|
)}
|
|
866
890
|
|
|
867
|
-
|
|
891
|
+
{entry.terminal?.kind === "interrupted" && (
|
|
868
892
|
<div
|
|
869
893
|
className="px-1.5 py-1 text-[13px] leading-[19px] text-kumo-inactive"
|
|
870
894
|
role="status"
|
|
@@ -873,8 +897,11 @@ export function CloudOsChatMessages({
|
|
|
873
897
|
</div>
|
|
874
898
|
)}
|
|
875
899
|
|
|
876
|
-
|
|
877
|
-
<div
|
|
900
|
+
{footerTimestamp !== undefined && (
|
|
901
|
+
<div
|
|
902
|
+
data-agent-message-footer=""
|
|
903
|
+
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"
|
|
904
|
+
>
|
|
878
905
|
<Tooltip
|
|
879
906
|
content={copied === entry.copyText ? "Copied!" : "Copy message"}
|
|
880
907
|
asChild
|
|
@@ -892,24 +919,21 @@ export function CloudOsChatMessages({
|
|
|
892
919
|
)}
|
|
893
920
|
</button>
|
|
894
921
|
</Tooltip>
|
|
895
|
-
<Tooltip content={formatFullTimestamp(
|
|
922
|
+
<Tooltip content={formatFullTimestamp(footerTimestamp)} asChild>
|
|
896
923
|
<span className="px-1 font-mono text-[11px] leading-4 text-kumo-inactive">
|
|
897
|
-
{formatClockTime(
|
|
924
|
+
{formatClockTime(footerTimestamp)}
|
|
898
925
|
</span>
|
|
899
926
|
</Tooltip>
|
|
900
927
|
</div>
|
|
901
928
|
)}
|
|
902
929
|
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
) : null
|
|
911
|
-
)}
|
|
912
|
-
</div>
|
|
930
|
+
{followUpBlocks.map((block) => (
|
|
931
|
+
<SuggestionsBlock
|
|
932
|
+
key={block.key}
|
|
933
|
+
items={block.items}
|
|
934
|
+
onSuggestion={(text) => onSuggestion?.(text)}
|
|
935
|
+
/>
|
|
936
|
+
))}
|
|
913
937
|
</div>
|
|
914
938
|
);
|
|
915
939
|
})}
|
|
@@ -938,19 +962,46 @@ export function CloudOsChatMessages({
|
|
|
938
962
|
|
|
939
963
|
<div ref={endRef} />
|
|
940
964
|
</div>
|
|
965
|
+
</div>
|
|
966
|
+
|
|
967
|
+
<WorkshopIconButton
|
|
968
|
+
aria-label="Scroll to bottom"
|
|
969
|
+
onClick={() => endRef.current?.scrollIntoView({ behavior: "smooth" })}
|
|
970
|
+
className={`themed-floating-shadow absolute bottom-3 left-1/2 z-20 -translate-x-1/2 !rounded-full border border-kumo-line bg-kumo-base transition-all duration-200 ${
|
|
971
|
+
showScrollButton
|
|
972
|
+
? "translate-y-0 opacity-100"
|
|
973
|
+
: "pointer-events-none translate-y-2 opacity-0"
|
|
974
|
+
}`}
|
|
975
|
+
>
|
|
976
|
+
<ArrowDown size={15} />
|
|
977
|
+
</WorkshopIconButton>
|
|
941
978
|
</div>
|
|
942
979
|
|
|
943
|
-
<
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
:
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
980
|
+
<AnimatePresence initial={false}>
|
|
981
|
+
{currentPlan && completedPlanSteps < currentPlan.steps.length && (
|
|
982
|
+
<motion.div
|
|
983
|
+
key="task-progress"
|
|
984
|
+
initial={reducedMotion ? { opacity: 1 } : { opacity: 0, y: 8, scale: 0.98 }}
|
|
985
|
+
animate={{ opacity: 1, y: 0, scale: 1 }}
|
|
986
|
+
exit={reducedMotion ? { opacity: 0 } : { opacity: 0, y: 8, scale: 0.98 }}
|
|
987
|
+
transition={{ duration: reducedMotion ? 0 : 0.2, ease: [0.32, 0.72, 0, 1] }}
|
|
988
|
+
className="relative z-10 mx-auto w-full max-w-3xl shrink-0 px-4"
|
|
989
|
+
>
|
|
990
|
+
<section
|
|
991
|
+
data-cloud-os-plan-progress=""
|
|
992
|
+
aria-label="Task progress"
|
|
993
|
+
className="mx-auto border border-b-0 border-kumo-line bg-kumo-base px-4 py-2.5"
|
|
994
|
+
style={{ width: "90%", borderRadius: "1rem 1rem 0 0" }}
|
|
995
|
+
>
|
|
996
|
+
<PlanBlock
|
|
997
|
+
steps={currentPlan.steps}
|
|
998
|
+
running={isActive}
|
|
999
|
+
defaultOpen={false}
|
|
1000
|
+
/>
|
|
1001
|
+
</section>
|
|
1002
|
+
</motion.div>
|
|
1003
|
+
)}
|
|
1004
|
+
</AnimatePresence>
|
|
954
1005
|
|
|
955
1006
|
</div>
|
|
956
1007
|
);
|