@springbrand/message-panel 0.1.3-alpha.8 → 0.2.0-alpha.37

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.
Files changed (67) hide show
  1. package/cloud-os/README.md +4 -3
  2. package/cloud-os/assets/followup-arrow.svg +3 -0
  3. package/cloud-os/assets/loading-corner.svg +3 -0
  4. package/cloud-os/assets/loading-mark.svg +16 -0
  5. package/cloud-os/assets/loading-spark.svg +3 -0
  6. package/cloud-os/assets/model-selected.svg +5 -0
  7. package/cloud-os/assets/thinking-star.svg +9 -0
  8. package/cloud-os/capability-chip.tsx +1 -1
  9. package/cloud-os/chat/cloud-os-chat-messages.tsx +441 -98
  10. package/cloud-os/chat/code-runner.tsx +106 -0
  11. package/cloud-os/chat/image-generation.tsx +76 -0
  12. package/cloud-os/chat/loading-state.tsx +25 -0
  13. package/cloud-os/chat/markdown-message.tsx +144 -41
  14. package/cloud-os/chat/range.ts +8 -0
  15. package/cloud-os/chat/rich-blocks.tsx +285 -227
  16. package/cloud-os/chat/surfaces.tsx +90 -0
  17. package/cloud-os/chat/tool-call.tsx +94 -0
  18. package/cloud-os/chat/tool-presentation.ts +31 -22
  19. package/cloud-os/chat/tool-rows.tsx +195 -143
  20. package/cloud-os/chat/tool-timeline.tsx +123 -0
  21. package/cloud-os/chat/transcript-model.ts +258 -20
  22. package/cloud-os/chat/web-search.tsx +135 -0
  23. package/cloud-os/composer/cloud-os-chat-input.tsx +38 -88
  24. package/cloud-os/composer/cloud-os-model-select.tsx +105 -0
  25. package/cloud-os/file-view.tsx +195 -12
  26. package/cloud-os/index.ts +24 -3
  27. package/cloud-os/layout/cloud-os-workspace-split.tsx +60 -3
  28. package/cloud-os/primitives/collapsible.tsx +21 -0
  29. package/cloud-os/primitives/workshop-controls.tsx +2 -2
  30. package/cloud-os/styles/cloud-os.css +132 -1
  31. package/demo/camel-chat-showcase.tsx +21 -13
  32. package/demo/chat-scenarios.ts +214 -40
  33. package/demo/cloud-os-chat-showcase.tsx +37 -14
  34. package/demo/fixtures.ts +4 -5
  35. package/demo/message-panel-gallery.tsx +16 -2
  36. package/package.json +8 -4
  37. package/springbrand-pet/LICENSE.bloub +21 -0
  38. package/springbrand-pet/bot/cycles.test.ts +221 -0
  39. package/springbrand-pet/bot/cycles.ts +225 -0
  40. package/springbrand-pet/bot/decor.ts +285 -0
  41. package/springbrand-pet/bot/engine.test.ts +543 -0
  42. package/springbrand-pet/bot/engine.ts +558 -0
  43. package/springbrand-pet/bot/expressions.test.ts +135 -0
  44. package/springbrand-pet/bot/expressions.ts +192 -0
  45. package/springbrand-pet/bot/eyefit.ts +455 -0
  46. package/springbrand-pet/bot/face.test.ts +101 -0
  47. package/springbrand-pet/bot/face.ts +179 -0
  48. package/springbrand-pet/bot/math.ts +42 -0
  49. package/springbrand-pet/bot/profiles.ts +22 -0
  50. package/springbrand-pet/bot/repere.ts +31 -0
  51. package/springbrand-pet/bot/shape.test.ts +97 -0
  52. package/springbrand-pet/bot/shape.ts +310 -0
  53. package/springbrand-pet/bot/skins.test.ts +325 -0
  54. package/springbrand-pet/bot/skins.ts +161 -0
  55. package/springbrand-pet/bot/states.ts +616 -0
  56. package/springbrand-pet/drag.test.ts +19 -0
  57. package/springbrand-pet/index.tsx +569 -0
  58. package/springbrand-pet/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  59. package/springbrand-pet/vitest.config.ts +9 -0
  60. package/src/camel/camel-chat-messages.tsx +78 -78
  61. package/src/camel/camel-tool-presentation.tsx +19 -15
  62. package/src/camel/camel-turn.ts +1 -17
  63. package/src/composer/composer-attachments.tsx +1 -1
  64. package/src/composer/composer.tsx +2 -2
  65. package/src/contracts.ts +0 -2
  66. package/src/message-panel.tsx +1 -24
  67. package/src/parts/index.tsx +103 -64
