@marimo-team/islands 0.23.14-dev5 → 0.23.14-dev50
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-Du3_nUzI.js → ConnectedDataExplorerComponent-DXBx_nQg.js} +4 -4
- package/dist/{chat-ui-BZxLHwyD.js → chat-ui-DXPRhRO2.js} +3248 -3044
- package/dist/{click-outside-container-BDd67_1U.js → click-outside-container-BLPjMamz.js} +141 -113
- package/dist/{code-visibility-rxZi4Phe.js → code-visibility-BFDT-GEQ.js} +1153 -946
- package/dist/data-grid-overlay-editor-C6lxUJp-.js +136 -0
- package/dist/{dist-D_bzzWBm.js → dist-Bf9f8MuT.js} +3 -3
- package/dist/{formats-d6MhLuQ9.js → formats-WsOgyW_K.js} +1 -1
- package/dist/{glide-data-editor-DkzAInWG.js → glide-data-editor-7wxMGXjG.js} +2084 -1889
- package/dist/{html-to-image-CGp_08St.js → html-to-image-DOqQBSQc.js} +2367 -2301
- package/dist/{input-CbEz_aj_.js → input-BSdZp5Ng.js} +1 -1
- package/dist/main.js +2707 -1186
- package/dist/{mermaid-CJW9vIyO.js → mermaid-D-HYBMEV.js} +2 -2
- package/dist/{number-overlay-editor-D-a0qCT8.js → number-overlay-editor-BLJXvX9c.js} +1 -1
- package/dist/{process-output-R6JsYrv3.js → process-output-kGk2Jc2x.js} +1 -1
- package/dist/{reveal-component-sEb3Wd1x.js → reveal-component-uSCMaaUE.js} +600 -596
- package/dist/{spec-Bv-XlYiv.js → spec-CnTgI25l.js} +1 -1
- package/dist/style.css +1 -1
- package/dist/{toDate-D-l5s8nn.js → toDate-D1Z7ZXWh.js} +1 -1
- package/dist/{useAsyncData-1Dhzjfwf.js → useAsyncData-BMc8itk2.js} +1 -1
- package/dist/{useDeepCompareMemoize-CDWT3BDz.js → useDeepCompareMemoize-ZwmDBRDY.js} +1 -1
- package/dist/{useLifecycle-AHlswLw-.js → useLifecycle-CxffarYV.js} +1 -1
- package/dist/{useTheme-BrYvK-_A.js → useTheme-yGsGEk82.js} +26 -24
- package/dist/{vega-component-Pk6lyc_a.js → vega-component-BFJTyykA.js} +5 -5
- package/package.json +3 -3
- package/src/__mocks__/requests.ts +1 -0
- package/src/components/app-config/user-config-form.tsx +26 -0
- package/src/components/chat/acp/agent-panel.tsx +35 -1
- package/src/components/chat/chat-panel.tsx +68 -29
- package/src/components/data-table/__tests__/column-explorer.test.tsx +25 -0
- package/src/components/data-table/__tests__/column-visibility-dropdown.test.tsx +60 -3
- package/src/components/data-table/charts/__tests__/altair-generator.test.ts +24 -0
- package/src/components/data-table/charts/__tests__/merge-index-fields.test.ts +33 -0
- package/src/components/data-table/charts/chart-spec/altair-generator.ts +4 -1
- package/src/components/data-table/charts/charts.tsx +23 -1
- package/src/components/data-table/column-explorer-panel/column-explorer.tsx +33 -12
- package/src/components/data-table/column-visibility-dropdown.tsx +15 -0
- package/src/components/debugger/debugger-code.tsx +7 -2
- package/src/components/editor/actions/useNotebookActions.tsx +2 -2
- package/src/components/editor/cell/cell-context-menu.tsx +2 -2
- package/src/components/editor/chrome/panels/snippets-panel.tsx +3 -3
- package/src/components/editor/chrome/wrapper/__tests__/useOpenAiAssistant.test.ts +36 -0
- package/src/components/editor/chrome/wrapper/footer-items/pyodide-status.tsx +6 -36
- package/src/components/editor/chrome/wrapper/useAiPanel.ts +3 -1
- package/src/components/editor/chrome/wrapper/useOpenAiAssistant.ts +88 -0
- package/src/components/editor/code/__tests__/readonly-python-code.test.tsx +79 -0
- package/src/components/editor/code/readonly-python-code.tsx +60 -33
- package/src/components/editor/errors/__tests__/auto-fix.test.ts +23 -0
- package/src/components/editor/errors/auto-fix.tsx +88 -34
- package/src/components/editor/errors/fix-mode.ts +1 -1
- package/src/components/editor/notebook-cell.tsx +7 -0
- package/src/components/editor/output/MarimoTracebackOutput.tsx +28 -2
- package/src/components/editor/output/__tests__/traceback.test.tsx +14 -6
- package/src/components/editor/renderers/grid-layout/grid-layout.tsx +7 -2
- package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +7 -3
- package/src/components/lifecycle/ProgressiveBoundary.tsx +42 -0
- package/src/components/lifecycle/RuntimeStatusBadge.tsx +59 -0
- package/src/components/lifecycle/__tests__/ProgressiveBoundary.test.tsx +147 -0
- package/src/components/lifecycle/__tests__/RuntimeStatusBadge.test.tsx +72 -0
- package/src/components/slides/__tests__/slide.test.tsx +33 -0
- package/src/components/slides/minimap.tsx +7 -1
- package/src/components/slides/reveal-component.tsx +3 -0
- package/src/components/slides/slide-cell-view.tsx +10 -6
- package/src/components/slides/slide.tsx +16 -13
- package/src/core/ai/config.ts +2 -2
- package/src/core/ai/context/__tests__/registry.test.ts +133 -3
- package/src/core/ai/context/providers/__tests__/datasource.test.ts +3 -1
- package/src/core/ai/context/providers/__tests__/error.test.ts +200 -15
- package/src/core/ai/context/providers/datasource.ts +27 -2
- package/src/core/ai/context/providers/error.ts +226 -36
- package/src/core/ai/context/registry.ts +77 -43
- package/src/core/ai/state.ts +11 -0
- package/src/core/cells/__tests__/cell.test.ts +39 -0
- package/src/core/cells/__tests__/readonly-code-display.test.ts +46 -0
- package/src/core/cells/cell.ts +5 -3
- package/src/core/cells/readonly-code-display.ts +35 -0
- package/src/core/codemirror/__tests__/setup.test.ts +33 -1
- package/src/core/codemirror/ai/resources.ts +15 -10
- package/src/core/codemirror/cells/__tests__/debugger-decorations.test.ts +185 -0
- package/src/core/codemirror/cells/__tests__/debugger-state.test.ts +139 -0
- package/src/core/codemirror/cells/debugger-decorations.ts +188 -0
- package/src/core/codemirror/cells/debugger-state.ts +100 -0
- package/src/core/codemirror/cells/extensions.ts +23 -4
- package/src/core/codemirror/go-to-definition/__tests__/utils.test.ts +75 -3
- package/src/core/codemirror/go-to-definition/extension.ts +3 -3
- package/src/core/codemirror/go-to-definition/underline.ts +5 -14
- package/src/core/codemirror/go-to-definition/utils.ts +49 -1
- package/src/core/codemirror/utils.ts +15 -0
- package/src/core/config/feature-flag.tsx +2 -0
- package/src/core/edit-app.tsx +8 -5
- package/src/core/errors/errors.ts +2 -1
- package/src/core/islands/bootstrap.ts +1 -0
- package/src/core/islands/bridge.ts +1 -0
- package/src/core/lifecycle/__tests__/render-policy.test.ts +247 -0
- package/src/core/lifecycle/render-policy.ts +125 -0
- package/src/core/mime.ts +11 -4
- package/src/core/network/__tests__/api.test.ts +17 -0
- package/src/core/network/__tests__/requests-toasting.test.tsx +46 -0
- package/src/core/network/api.ts +17 -6
- package/src/core/network/requests-lazy.ts +1 -0
- package/src/core/network/requests-network.ts +8 -0
- package/src/core/network/requests-static.ts +1 -0
- package/src/core/network/requests-toasting.tsx +10 -1
- package/src/core/network/types.ts +2 -0
- package/src/core/run-app.tsx +27 -23
- package/src/core/runtime/__tests__/adapter.test.ts +160 -0
- package/src/core/runtime/adapter.ts +182 -0
- package/src/core/wasm/PyodideLoader.tsx +20 -62
- package/src/core/wasm/bridge.ts +13 -4
- package/src/core/wasm/state.ts +8 -19
- package/src/core/websocket/useMarimoKernelConnection.tsx +17 -0
- package/src/css/app/codemirror.css +14 -0
- package/src/hooks/__tests__/useDelayElapsed.test.tsx +55 -0
- package/src/hooks/useDelayElapsed.ts +27 -0
- package/src/plugins/core/RenderHTML.tsx +35 -1
- package/src/plugins/core/__test__/RenderHTML.test.ts +44 -0
- package/src/plugins/impl/DataTablePlugin.tsx +1 -0
- package/src/plugins/impl/anywidget/AnyWidgetPlugin.tsx +53 -2
- package/src/plugins/impl/anywidget/__tests__/AnyWidgetPlugin.test.tsx +52 -2
- package/src/plugins/impl/data-editor/__tests__/glide-data-editor.test.tsx +187 -0
- package/src/plugins/impl/data-editor/glide-data-editor.tsx +6 -0
- package/src/plugins/impl/data-editor/glide-portal.tsx +73 -0
- package/src/utils/errors.ts +15 -0
- package/dist/data-grid-overlay-editor-mfEJ5475.js +0 -128
- package/src/components/editor/chrome/panels/__tests__/snippet-display.test.ts +0 -22
- package/src/components/editor/chrome/panels/snippet-display.ts +0 -27
- package/src/core/ai/context/providers/__tests__/__snapshots__/error.test.ts.snap +0 -3
- package/src/core/wasm/__tests__/PyodideLoader.test.ts +0 -72
- package/src/core/wasm/__tests__/state.test.ts +0 -124
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
+
import { markdown } from "@codemirror/lang-markdown";
|
|
3
4
|
import { sql } from "@codemirror/lang-sql";
|
|
5
|
+
import {
|
|
6
|
+
defaultHighlightStyle,
|
|
7
|
+
syntaxHighlighting,
|
|
8
|
+
} from "@codemirror/language";
|
|
4
9
|
import CodeMirror, {
|
|
5
10
|
EditorView,
|
|
6
11
|
type ReactCodeMirrorProps,
|
|
7
12
|
} from "@uiw/react-codemirror";
|
|
8
13
|
import { CopyIcon, EyeIcon, EyeOffIcon, PlusIcon } from "lucide-react";
|
|
9
|
-
import { memo, useState } from "react";
|
|
14
|
+
import { memo, useMemo, useState } from "react";
|
|
10
15
|
import { useAddCodeToNewCell } from "@/components/editor/cell/useAddCell";
|
|
11
16
|
import { Button } from "@/components/ui/button";
|
|
12
17
|
import { Tooltip } from "@/components/ui/tooltip";
|
|
13
18
|
import { toast } from "@/components/ui/use-toast";
|
|
19
|
+
import type { LanguageAdapterType } from "@/core/codemirror/language/types";
|
|
14
20
|
import { customPythonLanguageSupport } from "@/core/codemirror/language/languages/python";
|
|
21
|
+
import { darkTheme } from "@/core/codemirror/theme/dark";
|
|
22
|
+
import { lightTheme } from "@/core/codemirror/theme/light";
|
|
15
23
|
import { useTheme } from "@/theme/useTheme";
|
|
16
24
|
import { cn } from "@/utils/cn";
|
|
17
25
|
import { copyToClipboard } from "@/utils/copy";
|
|
@@ -22,6 +30,18 @@ const pythonExtensions = [
|
|
|
22
30
|
EditorView.lineWrapping,
|
|
23
31
|
];
|
|
24
32
|
const sqlExtensions = [sql(), EditorView.lineWrapping];
|
|
33
|
+
const markdownExtensions = [markdown(), EditorView.lineWrapping];
|
|
34
|
+
|
|
35
|
+
function readonlyCodeExtensions(language: LanguageAdapterType) {
|
|
36
|
+
switch (language) {
|
|
37
|
+
case "sql":
|
|
38
|
+
return sqlExtensions;
|
|
39
|
+
case "markdown":
|
|
40
|
+
return markdownExtensions;
|
|
41
|
+
default:
|
|
42
|
+
return pythonExtensions;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
25
45
|
|
|
26
46
|
/**
|
|
27
47
|
* A readonly code component that can be used to display code in a readonly state.
|
|
@@ -42,7 +62,7 @@ export const ReadonlyCode = memo(
|
|
|
42
62
|
showHideCode?: boolean;
|
|
43
63
|
showCopyCode?: boolean;
|
|
44
64
|
insertNewCell?: boolean;
|
|
45
|
-
language?:
|
|
65
|
+
language?: LanguageAdapterType;
|
|
46
66
|
} & ReactCodeMirrorProps,
|
|
47
67
|
) => {
|
|
48
68
|
const { theme } = useTheme();
|
|
@@ -56,35 +76,41 @@ export const ReadonlyCode = memo(
|
|
|
56
76
|
language = "python",
|
|
57
77
|
...rest
|
|
58
78
|
} = props;
|
|
59
|
-
const [hideCode, setHideCode] = useState(initiallyHideCode);
|
|
79
|
+
const [hideCode, setHideCode] = useState(!!initiallyHideCode);
|
|
80
|
+
|
|
81
|
+
const extensions = useMemo(
|
|
82
|
+
() => [
|
|
83
|
+
theme === "dark" ? darkTheme : lightTheme,
|
|
84
|
+
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
|
|
85
|
+
...readonlyCodeExtensions(language),
|
|
86
|
+
],
|
|
87
|
+
[theme, language],
|
|
88
|
+
);
|
|
60
89
|
|
|
61
90
|
return (
|
|
62
91
|
<div
|
|
63
92
|
className={cn(
|
|
64
|
-
"relative hover-actions-parent w-full overflow-hidden",
|
|
93
|
+
"relative hover-actions-parent w-full overflow-hidden pb-1",
|
|
65
94
|
className,
|
|
66
95
|
)}
|
|
67
96
|
>
|
|
68
|
-
{showHideCode && hideCode && (
|
|
69
|
-
<HideCodeButton
|
|
70
|
-
tooltip="Show code"
|
|
71
|
-
onClick={() => setHideCode(false)}
|
|
72
|
-
/>
|
|
73
|
-
)}
|
|
74
97
|
<div className="absolute top-0 right-0 my-1 mx-2 z-10 hover-action flex gap-2">
|
|
75
98
|
{showCopyCode && <CopyButton text={code} />}
|
|
76
99
|
{insertNewCell && <InsertNewCell code={code} />}
|
|
77
|
-
{showHideCode &&
|
|
78
|
-
<
|
|
100
|
+
{showHideCode && (
|
|
101
|
+
<ToggleCodeButton
|
|
102
|
+
hidden={hideCode}
|
|
103
|
+
onClick={() => setHideCode((prev) => !prev)}
|
|
104
|
+
/>
|
|
79
105
|
)}
|
|
80
106
|
</div>
|
|
81
107
|
<CodeMirror
|
|
82
108
|
{...rest}
|
|
83
109
|
className={cn("cm", hideCode && "opacity-20 h-8 overflow-hidden")}
|
|
84
|
-
theme=
|
|
110
|
+
theme="none"
|
|
85
111
|
height="100%"
|
|
86
|
-
editable={
|
|
87
|
-
extensions={
|
|
112
|
+
editable={false}
|
|
113
|
+
extensions={extensions}
|
|
88
114
|
value={code}
|
|
89
115
|
readOnly={true}
|
|
90
116
|
/>
|
|
@@ -102,42 +128,42 @@ const CopyButton = (props: { text: string }) => {
|
|
|
102
128
|
|
|
103
129
|
return (
|
|
104
130
|
<Tooltip content="Copy code" usePortal={false}>
|
|
105
|
-
<Button
|
|
131
|
+
<Button
|
|
132
|
+
onClick={copy}
|
|
133
|
+
size="xs"
|
|
134
|
+
className="py-0"
|
|
135
|
+
variant="secondary"
|
|
136
|
+
aria-label="Copy code"
|
|
137
|
+
>
|
|
106
138
|
<CopyIcon size={14} strokeWidth={1.5} />
|
|
107
139
|
</Button>
|
|
108
140
|
</Tooltip>
|
|
109
141
|
);
|
|
110
142
|
};
|
|
111
143
|
|
|
112
|
-
const
|
|
144
|
+
const ToggleCodeButton = (props: { hidden: boolean; onClick: () => void }) => {
|
|
113
145
|
return (
|
|
114
|
-
<Tooltip
|
|
146
|
+
<Tooltip
|
|
147
|
+
content={props.hidden ? "Show code" : "Hide code"}
|
|
148
|
+
usePortal={false}
|
|
149
|
+
>
|
|
115
150
|
<Button
|
|
116
151
|
onClick={props.onClick}
|
|
152
|
+
aria-label={props.hidden ? "Show code" : "Hide code"}
|
|
117
153
|
size="xs"
|
|
118
154
|
className="py-0"
|
|
119
155
|
variant="secondary"
|
|
120
156
|
>
|
|
121
|
-
|
|
157
|
+
{props.hidden ? (
|
|
158
|
+
<EyeIcon size={14} strokeWidth={1.5} />
|
|
159
|
+
) : (
|
|
160
|
+
<EyeOffIcon size={14} strokeWidth={1.5} />
|
|
161
|
+
)}
|
|
122
162
|
</Button>
|
|
123
163
|
</Tooltip>
|
|
124
164
|
);
|
|
125
165
|
};
|
|
126
166
|
|
|
127
|
-
export const HideCodeButton = (props: {
|
|
128
|
-
tooltip?: string;
|
|
129
|
-
className?: string;
|
|
130
|
-
onClick: () => void;
|
|
131
|
-
}) => {
|
|
132
|
-
return (
|
|
133
|
-
<div className={props.className} onClick={props.onClick}>
|
|
134
|
-
<Tooltip usePortal={false} content={props.tooltip}>
|
|
135
|
-
<EyeIcon className="hover-action w-5 h-5 text-muted-foreground cursor-pointer absolute left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 opacity-80 hover:opacity-100 z-20" />
|
|
136
|
-
</Tooltip>
|
|
137
|
-
</div>
|
|
138
|
-
);
|
|
139
|
-
};
|
|
140
|
-
|
|
141
167
|
const InsertNewCell = (props: { code: string }) => {
|
|
142
168
|
const addCodeToNewCell = useAddCodeToNewCell();
|
|
143
169
|
|
|
@@ -152,6 +178,7 @@ const InsertNewCell = (props: { code: string }) => {
|
|
|
152
178
|
size="xs"
|
|
153
179
|
className="py-0"
|
|
154
180
|
variant="secondary"
|
|
181
|
+
aria-label="Add code to notebook"
|
|
155
182
|
>
|
|
156
183
|
<PlusIcon size={14} strokeWidth={1.5} />
|
|
157
184
|
</Button>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { cellId } from "@/__tests__/branded";
|
|
5
|
+
import { buildFixInChatPrompt } from "../auto-fix";
|
|
6
|
+
|
|
7
|
+
describe("buildFixInChatPrompt", () => {
|
|
8
|
+
it("references the per-cell error context URI", () => {
|
|
9
|
+
expect(buildFixInChatPrompt(cellId("cell-1"))).toBe(
|
|
10
|
+
"@error://cell-1\n\nPlease fix this error.",
|
|
11
|
+
);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("falls back to inline error text when no cell id", () => {
|
|
15
|
+
expect(buildFixInChatPrompt(undefined, "ValueError: boom")).toBe(
|
|
16
|
+
"My code gives the following error. Please fix it:\n\nValueError: boom",
|
|
17
|
+
);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("uses a generic prompt when no cell id and no error text", () => {
|
|
21
|
+
expect(buildFixInChatPrompt(undefined)).toBe("Please fix this error.");
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
3
|
import { useAtomValue, useSetAtom, useStore } from "jotai";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
CheckIcon,
|
|
6
|
+
ChevronDownIcon,
|
|
7
|
+
HatGlasses,
|
|
8
|
+
type LucideIcon,
|
|
9
|
+
SparklesIcon,
|
|
10
|
+
WrenchIcon,
|
|
11
|
+
} from "lucide-react";
|
|
5
12
|
import { Button } from "@/components/ui/button";
|
|
6
13
|
import {
|
|
7
14
|
DropdownMenu,
|
|
@@ -17,8 +24,23 @@ import { aiFeaturesEnabledAtom } from "@/core/config/config";
|
|
|
17
24
|
import { getAutoFixes } from "@/core/errors/errors";
|
|
18
25
|
import type { MarimoError } from "@/core/kernel/messages";
|
|
19
26
|
import { cn } from "@/utils/cn";
|
|
27
|
+
import { useOpenAiAssistant } from "../chrome/wrapper/useOpenAiAssistant";
|
|
20
28
|
import { type FixMode, useFixMode } from "./fix-mode";
|
|
21
29
|
|
|
30
|
+
export function buildFixInChatPrompt(
|
|
31
|
+
cellId: CellId | undefined,
|
|
32
|
+
fallbackErrorText?: string,
|
|
33
|
+
): string {
|
|
34
|
+
if (cellId != null) {
|
|
35
|
+
return `@error://${cellId}\n\nPlease fix this error.`;
|
|
36
|
+
}
|
|
37
|
+
const errorText = fallbackErrorText?.trim();
|
|
38
|
+
if (errorText) {
|
|
39
|
+
return `My code gives the following error. Please fix it:\n\n${errorText}`;
|
|
40
|
+
}
|
|
41
|
+
return "Please fix this error.";
|
|
42
|
+
}
|
|
43
|
+
|
|
22
44
|
export const AutoFixButton = ({
|
|
23
45
|
errors,
|
|
24
46
|
cellId,
|
|
@@ -35,6 +57,7 @@ export const AutoFixButton = ({
|
|
|
35
57
|
getAutoFixes(error, { aiEnabled: aiFeaturesEnabled }),
|
|
36
58
|
);
|
|
37
59
|
const setAiCompletionCell = useSetAtom(aiCompletionCellAtom);
|
|
60
|
+
const openAiAssistant = useOpenAiAssistant();
|
|
38
61
|
|
|
39
62
|
if (autoFixes.length === 0) {
|
|
40
63
|
return null;
|
|
@@ -67,6 +90,12 @@ export const AutoFixButton = ({
|
|
|
67
90
|
editorView?.focus();
|
|
68
91
|
};
|
|
69
92
|
|
|
93
|
+
const openAISidebar = () => {
|
|
94
|
+
openAiAssistant({
|
|
95
|
+
prompt: buildFixInChatPrompt(cellId),
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
|
|
70
99
|
return (
|
|
71
100
|
<div className={cn("my-2", className)}>
|
|
72
101
|
{firstFix.fixType === "ai" ? (
|
|
@@ -74,6 +103,7 @@ export const AutoFixButton = ({
|
|
|
74
103
|
tooltip={firstFix.description}
|
|
75
104
|
openPrompt={() => handleFix(false)}
|
|
76
105
|
applyAutofix={() => handleFix(true)}
|
|
106
|
+
openChat={openAISidebar}
|
|
77
107
|
/>
|
|
78
108
|
) : (
|
|
79
109
|
<Tooltip content={firstFix.description} align="start">
|
|
@@ -92,23 +122,53 @@ export const AutoFixButton = ({
|
|
|
92
122
|
);
|
|
93
123
|
};
|
|
94
124
|
|
|
95
|
-
|
|
96
|
-
|
|
125
|
+
interface FixModeConfig {
|
|
126
|
+
Icon: LucideIcon;
|
|
127
|
+
title: string;
|
|
128
|
+
description: string;
|
|
129
|
+
}
|
|
97
130
|
|
|
98
|
-
const
|
|
99
|
-
|
|
131
|
+
const MODE_CONFIG: Record<FixMode, FixModeConfig> = {
|
|
132
|
+
autofix: {
|
|
133
|
+
Icon: WrenchIcon,
|
|
134
|
+
title: "Inline AI Fix",
|
|
135
|
+
description: "Apply AI fixes inline in the cell",
|
|
136
|
+
},
|
|
137
|
+
chat: {
|
|
138
|
+
Icon: HatGlasses,
|
|
139
|
+
title: "Fix with AI assistant",
|
|
140
|
+
description: "Open the AI sidebar to fix",
|
|
141
|
+
},
|
|
142
|
+
prompt: {
|
|
143
|
+
Icon: SparklesIcon,
|
|
144
|
+
title: "Suggest a prompt",
|
|
145
|
+
description: "Edit the prompt before applying",
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const FIX_MODES: FixMode[] = ["autofix", "prompt", "chat"];
|
|
100
150
|
|
|
101
151
|
export const AIFixButton = ({
|
|
102
152
|
tooltip,
|
|
103
153
|
openPrompt,
|
|
104
154
|
applyAutofix,
|
|
155
|
+
openChat,
|
|
105
156
|
}: {
|
|
106
157
|
tooltip: string;
|
|
107
158
|
openPrompt: () => void;
|
|
108
159
|
applyAutofix: () => void;
|
|
160
|
+
openChat: () => void;
|
|
109
161
|
}) => {
|
|
110
162
|
const { fixMode, setFixMode } = useFixMode();
|
|
111
163
|
|
|
164
|
+
let onAction = openPrompt;
|
|
165
|
+
if (fixMode === "chat") {
|
|
166
|
+
onAction = openChat;
|
|
167
|
+
} else if (fixMode === "autofix") {
|
|
168
|
+
onAction = applyAutofix;
|
|
169
|
+
}
|
|
170
|
+
const { Icon, title } = MODE_CONFIG[fixMode];
|
|
171
|
+
|
|
112
172
|
return (
|
|
113
173
|
<div className="flex">
|
|
114
174
|
<Tooltip content={tooltip} align="start">
|
|
@@ -116,14 +176,10 @@ export const AIFixButton = ({
|
|
|
116
176
|
size="xs"
|
|
117
177
|
variant="outline"
|
|
118
178
|
className="font-normal rounded-r-none border-r-0"
|
|
119
|
-
onClick={
|
|
179
|
+
onClick={onAction}
|
|
120
180
|
>
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
) : (
|
|
124
|
-
<AutofixIcon className="h-3 w-3 mr-2 mb-0.5" />
|
|
125
|
-
)}
|
|
126
|
-
{fixMode === "prompt" ? PromptTitle : AutofixTitle}
|
|
181
|
+
<Icon className="h-3 w-3 mr-2 mb-0.5" />
|
|
182
|
+
{title}
|
|
127
183
|
</Button>
|
|
128
184
|
</Tooltip>
|
|
129
185
|
<DropdownMenu>
|
|
@@ -138,40 +194,38 @@ export const AIFixButton = ({
|
|
|
138
194
|
</Button>
|
|
139
195
|
</DropdownMenuTrigger>
|
|
140
196
|
<DropdownMenuContent align="end" className="w-56">
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
197
|
+
{FIX_MODES.map((mode) => (
|
|
198
|
+
<DropdownMenuItem
|
|
199
|
+
key={mode}
|
|
200
|
+
className="flex items-center gap-2"
|
|
201
|
+
onClick={() => setFixMode(mode)}
|
|
202
|
+
>
|
|
203
|
+
<AiModeItem mode={mode} selected={mode === fixMode} />
|
|
204
|
+
</DropdownMenuItem>
|
|
205
|
+
))}
|
|
149
206
|
</DropdownMenuContent>
|
|
150
207
|
</DropdownMenu>
|
|
151
208
|
</div>
|
|
152
209
|
);
|
|
153
210
|
};
|
|
154
211
|
|
|
155
|
-
const AiModeItem = ({
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const
|
|
163
|
-
const description =
|
|
164
|
-
mode === "prompt"
|
|
165
|
-
? "Edit the prompt before applying"
|
|
166
|
-
: "Apply AI fixes automatically";
|
|
212
|
+
const AiModeItem = ({
|
|
213
|
+
mode,
|
|
214
|
+
selected,
|
|
215
|
+
}: {
|
|
216
|
+
mode: FixMode;
|
|
217
|
+
selected: boolean;
|
|
218
|
+
}) => {
|
|
219
|
+
const { Icon, title, description } = MODE_CONFIG[mode];
|
|
167
220
|
|
|
168
221
|
return (
|
|
169
|
-
<div className="flex items-center gap-2">
|
|
170
|
-
|
|
222
|
+
<div className="flex items-center gap-2 w-full">
|
|
223
|
+
<Icon className="h-4 w-4 shrink-0" />
|
|
171
224
|
<div className="flex flex-col">
|
|
172
225
|
<span className="font-medium">{title}</span>
|
|
173
226
|
<span className="text-xs text-muted-foreground">{description}</span>
|
|
174
227
|
</div>
|
|
228
|
+
{selected && <CheckIcon className="h-4 w-4 ml-auto shrink-0" />}
|
|
175
229
|
</div>
|
|
176
230
|
);
|
|
177
231
|
};
|
|
@@ -4,7 +4,7 @@ import { useAtom } from "jotai";
|
|
|
4
4
|
import { atomWithStorage } from "jotai/utils";
|
|
5
5
|
import { jotaiJsonStorage } from "@/utils/storage/jotai";
|
|
6
6
|
|
|
7
|
-
export type FixMode = "prompt" | "autofix";
|
|
7
|
+
export type FixMode = "prompt" | "autofix" | "chat";
|
|
8
8
|
|
|
9
9
|
const BASE_KEY = "marimo:ai-autofix-mode";
|
|
10
10
|
|
|
@@ -29,6 +29,7 @@ import { outputIsLoading, outputIsStale } from "@/core/cells/cell";
|
|
|
29
29
|
import { isOutputEmpty } from "@/core/cells/outputs";
|
|
30
30
|
import { useIsPendingCut } from "@/core/cells/pending-cut-service";
|
|
31
31
|
import { autocompletionKeymap } from "@/core/codemirror/cm";
|
|
32
|
+
import { clearCellBreakpoints } from "@/core/codemirror/cells/debugger-state";
|
|
32
33
|
import type { LanguageAdapterType } from "@/core/codemirror/language/types";
|
|
33
34
|
import { CSSClasses } from "@/core/constants";
|
|
34
35
|
import { canCollapseOutline } from "@/core/dom/outline";
|
|
@@ -746,6 +747,9 @@ const EditableCellComponent = ({
|
|
|
746
747
|
onRefactorWithAI={handleRefactorWithAI}
|
|
747
748
|
onClear={() => {
|
|
748
749
|
actions.clearCellConsoleOutput({ cellId });
|
|
750
|
+
// The debugger "Clear" (trashcan) also drops this cell's
|
|
751
|
+
// breakpoints; no-op when none are set.
|
|
752
|
+
clearCellBreakpoints(cellId);
|
|
749
753
|
}}
|
|
750
754
|
onSubmitDebugger={(text, index) => {
|
|
751
755
|
actions.setStdinResponse({
|
|
@@ -1211,6 +1215,9 @@ const SetupCellComponent = ({
|
|
|
1211
1215
|
onRefactorWithAI={handleRefactorWithAI}
|
|
1212
1216
|
onClear={() => {
|
|
1213
1217
|
actions.clearCellConsoleOutput({ cellId });
|
|
1218
|
+
// The debugger "Clear" (trashcan) also drops this cell's
|
|
1219
|
+
// breakpoints; no-op when none are set.
|
|
1220
|
+
clearCellBreakpoints(cellId);
|
|
1214
1221
|
}}
|
|
1215
1222
|
onSubmitDebugger={(text, index) => {
|
|
1216
1223
|
actions.setStdinResponse({
|
|
@@ -24,8 +24,10 @@ import { Tooltip } from "@/components/ui/tooltip";
|
|
|
24
24
|
import { getCellEditorView } from "@/core/cells/cells";
|
|
25
25
|
import type { CellId } from "@/core/cells/ids";
|
|
26
26
|
import { SCRATCH_CELL_ID } from "@/core/cells/ids";
|
|
27
|
+
import { toggleBreakpoint } from "@/core/codemirror/cells/debugger-state";
|
|
27
28
|
import { insertDebuggerAtLine } from "@/core/codemirror/editing/debugging";
|
|
28
29
|
import { aiFeaturesEnabledAtom } from "@/core/config/config";
|
|
30
|
+
import { getFeatureFlag } from "@/core/config/feature-flag";
|
|
29
31
|
import { getRequestClient } from "@/core/network/requests";
|
|
30
32
|
import { isStaticNotebook } from "@/core/static/static-state";
|
|
31
33
|
import { isWasm } from "@/core/wasm/utils";
|
|
@@ -41,7 +43,8 @@ import {
|
|
|
41
43
|
extractAllTracebackInfo,
|
|
42
44
|
getTracebackInfo,
|
|
43
45
|
} from "@/utils/traceback";
|
|
44
|
-
import {
|
|
46
|
+
import { useOpenAiAssistant } from "../chrome/wrapper/useOpenAiAssistant";
|
|
47
|
+
import { AIFixButton, buildFixInChatPrompt } from "../errors/auto-fix";
|
|
45
48
|
import { MangledSegments } from "../errors/mangled-local-chip";
|
|
46
49
|
import { CellLinkTraceback } from "../links/cell-link";
|
|
47
50
|
import type { OnRefactorWithAI } from "../Output";
|
|
@@ -71,6 +74,7 @@ export const MarimoTracebackOutput = ({
|
|
|
71
74
|
|
|
72
75
|
const lastTracebackLine = lastLine(traceback);
|
|
73
76
|
const aiFeaturesEnabled = useAtomValue(aiFeaturesEnabledAtom);
|
|
77
|
+
const openAiAssistant = useOpenAiAssistant();
|
|
74
78
|
|
|
75
79
|
// Get last traceback info
|
|
76
80
|
const tracebackInfo = extractAllTracebackInfo(traceback)?.at(0);
|
|
@@ -97,6 +101,12 @@ export const MarimoTracebackOutput = ({
|
|
|
97
101
|
});
|
|
98
102
|
};
|
|
99
103
|
|
|
104
|
+
const openAISidebar = () => {
|
|
105
|
+
openAiAssistant({
|
|
106
|
+
prompt: buildFixInChatPrompt(cellId, lastTracebackLine),
|
|
107
|
+
});
|
|
108
|
+
};
|
|
109
|
+
|
|
100
110
|
return (
|
|
101
111
|
<div className="flex flex-col gap-2 min-w-full w-fit">
|
|
102
112
|
<button
|
|
@@ -126,6 +136,7 @@ export const MarimoTracebackOutput = ({
|
|
|
126
136
|
tooltip="Fix with AI"
|
|
127
137
|
openPrompt={() => handleRefactorWithAI(false)}
|
|
128
138
|
applyAutofix={() => handleRefactorWithAI(true)}
|
|
139
|
+
openChat={openAISidebar}
|
|
129
140
|
/>
|
|
130
141
|
)}
|
|
131
142
|
{showDebugger && (
|
|
@@ -203,7 +214,14 @@ function lastLine(text: string): string {
|
|
|
203
214
|
export const replaceTracebackFilenames = (domNode: DOMNode) => {
|
|
204
215
|
const info = getTracebackInfo(domNode);
|
|
205
216
|
if (info?.kind === "cell") {
|
|
206
|
-
|
|
217
|
+
// With the live debugger enabled, toggle a real gutter breakpoint
|
|
218
|
+
// instead of mutating the user's code with a `breakpoint()` statement.
|
|
219
|
+
const debuggerEnabled = getFeatureFlag("debugger");
|
|
220
|
+
const tooltipContent = debuggerEnabled ? (
|
|
221
|
+
<ToggleBreakpointContent />
|
|
222
|
+
) : (
|
|
223
|
+
<InsertBreakpointContent />
|
|
224
|
+
);
|
|
207
225
|
return (
|
|
208
226
|
<span className="nb">
|
|
209
227
|
<span className="inline-flex items-center">
|
|
@@ -219,6 +237,10 @@ export const replaceTracebackFilenames = (domNode: DOMNode) => {
|
|
|
219
237
|
>
|
|
220
238
|
<BugPlayIcon
|
|
221
239
|
onClick={() => {
|
|
240
|
+
if (debuggerEnabled && !isStaticNotebook()) {
|
|
241
|
+
toggleBreakpoint(info.cellId, info.lineNumber);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
222
244
|
const view = getCellEditorView(info.cellId);
|
|
223
245
|
if (view) {
|
|
224
246
|
insertDebuggerAtLine(view, info.lineNumber);
|
|
@@ -285,3 +307,7 @@ const InsertBreakpointContent = () => {
|
|
|
285
307
|
</>
|
|
286
308
|
);
|
|
287
309
|
};
|
|
310
|
+
|
|
311
|
+
const ToggleBreakpointContent = () => {
|
|
312
|
+
return <>Toggle a breakpoint at this line</>;
|
|
313
|
+
};
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
3
|
import { render } from "@testing-library/react";
|
|
4
|
+
import { Provider } from "jotai";
|
|
4
5
|
import { beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
|
|
6
|
+
import { MockRequestClient } from "@/__mocks__/requests";
|
|
5
7
|
import { Tracebacks } from "@/__mocks__/tracebacks";
|
|
6
8
|
import { cellId } from "@/__tests__/branded";
|
|
7
9
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
8
10
|
import { initialModeAtom } from "@/core/mode";
|
|
11
|
+
import { requestClientAtom } from "@/core/network/requests";
|
|
9
12
|
import { store } from "@/core/state/jotai";
|
|
10
13
|
import { renderHTML } from "@/plugins/core/RenderHTML";
|
|
11
14
|
import {
|
|
@@ -20,13 +23,16 @@ describe("traceback component", () => {
|
|
|
20
23
|
beforeEach(() => {
|
|
21
24
|
vi.resetAllMocks();
|
|
22
25
|
store.set(initialModeAtom, "edit");
|
|
26
|
+
store.set(requestClientAtom, MockRequestClient.create());
|
|
23
27
|
});
|
|
24
28
|
|
|
25
29
|
test("extracts cell-link", () => {
|
|
26
30
|
const traceback = (
|
|
27
|
-
<
|
|
28
|
-
<
|
|
29
|
-
|
|
31
|
+
<Provider store={store}>
|
|
32
|
+
<TooltipProvider>
|
|
33
|
+
<MarimoTracebackOutput traceback={Tracebacks.raw} cellId={cid} />
|
|
34
|
+
</TooltipProvider>
|
|
35
|
+
</Provider>
|
|
30
36
|
);
|
|
31
37
|
const { unmount, getAllByRole } = render(traceback);
|
|
32
38
|
|
|
@@ -44,9 +50,11 @@ describe("traceback component", () => {
|
|
|
44
50
|
|
|
45
51
|
test("renames File to Cell for relevant lines", () => {
|
|
46
52
|
const traceback = (
|
|
47
|
-
<
|
|
48
|
-
<
|
|
49
|
-
|
|
53
|
+
<Provider store={store}>
|
|
54
|
+
<TooltipProvider>
|
|
55
|
+
<MarimoTracebackOutput traceback={Tracebacks.raw} cellId={cid} />
|
|
56
|
+
</TooltipProvider>
|
|
57
|
+
</Provider>
|
|
50
58
|
);
|
|
51
59
|
const { unmount, container } = render(traceback);
|
|
52
60
|
|
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
import { Label } from "@/components/ui/label";
|
|
36
36
|
import { NumberField } from "@/components/ui/number-field";
|
|
37
37
|
import { Switch } from "@/components/ui/switch";
|
|
38
|
-
import { outputIsLoading } from "@/core/cells/cell";
|
|
38
|
+
import { outputIsLoading, outputIsStale } from "@/core/cells/cell";
|
|
39
39
|
import type { CellId } from "@/core/cells/ids";
|
|
40
40
|
import type { AppMode } from "@/core/mode";
|
|
41
41
|
import { useIsDragging } from "@/hooks/useIsDragging";
|
|
@@ -222,6 +222,7 @@ export const GridLayoutRenderer: React.FC<Props> = ({
|
|
|
222
222
|
cellId={cell.id}
|
|
223
223
|
output={cell.output}
|
|
224
224
|
status={cell.status}
|
|
225
|
+
stale={outputIsStale(cell, false)}
|
|
225
226
|
isScrollable={isScrollable}
|
|
226
227
|
side={side}
|
|
227
228
|
hidden={cell.errored || cell.interrupted || cell.stopped}
|
|
@@ -288,6 +289,7 @@ export const GridLayoutRenderer: React.FC<Props> = ({
|
|
|
288
289
|
cellId={cell.id}
|
|
289
290
|
output={cell.output}
|
|
290
291
|
status={cell.status}
|
|
292
|
+
stale={outputIsStale(cell, false)}
|
|
291
293
|
isScrollable={false}
|
|
292
294
|
hidden={false}
|
|
293
295
|
/>
|
|
@@ -358,6 +360,7 @@ export const GridLayoutRenderer: React.FC<Props> = ({
|
|
|
358
360
|
output={cell.output}
|
|
359
361
|
isScrollable={false}
|
|
360
362
|
status={cell.status}
|
|
363
|
+
stale={outputIsStale(cell, false)}
|
|
361
364
|
hidden={false}
|
|
362
365
|
/>
|
|
363
366
|
</div>
|
|
@@ -376,6 +379,7 @@ interface GridCellProps extends Pick<CellRuntimeState, "output" | "status"> {
|
|
|
376
379
|
hidden: boolean;
|
|
377
380
|
isScrollable: boolean;
|
|
378
381
|
side?: GridLayoutCellSide;
|
|
382
|
+
stale: boolean;
|
|
379
383
|
}
|
|
380
384
|
|
|
381
385
|
const GridCell = memo(
|
|
@@ -389,6 +393,7 @@ const GridCell = memo(
|
|
|
389
393
|
isScrollable,
|
|
390
394
|
side,
|
|
391
395
|
className,
|
|
396
|
+
stale,
|
|
392
397
|
}: GridCellProps) => {
|
|
393
398
|
const loading = outputIsLoading(status);
|
|
394
399
|
|
|
@@ -415,7 +420,7 @@ const GridCell = memo(
|
|
|
415
420
|
allowExpand={false}
|
|
416
421
|
output={output}
|
|
417
422
|
cellId={cellId}
|
|
418
|
-
stale={
|
|
423
|
+
stale={stale}
|
|
419
424
|
loading={loading}
|
|
420
425
|
/>
|
|
421
426
|
</div>
|
|
@@ -29,6 +29,7 @@ import { outputIsLoading, outputIsStale } from "@/core/cells/cell";
|
|
|
29
29
|
import type { CellId } from "@/core/cells/ids";
|
|
30
30
|
import { isOutputEmpty } from "@/core/cells/outputs";
|
|
31
31
|
import type { CellData, CellRuntimeState } from "@/core/cells/types";
|
|
32
|
+
import { getReadonlyCodeDisplay } from "@/core/cells/readonly-code-display";
|
|
32
33
|
import { MarkdownLanguageAdapter } from "@/core/codemirror/language/languages/markdown";
|
|
33
34
|
import { useResolvedMarimoConfig } from "@/core/config/config";
|
|
34
35
|
import { CSSClasses, KnownQueryParams } from "@/core/constants";
|
|
@@ -384,6 +385,8 @@ const VerticalCell = memo(
|
|
|
384
385
|
|
|
385
386
|
// Hide the code if it's pure markdown and there's an output, or if the code is empty
|
|
386
387
|
const hideCode = shouldHideCode(code, output);
|
|
388
|
+
// Only unwrap SQL when the code will actually be rendered.
|
|
389
|
+
const display = hideCode ? null : getReadonlyCodeDisplay(code);
|
|
387
390
|
|
|
388
391
|
return (
|
|
389
392
|
<div
|
|
@@ -393,11 +396,12 @@ const VerticalCell = memo(
|
|
|
393
396
|
{...cellDomProps(cellId, name)}
|
|
394
397
|
>
|
|
395
398
|
{cellOutputArea === "above" && outputArea}
|
|
396
|
-
{
|
|
399
|
+
{display && (
|
|
397
400
|
<div className="tray">
|
|
398
401
|
<ReadonlyCode
|
|
399
|
-
initiallyHideCode={config.hide_code
|
|
400
|
-
code={code}
|
|
402
|
+
initiallyHideCode={config.hide_code}
|
|
403
|
+
code={display.code}
|
|
404
|
+
language={display.language}
|
|
401
405
|
/>
|
|
402
406
|
</div>
|
|
403
407
|
)}
|