@springbrand/message-panel 0.1.3-alpha.2 → 0.1.3-alpha.21

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 (41) hide show
  1. package/cloud-os/README.md +6 -6
  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/capability-chip.tsx +35 -0
  8. package/cloud-os/chat/activity-indicator.tsx +29 -0
  9. package/cloud-os/chat/cloud-os-chat-messages.tsx +123 -69
  10. package/cloud-os/chat/markdown-message.tsx +82 -40
  11. package/cloud-os/chat/rich-blocks.tsx +494 -190
  12. package/cloud-os/chat/tool-presentation.ts +67 -6
  13. package/cloud-os/chat/tool-rows.tsx +87 -43
  14. package/cloud-os/chat/transcript-model.ts +215 -23
  15. package/cloud-os/composer/cloud-os-chat-input.tsx +242 -127
  16. package/cloud-os/composer/cloud-os-model-select.tsx +105 -0
  17. package/cloud-os/file-view.tsx +266 -0
  18. package/cloud-os/index.ts +29 -2
  19. package/cloud-os/layout/cloud-os-workspace-split.tsx +107 -24
  20. package/cloud-os/primitives/workshop-controls.tsx +2 -2
  21. package/cloud-os/styles/cloud-os.css +93 -19
  22. package/demo/camel-chat-showcase.tsx +21 -13
  23. package/demo/chat-scenarios.ts +100 -40
  24. package/demo/cloud-os-chat-showcase.tsx +51 -17
  25. package/demo/fixtures.ts +4 -5
  26. package/demo/message-panel-gallery.tsx +16 -2
  27. package/package.json +3 -4
  28. package/src/camel/camel-chat-messages.tsx +82 -77
  29. package/src/camel/camel-prompt-input.tsx +2 -1
  30. package/src/camel/camel-tool-presentation.tsx +19 -15
  31. package/src/camel/camel-turn.ts +1 -17
  32. package/src/chat-summary-panel.tsx +1 -1
  33. package/src/composer/chat-composer.tsx +2 -1
  34. package/src/composer/composer-trigger-popover.tsx +1 -1
  35. package/src/composer/composer.tsx +2 -1
  36. package/src/composer/index.ts +1 -0
  37. package/src/composer/key-rules.ts +12 -0
  38. package/src/contracts.ts +0 -2
  39. package/src/message-panel.tsx +0 -23
  40. package/src/parts/index.tsx +102 -63
  41. package/src/styles/index.css +4 -1
@@ -341,6 +341,54 @@
341
341
  display: none;
342
342
  }
343
343
 
344
+ .cos-model-select {
345
+ --cos-model-select-bg: #fff;
346
+ --cos-model-select-border: rgb(0 0 0 / 7%);
347
+ --cos-model-select-text: #000;
348
+ --cos-model-select-muted: rgb(0 0 0 / 50%);
349
+ --cos-model-select-disabled: rgb(0 0 0 / 30%);
350
+ --cos-model-select-highlight: rgb(0 0 0 / 3%);
351
+ border-color: var(--cos-model-select-border);
352
+ background: var(--cos-model-select-bg);
353
+ box-shadow: 0 8px 14px rgb(0 0 0 / 6%);
354
+ color: var(--cos-model-select-text);
355
+ font-family: "Inter Variable", Inter, sans-serif;
356
+ }
357
+
358
+ .cos-model-select__item[data-highlighted] {
359
+ background: var(--cos-model-select-highlight);
360
+ }
361
+
362
+ .cos-model-select__item[data-disabled] {
363
+ cursor: not-allowed;
364
+ opacity: 1;
365
+ }
366
+
367
+ .cos-model-select__name {
368
+ color: var(--cos-model-select-text);
369
+ }
370
+
371
+ .cos-model-select__name[data-muted] {
372
+ color: var(--cos-model-select-muted);
373
+ }
374
+
375
+ .cos-model-select__plan {
376
+ color: var(--cos-model-select-muted);
377
+ }
378
+
379
+ .cos-model-select__plan[data-disabled] {
380
+ color: var(--cos-model-select-disabled);
381
+ }
382
+
383
+ [data-cloud-os-portal][data-mode="dark"].cos-model-select {
384
+ --cos-model-select-bg: #343434;
385
+ --cos-model-select-border: rgb(255 255 255 / 10%);
386
+ --cos-model-select-text: #fff;
387
+ --cos-model-select-muted: rgb(255 255 255 / 50%);
388
+ --cos-model-select-disabled: rgb(255 255 255 / 30%);
389
+ --cos-model-select-highlight: rgb(255 255 255 / 7%);
390
+ }
391
+
344
392
  .cos-chat-panel {
345
393
  scrollbar-width: thin;
346
394
  scrollbar-color: var(--color-kumo-line) transparent;
@@ -356,23 +404,6 @@
356
404
  background: transparent;
357
405
  }