package/cloud-os/index.ts CHANGED
@@ -20,8 +20,12 @@ export type {
20
20
  FileViewPlacement,
21
21
  FileViewProps,
22
22
  FileViewStatus,
23
+ FileViewVariant,
23
24
  } from "./file-view";
24
- export { CloudOsWorkspaceSplit } from "./layout/cloud-os-workspace-split";
25
+ export {
26
+ CloudOsConversationLoading,
27
+ CloudOsWorkspaceSplit,
28
+ } from "./layout/cloud-os-workspace-split";
25
29
  export type { CloudOsWorkspaceSplitProps } from "./layout/cloud-os-workspace-split";
26
30
 
27
31
  export { CloudOsChatMessages } from "./chat/cloud-os-chat-messages";
@@ -36,8 +40,12 @@ export type {
36
40
  CloudOsCapabilityView,
37
41
  CloudOsChatInputProps,
38
42
  CloudOsComposerCommand,
39
- CloudOsModelOption,
40
43
  } from "./composer/cloud-os-chat-input";
44
+ export { CloudOsModelSelect } from "./composer/cloud-os-model-select";
45
+ export type {
46
+ CloudOsModelOption,
47
+ CloudOsModelSelectProps,
48
+ } from "./composer/cloud-os-model-select";
41
49
 
