@marimo-team/islands 0.25.3-dev265 → 0.25.3-dev267

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 (106) hide show
  1. package/dist/{ConnectedDataExplorerComponent-C_hj7Poi.js → ConnectedDataExplorerComponent-C4yCN38h.js} +1 -1
  2. package/dist/{ErrorBoundary-DzYPyiOs.js → ErrorBoundary-CewXPyL5.js} +64 -69
  3. package/dist/{abnfDiagram-VCTEODGH-DzrZZXRD.js → abnfDiagram-VCTEODGH-o6Ay6Q63.js} +2 -2
  4. package/dist/{any-language-editor-CFeWfC3o.js → any-language-editor-DnG26QXh.js} +1 -1
  5. package/dist/{architectureDiagram-5GKGNRK7-iF_snyFL.js → architectureDiagram-5GKGNRK7-BhiwwlVg.js} +1 -1
  6. package/dist/{chat-ui-DFxNgzXF.js → chat-ui-CbgR5ivy.js} +1964 -1740
  7. package/dist/{chunk-IMKFNOWR-B7xytD4B.js → chunk-IMKFNOWR-WMSMf4Nj.js} +1 -1
  8. package/dist/{chunk-SHT3W25Y-I8ZFP5L1.js → chunk-SHT3W25Y-BRfADrzD.js} +1 -1
  9. package/dist/{chunk-SVP7TREG-Di-uAPdT.js → chunk-SVP7TREG-CuK5QFre.js} +1 -1
  10. package/dist/{chunk-TICWLB2K-BFpUy0ud.js → chunk-TICWLB2K-Bcun_jhJ.js} +1 -1
  11. package/dist/{classDiagram-ZZMXUADV-Cqm4JGpx.js → classDiagram-ZZMXUADV-CgWDbUDT.js} +2 -2
  12. package/dist/{classDiagram-v2-VYDZK3BY-DFHRJpN3.js → classDiagram-v2-VYDZK3BY-DYmc9SFU.js} +2 -2
  13. package/dist/{code-visibility-CJExrSFe.js → code-visibility-BKBl9Rh6.js} +9 -9
  14. package/dist/{common-5mM7a8cm.js → common-N3Jd_6m9.js} +5944 -5626
  15. package/dist/{cynefinDiagram-5FMLGOSQ-cuGOeTBC.js → cynefinDiagram-5FMLGOSQ-PPKWipjT.js} +1 -1
  16. package/dist/{dagre-GXQ25YYZ-DJ76aC4k.js → dagre-GXQ25YYZ-niRBZh--.js} +1 -1
  17. package/dist/{data-grid-overlay-editor--Un2Lk6m.js → data-grid-overlay-editor-Cp59CrqZ.js} +1 -1
  18. package/dist/{diagram-S7CK7UJ4-DLLc0sw7.js → diagram-S7CK7UJ4-DPiP9rot.js} +1 -1
  19. package/dist/{diagram-UQ7AKVKN-Tz_d6qmk.js → diagram-UQ7AKVKN-B0WZuaxV.js} +1 -1
  20. package/dist/{diagram-VX7I27RA-D7SzPrDN.js → diagram-VX7I27RA-Rn42WBuN.js} +1 -1
  21. package/dist/{diagram-Z3DM3KII-qr37XV58.js → diagram-Z3DM3KII-BB93--ry.js} +1 -1
  22. package/dist/{dropdown-menu-CkJ7ErY6.js → dropdown-menu-BpImEfn3.js} +1211 -1205
  23. package/dist/{ebnfDiagram-PWID7BFC-BKOQb4J0.js → ebnfDiagram-PWID7BFC-V4t5h6mZ.js} +2 -2
  24. package/dist/{erDiagram-RLTQ6QDP-CM15Vq68.js → erDiagram-RLTQ6QDP-C1MsAKXd.js} +1 -1
  25. package/dist/{error-banner-DbRFOd2O.js → error-banner-DPc9eIw5.js} +3 -3
  26. package/dist/{error-entries-CMoaUX9V.js → error-entries-CJGdD2ky.js} +49 -42
  27. package/dist/{feedback-button-C2NUXbzh.js → feedback-button-Qc1f9iPq.js} +70 -70
  28. package/dist/{flowDiagram-HODETNUW-Dt3N5yFT.js → flowDiagram-HODETNUW-DHjeJP96.js} +2 -2
  29. package/dist/{glide-data-editor-Dj9U-NK9.js → glide-data-editor-hCq3K0sd.js} +2042 -2041
  30. package/dist/{glide-utils-Ba9WD2IN.js → glide-utils-DC3sk6EX.js} +28 -34
  31. package/dist/{globals-CkbsxIrq.js → globals-B2_uMYDY.js} +1 -1
  32. package/dist/{html-to-image-D_5CMreP.js → html-to-image-S7spRQCi.js} +5526 -5828
  33. package/dist/{infoDiagram-27XIBGKW-C7axJMCl.js → infoDiagram-27XIBGKW-Dovv6iKx.js} +1 -1
  34. package/dist/{ishikawaDiagram-5VMMS53U-CdC9Eur7.js → ishikawaDiagram-5VMMS53U-D2nyygsc.js} +1 -1
  35. package/dist/{kanban-definition-UXKFOSKX-BHnMIgD7.js → kanban-definition-UXKFOSKX-KyVx0uTY.js} +1 -1
  36. package/dist/main.js +1288 -1287
  37. package/dist/{mermaid-DFYN_PXd.js → mermaid-B-jKSwGt.js} +33 -33
  38. package/dist/{mindmap-definition-YA3MSWOX-BagHoCGU.js → mindmap-definition-YA3MSWOX-Bz9tKQFl.js} +1 -1
  39. package/dist/{number-overlay-editor-H8QAAmdr.js → number-overlay-editor-CfNYvbdz.js} +1 -1
  40. package/dist/{pegDiagram-XKGWAZYB-8IIZczip.js → pegDiagram-XKGWAZYB-C_qpq6Tv.js} +2 -2
  41. package/dist/pencil-DcT8AE0h.js +10 -0
  42. package/dist/{pieDiagram-E7YTZNPT-CE6dKBIr.js → pieDiagram-E7YTZNPT-Bp9ucgI4.js} +1 -1
  43. package/dist/{process-output-CGFNVC60.js → process-output-f34bkXTm.js} +2 -2
  44. package/dist/{railroadDiagram-O6MQD6OU-CkexPq3b.js → railroadDiagram-O6MQD6OU-tq-VEH6j.js} +2 -2
  45. package/dist/{requirementDiagram-BXWQKSXE-B0-OkuAe.js → requirementDiagram-BXWQKSXE-DW-vHadb.js} +1 -1
  46. package/dist/{reveal-component-BperrCFw.js → reveal-component-CUxv-kkP.js} +9 -9
  47. package/dist/{sequenceDiagram-WJ2MYXX4-Dxysg8DX.js → sequenceDiagram-WJ2MYXX4-Dv9U9qRP.js} +1 -1
  48. package/dist/{stateDiagram-D77RDMKH-DTEN3-OJ.js → stateDiagram-D77RDMKH-DTmPQ0EB.js} +2 -2
  49. package/dist/{stateDiagram-v2-MP3YSRHH-vktk3Xc5.js → stateDiagram-v2-MP3YSRHH-CkCif0Iw.js} +2 -2
  50. package/dist/style.css +1 -1
  51. package/dist/{swimlanes-42K2YHIH-Cjveh9OM.js → swimlanes-42K2YHIH-21kCq6l8.js} +1 -1
  52. package/dist/{swimlanesDiagram-VR7AAH4N-Bdr7Ev_2.js → swimlanesDiagram-VR7AAH4N-BpRJP6KU.js} +2 -2
  53. package/dist/{timeline-definition-24CTP7MA-KIuDcefn.js → timeline-definition-24CTP7MA-Ckgh4lS4.js} +1 -1
  54. package/dist/{toast-CY1_Qv41.js → toast-DpczUryP.js} +1 -1
  55. package/dist/{vega-component-C-lqjV0q.js → vega-component-CF9kI6C4.js} +1 -1
  56. package/dist/{vennDiagram-4TSXK5OY-DrIOUHfb.js → vennDiagram-4TSXK5OY-CDei2Bt3.js} +1 -1
  57. package/dist/{wardleyDiagram-VM6X3IG4-CJNjf4OH.js → wardleyDiagram-VM6X3IG4-CXavT601.js} +1 -1
  58. package/dist/{xychartDiagram-S5SC5T6Z-DEshFho-.js → xychartDiagram-S5SC5T6Z-CvJhcZri.js} +1 -1
  59. package/package.json +1 -1
  60. package/src/components/chat/__tests__/chat-display.test.tsx +157 -86
  61. package/src/components/chat/__tests__/chat-utils.test.ts +42 -0
  62. package/src/components/chat/__tests__/turn-timing.test.ts +84 -0
  63. package/src/components/chat/__tests__/use-assistant-turn.test.ts +95 -0
  64. package/src/components/chat/__tests__/user-message.test.tsx +27 -0
  65. package/src/components/chat/acp/agent-panel.tsx +2 -0
  66. package/src/components/chat/agent-activity.tsx +132 -0
  67. package/src/components/chat/chat-display.tsx +125 -96
  68. package/src/components/chat/chat-panel.tsx +123 -106
  69. package/src/components/chat/chat-utils.ts +12 -24
  70. package/src/components/chat/tool-call/__tests__/tool-labels.test.ts +76 -0
  71. package/src/components/chat/tool-call/__tests__/tool-step.test.tsx +57 -0
  72. package/src/components/chat/tool-call/shared.ts +6 -0
  73. package/src/components/chat/tool-call/tool-call-view.tsx +22 -76
  74. package/src/components/chat/tool-call/tool-labels.ts +172 -0
  75. package/src/components/chat/tool-call/tool-step.tsx +145 -0
  76. package/src/components/chat/turn-timing.ts +73 -0
  77. package/src/components/chat/use-assistant-turn.ts +65 -0
  78. package/src/components/chat/use-chat-generation.ts +5 -4
  79. package/src/components/chat/user-message.tsx +145 -0
  80. package/src/components/editor/actions/__tests__/useCellActionButton.test.tsx +18 -1
  81. package/src/components/editor/actions/useCellActionButton.tsx +3 -0
  82. package/src/components/editor/ai/completion-handlers.tsx +0 -37
  83. package/src/components/editor/cell/StagedAICell.tsx +51 -144
  84. package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +193 -32
  85. package/src/components/editor/chrome/wrapper/app-chrome.tsx +2 -2
  86. package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +145 -81
  87. package/src/components/editor/chrome/wrapper/pending-ai-changes-badge.tsx +25 -0
  88. package/src/components/editor/chrome/wrapper/sidebar.tsx +6 -0
  89. package/src/components/editor/links/cell-link.tsx +4 -4
  90. package/src/components/editor/notebook-cell.tsx +3 -11
  91. package/src/components/ui/toast.tsx +1 -1
  92. package/src/core/ai/__tests__/staged-cells.test.ts +74 -126
  93. package/src/core/ai/staged-cells.ts +48 -38
  94. package/src/core/cells/scrollCellIntoView.ts +6 -0
  95. package/src/core/codemirror/placeholder/__tests__/extensions.test.ts +59 -0
  96. package/src/core/codemirror/placeholder/extensions.ts +23 -0
  97. package/src/core/session/__tests__/useNotebookSession.hook.test.tsx +34 -0
  98. package/src/core/session/useNotebookSession.tsx +7 -2
  99. package/src/css/app/Cell.css +7 -36
  100. package/src/plugins/impl/chat/chat-ui.tsx +2 -3
  101. package/src/plugins/impl/common/error-banner.tsx +5 -3
  102. package/src/components/chat/reasoning-accordion.tsx +0 -61
  103. package/src/components/chat/tool-call/tool-activity-group.tsx +0 -49
  104. package/src/components/chat/tool-call/tool-error-card.tsx +0 -81
  105. package/src/components/chat/tool-call/tool-history-row.tsx +0 -147
  106. package/src/components/icons/loading-ellipsis.tsx +0 -46