358
406
 
359
- /* ── 顶部推进条(isAgentActive)──────────────────────────────────────────── */
360
- @keyframes cos-thinking {
361
- 0% {
362
- left: -33%;
363
- }
364
- 50% {
365
- left: 100%;
366
- }
367
- 100% {
368
- left: -33%;
369
- }
370
- }
371
-
372
- .cos-progress-sweep {
373
- animation: cos-thinking 1.5s ease-in-out infinite;
374
- }
375
-
376
407
  /* ============================================================================
377
408
  * 以下整段来自 ChatInterface.module.css —— CSS Module 类名改成 cos- 全局前缀
378
409
  * ========================================================================== */
@@ -382,6 +413,9 @@
382
413
  line-height: 1.6;
383
414
  overflow-wrap: anywhere;
384
415
  }
416
+ .cos-user-markdown :where(p, li) {
417
+ white-space: pre-wrap;
418
+ }
385
419
  .cos-markdown p {
386
420
  margin: 0.4em 0;
387
421
  }
@@ -505,6 +539,20 @@
505
539
  }
506
540
  }
507
541
 
542
+ /* ── 活动指示:回合在跑但屏幕上没东西在动时 ──────────────────────────────── */
543
+ .cos-activity {
544
+ display: flex;
545
+ align-items: center;
546
+ gap: 8px;
547
+ min-height: 20px;
548
+ color: var(--text-color-kumo-subtle);
549
+ }
550
+
551
+ /* 球是固定 20px 的画布,不许被 flex 压扁。 */
552
+ .cos-activity canvas {
553
+ flex: none;
554
+ }
555
+
508
556
  /* ── 流式占位:Thinking 扫光 ──────────────────────────────────────────────── */
509
557
  .cos-thinking-shimmer {
510
558
  color: var(--text-color-kumo-inactive);
@@ -536,8 +584,34 @@
536
584
  background: none;
537
585
  -webkit-text-fill-color: currentColor;
538
586
  }
539
- .cos-progress-sweep {
540
- animation: none;
587
+ }
588
+
589
+ /* ── 原位提问:逐题横向切换 ─────────────────────────────────────────────── */
590
+ .cos-ask-user-question[data-direction="forward"] {
591
+ animation: cos-ask-user-forward 180ms ease-out;
592
+ }
593
+
594
+ .cos-ask-user-question[data-direction="back"] {
595
+ animation: cos-ask-user-back 180ms ease-out;
596
+ }
597
+
598
+ @keyframes cos-ask-user-forward {
599
+ from {
600
+ opacity: 0;
601
+ transform: translateX(24px);
602
+ }
603
+ }
604
+
605
+ @keyframes cos-ask-user-back {
606
+ from {
607
+ opacity: 0;
608
+ transform: translateX(-24px);
609
+ }
610
+ }
611
+
612
+ @media (prefers-reduced-motion: reduce) {
613
+ .cos-ask-user-question {
614
+ animation: none !important;
541
615
  }
542
616
  }
543
617
 
@@ -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
  />
@@ -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
  },
@@ -239,42 +246,79 @@ function userMessage(prompt: string): UIMessage {
239
246
  } as UIMessage;
240
247
  }
241
248
 
