@marimo-team/islands 0.23.7-dev9 → 0.23.7-dev90
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ConnectedDataExplorerComponent-DnRhpPMJ.js → ConnectedDataExplorerComponent-2lBNiUv6.js} +13 -13
- package/dist/{ErrorBoundary-Da4UeYxT.js → ErrorBoundary-D3wrPNma.js} +1 -1
- package/dist/{any-language-editor-DDubl8YH.js → any-language-editor-VWs_7v27.js} +5 -5
- package/dist/assets/__vite-browser-external-CAdMKBac.js +1 -0
- package/dist/assets/worker-CpBbwbQo.js +73 -0
- package/dist/{button-CA5pI2YF.js → button-Dj4BTre0.js} +5 -0
- package/dist/{capabilities-6laDasij.js → capabilities-C9rrYCzf.js} +1 -1
- package/dist/{chat-ui-BmWZZ3mE.js → chat-ui-D3XBept8.js} +625 -233
- package/dist/{check-CFM2mVDr.js → check-BcUIXnUT.js} +1 -1
- package/dist/{code-visibility-CRHzv49w.js → code-visibility-C5NrPsUC.js} +11480 -1992
- package/dist/{copy-TGGAUEWp.js → copy-DLf4aN7I.js} +2 -2
- package/dist/{dist-ESg7xyoD.js → dist-D3ZI9nhS.js} +2 -2
- package/dist/{error-banner-DnBPzEWg.js → error-banner-CVkfBUT3.js} +2 -2
- package/dist/{esm-Dd1z1auZ.js → esm-CWp0KQeK.js} +1 -1
- package/dist/{extends-CzJgxo2J.js → extends-vAi97cpa.js} +4 -4
- package/dist/{formats-CgaK7Gmx.js → formats-Dsy9kkZu.js} +3 -3
- package/dist/{glide-data-editor-B-3A3G02.js → glide-data-editor-DucgdjRo.js} +9 -9
- package/dist/{html-to-image-BwZL1Pkk.js → html-to-image-CpggM7u1.js} +2667 -2408
- package/dist/{input-BAOe64zx.js → input-D4kjoQUB.js} +8 -6
- package/dist/{label-BCWi-Oqu.js → label-BLqV33b1.js} +2 -2
- package/dist/{loader-BvW0-YWZ.js → loader-Dr8Qem8p.js} +1 -1
- package/dist/main.js +1697 -10282
- package/dist/{mermaid-cXSZ1pfD.js → mermaid-DO-Daq7u.js} +5 -5
- package/dist/{process-output-lpVrk7d5.js → process-output-X8TR20AK.js} +3 -3
- package/dist/reveal-component-kMIwe09M.js +7447 -0
- package/dist/{spec-DSIuqd3f.js → spec-hVaaZsY5.js} +4 -4
- package/dist/{strings-B_FOH6eV.js → strings-BiIhGaI8.js} +4 -4
- package/dist/style.css +1 -1
- package/dist/{swiper-component-BHs0PWwp.js → swiper-component-DlD2GU2g.js} +2 -2
- package/dist/{toDate-CHtl9vts.js → toDate-CIpC_34u.js} +33 -20
- package/dist/{tooltip-B0mtKTXm.js → tooltip-DRaMBu06.js} +3 -3
- package/dist/{types-DBtDeUKD.js → types-Dzuoc3LN.js} +1 -1
- package/dist/{useAsyncData-B6hCGywC.js → useAsyncData-C56Khv_R.js} +1 -1
- package/dist/{useDateFormatter-B3mCQMP3.js → useDateFormatter-B_9k85Ex.js} +2 -2
- package/dist/{useDeepCompareMemoize-CmwDuYUH.js → useDeepCompareMemoize-Dt98v2ua.js} +1 -1
- package/dist/{useIframeCapabilities-DbdLoEDm.js → useIframeCapabilities-BkYHTrss.js} +1 -1
- package/dist/{useLifecycle-CjMjllqy.js → useLifecycle-BF6-z62y.js} +3 -3
- package/dist/{useTheme-CByZUW0p.js → useTheme-DykuNHR2.js} +2 -2
- package/dist/{vega-component-C2BYPkfd.js → vega-component-cSdqoAxe.js} +10 -10
- package/dist/{zod-BxdsqRPd.js → zod-BWkcDORu.js} +1 -1
- package/package.json +3 -3
- package/src/components/chat/chat-components.tsx +47 -0
- package/src/components/chat/chat-display.tsx +41 -7
- package/src/components/chat/chat-panel.tsx +37 -10
- package/src/components/chat/chat-utils.ts +42 -20
- package/src/components/chat/reasoning-accordion.tsx +14 -3
- package/src/components/chat/tool-call/shared.ts +13 -0
- package/src/components/chat/tool-call/tool-approval-card.tsx +62 -0
- package/src/components/chat/tool-call/tool-args.tsx +26 -0
- package/src/components/chat/tool-call/tool-call-view.tsx +99 -0
- package/src/components/chat/tool-call/tool-error-card.tsx +81 -0
- package/src/components/chat/tool-call/tool-history-row.tsx +153 -0
- package/src/components/chat/tool-call/tool-result.tsx +101 -0
- package/src/components/data-table/__tests__/column-header.test.ts +3 -1
- package/src/components/data-table/__tests__/column-header.test.tsx +308 -0
- package/src/components/data-table/__tests__/filter-by-values-picker.test.tsx +112 -0
- package/src/components/data-table/__tests__/filter-pill-editor.test.tsx +261 -0
- package/src/components/data-table/__tests__/filters.test.ts +196 -49
- package/src/components/data-table/charts/components/form-fields.tsx +1 -0
- package/src/components/data-table/column-header.tsx +349 -170
- package/src/components/data-table/date-filter-inputs.tsx +325 -0
- package/src/components/data-table/filter-by-values-picker.tsx +70 -9
- package/src/components/data-table/filter-pill-editor.tsx +410 -156
- package/src/components/data-table/filter-pills.tsx +69 -54
- package/src/components/data-table/filters.ts +218 -101
- package/src/components/data-table/header-items.tsx +8 -1
- package/src/components/data-table/operator-labels.ts +25 -0
- package/src/components/data-table/regex-input.tsx +61 -0
- package/src/components/dependency-graph/minimap-content.tsx +14 -3
- package/src/components/editor/actions/pair-with-agent-modal.tsx +140 -49
- package/src/components/editor/actions/useNotebookActions.tsx +3 -1
- package/src/components/editor/app-container.tsx +7 -1
- package/src/components/editor/chrome/panels/context-aware-panel/context-aware-panel.tsx +10 -2
- package/src/components/editor/chrome/wrapper/app-chrome.tsx +1 -0
- package/src/components/editor/chrome/wrapper/footer-items/backend-status.tsx +1 -1
- package/src/components/editor/chrome/wrapper/footer.tsx +4 -1
- package/src/components/editor/chrome/wrapper/panels.tsx +4 -1
- package/src/components/editor/chrome/wrapper/sidebar.tsx +4 -1
- package/src/components/editor/controls/Controls.tsx +11 -3
- package/src/components/editor/file-tree/file-explorer.tsx +12 -2
- package/src/components/editor/header/__tests__/status.test.tsx +108 -0
- package/src/components/editor/header/status.tsx +44 -10
- package/src/components/editor/navigation/__tests__/clipboard.test.ts +106 -0
- package/src/components/editor/navigation/__tests__/navigation.test.ts +70 -0
- package/src/components/editor/navigation/clipboard.ts +99 -25
- package/src/components/editor/navigation/navigation.ts +15 -1
- package/src/components/editor/notebook-cell.tsx +5 -0
- package/src/components/editor/output/console/ConsoleOutput.tsx +23 -5
- package/src/components/editor/output/console/__tests__/ConsoleOutput.test.tsx +114 -0
- package/src/components/editor/renderers/slides-layout/__tests__/compute-slide-cells.test.ts +5 -4
- package/src/components/editor/renderers/slides-layout/__tests__/plugin.test.ts +55 -15
- package/src/components/editor/renderers/slides-layout/plugin.tsx +8 -25
- package/src/components/editor/renderers/slides-layout/slides-layout.tsx +19 -6
- package/src/components/editor/renderers/slides-layout/types.ts +40 -31
- package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +1 -0
- package/src/components/home/components.tsx +6 -0
- package/src/components/pages/run-page.tsx +4 -1
- package/src/components/scratchpad/scratchpad.tsx +1 -0
- package/src/components/slides/__tests__/slide-notes.test.ts +131 -0
- package/src/components/slides/reveal-component.tsx +252 -147
- package/src/components/slides/slide-notes-editor.tsx +127 -0
- package/src/components/slides/slide-notes.ts +64 -0
- package/src/components/slides/slides.css +14 -0
- package/src/components/ui/combobox.tsx +24 -5
- package/src/components/ui/number-field.tsx +2 -0
- package/src/core/ai/tools/__tests__/registry.test.ts +10 -12
- package/src/core/ai/tools/registry.ts +9 -5
- package/src/core/cells/__tests__/cells.test.ts +187 -0
- package/src/core/cells/__tests__/pending-cut-service.test.tsx +123 -0
- package/src/core/cells/cells.ts +102 -17
- package/src/core/cells/document-changes.ts +6 -1
- package/src/core/cells/pending-cut-service.ts +55 -0
- package/src/core/cells/utils.ts +11 -0
- package/src/core/codemirror/cells/extensions.ts +10 -0
- package/src/core/codemirror/go-to-definition/__tests__/commands.test.ts +152 -0
- package/src/core/codemirror/go-to-definition/__tests__/utils.test.ts +99 -0
- package/src/core/codemirror/go-to-definition/commands.ts +382 -22
- package/src/core/codemirror/go-to-definition/utils.ts +23 -5
- package/src/core/edit-app.tsx +3 -2
- package/src/core/hotkeys/hotkeys.ts +5 -0
- package/src/core/islands/worker/worker.tsx +3 -2
- package/src/core/run-app.tsx +2 -1
- package/src/core/runtime/__tests__/runtime.test.ts +38 -17
- package/src/core/runtime/runtime.ts +57 -34
- package/src/core/wasm/__tests__/utils.test.ts +34 -0
- package/src/core/wasm/utils.ts +14 -0
- package/src/core/wasm/worker/bootstrap.ts +3 -2
- package/src/core/wasm/worker/worker.ts +3 -2
- package/src/core/websocket/__tests__/useMarimoKernelConnection.hook.test.tsx +156 -0
- package/src/core/websocket/__tests__/useMarimoKernelConnection.test.ts +101 -0
- package/src/core/websocket/transports/__tests__/ws.test.ts +125 -0
- package/src/core/websocket/transports/basic.ts +1 -1
- package/src/core/websocket/transports/ws.ts +96 -0
- package/src/core/websocket/useMarimoKernelConnection.tsx +133 -54
- package/src/core/websocket/useWebSocket.tsx +3 -15
- package/src/css/app/Cell.css +10 -0
- package/src/plugins/core/__test__/sanitize.test.ts +30 -0
- package/src/plugins/impl/DropdownPlugin.tsx +12 -1
- package/src/plugins/impl/MultiselectPlugin.tsx +4 -0
- package/src/plugins/impl/SearchableSelect.tsx +11 -1
- package/src/plugins/impl/TabsPlugin.tsx +35 -7
- package/src/plugins/impl/__tests__/DropdownPlugin.test.tsx +56 -0
- package/src/plugins/impl/__tests__/TabsPlugin.test.tsx +154 -0
- package/src/plugins/impl/data-frames/forms/__tests__/__snapshots__/form.test.tsx.snap +48 -36
- package/src/plugins/impl/data-frames/schema.ts +4 -1
- package/src/plugins/layout/DownloadPlugin.tsx +9 -7
- package/src/utils/__tests__/id-tree.test.ts +71 -0
- package/src/utils/download.ts +4 -2
- package/src/utils/id-tree.tsx +89 -0
- package/dist/assets/__vite-browser-external-rrUYDKRl.js +0 -1
- package/dist/assets/worker-Bfy15ViQ.js +0 -73
- package/dist/reveal-component-C97Ceb7e.js +0 -4863
- package/src/components/chat/tool-call-accordion.tsx +0 -247
|
@@ -21,20 +21,31 @@ export const ReasoningAccordion: React.FC<ReasoningAccordionProps> = ({
|
|
|
21
21
|
index = 0,
|
|
22
22
|
isStreaming = false,
|
|
23
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
|
+
|
|
24
34
|
return (
|
|
25
35
|
<Accordion
|
|
26
36
|
key={index}
|
|
27
37
|
type="single"
|
|
28
38
|
collapsible={true}
|
|
29
39
|
className="w-full mb-2"
|
|
30
|
-
value={isStreaming ? "reasoning" :
|
|
40
|
+
value={isStreaming ? "reasoning" : openItem}
|
|
41
|
+
onValueChange={setOpenItem}
|
|
31
42
|
>
|
|
32
43
|
<AccordionItem value="reasoning" className="border-0">
|
|
33
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">
|
|
34
45
|
<span className="flex items-center gap-2">
|
|
35
46
|
<BotMessageSquareIcon className="h-3 w-3" />
|
|
36
|
-
{isStreaming ? "Thinking" : "View reasoning"}
|
|
37
|
-
chars)
|
|
47
|
+
{isStreaming ? "Thinking" : "View reasoning"}
|
|
48
|
+
{reasoning.length > 0 && ` (${reasoning.length} chars)`}
|
|
38
49
|
</span>
|
|
39
50
|
</AccordionTrigger>
|
|
40
51
|
<AccordionContent className="pb-2 px-2">
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import type { ToolUIPart } from "ai";
|
|
4
|
+
|
|
5
|
+
export type ToolState = ToolUIPart["state"];
|
|
6
|
+
|
|
7
|
+
// The AI SDK declares `approval` inline on every variant of `UIToolInvocation`
|
|
8
|
+
// rather than exporting a named type, so we derive ours from there.
|
|
9
|
+
export type ToolApproval = NonNullable<ToolUIPart["approval"]>;
|
|
10
|
+
|
|
11
|
+
export function formatToolName(toolName: string): string {
|
|
12
|
+
return toolName.replace("tool-", "");
|
|
13
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import type { ChatAddToolApproveResponseFunction } from "ai";
|
|
4
|
+
import { ShieldQuestionIcon } from "lucide-react";
|
|
5
|
+
import React from "react";
|
|
6
|
+
import { Button } from "@/components/ui/button";
|
|
7
|
+
import { cn } from "@/utils/cn";
|
|
8
|
+
import { formatToolName, type ToolApproval } from "./shared";
|
|
9
|
+
import { ToolArgsRenderer } from "./tool-args";
|
|
10
|
+
|
|
11
|
+
interface ToolApprovalCardProps {
|
|
12
|
+
toolName: string;
|
|
13
|
+
input: unknown;
|
|
14
|
+
approval: ToolApproval;
|
|
15
|
+
onApprove: ChatAddToolApproveResponseFunction;
|
|
16
|
+
className?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const ToolApprovalCard: React.FC<ToolApprovalCardProps> = ({
|
|
20
|
+
toolName,
|
|
21
|
+
input,
|
|
22
|
+
approval,
|
|
23
|
+
onApprove,
|
|
24
|
+
className,
|
|
25
|
+
}) => {
|
|
26
|
+
return (
|
|
27
|
+
<div
|
|
28
|
+
className={cn(
|
|
29
|
+
"rounded-md border border-(--amber-6) bg-(--amber-2) p-3 space-y-3",
|
|
30
|
+
className,
|
|
31
|
+
)}
|
|
32
|
+
role="alertdialog"
|
|
33
|
+
aria-label={`Approval required for ${formatToolName(toolName)}`}
|
|
34
|
+
>
|
|
35
|
+
<div className="flex items-start gap-2">
|
|
36
|
+
<ShieldQuestionIcon className="h-4 w-4 text-(--amber-11) mt-0.5 shrink-0" />
|
|
37
|
+
<div className="text-xs text-(--amber-11) leading-relaxed">
|
|
38
|
+
<span className="font-semibold">Approval required:</span>{" "}
|
|
39
|
+
<code className="font-mono">{formatToolName(toolName)}</code>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<ToolArgsRenderer input={input} />
|
|
44
|
+
|
|
45
|
+
<div className="flex items-center justify-end gap-2">
|
|
46
|
+
<Button
|
|
47
|
+
size="xs"
|
|
48
|
+
variant="outline"
|
|
49
|
+
onClick={() => onApprove({ id: approval.id, approved: false })}
|
|
50
|
+
>
|
|
51
|
+
Deny
|
|
52
|
+
</Button>
|
|
53
|
+
<Button
|
|
54
|
+
size="xs"
|
|
55
|
+
onClick={() => onApprove({ id: approval.id, approved: true })}
|
|
56
|
+
>
|
|
57
|
+
Approve
|
|
58
|
+
</Button>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
|
|
5
|
+
interface ToolArgsRendererProps {
|
|
6
|
+
input: unknown;
|
|
7
|
+
label?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const ToolArgsRenderer: React.FC<ToolArgsRendererProps> = ({
|
|
11
|
+
input,
|
|
12
|
+
label = "Tool Request",
|
|
13
|
+
}) => {
|
|
14
|
+
if (input == null) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div className="space-y-2">
|
|
20
|
+
<h3 className="text-xs font-semibold text-muted-foreground">{label}</h3>
|
|
21
|
+
<pre className="bg-(--slate-2) p-2 text-muted-foreground border border-(--slate-4) rounded text-xs overflow-auto scrollbar-thin max-h-64">
|
|
22
|
+
{JSON.stringify(input, null, 2)}
|
|
23
|
+
</pre>
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import type { ChatAddToolApproveResponseFunction } from "ai";
|
|
4
|
+
import React from "react";
|
|
5
|
+
import { logNever } from "@/utils/assertNever";
|
|
6
|
+
import type { ToolApproval, ToolState } from "./shared";
|
|
7
|
+
import { ToolApprovalCard } from "./tool-approval-card";
|
|
8
|
+
import { ToolErrorCard } from "./tool-error-card";
|
|
9
|
+
import { ToolHistoryRow } from "./tool-history-row";
|
|
10
|
+
|
|
11
|
+
interface ToolCallViewProps {
|
|
12
|
+
toolName: string;
|
|
13
|
+
state: ToolState;
|
|
14
|
+
result?: unknown;
|
|
15
|
+
errorText?: string;
|
|
16
|
+
input?: unknown;
|
|
17
|
+
approval?: ToolApproval;
|
|
18
|
+
onApprove?: ChatAddToolApproveResponseFunction;
|
|
19
|
+
index?: number;
|
|
20
|
+
className?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Whether this tool call belongs to the latest message in the conversation.
|
|
23
|
+
* Used by error cards to decide whether to stay expanded (live) or collapse
|
|
24
|
+
* (the user has moved on).
|
|
25
|
+
*/
|
|
26
|
+
isLive?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const ToolCallView: React.FC<ToolCallViewProps> = ({
|
|
30
|
+
toolName,
|
|
31
|
+
state,
|
|
32
|
+
result,
|
|
33
|
+
errorText,
|
|
34
|
+
input,
|
|
35
|
+
approval,
|
|
36
|
+
onApprove,
|
|
37
|
+
index,
|
|
38
|
+
className,
|
|
39
|
+
isLive = true,
|
|
40
|
+
}) => {
|
|
41
|
+
switch (state) {
|
|
42
|
+
case "approval-requested":
|
|
43
|
+
// Approval is a live, blocking action — render it as a prominent card
|
|
44
|
+
// Fall back to history row if the wiring isn't
|
|
45
|
+
// available (shouldn't happen in practice).
|
|
46
|
+
if (approval != null && onApprove != null) {
|
|
47
|
+
return (
|
|
48
|
+
<ToolApprovalCard
|
|
49
|
+
toolName={toolName}
|
|
50
|
+
input={input}
|
|
51
|
+
approval={approval}
|
|
52
|
+
onApprove={onApprove}
|
|
53
|
+
className={className}
|
|
54
|
+
/>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
return (
|
|
58
|
+
<ToolHistoryRow
|
|
59
|
+
toolName={toolName}
|
|
60
|
+
state="input-available"
|
|
61
|
+
input={input}
|
|
62
|
+
index={index}
|
|
63
|
+
className={className}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
case "output-error":
|
|
68
|
+
return (
|
|
69
|
+
<ToolErrorCard
|
|
70
|
+
toolName={toolName}
|
|
71
|
+
input={input}
|
|
72
|
+
errorText={errorText}
|
|
73
|
+
isLive={isLive}
|
|
74
|
+
className={className}
|
|
75
|
+
/>
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
case "input-streaming":
|
|
79
|
+
case "input-available":
|
|
80
|
+
case "approval-responded":
|
|
81
|
+
case "output-available":
|
|
82
|
+
case "output-denied":
|
|
83
|
+
return (
|
|
84
|
+
<ToolHistoryRow
|
|
85
|
+
toolName={toolName}
|
|
86
|
+
state={state}
|
|
87
|
+
input={input}
|
|
88
|
+
result={result}
|
|
89
|
+
approval={approval}
|
|
90
|
+
index={index}
|
|
91
|
+
className={className}
|
|
92
|
+
/>
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
default:
|
|
96
|
+
logNever(state);
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { BanIcon, CheckCircleIcon, 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 StatusIcon: React.FC<{ state: HistoryState }> = ({ state }) => {
|
|
33
|
+
switch (state) {
|
|
34
|
+
case "input-streaming":
|
|
35
|
+
case "input-available":
|
|
36
|
+
case "approval-responded":
|
|
37
|
+
return <Loader2 className="h-3 w-3 animate-spin" />;
|
|
38
|
+
case "output-available":
|
|
39
|
+
return <CheckCircleIcon className="h-3 w-3 text-(--grass-11)" />;
|
|
40
|
+
case "output-denied":
|
|
41
|
+
return <BanIcon className="h-3 w-3 text-muted-foreground" />;
|
|
42
|
+
default:
|
|
43
|
+
logNever(state);
|
|
44
|
+
return <WrenchIcon className="h-3 w-3" />;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
function getTriggerToneClass(state: HistoryState): string {
|
|
49
|
+
switch (state) {
|
|
50
|
+
case "output-available":
|
|
51
|
+
return "text-(--grass-11)/80";
|
|
52
|
+
case "output-denied":
|
|
53
|
+
return "text-muted-foreground";
|
|
54
|
+
case "input-streaming":
|
|
55
|
+
case "input-available":
|
|
56
|
+
case "approval-responded":
|
|
57
|
+
return "";
|
|
58
|
+
default:
|
|
59
|
+
logNever(state);
|
|
60
|
+
return "";
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface ToolHistoryRowProps {
|
|
65
|
+
toolName: string;
|
|
66
|
+
state: HistoryState;
|
|
67
|
+
input: unknown;
|
|
68
|
+
result?: unknown;
|
|
69
|
+
approval?: ToolApproval;
|
|
70
|
+
index?: number;
|
|
71
|
+
className?: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const ToolHistoryRow: React.FC<ToolHistoryRowProps> = ({
|
|
75
|
+
toolName,
|
|
76
|
+
state,
|
|
77
|
+
input,
|
|
78
|
+
result,
|
|
79
|
+
approval,
|
|
80
|
+
index = 0,
|
|
81
|
+
className,
|
|
82
|
+
}) => {
|
|
83
|
+
return (
|
|
84
|
+
<Accordion
|
|
85
|
+
key={`tool-${index}`}
|
|
86
|
+
type="single"
|
|
87
|
+
collapsible={true}
|
|
88
|
+
className={cn("w-full", className)}
|
|
89
|
+
>
|
|
90
|
+
<AccordionItem value="tool-call" className="border-0">
|
|
91
|
+
<AccordionTrigger
|
|
92
|
+
className={cn(
|
|
93
|
+
"h-6 text-xs border-border shadow-none! ring-0! bg-muted/60 hover:bg-muted py-0 px-2 gap-1 rounded-sm [&[data-state=open]>svg]:rotate-180 hover:no-underline",
|
|
94
|
+
getTriggerToneClass(state),
|
|
95
|
+
)}
|
|
96
|
+
>
|
|
97
|
+
<span className="flex items-center gap-1">
|
|
98
|
+
<StatusIcon state={state} />
|
|
99
|
+
{STATUS_LABEL[state]}:
|
|
100
|
+
<code className="font-mono text-xs">
|
|
101
|
+
{formatToolName(toolName)}
|
|
102
|
+
</code>
|
|
103
|
+
</span>
|
|
104
|
+
</AccordionTrigger>
|
|
105
|
+
<AccordionContent className="py-2 px-2">
|
|
106
|
+
<HistoryContent
|
|
107
|
+
state={state}
|
|
108
|
+
input={input}
|
|
109
|
+
result={result}
|
|
110
|
+
approval={approval}
|
|
111
|
+
/>
|
|
112
|
+
</AccordionContent>
|
|
113
|
+
</AccordionItem>
|
|
114
|
+
</Accordion>
|
|
115
|
+
);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const HistoryContent: React.FC<{
|
|
119
|
+
state: HistoryState;
|
|
120
|
+
input: unknown;
|
|
121
|
+
result?: unknown;
|
|
122
|
+
approval?: ToolApproval;
|
|
123
|
+
}> = ({ state, input, result, approval }) => {
|
|
124
|
+
switch (state) {
|
|
125
|
+
case "input-streaming":
|
|
126
|
+
case "input-available":
|
|
127
|
+
case "approval-responded":
|
|
128
|
+
return <ToolArgsRenderer input={input} />;
|
|
129
|
+
|
|
130
|
+
case "output-available":
|
|
131
|
+
return (
|
|
132
|
+
<div className="space-y-3">
|
|
133
|
+
<ToolArgsRenderer input={input} />
|
|
134
|
+
{result != null && <ResultRenderer result={result} />}
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
case "output-denied":
|
|
139
|
+
return (
|
|
140
|
+
<div className="space-y-3">
|
|
141
|
+
<ToolArgsRenderer input={input} />
|
|
142
|
+
<div className="bg-muted/40 border border-border rounded-md p-3 text-xs text-muted-foreground leading-relaxed">
|
|
143
|
+
Tool execution was denied
|
|
144
|
+
{approval?.reason ? `: ${approval.reason}` : "."}
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
default:
|
|
150
|
+
logNever(state);
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { isEmpty } from "lodash-es";
|
|
4
|
+
import { InfoIcon } from "lucide-react";
|
|
5
|
+
import React from "react";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
|
|
8
|
+
// A value worth rendering: drop null/undefined and empty containers
|
|
9
|
+
// (`{}`, `[]`), but keep meaningful primitives (`0`, `false`, `""`).
|
|
10
|
+
function isUninformative(value: unknown): boolean {
|
|
11
|
+
if (value == null) {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
if (typeof value === "object") {
|
|
15
|
+
return isEmpty(value);
|
|
16
|
+
}
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Zod schema matching the Python SuccessResult dataclass
|
|
21
|
+
const SuccessResultSchema = z.looseObject({
|
|
22
|
+
status: z.string().default("success"),
|
|
23
|
+
auth_required: z.boolean().default(false),
|
|
24
|
+
action_url: z.any(),
|
|
25
|
+
next_steps: z.any(),
|
|
26
|
+
meta: z.any(),
|
|
27
|
+
message: z.string().nullish(),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
type SuccessResult = z.infer<typeof SuccessResultSchema>;
|
|
31
|
+
|
|
32
|
+
const PrettySuccessResult: React.FC<{ data: SuccessResult }> = ({ data }) => {
|
|
33
|
+
const {
|
|
34
|
+
status,
|
|
35
|
+
auth_required,
|
|
36
|
+
action_url: _action_url,
|
|
37
|
+
meta: _meta,
|
|
38
|
+
next_steps: _next_steps,
|
|
39
|
+
message,
|
|
40
|
+
...rest
|
|
41
|
+
} = data;
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div className="flex flex-col gap-1.5">
|
|
45
|
+
<div className="flex items-center justify-between">
|
|
46
|
+
<h3 className="text-xs font-semibold text-muted-foreground">
|
|
47
|
+
Tool Result
|
|
48
|
+
</h3>
|
|
49
|
+
<div className="flex items-center gap-2">
|
|
50
|
+
<span className="text-xs px-2 py-0.5 bg-(--grass-2) text-(--grass-11) rounded-full font-medium capitalize">
|
|
51
|
+
{status}
|
|
52
|
+
</span>
|
|
53
|
+
{auth_required && (
|
|
54
|
+
<span className="text-xs px-2 py-0.5 bg-(--amber-2) text-(--amber-11) rounded-full">
|
|
55
|
+
Auth Required
|
|
56
|
+
</span>
|
|
57
|
+
)}
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
{message && (
|
|
62
|
+
<div className="flex items-start gap-2">
|
|
63
|
+
<InfoIcon className="h-3 w-3 text-(--blue-11) mt-0.5 shrink-0" />
|
|
64
|
+
<div className="text-xs text-foreground">{message}</div>
|
|
65
|
+
</div>
|
|
66
|
+
)}
|
|
67
|
+
|
|
68
|
+
{rest && (
|
|
69
|
+
<div className="space-y-3">
|
|
70
|
+
{Object.entries(rest).map(([key, value]) => {
|
|
71
|
+
if (isUninformative(value)) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
return (
|
|
75
|
+
<div key={key} className="space-y-1.5">
|
|
76
|
+
<span className="text-xs text-muted-foreground">{key}</span>
|
|
77
|
+
<pre className="bg-(--slate-2) p-2 text-muted-foreground border border-(--slate-4) rounded text-xs overflow-auto scrollbar-thin max-h-64">
|
|
78
|
+
{JSON.stringify(value, null, 2)}
|
|
79
|
+
</pre>
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
})}
|
|
83
|
+
</div>
|
|
84
|
+
)}
|
|
85
|
+
</div>
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export const ResultRenderer: React.FC<{ result: unknown }> = ({ result }) => {
|
|
90
|
+
const parseResult = SuccessResultSchema.safeParse(result);
|
|
91
|
+
|
|
92
|
+
if (parseResult.success) {
|
|
93
|
+
return <PrettySuccessResult data={parseResult.data} />;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<div className="text-xs font-medium text-muted-foreground mb-1 max-h-64 overflow-y-auto scrollbar-thin">
|
|
98
|
+
{typeof result === "string" ? result : JSON.stringify(result, null, 2)}
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
};
|
|
@@ -49,7 +49,9 @@ describe("seedFromFilter", () => {
|
|
|
49
49
|
values: [],
|
|
50
50
|
operator: "in",
|
|
51
51
|
});
|
|
52
|
-
expect(
|
|
52
|
+
expect(
|
|
53
|
+
seedFromFilter(Filter.number({ operator: "between", min: 0, max: 10 })),
|
|
54
|
+
).toEqual({
|
|
53
55
|
values: [],
|
|
54
56
|
operator: "in",
|
|
55
57
|
});
|