42
50
  export {
43
51
  CloudOsWorkspacePanel,
@@ -53,13 +61,26 @@ export type {
53
61
  } from "./workspace/cloud-os-workspace-panel";
54
62
 
55
63
  export { MarkdownMessage } from "./chat/markdown-message";
56
- export type { CloudOsUrlResolver } from "./chat/markdown-message";
64
+ export type {
65
+ CloudOsArtifactResolver,
66
+ CloudOsUrlResolver,
67
+ } from "./chat/markdown-message";
57
68
  export {
58
69
  ThinkingTraceRow,
59
70
  ToolCallDetails,
60
71
  ToolGroupRow,
61
72
  WorkIcon,
62
73
  } from "./chat/tool-rows";
74
+ export { ToolCall } from "./chat/tool-call";
75
+ export type { ToolCallProps } from "./chat/tool-call";
76
+ export { CodeRunner } from "./chat/code-runner";
77
+ export type { RunState } from "./chat/code-runner";
78
+ export { ToolTimeline } from "./chat/tool-timeline";
79
+ export type {
80
+ TimelineStat,
81
+ TimelineStep,
82
+ ToolTimelineProps,
83
+ } from "./chat/tool-timeline";
63
84
  export {
64
85
  ApprovalBlock,
65
86
  AskUserBlock,
@@ -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
@@ -17,14 +18,64 @@ import {
17
18
  */
18
19
 
19
20
  const CHAT_WIDTH_STORAGE_KEY = "cloud-os:chatWidth";
20
- const MIN_CHAT_WIDTH = 280;
21
+ const MIN_CHAT_WIDTH = 360;
21
22
  const MIN_WORKSPACE_WIDTH = 400;
22
23
  const DEFAULT_CHAT_WIDTH = 420;
23
24
  const WORKSPACE_TRANSITION_MS = 200;
24
25
 
25
26
  const isBrowser = typeof window !== "undefined";
26
27
 
27
- function clampChatWidth(width: number, containerWidth?: number) {
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
+
78
+ export function clampChatWidth(width: number, containerWidth?: number) {
28
79
  if (!isBrowser) {
29
80
  return Math.max(MIN_CHAT_WIDTH, Math.min(DEFAULT_CHAT_WIDTH, width));
30
81
  }
@@ -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
@@ -199,7 +254,7 @@ export function CloudOsWorkspaceSplit({
199
254
 
200
255
  {/* ── 拖拽把手 ─────────────────────────────────────────────────────── */}
201
256
  <div
202
- className={`relative flex-shrink-0 cursor-col-resize touch-none overflow-visible bg-kumo-line ${transitionClass}`}
257
+ className={`cos-workspace-divider relative flex-shrink-0 cursor-col-resize touch-none overflow-visible bg-kumo-line ${transitionClass}`}
203
258
  style={{ width: workspaceOpen ? 1 : 0 }}
204
259
  onPointerDown={handleResizePointerDown}
205
260
  onPointerMove={handleResizePointerMove}
@@ -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
  }
@@ -0,0 +1,21 @@
1
+ "use client";
2
+
3
+ import { Collapsible as CollapsiblePrimitive } from "@base-ui/react/collapsible";
4
+
5
+ function Collapsible({ ...props }: CollapsiblePrimitive.Root.Props) {
6
+ return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />;
7
+ }
8
+
9
+ function CollapsibleTrigger({ ...props }: CollapsiblePrimitive.Trigger.Props) {
10
+ return (
11
+ <CollapsiblePrimitive.Trigger data-slot="collapsible-trigger" {...props} />
12
+ );
13
+ }
14
+
15
+ function CollapsibleContent({ ...props }: CollapsiblePrimitive.Panel.Props) {
16
+ return (
17
+ <CollapsiblePrimitive.Panel data-slot="collapsible-content" {...props} />
18
+ );
19
+ }
20
+
21
+ export { Collapsible, CollapsibleTrigger, CollapsibleContent };
@@ -11,7 +11,7 @@ const buttonBaseClassName =
11
11
 
12
12
  const buttonToneClassNames = {
13
13
  primary:
14
- "!h-9 bg-kumo-contrast px-3 text-kumo-inverse enabled:hover:bg-kumo-strong disabled:opacity-50",
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:
@@ -59,7 +59,7 @@ export function WorkshopIconButton({
59
59
  }: WorkshopIconButtonProps) {
60
60
  const toneClassName =
61
61
  tone === "primary"
62
- ? "bg-kumo-contrast text-kumo-inverse enabled:hover:bg-kumo-strong enabled:hover:text-kumo-inverse"
62
+ ? "bg-[var(--primary,var(--color-kumo-brand))] text-[var(--primary-foreground,var(--text-color-kumo-inverse))] enabled:hover:bg-[var(--primary-hover,var(--color-kumo-brand-hover))]"
63
63
  : danger
64
64
  ? "text-kumo-subtle enabled:hover:bg-kumo-danger-tint enabled:hover:text-kumo-danger"
65
65
  : "text-kumo-subtle enabled:hover:bg-kumo-tint enabled:hover:text-kumo-default";
@@ -99,6 +99,7 @@
99
99
  --radius-2xl: 1rem;
100
100
 
101
101
  --spacing: 0.25rem;
102
+ --cos-conversation-max-width: 48rem;
102
103
 
103
104
  --ease-in: cubic-bezier(0.4, 0, 1, 1);
104
105
  --ease-out: cubic-bezier(0, 0, 0.2, 1);
@@ -341,6 +342,54 @@
341
342
  display: none;
342
343
  }
343
344
 
345
+ .cos-model-select {
346
+ --cos-model-select-bg: #fff;
347
+ --cos-model-select-border: rgb(0 0 0 / 7%);
348
+ --cos-model-select-text: #000;
349
+ --cos-model-select-muted: rgb(0 0 0 / 50%);
350
+ --cos-model-select-disabled: rgb(0 0 0 / 30%);
351
+ --cos-model-select-highlight: rgb(0 0 0 / 3%);
352
+ border-color: var(--cos-model-select-border);
353
+ background: var(--cos-model-select-bg);
354
+ box-shadow: 0 8px 14px rgb(0 0 0 / 6%);
355
+ color: var(--cos-model-select-text);
356
+ font-family: "Inter Variable", Inter, sans-serif;
357
+ }
358
+
359
+ .cos-model-select__item[data-highlighted] {
360
+ background: var(--cos-model-select-highlight);
361
+ }
362
+
363
+ .cos-model-select__item[data-disabled] {
364
+ cursor: not-allowed;
365
+ opacity: 1;
366
+ }
367
+
368
+ .cos-model-select__name {
369
+ color: var(--cos-model-select-text);
370
+ }
371
+
372
+ .cos-model-select__name[data-muted] {
373
+ color: var(--cos-model-select-muted);
374
+ }
375
+
376
+ .cos-model-select__plan {
377
+ color: var(--cos-model-select-muted);
378
+ }
379
+
380
+ .cos-model-select__plan[data-disabled] {
381
+ color: var(--cos-model-select-disabled);
382
+ }
383
+
384
+ [data-cloud-os-portal][data-mode="dark"].cos-model-select {
385
+ --cos-model-select-bg: #343434;
386
+ --cos-model-select-border: rgb(255 255 255 / 10%);
387
+ --cos-model-select-text: #fff;
388
+ --cos-model-select-muted: rgb(255 255 255 / 50%);
389
+ --cos-model-select-disabled: rgb(255 255 255 / 30%);
390
+ --cos-model-select-highlight: rgb(255 255 255 / 7%);
391
+ }
392
+
344
393
  .cos-chat-panel {
345
394
  scrollbar-width: thin;
346
395
  scrollbar-color: var(--color-kumo-line) transparent;
@@ -356,6 +405,13 @@
356
405
  background: transparent;
357
406
  }
358
407
 
408
+ .cos-workspace-divider {
409
+ transition: background-color var(--default-transition-duration) ease;
410
+ }
411
+ .cos-workspace-divider:hover {
412
+ background-color: var(--color-kumo-interact);
413
+ }
414
+
359
415
  /* ============================================================================
360
416
  * 以下整段来自 ChatInterface.module.css —— CSS Module 类名改成 cos- 全局前缀
361
417
  * ========================================================================== */
@@ -365,6 +421,9 @@
365
421
  line-height: 1.6;
366
422
  overflow-wrap: anywhere;
367
423
  }
424
+ .cos-user-markdown :where(p, li) {
425
+ white-space: pre-wrap;
426
+ }
368
427
  .cos-markdown p {
369
428
  margin: 0.4em 0;
370
429
  }
@@ -469,8 +528,12 @@
469
528
  font-weight: 600;
470
529
  }
471
530
  .cos-markdown img {
472
- max-width: 100%;
531
+ max-width: min(100%, 16rem);
532
+ max-height: 13rem;
533
+ width: auto;
473
534
  height: auto;
535
+ border-radius: 0.75rem;
536
+ object-fit: contain;
474
537
  }
475
538
 
476
539
  /* ── 输入框容器(container query 决定「添加资源」标签是否显示)─────────────── */
@@ -502,6 +565,24 @@
502
565
  flex: none;
503
566
  }
504
567
 
568
+ /* Trace 新块进入时轻推上浮;已有块更新不会重复播放。 */
569
+ .cos-trace-chain > * {
570
+ animation: cos-trace-enter 180ms cubic-bezier(0.32, 0.72, 0, 1) both;
571
+ }
572
+
573
+ @keyframes cos-trace-enter {
574
+ from {
575
+ opacity: 0;
576
+ transform: translateY(6px);
577
+ }
578
+ }
579
+
580
+ @media (prefers-reduced-motion: reduce) {
581
+ .cos-trace-chain > * {
582
+ animation: none;
583
+ }
584
+ }
585
+
505
586
  /* ── 流式占位:Thinking 扫光 ──────────────────────────────────────────────── */
506
587
  .cos-thinking-shimmer {
507
588
  color: var(--text-color-kumo-inactive);
@@ -527,14 +608,64 @@
527
608
  }
528
609
  }
529
610
 
611
+ .cos-thinking-star {
612
+ animation: cos-thinking-star 1.4s ease-in-out infinite;
613
+ }
614
+
615
+ @keyframes cos-thinking-star {
616
+ 0%, 100% {
617
+ transform: scale(0.82);
618
+ }
619
+ 50% {
620
+ transform: scale(1.1);
621
+ }
622
+ }
623
+
530
624
  @media (prefers-reduced-motion: reduce) {
625
+ [data-sd-animate] {
626
+ animation: none !important;
627
+ }
628
+
629
+ .cos-thinking-star,
531
630
  .cos-thinking-shimmer {
532
631
  animation: none;
632
+ }
633
+
634
+ .cos-thinking-shimmer {
533
635
  background: none;
534
636
  -webkit-text-fill-color: currentColor;
535
637
  }
536
638
  }
537
639
 
640
+ /* ── 原位提问:逐题横向切换 ─────────────────────────────────────────────── */
641
+ .cos-ask-user-question[data-direction="forward"] {
642
+ animation: cos-ask-user-forward 180ms ease-out;
643
+ }
644
+
645
+ .cos-ask-user-question[data-direction="back"] {
646
+ animation: cos-ask-user-back 180ms ease-out;
647
+ }
648
+
649
+ @keyframes cos-ask-user-forward {
650
+ from {
651
+ opacity: 0;
652
+ transform: translateX(24px);
653
+ }
654
+ }
655
+
656
+ @keyframes cos-ask-user-back {
657
+ from {
658
+ opacity: 0;
659
+ transform: translateX(-24px);
660
+ }
661
+ }
662
+
663
+ @media (prefers-reduced-motion: reduce) {
664
+ .cos-ask-user-question {
665
+ animation: none !important;
666
+ }
667
+ }
668
+
538
669
  /* ── 观察类 markdown(行内、紧凑)────────────────────────────────────────── */
539
670
  .cos-observation-markdown {
540
671
  line-height: 1.5;
@@ -15,11 +15,13 @@ import {
15
15
  import {
16
16
  commands as sharedCommands,
17
17
  defaultPrompt,
18
+ demoAskUserResponse,
18
19
  demoModels,
19
20
  finalToolNames,
20
21
  flowSteps,
21
22
  messagesForScenario,
22
23
  scenarios,
24
+ type DemoAskUserResponse,
23
25
  type DemoScenario,
24
26
  type DemoScenarioMeta,
25
27
  } from "./chat-scenarios";
@@ -42,7 +44,7 @@ export function CamelChatShowcase() {
42
44
  const [theme, setTheme] = useState<"light" | "dark">("dark");
43
45
  const [scenario, setScenario] = useState<DemoScenario>(initialScenario);
44
46
  const [prompt, setPrompt] = useState(defaultPrompt);
45
- const [humanAnswer, setHumanAnswer] = useState("管理层");
47
+ const [humanAnswer, setHumanAnswer] = useState(demoAskUserResponse);
46
48
  const [model, setModel] = useState<string>(demoModels[0].value);
47
49
  const [value, setValue] = useState("");
48
50
  const [attachments, setAttachments] =
@@ -70,7 +72,8 @@ export function CamelChatShowcase() {
70
72
  : {};
71
73
  return (
72
74
  record.type === "tool-write_file" ||
73
- record.type === "tool-cancel_schedule"
75
+ record.type === "tool-cancel_schedule" ||
76
+ record.type === "tool-schedule"
74
77
  ) &&
75
78
  record.state === "approval-requested"
76
79
  ? { id: "camel-demo-approval", approved: approval }
@@ -254,6 +257,14 @@ export function CamelChatShowcase() {
254
257
  }}
255
258
  onApprove={(_approvalId, decision) => {
256
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
+ }
257
268
  if (scenario === "schedule_cancel") {
258
269
  setApproval(approved);
259
270
  setLastEvent(
@@ -274,20 +285,17 @@ export function CamelChatShowcase() {
274
285
  onAnswer={(payload) => {
275
286
  const data =
276
287
  payload && typeof payload === "object"
277
- ? (payload as {
278
- selections?: string[][];
279
- text?: string;
280
- })
281
- : {};
282
- const answer = [
283
- ...(data.selections?.flat() ?? []),
284
- data.text?.trim() ?? "",
285
- ]
288
+ ? (payload as DemoAskUserResponse)
289
+ : { answers: [] };
290
+ const answer = data.answers.flatMap((item) => [
291
+ ...item.selections,
292
+ item.text?.trim() ?? "",
293
+ ])
286
294
  .filter(Boolean)
287
295
  .join("、");
288
- if (answer) setHumanAnswer(answer);
296
+ if (data.answers.length > 0) setHumanAnswer(data);
289
297
  selectScenario("planning");
290
- setLastEvent(`已回答并继续:${answer || humanAnswer}`);
298
+ setLastEvent(`已回答并继续:${answer}`);
291
299
  }}
292
300
  getToolApproval={approvalView}
293
301
  />