242
- const askUserRequest = {
243
- id: "camel-demo-ask-user",
244
- role: "assistant",
245
- parts: [
246
- {
247
- type: "tool-ask_user",
248
- toolCallId: "camel-demo-ask-user-1",
249
- state: "output-available",
250
- input: {
251
- question: "这份建议优先面向哪个读者?",
252
- options: ["产品团队", "管理层", "设计团队"],
253
- multiSelect: false,
254
- },
255
- output: { asked: true },
256
- },
249
+ export interface DemoAskUserResponse {
250
+ answers: Array<{ selections: string[]; text?: string }>;
251
+ }
252
+
253
+ export const demoAskUserResponse: DemoAskUserResponse = {
254
+ answers: [
255
+ { selections: ["管理层"], text: "" },
256
+ { selections: ["报告"], text: "" },
257
257
  ],
258
- } as unknown as UIMessage;
258
+ };
259
+
260
+ const askUserQuestions = [
261
+ {
262
+ question: "这份建议优先面向哪个读者?",
263
+ options: ["产品团队", "管理层", "设计团队"],
264
+ },
265
+ {
266
+ question: "需要哪些交付物?",
267
+ options: ["报告", "演示文稿", "执行清单"],
268
+ multiSelect: true,
269
+ allowCustom: true,
270
+ },
271
+ ];
259
272
 
260
- function humanReplyMessage(answer: string): UIMessage {
273
+ function askUserRequest(response?: DemoAskUserResponse): UIMessage {
261
274
  return {
262
- id: "camel-demo-human-reply",
263
- role: "user",
264
- metadata: {
265
- authorDisplayName: "You",
266
- createdAt: demoTurnStartedAt + 2_000,
267
- messageSource: "Web",
268
- },
269
- parts: [{ type: "text", text: answer }],
270
- } as UIMessage;
275
+ id: "camel-demo-ask-user",
276
+ role: "assistant",
277
+ parts: [
278
+ {
279
+ type: "tool-ask_user",
280
+ toolCallId: "camel-demo-ask-user-1",
281
+ state: response ? "output-available" : "input-available",
282
+ input: { questions: askUserQuestions },
283
+ ...(response
284
+ ? {
285
+ output: {
286
+ answers: response.answers.map((answer, index) => ({
287
+ question: askUserQuestions[index]!.question,
288
+ selections: answer.selections,
289
+ text: answer.text?.trim() ?? "",
290
+ })),
291
+ },
292
+ }
293
+ : {}),
294
+ },
295
+ ],
296
+ } as unknown as UIMessage;
271
297
  }
272
298
 
273
299
  const planningWork = {
274
300
  id: "camel-demo-planning-work",
275
301
  role: "assistant",
276
- metadata: { turnStartedAt: demoTurnStartedAt + 3_000 },
302
+ metadata: {
303
+ turnStartedAt: demoTurnStartedAt + 3_000,
304
+ turnDurationMs: 18_000,
305
+ },
277
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
+ },
278
322
  {
279
323
  type: "tool-update_plan",
280
324
  state: "output-available",
@@ -459,6 +503,26 @@ const backgroundResult = {
459
503
  ],
460
504
  } as unknown as UIMessage;
461
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
+
462
526
  const scheduledTask = {
463
527
  id: "camel-demo-schedule",
464
528
  role: "assistant",
@@ -467,12 +531,7 @@ const scheduledTask = {
467
531
  type: "tool-schedule",
468
532
  toolCallId: "camel-demo-schedule-1",
469
533
  state: "output-available",
470
- input: {
471
- label: "每周发送竞品报告",
472
- prompt: "生成并发送本周竞品报告。",
473
- trigger: { kind: "cron", cron: "0 9 * * 1" },
474
- timezone: "Asia/Shanghai",
475
- },
534
+ input: demoScheduleInput,
476
535
  output: { scheduled: true, id: "schedule-1" },
477
536
  },
478
537
  ],
@@ -827,7 +886,7 @@ const failedWork = {
827
886
  export function messagesForScenario(
828
887
  scenario: DemoScenario,
829
888
  prompt: string,
830
- humanAnswer: string,
889
+ humanAnswer: DemoAskUserResponse,
831
890
  approval: boolean | undefined,
832
891
  ): UIMessage[] {
833
892
  if (scenario === "empty") return [];
@@ -835,15 +894,13 @@ export function messagesForScenario(
835
894
  return [noticeMessage(scenario), ...commandMessages];
836
895
  }
837
896
  const conversation = [noticeMessage(scenario), userMessage(prompt)];
838
- const answer = humanAnswer || "管理层";
839
897
  const decidedConversation = [
840
898
  ...conversation,
841
- askUserRequest,
842
- humanReplyMessage(answer),
899
+ askUserRequest(humanAnswer),
843
900
  ];
844
901
  if (scenario === "hydrating") return conversation;
845
902
  if (scenario === "submitted") return conversation;
846
- if (scenario === "ask") return [...conversation, askUserRequest];
903
+ if (scenario === "ask") return [...conversation, askUserRequest()];
847
904
  if (scenario === "planning") {
848
905
  return [...decidedConversation, planningWork];
849
906
  }
@@ -860,6 +917,9 @@ export function messagesForScenario(
860
917
  if (scenario === "background_agent") {
861
918
  return [...conversation, backgroundDispatch, backgroundResult];
862
919
  }
920
+ if (scenario === "schedule_create") {
921
+ return [...conversation, scheduleRequest];
922
+ }
863
923
  if (scenario === "schedule") {
864
924
  return [...conversation, scheduledTask];
865
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,
@@ -20,17 +21,19 @@ import {
20
21
  } from "../cloud-os";
21
22
  import {
22
23
  defaultPrompt,
24
+ demoAskUserResponse,
23
25
  demoModels,
24
26
  flowSteps,
25
27
  messagesForScenario,
26
28
  scenarios,
27
29
  type DemoScenario,
30
+ type DemoAskUserResponse,
28
31
  type DemoScenarioMeta,
29
32
  } from "./chat-scenarios";
30
33
 
31
34
  /**
32
35
  * cloud-os 版快速预览。场景表和 camel-chat-showcase 共用 `chat-scenarios.ts`,
33
- * 所以两边的状态逐条对齐(同样 22 个场景、同样的 7 步流程条、同样的初始 URL 参数)。
36
+ * 所以两边的状态逐条对齐(同样 23 个场景、同样的 7 步流程条、同样的初始 URL 参数)。
34
37
  * 差别只在外壳:这里是 cloudflare-os 的左聊天 / 右工作区双栏。
35
38
  */
36
39
 
@@ -122,7 +125,7 @@ export function CloudOsChatShowcase() {
122
125
  const [mode, setMode] = useState<CloudOsMode>("light");
123
126
  const [scenario, setScenario] = useState<DemoScenario>(initialScenario);
124
127
  const [prompt, setPrompt] = useState(defaultPrompt);
125
- const [humanAnswer, setHumanAnswer] = useState("管理层");
128
+ const [humanAnswer, setHumanAnswer] = useState(demoAskUserResponse);
126
129
  const [model, setModel] = useState<string | null>(cloudOsModels[0].id);
127
130
  const [value, setValue] = useState("");
128
131
  const [attachments, setAttachments] = useState<CloudOsAttachmentView[]>([]);
@@ -153,7 +156,8 @@ export function CloudOsChatShowcase() {
153
156
  ? (part as { type?: unknown; state?: unknown })
154
157
  : {};
155
158
  return (record.type === "tool-write_file" ||
156
- record.type === "tool-cancel_schedule") &&
159
+ record.type === "tool-cancel_schedule" ||
160
+ record.type === "tool-schedule") &&
157
161
  record.state === "approval-requested"
158
162
  ? "cloud-os-demo-approval"
159
163
  : undefined;
@@ -283,7 +287,7 @@ export function CloudOsChatShowcase() {
283
287
 
284
288
  <CloudOsWorkspaceSplit
285
289
  workspaceOpen={workspaceOpen}
286
- isAgentActive={status === "submitted" || status === "streaming"}
290
+ isLoading={scenario === "hydrating"}
287
291
  chat={
288
292
  <div className="flex h-full min-h-0 flex-col bg-kumo-base">
289
293
  <aside
@@ -348,11 +352,40 @@ export function CloudOsChatShowcase() {
348
352
  <CloudOsChatMessages
349
353
  messages={messages}
350
354
  status={status}
351
- isHydrating={scenario === "hydrating"}
355
+ turnActive={
356
+ status === "submitted" ||
357
+ status === "streaming" ||
358
+ scenario === "ask" ||
359
+ scenario === "schedule_create" ||
360
+ ((scenario === "approval" || scenario === "schedule_cancel") &&
361
+ approval === undefined)
362
+ }
352
363
  isRecovering={scenario === "recovering"}
353
- recoveryStatusLabel="模型响应暂时中断,正在重试…"
364
+ recoveryStatusLabel="Model response interrupted. Retrying…"
365
+ awaitingApproval={
366
+ (scenario === "approval" ||
367
+ scenario === "schedule_cancel" ||
368
+ scenario === "schedule_create") &&
369
+ approval === undefined
370
+ }
354
371
  showThinkingTraces={showThinkingTraces}
355
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}
356
389
  error={statusError}
357
390
  constrainWidth={!workspaceOpen}
358
391
  onRetry={() => {
@@ -376,20 +409,21 @@ export function CloudOsChatShowcase() {
376
409
  setValue("");
377
410
  setLastEvent(`已发送后续问题:${text}`);
378
411
  }}
379
- onAnswer={(payload) => {
412
+ onRespond={async (_toolCallId, response) => {
380
413
  const data =
381
- payload && typeof payload === "object"
382
- ? (payload as { selections?: string[][]; text?: string })
383
- : {};
384
- const answer = [
385
- ...(data.selections?.flat() ?? []),
386
- data.text?.trim() ?? "",
387
- ]
414
+ response && typeof response === "object"
415
+ ? (response as DemoAskUserResponse)
416
+ : { answers: [] };
417
+ const answer = data.answers.flatMap((item) => [
418
+ ...item.selections,
419
+ item.text?.trim() ?? "",
420
+ ])
388
421
  .filter(Boolean)
389
- .join("");
390
- if (answer) setHumanAnswer(answer);
422
+ .join(" / ");
423
+ if (data.answers.length > 0) setHumanAnswer(data);
391
424
  selectScenario("planning");
392
- setLastEvent(`已回答并继续:${answer || humanAnswer}`);
425
+ setLastEvent(`已回答并继续:${answer}`);
426
+ return true;
393
427
  }}
394
428
  />
395
429
 
package/demo/fixtures.ts CHANGED
@@ -166,17 +166,16 @@ export const allPartsMessage: DemoMessage = {
166
166
  type: "ask_user",
167
167
  questions: [
168
168
  {
169
- prompt: "报告通过什么渠道发送?",
170
- kind: "single",
169
+ question: "报告通过什么渠道发送?",
171
170
  options: ["Email", "Slack", "Both"],
172
171
  },
173
172
  {
174
- prompt: "需要包含哪些内容?",
175
- kind: "multi",
173
+ question: "需要包含哪些内容?",
176
174
  options: ["定价", "功能", "动态"],
175
+ multiSelect: true,
176
+ allowCustom: true,
177
177
  },
178
178
  ],
179
- freeTextPlaceholder: "补充竞争对手名称…",
180
179
  },
181
180
  {
182
181
  type: "suggestions",
@@ -220,6 +220,7 @@ function ComposerInteractionHarness() {
220
220
 
221
221
  export function MessagePanelGallery() {
222
222
  const [theme, setTheme] = useState<"light" | "dark">("dark");
223
+ const [askUserOutput, setAskUserOutput] = useState<unknown>();
223
224
  const rootClass = theme === "dark" ? "dark" : "";
224
225
  useEffect(() => {
225
226
  document.documentElement.classList.toggle("dark", theme === "dark");
@@ -309,12 +310,25 @@ export function MessagePanelGallery() {
309
310
  />
310
311
  <div style={{ height: 1100, overflow: "hidden", border: "1px solid var(--border)", borderRadius: 18 }}>
311
312
  <MessagePanel
312
- messages={[basicMessages[0], allPartsMessage]}
313
+ messages={[
314
+ basicMessages[0],
315
+ {
316
+ ...allPartsMessage,
317
+ parts: allPartsMessage.parts.map((part) =>
318
+ part.type === "ask_user" && askUserOutput
319
+ ? { ...part, output: askUserOutput }
320
+ : part
321
+ ),
322
+ },
323
+ ]}
313
324
  status="ready"
314
325
  adapter={demoMessageAdapter}
315
326
  actions={{
316
327
  onSuggestion: noOp,
317
- onAnswer: noOp,
328
+ onAnswer: async (output) => {
329
+ await new Promise((resolve) => setTimeout(resolve, 250));
330
+ setAskUserOutput(output);
331
+ },
318
332
  onApprove: noOp,
319
333
  onPreview: noOp,
320
334
  onDownload: noOp,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@springbrand/message-panel",
3
- "version": "0.1.3-alpha.2",
3
+ "version": "0.1.3-alpha.21",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src",
@@ -31,11 +31,10 @@
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
- "react-markdown": "^10.1.0",
36
- "remark-gfm": "^4.0.1",
37
36
  "streamdown": "^2.5.0",
38
- "thinking-orbs": "0.1.1",
37
+ "thinking-orbs": "0.2.0",
39
38
  "use-stick-to-bottom": "^1.1.6"
40
39
  },
41
40
  "devDependencies": {