@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
@@ -37,6 +37,9 @@ export type DemoScenario =
37
37
  | "temporary_agent"
38
38
  | "agent_tools"
39
39
  | "background_agent"
40
+ | "action_loading"
41
+ | "action_files"
42
+ | "schedule_create"
40
43
  | "schedule"
41
44
  | "schedule_cancel"
42
45
  | "approval"
@@ -118,9 +121,27 @@ export const scenarios: Record<DemoScenario, DemoScenarioMeta> = {
118
121
  status: "ready",
119
122
  flowStep: 3,
120
123
  },
124
+ action_loading: {
125
+ label: "Action product loading",
126
+ description: "Preview loading UI for every Action product type.",
127
+ status: "streaming",
128
+ flowStep: 2,
129
+ },
130
+ action_files: {
131
+ label: "Action file results",
132
+ description: "Preview image, audio, video, document, spreadsheet, archive, and unknown files.",
133
+ status: "ready",
134
+ flowStep: 3,
135
+ },
136
+ schedule_create: {
137
+ label: "Create scheduled task",
138
+ description: "Review the task, cadence, and timezone before creating the schedule.",
139
+ status: "ready",
140
+ flowStep: 2,
141
+ },
121
142
  schedule: {
122
- label: "定时任务已创建",
123
- description: "schedule 执行成功后展示任务名称、触发规则、时区和已安排状态。",
143
+ label: "Scheduled task created",
144
+ description: "A completed schedule shows its task, cadence, timezone, and final status.",
124
145
  status: "ready",
125
146
  flowStep: 3,
126
147
  },
@@ -239,42 +260,84 @@ function userMessage(prompt: string): UIMessage {
239
260
  } as UIMessage;
240
261
  }
241
262
 
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
- },
263
+ function actionUserMessage(prompt: string): UIMessage {
264
+ const message = userMessage(prompt);
265
+ return { ...message, parts: message.parts.filter((part) => part.type === "text") };
266
+ }
267
+
268
+ export interface DemoAskUserResponse {
269
+ answers: Array<{ selections: string[]; text?: string }>;
270
+ }
271
+
272
+ export const demoAskUserResponse: DemoAskUserResponse = {
273
+ answers: [
274
+ { selections: ["管理层"], text: "" },
275
+ { selections: ["报告"], text: "" },
257
276
  ],
258
- } as unknown as UIMessage;
277
+ };
259
278
 
260
- function humanReplyMessage(answer: string): UIMessage {
279
+ const askUserQuestions = [
280
+ {
281
+ question: "这份建议优先面向哪个读者?",
282
+ options: ["产品团队", "管理层", "设计团队"],
283
+ },
284
+ {
285
+ question: "需要哪些交付物?",
286
+ options: ["报告", "演示文稿", "执行清单"],
287
+ multiSelect: true,
288
+ allowCustom: true,
289
+ },
290
+ ];
291
+
292
+ function askUserRequest(response?: DemoAskUserResponse): UIMessage {
261
293
  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;
294
+ id: "camel-demo-ask-user",
295
+ role: "assistant",
296
+ parts: [
297
+ {
298
+ type: "tool-ask_user",
299
+ toolCallId: "camel-demo-ask-user-1",
300
+ state: response ? "output-available" : "input-available",
301
+ input: { questions: askUserQuestions },
302
+ ...(response
303
+ ? {
304
+ output: {
305
+ answers: response.answers.map((answer, index) => ({
306
+ question: askUserQuestions[index]!.question,
307
+ selections: answer.selections,
308
+ text: answer.text?.trim() ?? "",
309
+ })),
310
+ },
311
+ }
312
+ : {}),
313
+ },
314
+ ],
315
+ } as unknown as UIMessage;
271
316
  }
272
317
 
