@marimo-team/islands 0.23.9-dev9 → 0.23.10-dev0
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-OzrfMM5L.js → ConnectedDataExplorerComponent-CyV83R2m.js} +4 -4
- package/dist/assets/__vite-browser-external-Ci2ZQfXU.js +1 -0
- package/dist/assets/{worker-CpBbwbQo.js → worker-ip3AI_sN.js} +2 -2
- package/dist/{chat-ui-BDI3FMI8.js → chat-ui-ChD4VvCo.js} +3060 -3033
- package/dist/{code-visibility-DgHF4q8X.js → code-visibility-CjGICDxg.js} +1368 -1204
- package/dist/{formats-DQ5qjo_Q.js → formats-DHxc-FdY.js} +1 -1
- package/dist/{glide-data-editor-DqRY9naW.js → glide-data-editor-BOmK9ETQ.js} +2 -2
- package/dist/{html-to-image-CiSinpSR.js → html-to-image-BHv7CEU_.js} +2145 -2153
- package/dist/{input-CZD2z6X2.js → input-_2sjvfne.js} +1 -1
- package/dist/main.js +680 -705
- package/dist/{mermaid-IU93XzmY.js → mermaid-lXOw5Py9.js} +2 -2
- package/dist/{process-output-5qJjMRKh.js → process-output-BvySRgli.js} +33 -25
- package/dist/{reveal-component-qpHJES_u.js → reveal-component-DVWED--8.js} +312 -291
- package/dist/{spec-a6DaqW__.js → spec-B96zNUEA.js} +1 -1
- package/dist/style.css +1 -1
- package/dist/{toDate-ZVVIBmdk.js → toDate-x-WRDCH7.js} +1 -1
- package/dist/{useAsyncData-C008zUPi.js → useAsyncData-iRgKDT5s.js} +1 -1
- package/dist/{useDeepCompareMemoize-BrA3_n61.js → useDeepCompareMemoize-CkQ57VS2.js} +1 -1
- package/dist/{useLifecycle-BNaoJ5a4.js → useLifecycle-BBO9PIph.js} +1 -1
- package/dist/{useTheme-7O0YWlE5.js → useTheme-DHIrRQOe.js} +34 -21
- package/dist/{vega-component-DJNmOdUj.js → vega-component-Dq-SH463.js} +5 -5
- package/package.json +1 -1
- package/src/components/ai/__tests__/ai-utils.test.ts +43 -38
- package/src/components/ai/ai-model-dropdown.tsx +2 -2
- package/src/components/app-config/ai-config.tsx +147 -16
- package/src/components/app-config/user-config-form.tsx +37 -1
- package/src/components/chat/__tests__/chat-utils.test.ts +269 -0
- package/src/components/chat/chat-panel.tsx +38 -5
- package/src/components/chat/chat-utils.ts +14 -58
- package/src/components/data-table/TableBottomBar.tsx +5 -8
- package/src/components/data-table/__tests__/column-explorer.test.tsx +128 -0
- package/src/components/data-table/__tests__/header-items.test.tsx +220 -10
- package/src/components/data-table/column-explorer-panel/column-explorer.tsx +95 -29
- package/src/components/data-table/column-header.tsx +17 -12
- package/src/components/data-table/data-table.tsx +4 -0
- package/src/components/data-table/export-actions.tsx +19 -12
- package/src/components/data-table/header-items.tsx +40 -16
- package/src/components/data-table/hooks/use-column-visibility.ts +14 -0
- package/src/components/data-table/schemas.ts +2 -2
- package/src/components/data-table/table-explorer-panel/table-explorer-panel.tsx +16 -6
- package/src/components/databases/display.tsx +2 -0
- package/src/components/datasources/__tests__/utils.test.ts +82 -0
- package/src/components/datasources/utils.ts +16 -15
- package/src/components/editor/Disconnected.tsx +1 -60
- package/src/components/editor/__tests__/viewer-banner.test.tsx +89 -0
- package/src/components/editor/actions/pair-with-agent-modal.tsx +1 -0
- package/src/components/editor/actions/useCellActionButton.tsx +3 -3
- package/src/components/editor/actions/useNotebookActions.tsx +5 -2
- package/src/components/editor/cell/code/cell-editor.tsx +25 -5
- package/src/components/editor/chrome/types.ts +13 -6
- package/src/components/editor/chrome/wrapper/app-chrome.tsx +6 -4
- package/src/components/editor/chrome/wrapper/footer-items/ai-status.tsx +10 -1
- package/src/components/editor/chrome/wrapper/sidebar.tsx +7 -5
- package/src/components/editor/errors/auto-fix.tsx +3 -3
- package/src/components/editor/header/__tests__/status.test.tsx +0 -15
- package/src/components/editor/header/app-header.tsx +1 -4
- package/src/components/editor/header/status.tsx +4 -13
- package/src/components/editor/navigation/__tests__/navigation.test.ts +15 -0
- package/src/components/editor/navigation/navigation.ts +5 -0
- package/src/components/editor/output/MarimoErrorOutput.tsx +103 -25
- package/src/components/editor/output/MarimoTracebackOutput.tsx +28 -39
- package/src/components/editor/renderers/cell-array.tsx +27 -24
- package/src/components/editor/renderers/slides-layout/__tests__/compute-slide-cells.test.ts +30 -17
- package/src/components/editor/renderers/slides-layout/compute-slide-cells.ts +17 -8
- package/src/components/editor/renderers/slides-layout/slides-layout.tsx +10 -12
- package/src/components/editor/viewer-banner.tsx +82 -0
- package/src/components/slides/minimap.tsx +45 -9
- package/src/components/slides/reveal-component.tsx +82 -37
- package/src/components/slides/slide-cell-view.tsx +12 -1
- package/src/components/slides/slide-form.tsx +11 -3
- package/src/components/static-html/static-banner.tsx +28 -22
- package/src/core/ai/__tests__/model-registry.test.ts +72 -60
- package/src/core/ai/model-registry.ts +33 -28
- package/src/core/cells/__tests__/actions.test.ts +48 -0
- package/src/core/cells/actions.ts +5 -6
- package/src/core/codemirror/__tests__/setup.test.ts +29 -0
- package/src/core/codemirror/cells/traceback-decorations.ts +1 -1
- package/src/core/codemirror/cm.ts +50 -3
- package/src/core/codemirror/completion/hints.ts +4 -1
- package/src/core/codemirror/format.ts +1 -0
- package/src/core/codemirror/keymaps/vim.ts +63 -0
- package/src/core/codemirror/language/languages/sql/sql.ts +1 -0
- package/src/core/codemirror/language/languages/sql/utils.ts +2 -0
- package/src/core/config/__tests__/config-schema.test.ts +4 -0
- package/src/core/config/config-schema.ts +4 -0
- package/src/core/config/config.ts +16 -0
- package/src/core/edit-app.tsx +3 -0
- package/src/core/islands/bootstrap.ts +2 -0
- package/src/core/kernel/__tests__/handlers.test.ts +5 -0
- package/src/core/websocket/__tests__/useMarimoKernelConnection.test.ts +0 -13
- package/src/core/websocket/types.ts +0 -6
- package/src/core/websocket/useMarimoKernelConnection.tsx +3 -12
- package/src/css/app/Cell.css +0 -1
- package/src/plugins/impl/DataTablePlugin.tsx +48 -22
- package/src/plugins/impl/chat/ChatPlugin.tsx +7 -1
- package/src/plugins/impl/chat/__tests__/chat-ui.test.ts +278 -0
- package/src/plugins/impl/chat/chat-ui.tsx +106 -59
- package/src/plugins/impl/chat/types.ts +5 -0
- package/src/utils/__tests__/json-parser.test.ts +1 -69
- package/src/utils/json/json-parser.ts +0 -30
- package/dist/assets/__vite-browser-external-CAdMKBac.js +0 -1
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
connectedDocAtom,
|
|
27
27
|
realTimeCollaboration,
|
|
28
28
|
} from "@/core/codemirror/rtc/extension";
|
|
29
|
-
import { autoInstantiateAtom,
|
|
29
|
+
import { autoInstantiateAtom, isAiFeatureEnabled } from "@/core/config/config";
|
|
30
30
|
import type { UserConfig } from "@/core/config/config-schema";
|
|
31
31
|
import { OverridingHotkeyProvider } from "@/core/hotkeys/hotkeys";
|
|
32
32
|
import { connectionAtom } from "@/core/network/connection";
|
|
@@ -65,6 +65,11 @@ export interface CellEditorProps
|
|
|
65
65
|
hasOutput?: boolean;
|
|
66
66
|
languageAdapter: LanguageAdapterType | undefined;
|
|
67
67
|
showLanguageToggles?: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Override for the inline "Edit with AI" tooltip. Defaults to the user's
|
|
70
|
+
* `ai.inline_tooltip` config. Set to `false` to force-disable it.
|
|
71
|
+
*/
|
|
72
|
+
inlineAiTooltip?: boolean;
|
|
68
73
|
setLanguageAdapter: React.Dispatch<
|
|
69
74
|
React.SetStateAction<LanguageAdapterType | undefined>
|
|
70
75
|
>;
|
|
@@ -73,6 +78,12 @@ export interface CellEditorProps
|
|
|
73
78
|
editorViewParentRef?: React.RefObject<HTMLDivElement | null>;
|
|
74
79
|
showHiddenCode: (opts?: { focus?: boolean }) => void;
|
|
75
80
|
outputArea?: "above" | "below";
|
|
81
|
+
/**
|
|
82
|
+
* CSS selector for the element that editor tooltips (completions, hover,
|
|
83
|
+
* signature help) are appended to. Useful for fullscreen/dialog containers;
|
|
84
|
+
* defaults to `#App`.
|
|
85
|
+
*/
|
|
86
|
+
tooltipParentSelector?: string;
|
|
76
87
|
}
|
|
77
88
|
|
|
78
89
|
const CellEditorInternal = ({
|
|
@@ -94,7 +105,9 @@ const CellEditorInternal = ({
|
|
|
94
105
|
languageAdapter,
|
|
95
106
|
setLanguageAdapter,
|
|
96
107
|
showLanguageToggles = true,
|
|
108
|
+
inlineAiTooltip,
|
|
97
109
|
outputArea,
|
|
110
|
+
tooltipParentSelector,
|
|
98
111
|
}: CellEditorProps) => {
|
|
99
112
|
const [aiCompletionCell, setAiCompletionCell] = useAtom(aiCompletionCellAtom);
|
|
100
113
|
const deleteCell = useDeleteCellCallback();
|
|
@@ -173,13 +186,13 @@ const CellEditorInternal = ({
|
|
|
173
186
|
});
|
|
174
187
|
});
|
|
175
188
|
|
|
176
|
-
const
|
|
189
|
+
const aiFeaturesEnabled = isAiFeatureEnabled(userConfig);
|
|
177
190
|
|
|
178
191
|
const extensions = useMemo(() => {
|
|
179
192
|
const extensions = setupCodeMirror({
|
|
180
193
|
cellId,
|
|
181
194
|
showPlaceholder,
|
|
182
|
-
enableAI:
|
|
195
|
+
enableAI: aiFeaturesEnabled,
|
|
183
196
|
cellActions: {
|
|
184
197
|
...cellActions,
|
|
185
198
|
afterToggleMarkdown,
|
|
@@ -201,6 +214,9 @@ const CellEditorInternal = ({
|
|
|
201
214
|
splitCell,
|
|
202
215
|
toggleHideCode,
|
|
203
216
|
aiCellCompletion: () => {
|
|
217
|
+
if (!aiFeaturesEnabled) {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
204
220
|
let closed = false;
|
|
205
221
|
setAiCompletionCell((v) => {
|
|
206
222
|
// Toggle close
|
|
@@ -221,7 +237,9 @@ const CellEditorInternal = ({
|
|
|
221
237
|
hotkeys: new OverridingHotkeyProvider(userConfig.keymap.overrides ?? {}),
|
|
222
238
|
diagnosticsConfig: userConfig.diagnostics,
|
|
223
239
|
displayConfig: userConfig.display,
|
|
224
|
-
inlineAiTooltip:
|
|
240
|
+
inlineAiTooltip:
|
|
241
|
+
inlineAiTooltip ?? userConfig.ai?.inline_tooltip ?? false,
|
|
242
|
+
tooltipParentSelector,
|
|
225
243
|
});
|
|
226
244
|
|
|
227
245
|
extensions.push(
|
|
@@ -271,7 +289,9 @@ const CellEditorInternal = ({
|
|
|
271
289
|
userConfig.display,
|
|
272
290
|
userConfig.diagnostics,
|
|
273
291
|
userConfig.ai?.inline_tooltip,
|
|
274
|
-
|
|
292
|
+
inlineAiTooltip,
|
|
293
|
+
tooltipParentSelector,
|
|
294
|
+
aiFeaturesEnabled,
|
|
275
295
|
theme,
|
|
276
296
|
showPlaceholder,
|
|
277
297
|
cellActions,
|
|
@@ -201,16 +201,23 @@ export const PANEL_MAP = new Map<PanelType, PanelDescriptor>(
|
|
|
201
201
|
);
|
|
202
202
|
|
|
203
203
|
/**
|
|
204
|
-
* Check if a panel should be hidden based on its
|
|
205
|
-
* and `requiredCapability`.
|
|
204
|
+
* Check if a panel should be hidden based on its descriptor and runtime state.
|
|
206
205
|
*/
|
|
207
|
-
export function isPanelHidden(
|
|
208
|
-
panel
|
|
209
|
-
capabilities
|
|
210
|
-
|
|
206
|
+
export function isPanelHidden({
|
|
207
|
+
panel,
|
|
208
|
+
capabilities,
|
|
209
|
+
aiEnabled,
|
|
210
|
+
}: {
|
|
211
|
+
panel: PanelDescriptor;
|
|
212
|
+
capabilities: Capabilities;
|
|
213
|
+
aiEnabled: boolean;
|
|
214
|
+
}): boolean {
|
|
211
215
|
if (panel.hidden) {
|
|
212
216
|
return true;
|
|
213
217
|
}
|
|
218
|
+
if (panel.type === "ai" && !aiEnabled) {
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
214
221
|
if (panel.requiredCapability && !capabilities[panel.requiredCapability]) {
|
|
215
222
|
return true;
|
|
216
223
|
}
|
|
@@ -27,6 +27,7 @@ import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
|
27
27
|
import { LazyActivity } from "@/components/utils/lazy-mount";
|
|
28
28
|
import { cellErrorCount } from "@/core/cells/cells";
|
|
29
29
|
import { capabilitiesAtom } from "@/core/config/capabilities";
|
|
30
|
+
import { aiEnabledAtom } from "@/core/config/config";
|
|
30
31
|
import { getFeatureFlag } from "@/core/config/feature-flag";
|
|
31
32
|
import { cn } from "@/utils/cn";
|
|
32
33
|
import { ErrorBoundary } from "../../boundary/ErrorBoundary";
|
|
@@ -93,18 +94,19 @@ export const AppChrome: React.FC<PropsWithChildren> = ({ children }) => {
|
|
|
93
94
|
const [panelLayout, setPanelLayout] = useAtom(panelLayoutAtom);
|
|
94
95
|
// Subscribe to capabilities to re-render when they change (e.g., terminal capability)
|
|
95
96
|
const capabilities = useAtomValue(capabilitiesAtom);
|
|
97
|
+
const aiEnabled = useAtomValue(aiEnabledAtom);
|
|
96
98
|
|
|
97
99
|
// Convert current developer panel items to PanelDescriptors
|
|
98
100
|
// Filter out hidden panels (e.g., terminal when capability is not available)
|
|
99
101
|
const devPanelItems = useMemo(() => {
|
|
100
102
|
return panelLayout.developerPanel.flatMap((id) => {
|
|
101
103
|
const panel = PANEL_MAP.get(id);
|
|
102
|
-
if (!panel || isPanelHidden(panel, capabilities)) {
|
|
104
|
+
if (!panel || isPanelHidden({ panel, capabilities, aiEnabled })) {
|
|
103
105
|
return [];
|
|
104
106
|
}
|
|
105
107
|
return [panel];
|
|
106
108
|
});
|
|
107
|
-
}, [panelLayout.developerPanel, capabilities]);
|
|
109
|
+
}, [panelLayout.developerPanel, capabilities, aiEnabled]);
|
|
108
110
|
|
|
109
111
|
const handleSetDevPanelItems = (items: PanelDescriptor[]) => {
|
|
110
112
|
setPanelLayout((prev) => ({
|
|
@@ -141,7 +143,7 @@ export const AppChrome: React.FC<PropsWithChildren> = ({ children }) => {
|
|
|
141
143
|
const availableDevPanels = useMemo(() => {
|
|
142
144
|
const sidebarIds = new Set(panelLayout.sidebar);
|
|
143
145
|
return PANELS.filter((p) => {
|
|
144
|
-
if (isPanelHidden(p, capabilities)) {
|
|
146
|
+
if (isPanelHidden({ panel: p, capabilities, aiEnabled })) {
|
|
145
147
|
return false;
|
|
146
148
|
}
|
|
147
149
|
// Exclude panels that are in the sidebar
|
|
@@ -150,7 +152,7 @@ export const AppChrome: React.FC<PropsWithChildren> = ({ children }) => {
|
|
|
150
152
|
}
|
|
151
153
|
return true;
|
|
152
154
|
});
|
|
153
|
-
}, [panelLayout.sidebar, capabilities]);
|
|
155
|
+
}, [panelLayout.sidebar, capabilities, aiEnabled]);
|
|
154
156
|
|
|
155
157
|
const emitResizeEvent = useEvent(() => {
|
|
156
158
|
// HACK: Unfortunately, we have to do this twice to make sure the
|
|
@@ -4,18 +4,27 @@ import { useAtomValue } from "jotai";
|
|
|
4
4
|
import { SparklesIcon } from "lucide-react";
|
|
5
5
|
import React from "react";
|
|
6
6
|
import { useOpenSettingsToTab } from "@/components/app-config/state";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
aiAtom,
|
|
9
|
+
aiEnabledAtom,
|
|
10
|
+
aiModelConfiguredAtom,
|
|
11
|
+
} from "@/core/config/config";
|
|
8
12
|
import { DEFAULT_AI_MODEL } from "@/core/config/config-schema";
|
|
9
13
|
import { FooterItem } from "../footer-item";
|
|
10
14
|
|
|
11
15
|
export const AIStatusIcon: React.FC = () => {
|
|
12
16
|
const ai = useAtomValue(aiAtom);
|
|
13
17
|
const aiEnabled = useAtomValue(aiEnabledAtom);
|
|
18
|
+
const aiModelConfigured = useAtomValue(aiModelConfiguredAtom);
|
|
14
19
|
const chatModel = ai?.models?.chat_model || DEFAULT_AI_MODEL;
|
|
15
20
|
const editModel = ai?.models?.edit_model || chatModel;
|
|
16
21
|
const { handleClick } = useOpenSettingsToTab();
|
|
17
22
|
|
|
18
23
|
if (!aiEnabled) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!aiModelConfigured) {
|
|
19
28
|
return (
|
|
20
29
|
<FooterItem
|
|
21
30
|
tooltip="Assist is disabled"
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
notebookQueuedOrRunningCountAtom,
|
|
13
13
|
} from "@/core/cells/cells";
|
|
14
14
|
import { capabilitiesAtom } from "@/core/config/capabilities";
|
|
15
|
+
import { aiEnabledAtom } from "@/core/config/config";
|
|
15
16
|
import { cn } from "@/utils/cn";
|
|
16
17
|
import { FeedbackButton } from "../components/feedback-button";
|
|
17
18
|
import { panelLayoutAtom, useChromeActions, useChromeState } from "../state";
|
|
@@ -30,6 +31,7 @@ export const Sidebar: React.FC = () => {
|
|
|
30
31
|
const [panelLayout, setPanelLayout] = useAtom(panelLayoutAtom);
|
|
31
32
|
// Subscribe to capabilities to re-render when they change
|
|
32
33
|
const capabilities = useAtomValue(capabilitiesAtom);
|
|
34
|
+
const aiEnabled = useAtomValue(aiEnabledAtom);
|
|
33
35
|
|
|
34
36
|
const renderIcon = ({ Icon }: PanelDescriptor, className?: string) => {
|
|
35
37
|
return <Icon className={cn("h-5 w-5", className)} />;
|
|
@@ -40,7 +42,7 @@ export const Sidebar: React.FC = () => {
|
|
|
40
42
|
const availableSidebarPanels = useMemo(() => {
|
|
41
43
|
const devPanelIds = new Set(panelLayout.developerPanel);
|
|
42
44
|
return PANELS.filter((p) => {
|
|
43
|
-
if (isPanelHidden(p, capabilities)) {
|
|
45
|
+
if (isPanelHidden({ panel: p, capabilities, aiEnabled })) {
|
|
44
46
|
return false;
|
|
45
47
|
}
|
|
46
48
|
// Exclude panels that are in the developer panel
|
|
@@ -49,19 +51,19 @@ export const Sidebar: React.FC = () => {
|
|
|
49
51
|
}
|
|
50
52
|
return true;
|
|
51
53
|
});
|
|
52
|
-
}, [panelLayout.developerPanel, capabilities]);
|
|
54
|
+
}, [panelLayout.developerPanel, capabilities, aiEnabled]);
|
|
53
55
|
|
|
54
56
|
// Convert current sidebar items to PanelDescriptors
|
|
55
57
|
// Filter out hidden panels (e.g., when capability is not available)
|
|
56
58
|
const sidebarItems = useMemo(() => {
|
|
57
59
|
return panelLayout.sidebar.flatMap((id) => {
|
|
58
60
|
const panel = PANEL_MAP.get(id);
|
|
59
|
-
if (!panel || isPanelHidden(panel, capabilities)) {
|
|
61
|
+
if (!panel || isPanelHidden({ panel, capabilities, aiEnabled })) {
|
|
60
62
|
return [];
|
|
61
63
|
}
|
|
62
64
|
return [panel];
|
|
63
65
|
});
|
|
64
|
-
}, [panelLayout.sidebar, capabilities]);
|
|
66
|
+
}, [panelLayout.sidebar, capabilities, aiEnabled]);
|
|
65
67
|
|
|
66
68
|
const handleSetSidebarItems = (items: PanelDescriptor[]) => {
|
|
67
69
|
setPanelLayout((prev) => ({
|
|
@@ -218,7 +220,7 @@ const SidebarItem: React.FC<
|
|
|
218
220
|
// Render as div when not clickable (e.g., inside ReorderableList)
|
|
219
221
|
// This avoids nested interactive elements which break react-aria's drag behavior
|
|
220
222
|
const content = onClick ? (
|
|
221
|
-
<button className={itemClassName} onClick={onClick}>
|
|
223
|
+
<button type="button" className={itemClassName} onClick={onClick}>
|
|
222
224
|
{children}
|
|
223
225
|
</button>
|
|
224
226
|
) : (
|
|
@@ -13,7 +13,7 @@ import { Tooltip } from "@/components/ui/tooltip";
|
|
|
13
13
|
import { aiCompletionCellAtom } from "@/core/ai/state";
|
|
14
14
|
import { notebookAtom, useCellActions } from "@/core/cells/cells";
|
|
15
15
|
import type { CellId } from "@/core/cells/ids";
|
|
16
|
-
import {
|
|
16
|
+
import { aiFeaturesEnabledAtom } from "@/core/config/config";
|
|
17
17
|
import { getAutoFixes } from "@/core/errors/errors";
|
|
18
18
|
import type { MarimoError } from "@/core/kernel/messages";
|
|
19
19
|
import { cn } from "@/utils/cn";
|
|
@@ -30,9 +30,9 @@ export const AutoFixButton = ({
|
|
|
30
30
|
}) => {
|
|
31
31
|
const store = useStore();
|
|
32
32
|
const { createNewCell } = useCellActions();
|
|
33
|
-
const
|
|
33
|
+
const aiFeaturesEnabled = useAtomValue(aiFeaturesEnabledAtom);
|
|
34
34
|
const autoFixes = errors.flatMap((error) =>
|
|
35
|
-
getAutoFixes(error, { aiEnabled }),
|
|
35
|
+
getAutoFixes(error, { aiEnabled: aiFeaturesEnabled }),
|
|
36
36
|
);
|
|
37
37
|
const setAiCompletionCell = useSetAtom(aiCompletionCellAtom);
|
|
38
38
|
|
|
@@ -90,19 +90,4 @@ describe("StatusOverlay disconnect indicator", () => {
|
|
|
90
90
|
expect(onReconnect).not.toHaveBeenCalled();
|
|
91
91
|
},
|
|
92
92
|
);
|
|
93
|
-
|
|
94
|
-
it("does not render the disconnect icon when another tab has taken over", () => {
|
|
95
|
-
const onReconnect = vi.fn();
|
|
96
|
-
const { queryByTestId } = renderOverlay(
|
|
97
|
-
{
|
|
98
|
-
state: WebSocketState.CLOSED,
|
|
99
|
-
code: WebSocketClosedReason.ALREADY_RUNNING,
|
|
100
|
-
reason: "another browser tab is already connected to the kernel",
|
|
101
|
-
canTakeover: true,
|
|
102
|
-
},
|
|
103
|
-
onReconnect,
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
expect(queryByTestId("disconnected-indicator")).toBeNull();
|
|
107
|
-
});
|
|
108
93
|
});
|
|
@@ -18,10 +18,7 @@ export const AppHeader: React.FC<PropsWithChildren<Props>> = ({
|
|
|
18
18
|
<div className={className}>
|
|
19
19
|
{children}
|
|
20
20
|
{connection.state === WebSocketState.CLOSED && (
|
|
21
|
-
<Disconnected
|
|
22
|
-
reason={connection.reason}
|
|
23
|
-
canTakeover={connection.canTakeover}
|
|
24
|
-
/>
|
|
21
|
+
<Disconnected reason={connection.reason} />
|
|
25
22
|
)}
|
|
26
23
|
</div>
|
|
27
24
|
);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
3
|
import { useAtomValue } from "jotai";
|
|
4
|
-
import { HourglassIcon,
|
|
4
|
+
import { HourglassIcon, UnlinkIcon } from "lucide-react";
|
|
5
5
|
import React from "react";
|
|
6
6
|
import { Tooltip } from "@/components/ui/tooltip";
|
|
7
7
|
import { notebookScrollToRunning } from "@/core/cells/actions";
|
|
@@ -24,13 +24,13 @@ export const StatusOverlay: React.FC<{
|
|
|
24
24
|
const isOpen = connection.state === WebSocketState.OPEN;
|
|
25
25
|
// Only KERNEL_DISCONNECTED is recoverable by a retry. Other terminal
|
|
26
26
|
// reasons (MALFORMED_QUERY, KERNEL_STARTUP_ERROR) would deterministically
|
|
27
|
-
// fail the same way
|
|
27
|
+
// fail the same way.
|
|
28
28
|
const canReconnect =
|
|
29
29
|
isClosed && connection.code === WebSocketClosedReason.KERNEL_DISCONNECTED;
|
|
30
30
|
|
|
31
31
|
return (
|
|
32
32
|
<>
|
|
33
|
-
{isClosed &&
|
|
33
|
+
{isClosed && <NoiseBackground />}
|
|
34
34
|
<div
|
|
35
35
|
className={cn(
|
|
36
36
|
"z-50 top-4 left-4",
|
|
@@ -38,12 +38,11 @@ export const StatusOverlay: React.FC<{
|
|
|
38
38
|
)}
|
|
39
39
|
>
|
|
40
40
|
{isOpen && isRunning && <RunningIcon />}
|
|
41
|
-
{isClosed &&
|
|
41
|
+
{isClosed && (
|
|
42
42
|
<DisconnectedIcon
|
|
43
43
|
onReconnect={canReconnect ? onReconnect : undefined}
|
|
44
44
|
/>
|
|
45
45
|
)}
|
|
46
|
-
{isClosed && connection.canTakeover && <LockedIcon />}
|
|
47
46
|
</div>
|
|
48
47
|
</>
|
|
49
48
|
);
|
|
@@ -79,14 +78,6 @@ const DisconnectedIcon: React.FC<{ onReconnect?: () => void }> = ({
|
|
|
79
78
|
);
|
|
80
79
|
};
|
|
81
80
|
|
|
82
|
-
const LockedIcon = () => (
|
|
83
|
-
<Tooltip content="Notebook locked">
|
|
84
|
-
<div className={topLeftStatus}>
|
|
85
|
-
<LockIcon className="w-[25px] h-[25px] text-(--blue-11)" />
|
|
86
|
-
</div>
|
|
87
|
-
</Tooltip>
|
|
88
|
-
);
|
|
89
|
-
|
|
90
81
|
const RunningIcon = () => {
|
|
91
82
|
const scratchpadOnly = useAtomValue(onlyScratchpadIsRunningAtom);
|
|
92
83
|
const tooltip = scratchpadOnly
|
|
@@ -1207,6 +1207,21 @@ describe("useCellNavigationProps", () => {
|
|
|
1207
1207
|
});
|
|
1208
1208
|
|
|
1209
1209
|
describe("AI completion functionality", () => {
|
|
1210
|
+
beforeEach(() => {
|
|
1211
|
+
const config = defaultUserConfig();
|
|
1212
|
+
store.set(userConfigAtom, {
|
|
1213
|
+
...config,
|
|
1214
|
+
ai: {
|
|
1215
|
+
...config.ai,
|
|
1216
|
+
models: {
|
|
1217
|
+
displayed_models: [],
|
|
1218
|
+
custom_models: [],
|
|
1219
|
+
edit_model: "openai/gpt-4o",
|
|
1220
|
+
},
|
|
1221
|
+
},
|
|
1222
|
+
});
|
|
1223
|
+
});
|
|
1224
|
+
|
|
1210
1225
|
it("should toggle AI completion when shortcut is pressed", () => {
|
|
1211
1226
|
const { result } = renderWithProvider(() =>
|
|
1212
1227
|
useCellNavigationProps(cellId1, optionsWithMockEditor),
|
|
@@ -24,6 +24,7 @@ import { usePendingDeleteService } from "@/core/cells/pending-delete-service";
|
|
|
24
24
|
import { scrollCellIntoView } from "@/core/cells/scrollCellIntoView";
|
|
25
25
|
import {
|
|
26
26
|
hotkeysAtom,
|
|
27
|
+
isAiFeatureEnabled,
|
|
27
28
|
keymapPresetAtom,
|
|
28
29
|
userConfigAtom,
|
|
29
30
|
} from "@/core/config/config";
|
|
@@ -195,6 +196,7 @@ export function useCellNavigationProps(
|
|
|
195
196
|
const pendingDeleteService = usePendingDeleteService();
|
|
196
197
|
const deleteCells = useDeleteManyCellsCallback();
|
|
197
198
|
const userConfig = useAtomValue(userConfigAtom);
|
|
199
|
+
const aiFeaturesEnabled = isAiFeatureEnabled(userConfig);
|
|
198
200
|
|
|
199
201
|
// Wrap selection actions to clear pending cells on any selection change
|
|
200
202
|
const selectionActions = {
|
|
@@ -496,6 +498,9 @@ export function useCellNavigationProps(
|
|
|
496
498
|
return true;
|
|
497
499
|
}),
|
|
498
500
|
"cell.aiCompletion": (cellId) => {
|
|
501
|
+
if (!aiFeaturesEnabled) {
|
|
502
|
+
return false;
|
|
503
|
+
}
|
|
499
504
|
let closed = false;
|
|
500
505
|
setAiCompletionCell((v) => {
|
|
501
506
|
// Toggle close
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
+
import { useAtomValue } from "jotai";
|
|
3
4
|
import {
|
|
5
|
+
ChevronDown,
|
|
6
|
+
ChevronRight,
|
|
7
|
+
InfoIcon,
|
|
4
8
|
NotebookPenIcon,
|
|
5
9
|
PackageIcon,
|
|
6
10
|
SquareArrowOutUpRightIcon,
|
|
7
11
|
TerminalIcon,
|
|
8
12
|
} from "lucide-react";
|
|
9
|
-
import { Fragment, type JSX } from "react";
|
|
13
|
+
import { Fragment, type JSX, useState } from "react";
|
|
10
14
|
import {
|
|
11
15
|
Accordion,
|
|
12
16
|
AccordionContent,
|
|
@@ -16,7 +20,9 @@ import {
|
|
|
16
20
|
import { Button } from "@/components/ui/button";
|
|
17
21
|
import { Kbd } from "@/components/ui/kbd";
|
|
18
22
|
import { ExternalLink } from "@/components/ui/links";
|
|
23
|
+
import { Tooltip } from "@/components/ui/tooltip";
|
|
19
24
|
import type { CellId } from "@/core/cells/ids";
|
|
25
|
+
import { resolvedMarimoConfigAtom } from "@/core/config/config";
|
|
20
26
|
import { renderHTML } from "@/plugins/core/RenderHTML";
|
|
21
27
|
import { splitMangledLocals } from "@/utils/local-variables";
|
|
22
28
|
import type { MarimoError } from "../../../core/kernel/messages";
|
|
@@ -29,6 +35,39 @@ import { MangledSegments } from "../errors/mangled-local-chip";
|
|
|
29
35
|
import { CellLinkError } from "../links/cell-link";
|
|
30
36
|
import { processTextForUrls } from "./console/text-rendering";
|
|
31
37
|
|
|
38
|
+
const CollapsibleTraceback = ({
|
|
39
|
+
traceback,
|
|
40
|
+
}: {
|
|
41
|
+
traceback: string;
|
|
42
|
+
}): JSX.Element => {
|
|
43
|
+
const [isOpen, setIsOpen] = useState(true);
|
|
44
|
+
return (
|
|
45
|
+
<div className="mt-2">
|
|
46
|
+
<button
|
|
47
|
+
type="button"
|
|
48
|
+
onClick={() => setIsOpen(!isOpen)}
|
|
49
|
+
aria-expanded={isOpen}
|
|
50
|
+
aria-label={isOpen ? "Collapse traceback" : "Expand traceback"}
|
|
51
|
+
className="flex items-center gap-1 text-muted-foreground/70 hover:text-muted-foreground transition-colors"
|
|
52
|
+
>
|
|
53
|
+
{isOpen ? (
|
|
54
|
+
<ChevronDown className="h-3 w-3" />
|
|
55
|
+
) : (
|
|
56
|
+
<ChevronRight className="h-3 w-3" />
|
|
57
|
+
)}
|
|
58
|
+
<span className="text-[0.6875rem] uppercase tracking-wider">
|
|
59
|
+
Traceback
|
|
60
|
+
</span>
|
|
61
|
+
</button>
|
|
62
|
+
{isOpen && (
|
|
63
|
+
<div className="font-code text-sm mt-1 p-3 bg-muted rounded border overflow-auto max-h-[50vh] cursor-text select-text">
|
|
64
|
+
{renderHTML({ html: traceback })}
|
|
65
|
+
</div>
|
|
66
|
+
)}
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
69
|
+
};
|
|
70
|
+
|
|
32
71
|
const Tip = (props: {
|
|
33
72
|
title?: string;
|
|
34
73
|
className?: string;
|
|
@@ -37,10 +76,13 @@ const Tip = (props: {
|
|
|
37
76
|
return (
|
|
38
77
|
<Accordion type="single" collapsible={true} className={props.className}>
|
|
39
78
|
<AccordionItem value="item-1" className="text-muted-foreground">
|
|
40
|
-
<AccordionTrigger className="pt-2 pb-
|
|
79
|
+
<AccordionTrigger className="pt-2 pb-0 font-normal">
|
|
41
80
|
{props.title ?? "Tip"}
|
|
42
81
|
</AccordionTrigger>
|
|
43
|
-
<AccordionContent
|
|
82
|
+
<AccordionContent
|
|
83
|
+
className="mr-24 text-[0.84375rem]"
|
|
84
|
+
wrapperClassName="pt-0 pb-2"
|
|
85
|
+
>
|
|
44
86
|
{props.children}
|
|
45
87
|
</AccordionContent>
|
|
46
88
|
</AccordionItem>
|
|
@@ -63,10 +105,21 @@ export const MarimoErrorOutput = ({
|
|
|
63
105
|
className,
|
|
64
106
|
}: Props): JSX.Element => {
|
|
65
107
|
const chromeActions = useChromeActions();
|
|
66
|
-
|
|
67
|
-
|
|
108
|
+
// The console area (where tracebacks are shown) sits below the cell editor.
|
|
109
|
+
// When cell outputs are also displayed below the editor, the traceback is
|
|
110
|
+
// already adjacent, so the "See the console area" hint is redundant. It is
|
|
111
|
+
// only helpful when outputs are displayed above the editor.
|
|
112
|
+
const showConsoleHint =
|
|
113
|
+
useAtomValue(resolvedMarimoConfigAtom).display.cell_output === "above";
|
|
114
|
+
|
|
115
|
+
let titleContents: string | null =
|
|
116
|
+
"This cell wasn't run because it has errors";
|
|
68
117
|
let alertVariant: "destructive" | "default" = "destructive";
|
|
69
118
|
let titleColor = "text-error";
|
|
119
|
+
// A small muted overline shown above the title for static errors, where
|
|
120
|
+
// the cell genuinely never executes. These errors have no separate title:
|
|
121
|
+
// the body text already describes them, so the overline carries the status.
|
|
122
|
+
let statusOverline: string | null = null;
|
|
70
123
|
const liStyle = "my-0.5 ml-8 text-muted-foreground/40";
|
|
71
124
|
|
|
72
125
|
// Check for certain error types to adjust title and appearance
|
|
@@ -77,7 +130,6 @@ export const MarimoErrorOutput = ({
|
|
|
77
130
|
} else if (errors.some((e) => e.type === "ancestor-prevented")) {
|
|
78
131
|
titleContents = "Ancestor prevented from running";
|
|
79
132
|
alertVariant = "default";
|
|
80
|
-
titleColor = "text-muted-foreground";
|
|
81
133
|
titleColor = "text-secondary-foreground";
|
|
82
134
|
} else if (errors.some((e) => e.type === "ancestor-stopped")) {
|
|
83
135
|
titleContents = "Ancestor stopped";
|
|
@@ -85,6 +137,19 @@ export const MarimoErrorOutput = ({
|
|
|
85
137
|
titleColor = "text-secondary-foreground";
|
|
86
138
|
} else if (errors.some((e) => e.type === "sql-error")) {
|
|
87
139
|
titleContents = "SQL error";
|
|
140
|
+
} else if (
|
|
141
|
+
errors.some(
|
|
142
|
+
(e) =>
|
|
143
|
+
e.type === "multiple-defs" ||
|
|
144
|
+
e.type === "cycle" ||
|
|
145
|
+
e.type === "setup-refs" ||
|
|
146
|
+
e.type === "import-star",
|
|
147
|
+
)
|
|
148
|
+
) {
|
|
149
|
+
// Static errors: the body text describes the problem, so there is no
|
|
150
|
+
// separate title — only the status overline.
|
|
151
|
+
titleContents = null;
|
|
152
|
+
statusOverline = "Cell not run";
|
|
88
153
|
} else {
|
|
89
154
|
// Check for exception type
|
|
90
155
|
const exceptionError = errors.find((e) => e.type === "exception");
|
|
@@ -475,9 +540,11 @@ export const MarimoErrorOutput = ({
|
|
|
475
540
|
</ExternalLink>
|
|
476
541
|
).
|
|
477
542
|
</p>
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
543
|
+
{showConsoleHint && (
|
|
544
|
+
<div className="text-muted-foreground mt-2">
|
|
545
|
+
See the console area for a traceback.
|
|
546
|
+
</div>
|
|
547
|
+
)}
|
|
481
548
|
</div>
|
|
482
549
|
</li>
|
|
483
550
|
);
|
|
@@ -492,13 +559,13 @@ export const MarimoErrorOutput = ({
|
|
|
492
559
|
{processTextForUrls(error.msg, `exception-${idx}`)}
|
|
493
560
|
</p>
|
|
494
561
|
{"traceback" in error && error.traceback ? (
|
|
495
|
-
<
|
|
496
|
-
{renderHTML({ html: error.traceback })}
|
|
497
|
-
</div>
|
|
562
|
+
<CollapsibleTraceback traceback={error.traceback} />
|
|
498
563
|
) : (
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
564
|
+
showConsoleHint && (
|
|
565
|
+
<div className="text-muted-foreground mt-2">
|
|
566
|
+
See the console area for a traceback.
|
|
567
|
+
</div>
|
|
568
|
+
)
|
|
502
569
|
)}
|
|
503
570
|
</div>
|
|
504
571
|
) : (
|
|
@@ -506,9 +573,7 @@ export const MarimoErrorOutput = ({
|
|
|
506
573
|
{processTextForUrls(error.msg, `exception-${idx}`)}
|
|
507
574
|
<CellLinkError cellId={error.raising_cell} />
|
|
508
575
|
{"traceback" in error && error.traceback && (
|
|
509
|
-
<
|
|
510
|
-
{renderHTML({ html: error.traceback })}
|
|
511
|
-
</div>
|
|
576
|
+
<CollapsibleTraceback traceback={error.traceback} />
|
|
512
577
|
)}
|
|
513
578
|
</div>
|
|
514
579
|
)}
|
|
@@ -633,23 +698,36 @@ export const MarimoErrorOutput = ({
|
|
|
633
698
|
};
|
|
634
699
|
|
|
635
700
|
const title = (
|
|
636
|
-
<
|
|
637
|
-
{
|
|
638
|
-
|
|
701
|
+
<div className="space-y-0.5">
|
|
702
|
+
{statusOverline && (
|
|
703
|
+
<div className="flex items-center gap-1 font-code text-[0.6875rem] uppercase tracking-wider text-muted-foreground/70">
|
|
704
|
+
{statusOverline}
|
|
705
|
+
<Tooltip
|
|
706
|
+
content="marimo didn't run this cell because it detected an error."
|
|
707
|
+
delayDuration={200}
|
|
708
|
+
>
|
|
709
|
+
<InfoIcon className="size-3 cursor-help" />
|
|
710
|
+
</Tooltip>
|
|
711
|
+
</div>
|
|
712
|
+
)}
|
|
713
|
+
{titleContents && (
|
|
714
|
+
<AlertTitle className={`font-code font-medium ${titleColor}`}>
|
|
715
|
+
{titleContents}
|
|
716
|
+
</AlertTitle>
|
|
717
|
+
)}
|
|
718
|
+
</div>
|
|
639
719
|
);
|
|
640
720
|
|
|
641
721
|
return (
|
|
642
722
|
<Alert
|
|
643
723
|
variant={alertVariant}
|
|
644
724
|
className={cn(
|
|
645
|
-
"border-none font-code text-sm text-[0.84375rem]
|
|
725
|
+
"border-none font-code text-sm text-[0.84375rem] p-0 text-muted-foreground normal [&:has(svg)]:pl-0 space-y-2",
|
|
646
726
|
className,
|
|
647
727
|
)}
|
|
648
728
|
>
|
|
649
729
|
{title}
|
|
650
|
-
<div>
|
|
651
|
-
<div className="flex flex-col gap-8">{renderMessages()}</div>
|
|
652
|
-
</div>
|
|
730
|
+
<div className="flex flex-col gap-4">{renderMessages()}</div>
|
|
653
731
|
</Alert>
|
|
654
732
|
);
|
|
655
733
|
};
|