@@ -27,20 +27,9 @@
27
27
  outline: none;
28
28
  }
29
29
 
30
- &:has(.mo-ai-generated-cell) {
31
- border-color: var(--blue-7);
32
- }
33
-
34
30
  /* Styling for cells marked for deletion by AI */
35
31
  &:has(.mo-ai-deleted-cell) {
36
- position: relative;
37
- border-color: var(--red-7);
38
-
39
- .output-area,
40
- .cm-gutters,
41
- .cm {
42
- background-color: var(--red-2);
43
- }
32
+ box-shadow: -2px 0 0 var(--red-7);
44
33
 
45
34
  /* Add strikethrough effect to the code */
46
35
  .cm-content {
@@ -218,14 +207,18 @@
218
207
 
219
208
  /* Focus state */
220
209
 
221
- &.focus-outline {
222
- transition: all 0.6s;
210
+ &.focus-outline,
211
+ &[data-ai-review-selected] {
223
212
  border-color: var(--blue-8) !important;
224
213
 
225
214
  /* custom shadow until our theme overrides can support colored shadow */
226
215
  @apply shadow-lg shadow-[var(--blue-8)];
227
216
  }
228
217
 
218
+ &.focus-outline {
219
+ transition: all 0.6s;
220
+ }
221
+
229
222
  /* Published - when its just the output */
230
223
 
231
224
  &.published {
@@ -303,20 +296,6 @@
303
296
  @apply bg-background;
304
297
  }
305
298
 
306
- .mo-ai-cell-footer {
307
- display: flex;
308
- align-items: center;
309
- justify-content: space-between;
310
- gap: 0.75rem;
311
- width: 100%;
312
- padding: 0.375rem 0.5rem;
313
- background: var(--gray-2);
314
- }
315
-
316
- .dark .mo-ai-cell-footer {
317
- background: var(--gray-3);
318
- }
319
-
320
299
  .dark .marimo-cell {
321
300
  @apply border-border;
322
301
 
@@ -355,14 +334,6 @@
355
334
  }
356
335
  }
357
336
 
358
- .dark .marimo-cell:has(.mo-ai-generated-cell) {
359
- border-color: var(--blue-8);
360
- }
361
-
362
- .dark .marimo-cell:has(.mo-ai-deleted-cell) {
363
- border-color: var(--red-8);
364
- }
365
-
366
337
  #cell-setup,
367
338
  #cell-setup .cm-editor,
368
339
  #cell-setup .cm-gutter {
@@ -482,12 +482,11 @@ export const Chatbot: React.FC<Props> = (props) => {
482
482
  className={`max-w-[80%] p-3 rounded-lg ${
483
483
  message.role === "user"
484
484
  ? "bg-(--sky-11) text-(--slate-1) whitespace-pre-wrap"
485
- : "bg-(--slate-4) text-(--slate-12)"
485
+ : "bg-(--slate-4) text-(--slate-12) [--muted-foreground:var(--slate-11)]"
486
486
  }`}
487
487
  >
488
488
  {renderUIMessage({
489
489
  message,
490
- isStreamingReasoning: status === "streaming",
491
490
  isLast,
492
491
  isActive: isLoading,
493
492
  addToolApprovalResponse: isLast
@@ -514,7 +513,7 @@ export const Chatbot: React.FC<Props> = (props) => {
514
513
  );
515
514
  })}
516
515
 
517
- {isLoading && (
516
+ {isLoading && messages.at(-1)?.role !== "assistant" && (
518
517
  <div className="flex items-center justify-center mb-4">
519
518
  <Spinner size="small" />
520
519
  </div>
@@ -47,11 +47,13 @@ export const ErrorBanner = ({
47
47
  <AlertDialog open={open} onOpenChange={setOpen}>
48
48
  <AlertDialogContent className="max-w-[80%] max-h-[80%] overflow-hidden flex flex-col">
49
49
  <AlertDialogHeader>
50
- <AlertDialogTitle className="text-error">Error</AlertDialogTitle>
50
+ <AlertDialogTitle className="text-(--red-11)">
51
+ Error
52
+ </AlertDialogTitle>
51
53
  </AlertDialogHeader>
52
54
  <AlertDialogDescription
53
55
  asChild={true}
54
- className="text-error text-sm p-2 font-mono overflow-auto whitespace-pre-wrap"
56
+ className="text-(--red-11) text-sm p-2 font-mono overflow-auto whitespace-pre-wrap"
55
57
  >
56
58
  <pre>{message}</pre>
57
59
  </AlertDialogDescription>
@@ -72,7 +74,7 @@ const bannerStyle = cva(
72
74
  variants: {
73
75
  kind: {
74
76
  danger:
75
- "text-error border-(--red-6) shadow-md-solid shadow-error bg-(--red-1)",
77
+ "text-(--red-11) border-(--red-6) shadow-md-solid shadow-error bg-(--red-1)",
76
78
  info: "text-primary border-(--blue-6) shadow-md-solid shadow-accent bg-(--blue-1)",
77
79
  warn: "border-(--yellow-6) bg-(--yellow-2) dark:bg-(--yellow-4) text-(--yellow-11) dark:text-(--yellow-12)",
78
80
  },
@@ -1,61 +0,0 @@
1
- /* Copyright 2026 Marimo. All rights reserved. */
2
-
3
- import { BotMessageSquareIcon } from "lucide-react";
4
- import React from "react";
5
- import { ChatMarkdown } from "./chat-markdown";
6
- import {
7
- Accordion,
8
- AccordionContent,
9
- AccordionItem,
10
- AccordionTrigger,
11
- } from "@/components/ui/accordion";
12
-
13
- interface ReasoningAccordionProps {
14
- reasoning: string;
15
- index?: number;
16
- isStreaming?: boolean;
17
- }
18
-
19
- export const ReasoningAccordion: React.FC<ReasoningAccordionProps> = ({
20
- reasoning,
21
- index = 0,
22
- isStreaming = false,
23
- }) => {
24
- const [openItem, setOpenItem] = React.useState<string>("");
25
-
26
- // Some reasoning models emit a reasoning part with no surfaced content
27
- // (e.g. OpenAI's o-series, which hides chain-of-thought but still marks
28
- // its boundaries on the wire). pydantic-ai's Vercel adapter forwards the
29
- // empty start/end pair, producing a 0-char ReasoningUIPart. Skip it.
30
- if (!reasoning && !isStreaming) {
31
- return null;
32
- }
33
-
34
- return (
35
- <Accordion
36
- key={index}
37
- type="single"
38
- collapsible={true}
39
- className="w-full mb-2"
40
- value={isStreaming ? "reasoning" : openItem}
41
- onValueChange={setOpenItem}
42
- >
43
- <AccordionItem value="reasoning" className="border-0">
44
- <AccordionTrigger className="text-xs text-muted-foreground hover:bg-muted/50 px-2 py-1 h-auto rounded-sm [&[data-state=open]>svg]:rotate-180">
45
- <span className="flex items-center gap-2">
46
- <BotMessageSquareIcon className="h-3 w-3" />
47
- {isStreaming ? "Thinking" : "View reasoning"}
48
- {reasoning.length > 0 && ` (${reasoning.length} chars)`}
49
- </span>
50
- </AccordionTrigger>
51
- <AccordionContent className="pb-2 px-2">
52
- <div className="bg-muted/30 border border-muted/50 rounded-md p-3 text-muted-foreground relative">
53
- <div className="pr-6">
54
- <ChatMarkdown content={reasoning} />
55
- </div>
56
- </div>
57
- </AccordionContent>
58
- </AccordionItem>
59
- </Accordion>
60
- );
61
- };
@@ -1,49 +0,0 @@
1
- /* Copyright 2026 Marimo. All rights reserved. */
2
-
3
- import type { PropsWithChildren } from "react";
4
- import {
5
- Accordion,
6
- AccordionContent,
7
- AccordionItem,
8
- AccordionTrigger,
9
- } from "@/components/ui/accordion";
10
- import { formatToolName, type ToolState } from "./shared";
11
-
12
- interface ToolActivity {
13
- toolName: string;
14
- state: ToolState;
15
- }
16
-
17
- export function ToolActivityGroup({
18
- tools,
19
- isActive,
20
- children,
21
- }: PropsWithChildren<{ tools: ToolActivity[]; isActive: boolean }>) {
22
- const pending = tools.some((tool) => tool.state !== "output-available");
23
- const editing = tools.every(
24
- (tool) => formatToolName(tool.toolName) === "edit_notebook_tool",
25
- );
26
- const count = tools.length;
27
- const label = pending
28
- ? isActive
29
- ? editing
30
- ? "Editing notebook…"
31
- : "Working…"
32
- : "Stopped before completing all steps"
33
- : editing
34
- ? `Edited notebook · ${count} ${count === 1 ? "operation" : "operations"}`
35
- : `Completed ${count} ${count === 1 ? "step" : "steps"}`;
36
-
37
- return (
38
- <Accordion type="single" collapsible={true} className="my-1 w-full">
39
- <AccordionItem value="activity" className="border-0">
40
- <AccordionTrigger className="h-auto pl-0 pr-2 py-1 gap-2 rounded text-xs font-normal text-muted-foreground hover:bg-(--slate-2) hover:text-foreground hover:no-underline">
41
- {label}
42
- </AccordionTrigger>
43
- <AccordionContent className="pb-2 pl-3 border-l border-border">
44
- {children}
45
- </AccordionContent>
46
- </AccordionItem>
47
- </Accordion>
48
- );
49
- }
@@ -1,81 +0,0 @@
1
- /* Copyright 2026 Marimo. All rights reserved. */
2
-
3
- import { ChevronDownIcon, XCircleIcon } from "lucide-react";
4
- import React from "react";
5
- import { cn } from "@/utils/cn";
6
- import { formatToolName } from "./shared";
7
- import { ToolArgsRenderer } from "./tool-args";
8
-
9
- interface ToolErrorCardProps {
10
- toolName: string;
11
- input: unknown;
12
- errorText?: string;
13
- // When false, defaults to collapsed (the conversation has moved past this).
14
- isLive: boolean;
15
- className?: string;
16
- }
17
-
18
- export const ToolErrorCard: React.FC<ToolErrorCardProps> = ({
19
- toolName,
20
- input,
21
- errorText,
22
- isLive,
23
- className,
24
- }) => {
25
- const [open, setOpen] = React.useState(isLive);
26
-
27
- // Auto-collapse once when the conversation moves past this turn.
28
- // The user can still re-open manually afterwards; we only do this on the
29
- // live → not-live transition, never the reverse.
30
- const wasLive = React.useRef(isLive);
31
- React.useEffect(() => {
32
- if (wasLive.current && !isLive) {
33
- setOpen(false);
34
- }
35
- wasLive.current = isLive;
36
- }, [isLive]);
37
-
38
- return (
39
- <div
40
- className={cn(
41
- "rounded-md border border-(--red-6) bg-(--red-2)",
42
- className,
43
- )}
44
- >
45
- <button
46
- type="button"
47
- onClick={() => setOpen((prev) => !prev)}
48
- className="w-full flex items-center gap-2 px-3 py-2 text-xs text-(--red-11) hover:bg-(--red-3) rounded-md transition-colors"
49
- aria-expanded={open}
50
- >
51
- <XCircleIcon className="h-3.5 w-3.5 shrink-0" />
52
- <span className="flex-1 text-left">
53
- <span className="font-semibold">Failed:</span>{" "}
54
- <code className="font-mono">{formatToolName(toolName)}</code>
55
- </span>
56
- <ChevronDownIcon
57
- className={cn(
58
- "h-3.5 w-3.5 shrink-0 transition-transform",
59
- open && "rotate-180",
60
- )}
61
- />
62
- </button>
63
-
64
- {open && (
65
- <div className="px-3 pb-3 space-y-3 border-t border-(--red-6)/40 pt-3">
66
- <ToolArgsRenderer input={input} />
67
- {errorText && (
68
- <div>
69
- <h3 className="text-xs font-semibold text-(--red-11) mb-1">
70
- Error
71
- </h3>
72
- <pre className="bg-(--red-2) border border-(--red-6) rounded p-2 text-xs text-(--red-11) leading-relaxed overflow-auto scrollbar-thin max-h-64 whitespace-pre-wrap">
73
- {errorText}
74
- </pre>
75
- </div>
76
- )}
77
- </div>
78
- )}
79
- </div>
80
- );
81
- };
@@ -1,147 +0,0 @@
1
- /* Copyright 2026 Marimo. All rights reserved. */
2
-
3
- import { BanIcon, CircleSlashIcon, Loader2, WrenchIcon } from "lucide-react";
4
- import React from "react";
5
- import {
6
- Accordion,
7
- AccordionContent,
8
- AccordionItem,
9
- AccordionTrigger,
10
- } from "@/components/ui/accordion";
11
- import { logNever } from "@/utils/assertNever";
12
- import { cn } from "@/utils/cn";
13
- import { formatToolName, type ToolApproval, type ToolState } from "./shared";
14
- import { ToolArgsRenderer } from "./tool-args";
15
- import { ResultRenderer } from "./tool-result";
16
-
17
- // States considered "inert" — they represent past or background work that the
18
- // user does not need to act on.
19
- export type HistoryState = Exclude<
20
- ToolState,
21
- "approval-requested" | "output-error"
22
- >;
23
-
24
- const STATUS_LABEL: Record<HistoryState, string> = {
25
- "input-streaming": "Generating",
26
- "input-available": "Running",
27
- "approval-responded": "Awaiting result",
28
- "output-available": "Done",
29
- "output-denied": "Denied",
30
- };
31
-
32
- const PENDING_STATES = new Set<HistoryState>([
33
- "input-streaming",
34
- "input-available",
35
- "approval-responded",
36
- ]);
37
-
38
- const StatusIcon: React.FC<{ state: HistoryState; interrupted: boolean }> = ({
39
- state,
40
- interrupted,
41
- }) => {
42
- if (interrupted) {
43
- return <CircleSlashIcon className="h-3 w-3 text-muted-foreground" />;
44
- }
45
- switch (state) {
46
- case "input-streaming":
47
- case "input-available":
48
- case "approval-responded":
49
- return <Loader2 className="h-3 w-3 animate-spin" />;
50
- case "output-available":
51
- return null;
52
- case "output-denied":
53
- return <BanIcon className="h-3 w-3 text-muted-foreground" />;
54
- default:
55
- logNever(state);
56
- return <WrenchIcon className="h-3 w-3" />;
57
- }
58
- };
59
-
60
- interface ToolHistoryRowProps {
61
- toolName: string;
62
- state: HistoryState;
63
- input: unknown;
64
- result?: unknown;
65
- approval?: ToolApproval;
66
- index?: number;
67
- className?: string;
68
- isActive?: boolean;
69
- }
70
-
71
- export const ToolHistoryRow: React.FC<ToolHistoryRowProps> = ({
72
- toolName,
73
- state,
74
- input,
75
- result,
76
- approval,
77
- index = 0,
78
- className,
79
- isActive = true,
80
- }) => {
81
- const interrupted = !isActive && PENDING_STATES.has(state);
82
- return (
83
- <Accordion
84
- key={`tool-${index}`}
85
- type="single"
86
- collapsible={true}
87
- className={cn("w-full", className)}
88
- >
89
- <AccordionItem value="tool-call" className="border-0">
90
- <AccordionTrigger className="h-auto pl-0 pr-2 py-1 gap-2 rounded text-xs font-normal text-muted-foreground hover:bg-(--slate-2) hover:text-foreground hover:no-underline">
91
- <span className="flex items-center gap-1">
92
- <StatusIcon state={state} interrupted={interrupted} />
93
- {interrupted ? "Interrupted" : STATUS_LABEL[state]}:
94
- <code className="font-mono text-xs">
95
- {formatToolName(toolName)}
96
- </code>
97
- </span>
98
- </AccordionTrigger>
99
- <AccordionContent className="py-2 px-2">
100
- <HistoryContent
101
- state={state}
102
- input={input}
103
- result={result}
104
- approval={approval}
105
- />
106
- </AccordionContent>
107
- </AccordionItem>
108
- </Accordion>
109
- );
110
- };
111
-
112
- const HistoryContent: React.FC<{
113
- state: HistoryState;
114
- input: unknown;
115
- result?: unknown;
116
- approval?: ToolApproval;
117
- }> = ({ state, input, result, approval }) => {
118
- switch (state) {
119
- case "input-streaming":
120
- case "input-available":
121
- case "approval-responded":
122
- return <ToolArgsRenderer input={input} />;
123
-
124
- case "output-available":
125
- return (
126
- <div className="space-y-3">
127
- <ToolArgsRenderer input={input} />
128
- {result != null && <ResultRenderer result={result} />}
129
- </div>
130
- );
131
-
132
- case "output-denied":
133
- return (
134
- <div className="space-y-3">
135
- <ToolArgsRenderer input={input} />
136
- <div className="bg-muted/40 border border-border rounded-md p-3 text-xs text-muted-foreground leading-relaxed">
137
- Tool execution was denied
138
- {approval?.reason ? `: ${approval.reason}` : "."}
139
- </div>
140
- </div>
141
- );
142
-
143
- default:
144
- logNever(state);
145
- return null;
146
- }
147
- };
@@ -1,46 +0,0 @@
1
- /* Copyright 2026 Marimo. All rights reserved. */
2
-
3
- import clsx from "clsx";
4
- import { Circle } from "lucide-react";
5
- import React from "react";
6
-
7
- interface LoadingEllipsisProps {
8
- /** Extra Tailwind classes */
9
- className?: string;
10
- /** Diameter of each dot in pixels */
11
- size?: number;
12
- /** Tailwind gap utility between dots (e.g. "gap-1" or "gap-2") */
13
- gap?: string;
14
- /** Pulse cycle length in milliseconds */
15
- durationMs?: number;
16
- }
17
-
18
- const LoadingEllipsis: React.FC<LoadingEllipsisProps> = ({
19
- className,
20
- size = 8,
21
- gap = "gap-1",
22
- durationMs = 1200,
23
- }) => {
24
- const baseStyle: React.CSSProperties = {
25
- animationDuration: `${durationMs}ms`,
26
- };
27
-
28
- return (
29
- <div className={clsx("flex", gap, className)} aria-label="Loading">
30
- {[0, 1, 2].map((i) => (
31
- <Circle
32
- key={i}
33
- width={size}
34
- height={size}
35
- className="fill-current text-current animate-ellipsis-dot"
36
- style={{
37
- ...baseStyle,
38
- animationDelay: `${(durationMs / 3) * i}ms`,
39
- }}
40
- />
41
- ))}
42
- </div>
43
- );
44
- };
45
-
46
- export { LoadingEllipsis };