273
318
  const planningWork = {
274
319
  id: "camel-demo-planning-work",
275
320
  role: "assistant",
276
- metadata: { turnStartedAt: demoTurnStartedAt + 3_000 },
321
+ metadata: {
322
+ turnStartedAt: demoTurnStartedAt + 3_000,
323
+ turnDurationMs: 18_000,
324
+ },
277
325
  parts: [
326
+ {
327
+ type: "reasoning",
328
+ text: `先确认受众和交付范围,再验证产品案例。
329
+
330
+ 用户选择的读者是管理层,交付物是报告。因此内容不能只罗列界面趋势,还需要把每项趋势连接到业务价值、落地成本和潜在风险,帮助管理层判断优先级。
331
+
332
+ 接下来会分三步推进:
333
+
334
+ 1. 检索 2025–2026 年主流 AI Agent 产品,优先采集官方发布说明、产品文档和可验证的真实界面。
335
+ 2. 对比任务规划、工具调用、人工确认、后台执行和结果交付等关键交互,区分普遍模式与单个产品的特色设计。
336
+ 3. 把证据整理成可执行建议,并为每条建议标注适用场景、实现复杂度和预期收益。
337
+
338
+ 第一轮检索先覆盖通用 Agent、开发者 Agent 与企业自动化产品。如果不同来源对同一趋势的描述冲突,会保留分歧并继续寻找一手证据,而不是直接下结论。`,
339
+ state: "done",
340
+ },
278
341
  {
279
342
  type: "tool-update_plan",
280
343
  state: "output-available",
@@ -459,6 +522,115 @@ const backgroundResult = {
459
522
  ],
460
523
  } as unknown as UIMessage;
461
524
 
525
+ const actionProductHints = [
526
+ { type: "image", mediaType: "image/png" },
527
+ { type: "audio", mediaType: "audio/mpeg" },
528
+ { type: "video", mediaType: "video/mp4" },
529
+ { type: "document", mediaType: "application/pdf" },
530
+ { type: "archive", mediaType: "application/zip" },
531
+ { type: "data", mediaType: "application/json" },
532
+ { type: "other" },
533
+ ] as const;
534
+
535
+ function demoExecutionId(index: number) {
536
+ return `00000000-0000-4000-8000-${String(index + 1).padStart(12, "0")}`;
537
+ }
538
+
539
+ const actionProductLoading = {
540
+ id: "camel-demo-action-product-loading",
541
+ role: "assistant",
542
+ parts: actionProductHints.map((produces, index) => ({
543
+ type: "dynamic-tool",
544
+ toolCallId: `camel-demo-action-loading-${index}`,
545
+ toolName: `action_generate_${produces.type}`,
546
+ state: "output-available",
547
+ input: {},
548
+ output: {
549
+ status: "running",
550
+ executionId: demoExecutionId(index),
551
+ presentation: {
552
+ type: "action-execution",
553
+ executionId: demoExecutionId(index),
554
+ state: "running",
555
+ produces,
556
+ content: [],
557
+ },
558
+ },
559
+ })),
560
+ } as unknown as UIMessage;
561
+
562
+ const actionFileFixtures = [
563
+ {
564
+ name: "image",
565
+ mediaType: "image/png",
566
+ url: "https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?auto=format&fit=crop&w=1024&q=80",
567
+ },
568
+ { name: "audio", mediaType: "audio/mpeg", url: "https://files.example/action-demo/audio.mp3" },
569
+ { name: "video", mediaType: "video/mp4", url: "https://files.example/action-demo/video.mp4" },
570
+ { name: "pdf", mediaType: "application/pdf", url: "https://files.example/action-demo/report.pdf" },
571
+ { name: "markdown", mediaType: "text/markdown", url: "https://files.example/action-demo/report.md" },
572
+ {
573
+ name: "spreadsheet",
574
+ mediaType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
575
+ url: "https://files.example/action-demo/data.xlsx",
576
+ },
577
+ { name: "archive", mediaType: "application/zip", url: "https://files.example/action-demo/files.zip" },
578
+ {
579
+ name: "unknown",
580
+ mediaType: "application/octet-stream",
581
+ url: "https://files.example/action-demo/result.bin",
582
+ },
583
+ ] as const;
584
+
585
+ const actionFileResults = {
586
+ id: "camel-demo-action-file-results",
587
+ role: "assistant",
588
+ parts: [
589
+ ...actionFileFixtures.map((file, index) => ({
590
+ type: "dynamic-tool",
591
+ toolCallId: `camel-demo-action-file-${index}`,
592
+ toolName: `action_generate_${file.name}`,
593
+ state: "output-available",
594
+ input: {},
595
+ output: {
596
+ status: "succeeded",
597
+ executionId: demoExecutionId(index + actionProductHints.length),
598
+ result: { kind: "file", content_type: file.mediaType, url: file.url },
599
+ presentation: {
600
+ type: "action-execution",
601
+ executionId: demoExecutionId(index + actionProductHints.length),
602
+ state: "ready",
603
+ content: [{ type: "file", url: file.url, mediaType: file.mediaType }],
604
+ },
605
+ },
606
+ })),
607
+ {
608
+ type: "text",
609
+ text: "All Action file result types are rendered above from structured output.",
610
+ },
611
+ ],
612
+ } as unknown as UIMessage;
613
+
614
+ export const demoScheduleInput = {
615
+ label: "Weekly competitor report",
616
+ prompt: "Generate and send this week's competitor report.",
617
+ trigger: { kind: "cron", cron: "0 9 * * 1" },
618
+ timezone: "Asia/Shanghai",
619
+ };
620
+
621
+ const scheduleRequest = {
622
+ id: "camel-demo-schedule-request",
623
+ role: "assistant",
624
+ parts: [
625
+ {
626
+ type: "tool-schedule",
627
+ toolCallId: "camel-demo-schedule-request-1",
628
+ state: "approval-requested",
629
+ input: demoScheduleInput,
630
+ },
631
+ ],
632
+ } as unknown as UIMessage;
633
+
462
634
  const scheduledTask = {
463
635
  id: "camel-demo-schedule",
464
636
  role: "assistant",
@@ -467,12 +639,7 @@ const scheduledTask = {
467
639
  type: "tool-schedule",
468
640
  toolCallId: "camel-demo-schedule-1",
469
641
  state: "output-available",
470
- input: {
471
- label: "每周发送竞品报告",
472
- prompt: "生成并发送本周竞品报告。",
473
- trigger: { kind: "cron", cron: "0 9 * * 1" },
474
- timezone: "Asia/Shanghai",
475
- },
642
+ input: demoScheduleInput,
476
643
  output: { scheduled: true, id: "schedule-1" },
477
644
  },
478
645
  ],
@@ -827,7 +994,7 @@ const failedWork = {
827
994
  export function messagesForScenario(
828
995
  scenario: DemoScenario,
829
996
  prompt: string,
830
- humanAnswer: string,
997
+ humanAnswer: DemoAskUserResponse,
831
998
  approval: boolean | undefined,
832
999
  ): UIMessage[] {
833
1000
  if (scenario === "empty") return [];
@@ -835,15 +1002,13 @@ export function messagesForScenario(
835
1002
  return [noticeMessage(scenario), ...commandMessages];
836
1003
  }
837
1004
  const conversation = [noticeMessage(scenario), userMessage(prompt)];
838
- const answer = humanAnswer || "管理层";
839
1005
  const decidedConversation = [
840
1006
  ...conversation,
841
- askUserRequest,
842
- humanReplyMessage(answer),
1007
+ askUserRequest(humanAnswer),
843
1008
  ];
844
1009
  if (scenario === "hydrating") return conversation;
845
1010
  if (scenario === "submitted") return conversation;
846
- if (scenario === "ask") return [...conversation, askUserRequest];
1011
+ if (scenario === "ask") return [...conversation, askUserRequest()];
847
1012
  if (scenario === "planning") {
848
1013
  return [...decidedConversation, planningWork];
849
1014
  }
@@ -860,6 +1025,15 @@ export function messagesForScenario(
860
1025
  if (scenario === "background_agent") {
861
1026
  return [...conversation, backgroundDispatch, backgroundResult];
862
1027
  }
1028
+ if (scenario === "action_loading") {
1029
+ return [noticeMessage(scenario), actionUserMessage(prompt), actionProductLoading];
1030
+ }
1031
+ if (scenario === "action_files") {
1032
+ return [noticeMessage(scenario), actionUserMessage(prompt), actionFileResults];
1033
+ }
1034
+ if (scenario === "schedule_create") {
1035
+ return [...conversation, scheduleRequest];
1036
+ }
863
1037
  if (scenario === "schedule") {
864
1038
  return [...conversation, scheduledTask];
865
1039
  }
@@ -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,6 +287,7 @@ export function CloudOsChatShowcase() {
283
287
 
284
288
  <CloudOsWorkspaceSplit
285
289
  workspaceOpen={workspaceOpen}
290
+ isLoading={scenario === "hydrating"}
286
291
  chat={
287
292
  <div className="flex h-full min-h-0 flex-col bg-kumo-base">
288
293
  <aside
@@ -351,18 +356,36 @@ export function CloudOsChatShowcase() {
351
356
  status === "submitted" ||
352
357
  status === "streaming" ||
353
358
  scenario === "ask" ||
359
+ scenario === "schedule_create" ||
354
360
  ((scenario === "approval" || scenario === "schedule_cancel") &&
355
361
  approval === undefined)
356
362
  }
357
- isHydrating={scenario === "hydrating"}
358
363
  isRecovering={scenario === "recovering"}
359
- recoveryStatusLabel="模型响应暂时中断,正在重试…"
364
+ recoveryStatusLabel="Model response interrupted. Retrying…"
360
365
  awaitingApproval={
361
- (scenario === "approval" || scenario === "schedule_cancel") &&
366
+ (scenario === "approval" ||
367
+ scenario === "schedule_cancel" ||
368
+ scenario === "schedule_create") &&
362
369
  approval === undefined
363
370
  }
364
371
  showThinkingTraces={showThinkingTraces}
365
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}
366
389
  error={statusError}
367
390
  constrainWidth={!workspaceOpen}
368
391
  onRetry={() => {
@@ -389,17 +412,17 @@ export function CloudOsChatShowcase() {
389
412
  onRespond={async (_toolCallId, response) => {
390
413
  const data =
391
414
  response && typeof response === "object"
392
- ? (response as { selections?: string[]; text?: string })
393
- : {};
394
- const answer = [
395
- ...(data.selections ?? []),
396
- data.text?.trim() ?? "",
397
- ]
415
+ ? (response as DemoAskUserResponse)
416
+ : { answers: [] };
417
+ const answer = data.answers.flatMap((item) => [
418
+ ...item.selections,
419
+ item.text?.trim() ?? "",
420
+ ])
398
421
  .filter(Boolean)
399
422
  .join(" / ");
400
- if (answer) setHumanAnswer(answer);
423
+ if (data.answers.length > 0) setHumanAnswer(data);
401
424
  selectScenario("planning");
402
- setLastEvent(`已回答并继续:${answer || humanAnswer}`);
425
+ setLastEvent(`已回答并继续:${answer}`);
403
426
  return true;
404
427
  }}
405
428
  />
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,11 +1,12 @@
1
1
  {
2
2
  "name": "@springbrand/message-panel",
3
- "version": "0.1.3-alpha.8",
3
+ "version": "0.2.0-alpha.37",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src",
7
7
  "demo",
8
- "cloud-os"
8
+ "cloud-os",
9
+ "springbrand-pet"
9
10
  ],
10
11
  "publishConfig": {
11
12
  "access": "public"
@@ -16,6 +17,7 @@
16
17
  "./styles.css": "./src/styles/index.css",
17
18
  "./cloud-os": "./cloud-os/index.ts",
18
19
  "./cloud-os.css": "./cloud-os/styles/cloud-os.css",
20
+ "./springbrand-pet": "./springbrand-pet/index.tsx",
19
21
  "./demo": "./demo/index.ts"
20
22
  },
21
23
  "peerDependencies": {
@@ -25,15 +27,15 @@
25
27
  },
26
28
  "dependencies": {
27
29
  "@assistant-ui/react": "^0.15.1",
30
+ "@base-ui/react": "^1.7.0",
28
31
  "@phosphor-icons/react": "^2.1.10",
29
32
  "@streamdown/cjk": "^1.0.3",
30
33
  "@streamdown/code": "^1.1.1",
31
34
  "@streamdown/math": "^1.0.2",
32
35
  "@streamdown/mermaid": "^1.0.2",
33
36
  "lucide-react": "^1.24.0",
37
+ "motion": "^13.1.0",
34
38
  "radix-ui": "^1.6.2",
35
- "react-markdown": "^10.1.0",
36
- "remark-gfm": "^4.0.1",
37
39
  "streamdown": "^2.5.0",
38
40
  "thinking-orbs": "0.2.0",
39
41
  "use-stick-to-bottom": "^1.1.6"
@@ -44,11 +46,13 @@
44
46
  "@types/react-dom": "^19.2.3",
45
47
  "@vitejs/plugin-react": "^6.0.3",
46
48
  "tailwindcss": "^4.3.2",
49
+ "tw-animate-css": "^1.4.0",
47
50
  "typescript": "^7.0.2",
48
51
  "vite": "^8.1.4"
49
52
  },
50
53
  "scripts": {
51
54
  "typecheck": "tsc --noEmit",
55
+ "test:springbrand-pet": "vitest run --config springbrand-pet/vitest.config.ts",
52
56
  "dev:cloud-os": "vite",
53
57
  "build:cloud-os": "vite build"
54
58
  }
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jérémy Perret
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.