@springbrand/message-panel 0.1.3-alpha.13 → 0.1.3-alpha.14
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/assets/followup-arrow.svg +3 -0
- package/cloud-os/assets/loading-corner.svg +3 -0
- package/cloud-os/assets/loading-mark.svg +16 -0
- package/cloud-os/assets/loading-spark.svg +3 -0
- package/cloud-os/chat/cloud-os-chat-messages.tsx +9 -17
- package/cloud-os/chat/rich-blocks.tsx +51 -51
- package/cloud-os/chat/tool-rows.tsx +82 -39
- package/cloud-os/chat/transcript-model.ts +12 -2
- package/cloud-os/index.ts +4 -1
- package/cloud-os/layout/cloud-os-workspace-split.tsx +57 -0
- package/cloud-os/primitives/workshop-controls.tsx +1 -1
- package/demo/camel-chat-showcase.tsx +10 -1
- package/demo/chat-scenarios.ts +52 -9
- package/demo/cloud-os-chat-showcase.tsx +26 -5
- package/package.json +2 -1
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="11.6667" height="8.33333" viewBox="0 0 11.6667 8.33333" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path id="Vector (Stroke)" d="M3.92575 0.159107C4.14614 -0.0530669 4.50354 -0.0530047 4.72403 0.159107C4.94441 0.371284 4.94435 0.715363 4.72403 0.927642L1.92639 3.62317H11.1021C11.4139 3.62317 11.6667 3.8665 11.6667 4.16667C11.6667 4.46683 11.4139 4.71016 11.1021 4.71016H1.92639L4.72403 7.40569C4.94435 7.61797 4.94441 7.96205 4.72403 8.17423C4.50354 8.38634 4.14614 8.3864 3.92575 8.17423L0.165189 4.55093C-0.0550631 4.33872 -0.0550631 3.99461 0.165189 3.7824L3.92575 0.159107Z" fill="black" fill-opacity="0.5"/>
|
|
3
|
+
</svg>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="21.8891" height="21.9254" viewBox="0 0 21.8891 21.9254" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path id="Vector" d="M11.6017 6.11877H0.752769C0.337056 6.11877 3.87367e-05 5.78171 3.87367e-05 5.36611L0 0.849598C0 0.433872 0.337004 0.0968018 0.75273 0.0968018H11.5558L15.6704 0.00163975C19.0193 -0.0757287 21.7878 2.59451 21.8311 5.94405L21.8891 10.4301V21.1726C21.8891 21.5883 21.552 21.9254 21.1363 21.9254H16.6199C16.2042 21.9254 15.8671 21.5883 15.8671 21.1726V10.3695L15.7581 7.55744C15.7267 6.74739 15.0595 6.10781 14.249 6.11039L11.6017 6.11877Z" fill="#FF9938"/>
|
|
3
|
+
</svg>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="37.5518" height="37.4985" viewBox="0 0 37.5518 37.4985" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<g id="Group" filter="url(#filter0_f_0_4)">
|
|
3
|
+
<path id="Vector" d="M31.8173 8.87482C32.1113 9.16882 32.1113 9.64541 31.8173 9.93941L24.16 17.5967H35.009C35.4247 17.5967 35.7616 17.9338 35.7616 18.3494L35.7618 22.8659C35.7618 23.2816 35.4247 23.6185 35.009 23.6185H24.2059L31.8173 31.2299C32.1113 31.5239 32.1113 32.0005 31.8173 32.2945L28.6236 35.488C28.3298 35.782 27.8532 35.782 27.5592 35.488L22.4352 30.3641C22.2939 30.2227 22.2146 30.0309 22.2148 29.831L22.216 28.8867L22.1335 21.8329C22.0894 18.0581 18.9682 15.0475 15.1943 15.1395L8.62867 15.2995L7.68452 15.3007C7.48456 15.3009 7.29271 15.2216 7.15132 15.0802L2.0105 9.93941C1.71653 9.64541 1.71653 9.16882 2.0105 8.87482L5.20408 5.68127C5.49804 5.38731 5.97465 5.38731 6.26861 5.68127L13.8727 13.2853V2.54284C13.8727 2.12712 14.2096 1.79009 14.6254 1.79008L19.1417 1.79002C19.5575 1.79002 19.8945 2.12707 19.8945 2.54279V13.3458L27.5592 5.68127C27.8532 5.38731 28.3298 5.38731 28.6236 5.68127L31.8173 8.87482Z" fill="url(#paint0_linear_0_4)"/>
|
|
4
|
+
</g>
|
|
5
|
+
<defs>
|
|
6
|
+
<filter id="filter0_f_0_4" x="-1.86265e-09" y="0" width="37.5518" height="37.4985" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
7
|
+
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
8
|
+
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
|
9
|
+
<feGaussianBlur stdDeviation="0.895012" result="effect1_foregroundBlur_0_4"/>
|
|
10
|
+
</filter>
|
|
11
|
+
<linearGradient id="paint0_linear_0_4" x1="30.2205" y1="7.27806" x2="22.5471" y2="31.3091" gradientUnits="userSpaceOnUse">
|
|
12
|
+
<stop stop-color="#FFD078"/>
|
|
13
|
+
<stop offset="1" stop-color="#FF9938"/>
|
|
14
|
+
</linearGradient>
|
|
15
|
+
</defs>
|
|
16
|
+
</svg>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="9.81853" height="9.8186" viewBox="0 0 9.81853 9.8186" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path id="Vector" d="M4.67857 0.881903C5.85442 -0.293968 7.76085 -0.293968 8.93672 0.881903C10.1125 2.05777 10.1125 3.96426 8.93672 5.14L4.25811 9.8186L0 5.5605L4.67857 0.881903Z" fill="#FF9938"/>
|
|
3
|
+
</svg>
|
|
@@ -56,7 +56,6 @@ export interface CloudOsChatMessagesProps {
|
|
|
56
56
|
status: CloudOsChatStatus;
|
|
57
57
|
/** 权威 Turn 生命周期;提供时替代本地 status 推断。 */
|
|
58
58
|
turnActive?: boolean;
|
|
59
|
-
isHydrating?: boolean;
|
|
60
59
|
isRecovering?: boolean;
|
|
61
60
|
recoveryStatusLabel?: string;
|
|
62
61
|
awaitingApproval?: boolean;
|
|
@@ -173,7 +172,7 @@ function UserBubble({
|
|
|
173
172
|
</div>
|
|
174
173
|
)}
|
|
175
174
|
{entry.text && (
|
|
176
|
-
<div className="themed-user-bubble-shadow cos-markdown cos-user-markdown w-fit max-w-[min(680px,78%)] rounded-[
|
|
175
|
+
<div 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">
|
|
177
176
|
<MarkdownMessage message={entry.text} resolveUrl={resolveUrl} />
|
|
178
177
|
</div>
|
|
179
178
|
)}
|
|
@@ -205,7 +204,6 @@ export function CloudOsChatMessages({
|
|
|
205
204
|
messages,
|
|
206
205
|
status,
|
|
207
206
|
turnActive,
|
|
208
|
-
isHydrating = false,
|
|
209
207
|
isRecovering = false,
|
|
210
208
|
recoveryStatusLabel,
|
|
211
209
|
awaitingApproval = false,
|
|
@@ -308,7 +306,7 @@ export function CloudOsChatMessages({
|
|
|
308
306
|
className="relative flex min-h-0 flex-1 flex-col overflow-hidden bg-kumo-base"
|
|
309
307
|
data-cloud-os-transcript=""
|
|
310
308
|
data-status={status}
|
|
311
|
-
aria-busy={isActive ||
|
|
309
|
+
aria-busy={isActive || isRecovering}
|
|
312
310
|
>
|
|
313
311
|
<div
|
|
314
312
|
ref={scrollRef}
|
|
@@ -323,7 +321,7 @@ export function CloudOsChatMessages({
|
|
|
323
321
|
constrainWidth ? "mx-auto w-full max-w-[920px]" : ""
|
|
324
322
|
}`}
|
|
325
323
|
>
|
|
326
|
-
{entries.length === 0 && !startedWithPending &&
|
|
324
|
+
{entries.length === 0 && !startedWithPending && (
|
|
327
325
|
<div className="flex min-h-56 items-center justify-center text-center text-[24px] leading-8 font-semibold tracking-[-0.4px] text-kumo-default">
|
|
328
326
|
{emptyTitle}
|
|
329
327
|
</div>
|
|
@@ -349,7 +347,7 @@ export function CloudOsChatMessages({
|
|
|
349
347
|
if (entry.type === "slashCommand") {
|
|
350
348
|
return (
|
|
351
349
|
<div key={entry.key} className={`${topClass} flex flex-col items-end`}>
|
|
352
|
-
<div className="themed-user-bubble-shadow w-fit max-w-[min(680px,78%)] rounded-[
|
|
350
|
+
<div className="themed-user-bubble-shadow 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-brand">
|
|
353
351
|
<span className="whitespace-pre-wrap font-mono">{entry.text}</span>
|
|
354
352
|
</div>
|
|
355
353
|
</div>
|
|
@@ -440,7 +438,11 @@ export function CloudOsChatMessages({
|
|
|
440
438
|
switch (block.kind) {
|
|
441
439
|
case "reasoning":
|
|
442
440
|
return (
|
|
443
|
-
<ThinkingTraceRow
|
|
441
|
+
<ThinkingTraceRow
|
|
442
|
+
key={block.key}
|
|
443
|
+
reasoning={block.text}
|
|
444
|
+
running={block.running}
|
|
445
|
+
/>
|
|
444
446
|
);
|
|
445
447
|
case "text":
|
|
446
448
|
return (
|
|
@@ -653,16 +655,6 @@ export function CloudOsChatMessages({
|
|
|
653
655
|
<ArrowDown size={15} />
|
|
654
656
|
</WorkshopIconButton>
|
|
655
657
|
|
|
656
|
-
{isHydrating && (
|
|
657
|
-
<div className="absolute inset-0 z-30 grid place-items-center bg-kumo-base/90">
|
|
658
|
-
<div className="flex flex-col items-center gap-3">
|
|
659
|
-
<div className="h-6 w-6 animate-spin rounded-full border-2 border-kumo-brand border-t-transparent" />
|
|
660
|
-
<p className="m-0 text-[13px] leading-[18px] text-kumo-subtle">
|
|
661
|
-
Loading conversation…
|
|
662
|
-
</p>
|
|
663
|
-
</div>
|
|
664
|
-
</div>
|
|
665
|
-
)}
|
|
666
658
|
</div>
|
|
667
659
|
);
|
|
668
660
|
}
|
|
@@ -452,23 +452,26 @@ export function SuggestionsBlock({
|
|
|
452
452
|
onSuggestion: (text: string) => void;
|
|
453
453
|
}) {
|
|
454
454
|
return (
|
|
455
|
-
<div className="max-w-[860px]
|
|
456
|
-
|
|
457
|
-
<
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
455
|
+
<div className="flex max-w-[860px] flex-col items-start gap-3">
|
|
456
|
+
{items.map((item) => (
|
|
457
|
+
<button
|
|
458
|
+
key={item}
|
|
459
|
+
type="button"
|
|
460
|
+
onClick={() => onSuggestion(item)}
|
|
461
|
+
className="inline-flex cursor-pointer items-center gap-2 text-[14px] leading-[1.4] text-kumo-inactive transition-colors duration-150 ease-out hover:text-kumo-brand focus-visible:text-kumo-brand focus-visible:outline-none active:scale-[0.98]"
|
|
462
|
+
>
|
|
463
|
+
<span className="grid size-5 flex-shrink-0 place-items-center" aria-hidden="true">
|
|
464
|
+
<span className="rotate-180 -scale-y-100">
|
|
465
|
+
<img
|
|
466
|
+
src={new URL("../assets/followup-arrow.svg", import.meta.url).href}
|
|
467
|
+
alt=""
|
|
468
|
+
className="h-[8.33333px] w-[11.6667px]"
|
|
469
|
+
/>
|
|
470
|
+
</span>
|
|
471
|
+
</span>
|
|
472
|
+
<span className="whitespace-nowrap">{item}</span>
|
|
473
|
+
</button>
|
|
474
|
+
))}
|
|
472
475
|
</div>
|
|
473
476
|
);
|
|
474
477
|
}
|
|
@@ -513,7 +516,7 @@ function scheduleCadence(input: Record<string, unknown>): string {
|
|
|
513
516
|
return timezone ? `${cadence} · ${timezone}` : cadence;
|
|
514
517
|
}
|
|
515
518
|
|
|
516
|
-
/** `schedule` 工具的 details 是 `{ scheduled: true, id }`,id
|
|
519
|
+
/** `schedule` 工具的 details 是 `{ scheduled: true, id }`,id 即 Inbox Schedule Definition 主键。 */
|
|
517
520
|
function scheduleIdOf(output: unknown): string {
|
|
518
521
|
if (typeof output !== "object" || output === null) return "";
|
|
519
522
|
const id = (output as Record<string, unknown>).id;
|
|
@@ -539,7 +542,7 @@ export function ScheduleConfirmation({
|
|
|
539
542
|
return (
|
|
540
543
|
<section
|
|
541
544
|
data-slot="schedule-card"
|
|
542
|
-
aria-label={
|
|
545
|
+
aria-label={`Confirm scheduled task: ${name}`}
|
|
543
546
|
className="themed-surface-inset flex w-full max-w-sm flex-col gap-3 rounded-2xl border border-kumo-line/70 bg-kumo-elevated p-4 text-kumo-default shadow-[0_1px_2px_rgba(0,0,0,0.04),0_12px_32px_-16px_rgba(0,0,0,0.12)]"
|
|
544
547
|
>
|
|
545
548
|
<div className="flex items-center gap-2.5">
|
|
@@ -570,10 +573,10 @@ export function ScheduleConfirmation({
|
|
|
570
573
|
|
|
571
574
|
<div className="flex items-center justify-end gap-2">
|
|
572
575
|
<WorkshopButton disabled={disabled} onClick={onCancel}>
|
|
573
|
-
|
|
576
|
+
Cancel
|
|
574
577
|
</WorkshopButton>
|
|
575
578
|
<WorkshopButton tone="primary" disabled={disabled} onClick={onConfirm}>
|
|
576
|
-
{disabled ? "
|
|
579
|
+
{disabled ? "Confirming…" : "Confirm schedule"}
|
|
577
580
|
</WorkshopButton>
|
|
578
581
|
</div>
|
|
579
582
|
</section>
|
|
@@ -677,22 +680,22 @@ export function PermissionGrant({
|
|
|
677
680
|
"aria-label"?: string;
|
|
678
681
|
}) {
|
|
679
682
|
const actionClassName =
|
|
680
|
-
"inline-flex h-
|
|
683
|
+
"inline-flex h-10 w-[120px] items-center justify-center rounded-lg border border-kumo-line px-4 text-[14px] leading-[1.4] font-medium transition-[background-color,color,opacity,transform] duration-150 ease-out active:scale-[0.96] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-kumo-ring/40 disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100";
|
|
681
684
|
|
|
682
685
|
return (
|
|
683
686
|
<section
|
|
684
687
|
aria-label={ariaLabel}
|
|
685
|
-
className="themed-surface-inset w-full
|
|
688
|
+
className="themed-surface-inset w-full rounded-xl border border-kumo-line bg-kumo-elevated p-4 text-kumo-default"
|
|
686
689
|
>
|
|
687
|
-
<div className="flex items-center gap-2
|
|
690
|
+
<div className="flex items-center gap-2">
|
|
688
691
|
<span
|
|
689
|
-
className="flex size-
|
|
692
|
+
className="flex size-8 shrink-0 items-center justify-center rounded-lg bg-kumo-fill text-kumo-subtle"
|
|
690
693
|
aria-hidden="true"
|
|
691
694
|
>
|
|
692
695
|
<Key size={14} weight="bold" />
|
|
693
696
|
</span>
|
|
694
697
|
<div className="flex min-w-0 flex-1 flex-col">
|
|
695
|
-
<span className="truncate text-[
|
|
698
|
+
<span className="truncate text-[14px] leading-[1.4] font-medium">
|
|
696
699
|
{capability}
|
|
697
700
|
</span>
|
|
698
701
|
<span className="truncate text-[12px] leading-4 text-kumo-inactive">
|
|
@@ -701,41 +704,22 @@ export function PermissionGrant({
|
|
|
701
704
|
</div>
|
|
702
705
|
</div>
|
|
703
706
|
|
|
704
|
-
<div className="mt-
|
|
705
|
-
<span className="
|
|
707
|
+
<div className="mt-4 flex max-h-[200px] flex-col gap-2 overflow-y-auto">
|
|
708
|
+
<span className="text-[12px] leading-4 text-kumo-inactive">
|
|
706
709
|
this grants
|
|
707
710
|
</span>
|
|
708
711
|
{reach.map((item, index) => (
|
|
709
712
|
<span
|
|
710
713
|
key={`${index}:${item}`}
|
|
711
|
-
className="flex items-baseline gap-2 text-[
|
|
714
|
+
className="flex items-baseline gap-2 text-[14px] leading-[1.4] text-kumo-subtle"
|
|
712
715
|
>
|
|
713
|
-
<span
|
|
714
|
-
className="size-1 shrink-0 rounded-full bg-kumo-interact"
|
|
715
|
-
aria-hidden="true"
|
|
716
|
-
/>
|
|
716
|
+
<span aria-hidden="true">•</span>
|
|
717
717
|
<span className="min-w-0 break-words whitespace-pre-wrap">{item}</span>
|
|
718
718
|
</span>
|
|
719
719
|
))}
|
|
720
720
|
</div>
|
|
721
721
|
|
|
722
|
-
<div className="mt-
|
|
723
|
-
<button
|
|
724
|
-
type="button"
|
|
725
|
-
disabled={disabled}
|
|
726
|
-
onClick={() => onGrant("deny")}
|
|
727
|
-
className={`${actionClassName} text-kumo-subtle enabled:hover:bg-kumo-fill enabled:hover:text-kumo-default`}
|
|
728
|
-
>
|
|
729
|
-
Deny
|
|
730
|
-
</button>
|
|
731
|
-
<button
|
|
732
|
-
type="button"
|
|
733
|
-
disabled={disabled}
|
|
734
|
-
onClick={() => onGrant("allow_once")}
|
|
735
|
-
className={`${actionClassName} text-kumo-subtle enabled:hover:bg-kumo-fill enabled:hover:text-kumo-default`}
|
|
736
|
-
>
|
|
737
|
-
Allow once
|
|
738
|
-
</button>
|
|
722
|
+
<div className="mt-4 flex flex-wrap items-center gap-2">
|
|
739
723
|
<Tooltip
|
|
740
724
|
content="Allow every action at this execution level, without future prompts."
|
|
741
725
|
asChild
|
|
@@ -744,11 +728,27 @@ export function PermissionGrant({
|
|
|
744
728
|
type="button"
|
|
745
729
|
disabled={disabled}
|
|
746
730
|
onClick={() => onGrant("allow_level")}
|
|
747
|
-
className={`${actionClassName} bg-
|
|
731
|
+
className={`${actionClassName} border-0 bg-[linear-gradient(90deg,#ffd077_0%,#ff9938_100%)] text-kumo-inverse enabled:hover:brightness-95`}
|
|
748
732
|
>
|
|
749
733
|
Allow level
|
|
750
734
|
</button>
|
|
751
735
|
</Tooltip>
|
|
736
|
+
<button
|
|
737
|
+
type="button"
|
|
738
|
+
disabled={disabled}
|
|
739
|
+
onClick={() => onGrant("allow_once")}
|
|
740
|
+
className={`${actionClassName} text-kumo-subtle enabled:hover:bg-kumo-fill enabled:hover:text-kumo-default`}
|
|
741
|
+
>
|
|
742
|
+
Allow once
|
|
743
|
+
</button>
|
|
744
|
+
<button
|
|
745
|
+
type="button"
|
|
746
|
+
disabled={disabled}
|
|
747
|
+
onClick={() => onGrant("deny")}
|
|
748
|
+
className={`${actionClassName} text-kumo-subtle enabled:hover:bg-kumo-fill enabled:hover:text-kumo-default`}
|
|
749
|
+
>
|
|
750
|
+
Deny
|
|
751
|
+
</button>
|
|
752
752
|
</div>
|
|
753
753
|
</section>
|
|
754
754
|
);
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { CaretRight } from "@phosphor-icons/react";
|
|
2
|
-
import { memo } from "react";
|
|
1
|
+
import { Atom, CaretDown, CaretRight, Copy } from "@phosphor-icons/react";
|
|
2
|
+
import { memo, useEffect, useId, useState } from "react";
|
|
3
3
|
import { MarkdownMessage } from "./markdown-message";
|
|
4
4
|
import {
|
|
5
5
|
getToolCallSummary,
|
|
6
6
|
getToolIcon,
|
|
7
7
|
normalizeOutputText,
|
|
8
|
-
safeStringify,
|
|
9
8
|
type CloudOsToolCall,
|
|
10
9
|
type PhosphorIcon,
|
|
11
10
|
type ToolCallGroup,
|
|
@@ -22,19 +21,42 @@ export function WorkIcon({ Icon }: { Icon: PhosphorIcon }) {
|
|
|
22
21
|
return <Icon size={15} className="text-kumo-inactive" />;
|
|
23
22
|
}
|
|
24
23
|
|
|
25
|
-
function
|
|
26
|
-
if (
|
|
24
|
+
function displayOutput(value: unknown, fallback: string): string {
|
|
25
|
+
if (value == null || typeof value !== "object" || Array.isArray(value)) {
|
|
26
|
+
return fallback;
|
|
27
|
+
}
|
|
28
|
+
const record = value as Record<string, unknown>;
|
|
29
|
+
if (typeof record.summary === "string" && record.summary.trim()) {
|
|
30
|
+
return record.summary.trim();
|
|
31
|
+
}
|
|
32
|
+
const displayValue = (item: unknown): string =>
|
|
33
|
+
item != null && typeof item === "object"
|
|
34
|
+
? Object.entries(item).map(([key, child]) => `${key}: ${displayValue(child)}`).join("\n")
|
|
35
|
+
: String(item ?? "");
|
|
36
|
+
return displayValue(record);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function OutputPre({ value }: { value: string }) {
|
|
27
40
|
return (
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
41
|
+
<div
|
|
42
|
+
className="rounded-xl border border-kumo-line/70 bg-kumo-elevated/45 px-4 py-3 text-kumo-subtle"
|
|
43
|
+
data-output-card=""
|
|
44
|
+
>
|
|
45
|
+
<div className="flex items-center justify-between gap-3 text-[14px] font-medium leading-5">
|
|
46
|
+
<span>Output</span>
|
|
47
|
+
<button
|
|
48
|
+
type="button"
|
|
49
|
+
onClick={() => navigator.clipboard.writeText(value)}
|
|
50
|
+
className="flex h-4 w-4 items-center justify-center text-kumo-subtle transition-colors hover:text-kumo-default focus-visible:outline-none focus-visible:text-kumo-default"
|
|
51
|
+
aria-label="Copy output"
|
|
52
|
+
>
|
|
53
|
+
<Copy size={16} />
|
|
54
|
+
</button>
|
|
55
|
+
</div>
|
|
56
|
+
<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">
|
|
35
57
|
{value}
|
|
36
58
|
</pre>
|
|
37
|
-
|
|
59
|
+
</div>
|
|
38
60
|
);
|
|
39
61
|
}
|
|
40
62
|
|
|
@@ -43,14 +65,7 @@ export const ToolCallDetails = memo(function ToolCallDetails({
|
|
|
43
65
|
}: {
|
|
44
66
|
toolCall: CloudOsToolCall;
|
|
45
67
|
}) {
|
|
46
|
-
const
|
|
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) : "";
|
|
68
|
+
const output = displayOutput(tc.output, tc.outputText);
|
|
54
69
|
return (
|
|
55
70
|
<div className="space-y-2">
|
|
56
71
|
{tc.errorText && (
|
|
@@ -58,18 +73,8 @@ export const ToolCallDetails = memo(function ToolCallDetails({
|
|
|
58
73
|
{tc.errorText}
|
|
59
74
|
</pre>
|
|
60
75
|
)}
|
|
61
|
-
{
|
|
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 && (
|
|
76
|
+
{output && <OutputPre value={output} />}
|
|
77
|
+
{!output && !tc.errorText && (
|
|
73
78
|
<p className="m-0 text-[12px] leading-4 text-kumo-inactive">
|
|
74
79
|
No additional data
|
|
75
80
|
</p>
|
|
@@ -118,7 +123,7 @@ export const NestedToolCallRow = memo(function NestedToolCallRow({
|
|
|
118
123
|
</span>
|
|
119
124
|
</button>
|
|
120
125
|
{open && (
|
|
121
|
-
<div className="
|
|
126
|
+
<div className="ml-8 mt-1 space-y-3">
|
|
122
127
|
<ToolCallDetails toolCall={tc} />
|
|
123
128
|
</div>
|
|
124
129
|
)}
|
|
@@ -128,14 +133,52 @@ export const NestedToolCallRow = memo(function NestedToolCallRow({
|
|
|
128
133
|
|
|
129
134
|
export const ThinkingTraceRow = memo(function ThinkingTraceRow({
|
|
130
135
|
reasoning,
|
|
136
|
+
running,
|
|
131
137
|
}: {
|
|
132
138
|
reasoning: string;
|
|
139
|
+
running: boolean;
|
|
133
140
|
}) {
|
|
141
|
+
const [open, setOpen] = useState(running);
|
|
142
|
+
const contentId = useId();
|
|
143
|
+
const summary = reasoning.replace(/\s+/g, " ").trim();
|
|
144
|
+
|
|
145
|
+
useEffect(() => setOpen(running), [running]);
|
|
146
|
+
|
|
134
147
|
return (
|
|
135
|
-
<div
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
148
|
+
<div
|
|
149
|
+
className="flex min-w-0 flex-col items-start gap-2"
|
|
150
|
+
data-thinking-trace=""
|
|
151
|
+
data-state={open ? "open" : "closed"}
|
|
152
|
+
>
|
|
153
|
+
<button
|
|
154
|
+
type="button"
|
|
155
|
+
onClick={() => setOpen((current) => !current)}
|
|
156
|
+
className="group -ml-0.5 flex w-full min-w-0 items-center gap-1.5 rounded-xl px-1.5 py-1 text-left text-[14px] leading-5 text-kumo-inactive transition-colors hover:text-kumo-default focus-visible:text-kumo-default focus-visible:outline-none"
|
|
157
|
+
aria-expanded={open}
|
|
158
|
+
aria-controls={contentId}
|
|
159
|
+
>
|
|
160
|
+
<span className="relative grid size-4 flex-shrink-0 place-items-center" aria-hidden="true">
|
|
161
|
+
<Atom
|
|
162
|
+
size={14}
|
|
163
|
+
className="transition-opacity group-hover:opacity-0 group-focus-visible:opacity-0"
|
|
164
|
+
/>
|
|
165
|
+
<CaretDown
|
|
166
|
+
size={14}
|
|
167
|
+
className={`absolute opacity-0 transition-[opacity,transform] group-hover:opacity-100 group-focus-visible:opacity-100 ${open ? "" : "-rotate-90"}`}
|
|
168
|
+
/>
|
|
169
|
+
</span>
|
|
170
|
+
<span className="flex-shrink-0 text-kumo-subtle">Think</span>
|
|
171
|
+
<span aria-hidden="true">·</span>
|
|
172
|
+
<span className="min-w-0 flex-1 truncate">{summary}</span>
|
|
173
|
+
</button>
|
|
174
|
+
{open && (
|
|
175
|
+
<div
|
|
176
|
+
id={contentId}
|
|
177
|
+
className="cos-markdown w-full rounded-xl border border-kumo-line bg-kumo-elevated/45 px-4 py-3 text-[14px] leading-[1.4] text-kumo-default/70"
|
|
178
|
+
>
|
|
179
|
+
<MarkdownMessage message={reasoning} />
|
|
180
|
+
</div>
|
|
181
|
+
)}
|
|
139
182
|
</div>
|
|
140
183
|
);
|
|
141
184
|
});
|
|
@@ -189,7 +232,7 @@ export const ToolGroupRow = memo(function ToolGroupRow({
|
|
|
189
232
|
</button>
|
|
190
233
|
{open &&
|
|
191
234
|
(group.calls.length === 1 ? (
|
|
192
|
-
<div className="
|
|
235
|
+
<div className="ml-8 mt-1 space-y-3">
|
|
193
236
|
<ToolCallDetails toolCall={group.calls[0]} />
|
|
194
237
|
</div>
|
|
195
238
|
) : (
|
|
@@ -51,7 +51,12 @@ export interface PlanStep {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
export type AssistantBlock =
|
|
54
|
-
| {
|
|
54
|
+
| {
|
|
55
|
+
kind: "reasoning";
|
|
56
|
+
key: string;
|
|
57
|
+
text: string;
|
|
58
|
+
running: boolean;
|
|
59
|
+
}
|
|
55
60
|
| { kind: "text"; key: string; text: string }
|
|
56
61
|
| { kind: "toolGroup"; key: string; group: ToolCallGroup }
|
|
57
62
|
| { kind: "plan"; key: string; steps: PlanStep[]; running: boolean }
|
|
@@ -326,7 +331,12 @@ function assistantBlocks(
|
|
|
326
331
|
const text = String(record.text ?? "").trim();
|
|
327
332
|
if (!text || !showThinkingTraces) return;
|
|
328
333
|
flush();
|
|
329
|
-
blocks.push({
|
|
334
|
+
blocks.push({
|
|
335
|
+
kind: "reasoning",
|
|
336
|
+
key,
|
|
337
|
+
text,
|
|
338
|
+
running: record.state === "streaming",
|
|
339
|
+
});
|
|
330
340
|
return;
|
|
331
341
|
}
|
|
332
342
|
|
package/cloud-os/index.ts
CHANGED
|
@@ -21,7 +21,10 @@ export type {
|
|
|
21
21
|
FileViewProps,
|
|
22
22
|
FileViewStatus,
|
|
23
23
|
} from "./file-view";
|
|
24
|
-
export {
|
|
24
|
+
export {
|
|
25
|
+
CloudOsConversationLoading,
|
|
26
|
+
CloudOsWorkspaceSplit,
|
|
27
|
+
} from "./layout/cloud-os-workspace-split";
|
|
25
28
|
export type { CloudOsWorkspaceSplitProps } from "./layout/cloud-os-workspace-split";
|
|
26
29
|
|
|
27
30
|
export { CloudOsChatMessages } from "./chat/cloud-os-chat-messages";
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
type PointerEvent as ReactPointerEvent,
|
|
7
7
|
type ReactNode,
|
|
8
8
|
} from "react";
|
|
9
|
+
import { motion, useReducedMotion } from "motion/react";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* 左聊天 / 右工作区的双栏骨架。从 cloudflare-os-main `GadgetEditor.tsx` 的 BODY
|
|
@@ -24,6 +25,56 @@ const WORKSPACE_TRANSITION_MS = 200;
|
|
|
24
25
|
|
|
25
26
|
const isBrowser = typeof window !== "undefined";
|
|
26
27
|
|
|
28
|
+
const loadingAssets = {
|
|
29
|
+
spark: new URL("../assets/loading-spark.svg", import.meta.url).href,
|
|
30
|
+
corner: new URL("../assets/loading-corner.svg", import.meta.url).href,
|
|
31
|
+
mark: new URL("../assets/loading-mark.svg", import.meta.url).href,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const loadingSpring = (t: number) =>
|
|
35
|
+
1 - Math.exp(-t * 7.6657) * (Math.cos(t * 6.7605) + 1.1339 * Math.sin(t * 6.7605));
|
|
36
|
+
|
|
37
|
+
export function CloudOsConversationLoading() {
|
|
38
|
+
const reducedMotion = useReducedMotion();
|
|
39
|
+
const repeat = reducedMotion ? 0 : Infinity;
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div className="absolute inset-0 z-30 grid place-items-center bg-kumo-base/90">
|
|
43
|
+
<div role="status" aria-label="Loading conversation..." className="flex flex-col items-center gap-4">
|
|
44
|
+
<div className="relative h-[49px] w-[49px] overflow-hidden" aria-hidden="true">
|
|
45
|
+
<motion.img
|
|
46
|
+
src={loadingAssets.spark}
|
|
47
|
+
alt=""
|
|
48
|
+
className="absolute bottom-[18.5%] right-[61.46%] h-[20.04%] w-[20.04%] will-change-transform"
|
|
49
|
+
initial={reducedMotion ? false : { opacity: 0, rotate: 200, scaleX: 0.5, scaleY: 0.5, x: -140, y: 120 }}
|
|
50
|
+
animate={reducedMotion ? { opacity: 1, rotate: 0, scaleX: 1, scaleY: 1, x: 0, y: 0 } : { opacity: [0, 0, 1, 1, 0, 0], rotate: [200, 200, 10, 0, 0, 200, 200], scaleX: [0.5, 0.5, 1.3, 0.8, 1, 1], scaleY: [0.5, 0.5, 0.75, 1.2, 1, 1], x: [-140, -140, 2, 0, 0, -140, -140], y: [120, 120, -2, 0, 0, 120, 120] }}
|
|
51
|
+
transition={{ opacity: { duration: 1.68, times: [0, 0.2083, 0.25, 0.9998, 0.9999, 1], ease: ["linear", "linear", "linear", "easeIn", "linear"], repeat }, rotate: { duration: 1.68, times: [0, 0.2083, 0.3869, 0.506, 0.9998, 0.9999, 1], ease: ["linear", [0, 0, 0.15, 1], loadingSpring, "linear", "easeIn", "linear"], repeat }, scaleX: { duration: 1.68, times: [0, 0.2083, 0.3274, 0.3869, 0.506, 1], ease: ["linear", [0, 0, 0.15, 1], "easeOut", loadingSpring, "linear"], repeat }, scaleY: { duration: 1.68, times: [0, 0.2083, 0.3274, 0.3869, 0.506, 1], ease: ["linear", [0, 0, 0.15, 1], "easeOut", loadingSpring, "linear"], repeat }, x: { duration: 1.68, times: [0, 0.2083, 0.3869, 0.506, 0.9998, 0.9999, 1], ease: ["linear", [0, 0, 0.15, 1], loadingSpring, "linear", "easeIn", "linear"], repeat }, y: { duration: 1.68, times: [0, 0.2083, 0.3869, 0.506, 0.9998, 0.9999, 1], ease: ["linear", [0, 0, 0.15, 1], loadingSpring, "linear", "easeIn", "linear"], repeat } }}
|
|
52
|
+
/>
|
|
53
|
+
<motion.img
|
|
54
|
+
src={loadingAssets.corner}
|
|
55
|
+
alt=""
|
|
56
|
+
className="absolute bottom-[11.65%] right-[43.79%] h-[44.74%] w-[44.67%] will-change-transform"
|
|
57
|
+
initial={reducedMotion ? false : { opacity: 0, scaleX: 1, scaleY: 1, x: 0, y: 0 }}
|
|
58
|
+
animate={reducedMotion ? { opacity: 1, scaleX: 1, scaleY: 1, x: 0, y: 0 } : { opacity: [0, 1, 1, 0, 0], scaleX: [1, 1, 1.06, 1, 1], scaleY: [1, 1, 0.95, 1, 1], x: [0, 0, 4, 0, 0], y: [0, 0, -2, 0, 0] }}
|
|
59
|
+
transition={{ opacity: { duration: 1.68, times: [0, 0.1786, 0.9998, 0.9999, 1], ease: [[0, 0, 0.3, 1], "linear", "easeIn", "linear"], repeat }, scaleX: { duration: 1.68, times: [0, 0.3988, 0.4345, 0.6548, 1], ease: ["linear", [0.2, 0.9, 0.3, 1], loadingSpring, "linear"], repeat }, scaleY: { duration: 1.68, times: [0, 0.3988, 0.4345, 0.6548, 1], ease: ["linear", [0.2, 0.9, 0.3, 1], loadingSpring, "linear"], repeat }, x: { duration: 1.68, times: [0, 0.3988, 0.4345, 0.6845, 1], ease: ["linear", [0.2, 0.9, 0.3, 1], loadingSpring, "linear"], repeat }, y: { duration: 1.68, times: [0, 0.3988, 0.4345, 0.6845, 1], ease: ["linear", [0.2, 0.9, 0.3, 1], loadingSpring, "linear"], repeat } }}
|
|
60
|
+
/>
|
|
61
|
+
<motion.img
|
|
62
|
+
src={loadingAssets.mark}
|
|
63
|
+
alt=""
|
|
64
|
+
className="absolute bottom-[19.14%] right-[11.53%] h-[69.22%] w-[69.33%] will-change-transform"
|
|
65
|
+
initial={reducedMotion ? false : { opacity: 0, rotate: 0, scaleX: 0.85, scaleY: 0.85, x: 0, y: 0 }}
|
|
66
|
+
animate={reducedMotion ? { opacity: 1, rotate: 0, scaleX: 1, scaleY: 1, x: 0, y: 0 } : { opacity: [0, 1, 1, 0, 0], rotate: [0, 0, -3, 0, 0], scaleX: [0.85, 1, 1, 1.1, 0.95, 1, 1], scaleY: [0.85, 1, 1, 0.92, 1.05, 1, 1], x: [0, 0, 6, 0, 0], y: [0, 0, -4, 0, 0] }}
|
|
67
|
+
transition={{ opacity: { duration: 1.68, times: [0, 0.2083, 0.9998, 0.9999, 1], ease: [[0, 0, 0.3, 1], "linear", "easeIn", "linear"], repeat }, rotate: { duration: 1.68, times: [0, 0.3869, 0.4286, 0.6548, 1], ease: ["linear", [0.2, 0.9, 0.3, 1], loadingSpring, "linear"], repeat }, scaleX: { duration: 1.68, times: [0, 0.2083, 0.3869, 0.4167, 0.4643, 0.6548, 1], ease: [[0, 0, 0.3, 1], "linear", [0.2, 0.9, 0.3, 1], "easeOut", loadingSpring, "linear"], repeat }, scaleY: { duration: 1.68, times: [0, 0.2083, 0.3869, 0.4167, 0.4643, 0.6548, 1], ease: [[0, 0, 0.3, 1], "linear", [0.2, 0.9, 0.3, 1], "easeOut", loadingSpring, "linear"], repeat }, x: { duration: 1.68, times: [0, 0.3869, 0.4167, 0.6548, 1], ease: ["linear", [0.2, 0.9, 0.3, 1], loadingSpring, "linear"], repeat }, y: { duration: 1.68, times: [0, 0.3869, 0.4167, 0.6548, 1], ease: ["linear", [0.2, 0.9, 0.3, 1], loadingSpring, "linear"], repeat } }}
|
|
68
|
+
/>
|
|
69
|
+
</div>
|
|
70
|
+
<p className="m-0 text-center text-[14px] font-normal leading-[1.4] text-kumo-default">
|
|
71
|
+
Loading conversation...
|
|
72
|
+
</p>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
27
78
|
function clampChatWidth(width: number, containerWidth?: number) {
|
|
28
79
|
if (!isBrowser) {
|
|
29
80
|
return Math.max(MIN_CHAT_WIDTH, Math.min(DEFAULT_CHAT_WIDTH, width));
|
|
@@ -62,6 +113,8 @@ export interface CloudOsWorkspaceSplitProps {
|
|
|
62
113
|
/** 右栏收起时依然保留的窄边栏宽度(原文件的 Outputs rail)。 */
|
|
63
114
|
railWidth?: number;
|
|
64
115
|
rail?: ReactNode;
|
|
116
|
+
/** 覆盖整个聊天 + 工作区主体;页面级导航应放在分栏外。 */
|
|
117
|
+
isLoading?: boolean;
|
|
65
118
|
}
|
|
66
119
|
|
|
67
120
|
export function CloudOsWorkspaceSplit({
|
|
@@ -70,6 +123,7 @@ export function CloudOsWorkspaceSplit({
|
|
|
70
123
|
workspaceOpen,
|
|
71
124
|
railWidth = 0,
|
|
72
125
|
rail,
|
|
126
|
+
isLoading = false,
|
|
73
127
|
}: CloudOsWorkspaceSplitProps) {
|
|
74
128
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
75
129
|
const resizeSessionRef = useRef<ResizeSession | null>(null);
|
|
@@ -184,6 +238,7 @@ export function CloudOsWorkspaceSplit({
|
|
|
184
238
|
<div
|
|
185
239
|
ref={containerRef}
|
|
186
240
|
className="relative flex h-full min-h-0 flex-1 overflow-hidden bg-kumo-base"
|
|
241
|
+
aria-busy={isLoading}
|
|
187
242
|
>
|
|
188
243
|
{/* ── 左:聊天 ──────────────────────────────────────────────────────── */}
|
|
189
244
|
<div
|
|
@@ -223,6 +278,8 @@ export function CloudOsWorkspaceSplit({
|
|
|
223
278
|
</div>
|
|
224
279
|
|
|
225
280
|
{rail}
|
|
281
|
+
|
|
282
|
+
{isLoading && <CloudOsConversationLoading />}
|
|
226
283
|
</div>
|
|
227
284
|
);
|
|
228
285
|
}
|
|
@@ -11,7 +11,7 @@ const buttonBaseClassName =
|
|
|
11
11
|
|
|
12
12
|
const buttonToneClassNames = {
|
|
13
13
|
primary:
|
|
14
|
-
"!h-9 bg-
|
|
14
|
+
"!h-9 bg-gradient-to-br from-[#FFD077] to-[#FF9938] px-3 text-white enabled:hover:opacity-90 disabled:opacity-50",
|
|
15
15
|
secondary:
|
|
16
16
|
"!h-8 border border-kumo-line bg-kumo-base px-3 text-kumo-default enabled:hover:bg-kumo-elevated disabled:opacity-40",
|
|
17
17
|
danger:
|
|
@@ -72,7 +72,8 @@ export function CamelChatShowcase() {
|
|
|
72
72
|
: {};
|
|
73
73
|
return (
|
|
74
74
|
record.type === "tool-write_file" ||
|
|
75
|
-
record.type === "tool-cancel_schedule"
|
|
75
|
+
record.type === "tool-cancel_schedule" ||
|
|
76
|
+
record.type === "tool-schedule"
|
|
76
77
|
) &&
|
|
77
78
|
record.state === "approval-requested"
|
|
78
79
|
? { id: "camel-demo-approval", approved: approval }
|
|
@@ -256,6 +257,14 @@ export function CamelChatShowcase() {
|
|
|
256
257
|
}}
|
|
257
258
|
onApprove={(_approvalId, decision) => {
|
|
258
259
|
const approved = decision !== "deny";
|
|
260
|
+
if (scenario === "schedule_create") {
|
|
261
|
+
setApproval(approved);
|
|
262
|
+
selectScenario(approved ? "schedule" : "submitted");
|
|
263
|
+
setLastEvent(approved
|
|
264
|
+
? "Scheduled task created"
|
|
265
|
+
: "Schedule creation cancelled");
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
259
268
|
if (scenario === "schedule_cancel") {
|
|
260
269
|
setApproval(approved);
|
|
261
270
|
setLastEvent(
|
package/demo/chat-scenarios.ts
CHANGED
|
@@ -37,6 +37,7 @@ export type DemoScenario =
|
|
|
37
37
|
| "temporary_agent"
|
|
38
38
|
| "agent_tools"
|
|
39
39
|
| "background_agent"
|
|
40
|
+
| "schedule_create"
|
|
40
41
|
| "schedule"
|
|
41
42
|
| "schedule_cancel"
|
|
42
43
|
| "approval"
|
|
@@ -118,9 +119,15 @@ export const scenarios: Record<DemoScenario, DemoScenarioMeta> = {
|
|
|
118
119
|
status: "ready",
|
|
119
120
|
flowStep: 3,
|
|
120
121
|
},
|
|
122
|
+
schedule_create: {
|
|
123
|
+
label: "Create scheduled task",
|
|
124
|
+
description: "Review the task, cadence, and timezone before creating the schedule.",
|
|
125
|
+
status: "ready",
|
|
126
|
+
flowStep: 2,
|
|
127
|
+
},
|
|
121
128
|
schedule: {
|
|
122
|
-
label: "
|
|
123
|
-
description: "schedule
|
|
129
|
+
label: "Scheduled task created",
|
|
130
|
+
description: "A completed schedule shows its task, cadence, timezone, and final status.",
|
|
124
131
|
status: "ready",
|
|
125
132
|
flowStep: 3,
|
|
126
133
|
},
|
|
@@ -292,8 +299,26 @@ function askUserRequest(response?: DemoAskUserResponse): UIMessage {
|
|
|
292
299
|
const planningWork = {
|
|
293
300
|
id: "camel-demo-planning-work",
|
|
294
301
|
role: "assistant",
|
|
295
|
-
metadata: {
|
|
302
|
+
metadata: {
|
|
303
|
+
turnStartedAt: demoTurnStartedAt + 3_000,
|
|
304
|
+
turnDurationMs: 18_000,
|
|
305
|
+
},
|
|
296
306
|
parts: [
|
|
307
|
+
{
|
|
308
|
+
type: "reasoning",
|
|
309
|
+
text: `先确认受众和交付范围,再验证产品案例。
|
|
310
|
+
|
|
311
|
+
用户选择的读者是管理层,交付物是报告。因此内容不能只罗列界面趋势,还需要把每项趋势连接到业务价值、落地成本和潜在风险,帮助管理层判断优先级。
|
|
312
|
+
|
|
313
|
+
接下来会分三步推进:
|
|
314
|
+
|
|
315
|
+
1. 检索 2025–2026 年主流 AI Agent 产品,优先采集官方发布说明、产品文档和可验证的真实界面。
|
|
316
|
+
2. 对比任务规划、工具调用、人工确认、后台执行和结果交付等关键交互,区分普遍模式与单个产品的特色设计。
|
|
317
|
+
3. 把证据整理成可执行建议,并为每条建议标注适用场景、实现复杂度和预期收益。
|
|
318
|
+
|
|
319
|
+
第一轮检索先覆盖通用 Agent、开发者 Agent 与企业自动化产品。如果不同来源对同一趋势的描述冲突,会保留分歧并继续寻找一手证据,而不是直接下结论。`,
|
|
320
|
+
state: "done",
|
|
321
|
+
},
|
|
297
322
|
{
|
|
298
323
|
type: "tool-update_plan",
|
|
299
324
|
state: "output-available",
|
|
@@ -478,6 +503,26 @@ const backgroundResult = {
|
|
|
478
503
|
],
|
|
479
504
|
} as unknown as UIMessage;
|
|
480
505
|
|
|
506
|
+
export const demoScheduleInput = {
|
|
507
|
+
label: "Weekly competitor report",
|
|
508
|
+
prompt: "Generate and send this week's competitor report.",
|
|
509
|
+
trigger: { kind: "cron", cron: "0 9 * * 1" },
|
|
510
|
+
timezone: "Asia/Shanghai",
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
const scheduleRequest = {
|
|
514
|
+
id: "camel-demo-schedule-request",
|
|
515
|
+
role: "assistant",
|
|
516
|
+
parts: [
|
|
517
|
+
{
|
|
518
|
+
type: "tool-schedule",
|
|
519
|
+
toolCallId: "camel-demo-schedule-request-1",
|
|
520
|
+
state: "approval-requested",
|
|
521
|
+
input: demoScheduleInput,
|
|
522
|
+
},
|
|
523
|
+
],
|
|
524
|
+
} as unknown as UIMessage;
|
|
525
|
+
|
|
481
526
|
const scheduledTask = {
|
|
482
527
|
id: "camel-demo-schedule",
|
|
483
528
|
role: "assistant",
|
|
@@ -486,12 +531,7 @@ const scheduledTask = {
|
|
|
486
531
|
type: "tool-schedule",
|
|
487
532
|
toolCallId: "camel-demo-schedule-1",
|
|
488
533
|
state: "output-available",
|
|
489
|
-
input:
|
|
490
|
-
label: "每周发送竞品报告",
|
|
491
|
-
prompt: "生成并发送本周竞品报告。",
|
|
492
|
-
trigger: { kind: "cron", cron: "0 9 * * 1" },
|
|
493
|
-
timezone: "Asia/Shanghai",
|
|
494
|
-
},
|
|
534
|
+
input: demoScheduleInput,
|
|
495
535
|
output: { scheduled: true, id: "schedule-1" },
|
|
496
536
|
},
|
|
497
537
|
],
|
|
@@ -877,6 +917,9 @@ export function messagesForScenario(
|
|
|
877
917
|
if (scenario === "background_agent") {
|
|
878
918
|
return [...conversation, backgroundDispatch, backgroundResult];
|
|
879
919
|
}
|
|
920
|
+
if (scenario === "schedule_create") {
|
|
921
|
+
return [...conversation, scheduleRequest];
|
|
922
|
+
}
|
|
880
923
|
if (scenario === "schedule") {
|
|
881
924
|
return [...conversation, scheduledTask];
|
|
882
925
|
}
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
CloudOsRoot,
|
|
14
14
|
CloudOsWorkspacePanel,
|
|
15
15
|
CloudOsWorkspaceSplit,
|
|
16
|
+
ScheduleConfirmation,
|
|
16
17
|
WorkshopIconButton,
|
|
17
18
|
buildCloudOsEntries,
|
|
18
19
|
type CloudOsAttachmentView,
|
|
@@ -32,7 +33,7 @@ import {
|
|
|
32
33
|
|
|
33
34
|
/**
|
|
34
35
|
* cloud-os 版快速预览。场景表和 camel-chat-showcase 共用 `chat-scenarios.ts`,
|
|
35
|
-
* 所以两边的状态逐条对齐(同样
|
|
36
|
+
* 所以两边的状态逐条对齐(同样 23 个场景、同样的 7 步流程条、同样的初始 URL 参数)。
|
|
36
37
|
* 差别只在外壳:这里是 cloudflare-os 的左聊天 / 右工作区双栏。
|
|
37
38
|
*/
|
|
38
39
|
|
|
@@ -155,7 +156,8 @@ export function CloudOsChatShowcase() {
|
|
|
155
156
|
? (part as { type?: unknown; state?: unknown })
|
|
156
157
|
: {};
|
|
157
158
|
return (record.type === "tool-write_file" ||
|
|
158
|
-
record.type === "tool-cancel_schedule"
|
|
159
|
+
record.type === "tool-cancel_schedule" ||
|
|
160
|
+
record.type === "tool-schedule") &&
|
|
159
161
|
record.state === "approval-requested"
|
|
160
162
|
? "cloud-os-demo-approval"
|
|
161
163
|
: undefined;
|
|
@@ -285,6 +287,7 @@ export function CloudOsChatShowcase() {
|
|
|
285
287
|
|
|
286
288
|
<CloudOsWorkspaceSplit
|
|
287
289
|
workspaceOpen={workspaceOpen}
|
|
290
|
+
isLoading={scenario === "hydrating"}
|
|
288
291
|
chat={
|
|
289
292
|
<div className="flex h-full min-h-0 flex-col bg-kumo-base">
|
|
290
293
|
<aside
|
|
@@ -353,18 +356,36 @@ export function CloudOsChatShowcase() {
|
|
|
353
356
|
status === "submitted" ||
|
|
354
357
|
status === "streaming" ||
|
|
355
358
|
scenario === "ask" ||
|
|
359
|
+
scenario === "schedule_create" ||
|
|
356
360
|
((scenario === "approval" || scenario === "schedule_cancel") &&
|
|
357
361
|
approval === undefined)
|
|
358
362
|
}
|
|
359
|
-
isHydrating={scenario === "hydrating"}
|
|
360
363
|
isRecovering={scenario === "recovering"}
|
|
361
|
-
recoveryStatusLabel="
|
|
364
|
+
recoveryStatusLabel="Model response interrupted. Retrying…"
|
|
362
365
|
awaitingApproval={
|
|
363
|
-
(scenario === "approval" ||
|
|
366
|
+
(scenario === "approval" ||
|
|
367
|
+
scenario === "schedule_cancel" ||
|
|
368
|
+
scenario === "schedule_create") &&
|
|
364
369
|
approval === undefined
|
|
365
370
|
}
|
|
366
371
|
showThinkingTraces={showThinkingTraces}
|
|
367
372
|
approvalIdOf={approvalIdOf}
|
|
373
|
+
renderApproval={({ toolName, input }) =>
|
|
374
|
+
toolName === "schedule"
|
|
375
|
+
? (
|
|
376
|
+
<ScheduleConfirmation
|
|
377
|
+
input={input}
|
|
378
|
+
onCancel={() => {
|
|
379
|
+
selectScenario("submitted");
|
|
380
|
+
setLastEvent("Schedule creation cancelled");
|
|
381
|
+
}}
|
|
382
|
+
onConfirm={() => {
|
|
383
|
+
selectScenario("schedule");
|
|
384
|
+
setLastEvent("Scheduled task created");
|
|
385
|
+
}}
|
|
386
|
+
/>
|
|
387
|
+
)
|
|
388
|
+
: undefined}
|
|
368
389
|
error={statusError}
|
|
369
390
|
constrainWidth={!workspaceOpen}
|
|
370
391
|
onRetry={() => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@springbrand/message-panel",
|
|
3
|
-
"version": "0.1.3-alpha.
|
|
3
|
+
"version": "0.1.3-alpha.14",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"src",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"@streamdown/math": "^1.0.2",
|
|
32
32
|
"@streamdown/mermaid": "^1.0.2",
|
|
33
33
|
"lucide-react": "^1.24.0",
|
|
34
|
+
"motion": "^13.1.0",
|
|
34
35
|
"radix-ui": "^1.6.2",
|
|
35
36
|
"react-markdown": "^10.1.0",
|
|
36
37
|
"remark-gfm": "^4.0.1",
|