@marimo-team/islands 0.23.14-dev6 → 0.23.14-dev61
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-CU4fZJzG.js} +4 -4
- package/dist/assets/__vite-browser-external-BQCLNwri.js +1 -0
- package/dist/assets/{worker-DEDLIQQV.js → worker-DAWRHcPq.js} +2 -2
- package/dist/{chat-ui-BZxLHwyD.js → chat-ui-DinOvbWu.js} +3248 -3044
- package/dist/{click-outside-container-BDd67_1U.js → click-outside-container-BLPjMamz.js} +141 -113
- package/dist/{code-visibility-C90Am97q.js → code-visibility-EvXXzjfW.js} +1169 -960
- 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-Dzx4J_z1.js} +1 -1
- package/dist/{glide-data-editor-DkzAInWG.js → glide-data-editor-CjTu7ukN.js} +2084 -1889
- package/dist/{html-to-image-CGp_08St.js → html-to-image-_wGfk8V-.js} +2389 -2322
- package/dist/{input-CbEz_aj_.js → input-DtsN7xm-.js} +1 -1
- package/dist/main.js +3630 -1801
- package/dist/{mermaid-CJW9vIyO.js → mermaid-BYqXy_NE.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-Mh4UrjwM.js} +1 -1
- package/dist/{reveal-component-C_u9FY4_.js → reveal-component-CKfV5wlk.js} +600 -596
- package/dist/{spec-Bv-XlYiv.js → spec-Cz-Bj1JI.js} +1 -1
- package/dist/style.css +1 -1
- package/dist/{toDate-D-l5s8nn.js → toDate-CWNNlFEX.js} +15 -7
- package/dist/{useAsyncData-1Dhzjfwf.js → useAsyncData-KfHB8wQR.js} +1 -1
- package/dist/{useDeepCompareMemoize-CDWT3BDz.js → useDeepCompareMemoize-nJMtxhm4.js} +1 -1
- package/dist/{useLifecycle-AHlswLw-.js → useLifecycle-DegSo0lV.js} +1 -1
- package/dist/{useTheme-BrYvK-_A.js → useTheme-6eZ3GOTS.js} +31 -26
- package/dist/{vega-component-Pk6lyc_a.js → vega-component-DzyyM9fc.js} +5 -5
- package/package.json +4 -4
- package/src/__mocks__/requests.ts +1 -0
- package/src/__tests__/CellStatus.test.tsx +1 -2
- package/src/components/app-config/user-config-form.tsx +52 -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/CellStatus.tsx +1 -20
- package/src/components/editor/cell/PendingDeleteConfirmation.tsx +1 -1
- 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/ImageOutput.tsx +20 -6
- package/src/components/editor/output/MarimoTracebackOutput.tsx +28 -2
- package/src/components/editor/output/__tests__/ImageOutput.test.tsx +53 -0
- 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/components/tracing/tracing.tsx +2 -1
- 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 +224 -0
- package/src/core/codemirror/cells/__tests__/line-timing-decorations.test.ts +126 -0
- package/src/core/codemirror/cells/debugger-decorations.ts +188 -0
- package/src/core/codemirror/cells/debugger-state.ts +133 -0
- package/src/core/codemirror/cells/extensions.ts +32 -4
- package/src/core/codemirror/cells/line-timing-decorations.ts +165 -0
- 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/__tests__/config-schema.test.ts +17 -0
- package/src/core/config/config-schema.ts +1 -0
- package/src/core/config/config.ts +10 -0
- package/src/core/config/feature-flag.tsx +4 -0
- package/src/core/edit-app.tsx +8 -5
- package/src/core/errors/errors.ts +2 -1
- package/src/core/islands/bootstrap.ts +4 -3
- 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/__tests__/runtime.test.ts +45 -0
- package/src/core/runtime/adapter.ts +182 -0
- package/src/core/runtime/runtime.ts +30 -16
- 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/__tests__/useMarimoKernelConnection.test.ts +4 -0
- package/src/core/websocket/__tests__/useWebSocket.test.ts +49 -0
- package/src/core/websocket/transports/__tests__/sse.test.ts +338 -0
- package/src/core/websocket/transports/sse.ts +308 -0
- package/src/core/websocket/useMarimoKernelConnection.tsx +58 -7
- package/src/core/websocket/useWebSocket.tsx +12 -2
- 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/mount.tsx +3 -3
- 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 +33 -254
- package/src/plugins/impl/anywidget/__tests__/AnyWidgetPlugin.test.tsx +116 -174
- package/src/plugins/impl/anywidget/__tests__/host.test.ts +313 -0
- package/src/plugins/impl/anywidget/__tests__/model-proxy.test.ts +158 -0
- package/src/plugins/impl/anywidget/__tests__/model.test.ts +8 -177
- package/src/plugins/impl/anywidget/__tests__/registry.test.ts +708 -0
- package/src/plugins/impl/anywidget/__tests__/resolve-widget.test.ts +132 -0
- package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +305 -133
- package/src/plugins/impl/anywidget/__tests__/widget-ref.test.ts +28 -0
- package/src/plugins/impl/anywidget/host.ts +54 -0
- package/src/plugins/impl/anywidget/model-proxy.ts +70 -0
- package/src/plugins/impl/anywidget/model.ts +59 -239
- package/src/plugins/impl/anywidget/registry.ts +268 -0
- package/src/plugins/impl/anywidget/resolve-widget.ts +138 -0
- package/src/plugins/impl/anywidget/runtime.ts +422 -0
- package/src/plugins/impl/anywidget/types.ts +14 -0
- package/src/plugins/impl/anywidget/widget-binding.ts +280 -119
- package/src/plugins/impl/anywidget/widget-ref.ts +29 -0
- 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/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +3 -2
- package/src/plugins/impl/mpl-interactive/__tests__/MplInteractivePlugin.test.tsx +16 -13
- package/src/plugins/impl/mpl-interactive/mpl-websocket-shim.ts +1 -1
- package/src/utils/errors.ts +15 -0
- package/src/utils/time.ts +20 -0
- package/dist/assets/__vite-browser-external-DAm_YW43.js +0 -1
- 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
- package/src/plugins/impl/anywidget/schemas.ts +0 -32
|
@@ -1,26 +1,133 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
3
|
import type { Completion } from "@codemirror/autocomplete";
|
|
4
|
-
import {
|
|
4
|
+
import { notebookAtom } from "@/core/cells/cells";
|
|
5
5
|
import type { CellId } from "@/core/cells/ids";
|
|
6
|
-
import
|
|
6
|
+
import { displayCellName } from "@/core/cells/names";
|
|
7
|
+
import type { MarimoError, OutputMessage } from "@/core/kernel/messages";
|
|
8
|
+
import { isErrorMime, isMarimoErrorsMime, isTracebackMime } from "@/core/mime";
|
|
7
9
|
import type { JotaiStore } from "@/core/state/jotai";
|
|
8
10
|
import { logNever } from "@/utils/assertNever";
|
|
11
|
+
import { parseHtmlContent } from "@/utils/dom";
|
|
9
12
|
import { PluralWord } from "@/utils/pluralize";
|
|
10
13
|
import { type AIContextItem, AIContextProvider } from "../registry";
|
|
11
14
|
import { contextToXml } from "../utils";
|
|
12
15
|
import { Sections } from "./common";
|
|
16
|
+
import { formatDatasourceContextForCell } from "./datasource";
|
|
17
|
+
|
|
18
|
+
export interface CellErrorEntry {
|
|
19
|
+
cellId: CellId;
|
|
20
|
+
cellName: string;
|
|
21
|
+
cellCode: string;
|
|
22
|
+
errorData: MarimoError[];
|
|
23
|
+
tracebackHtml?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type ErrorContextItemData =
|
|
27
|
+
| {
|
|
28
|
+
type: "all-errors";
|
|
29
|
+
errors: CellErrorEntry[];
|
|
30
|
+
}
|
|
31
|
+
| {
|
|
32
|
+
type: "cell-error";
|
|
33
|
+
error: CellErrorEntry;
|
|
34
|
+
};
|
|
13
35
|
|
|
14
36
|
export interface ErrorContextItem extends AIContextItem {
|
|
15
37
|
type: "error";
|
|
16
|
-
data:
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
38
|
+
data: ErrorContextItemData;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function parseCellErrorOutput(
|
|
42
|
+
output: OutputMessage,
|
|
43
|
+
): Pick<CellErrorEntry, "errorData" | "tracebackHtml"> | null {
|
|
44
|
+
if (isMarimoErrorsMime(output.mimetype)) {
|
|
45
|
+
if (!Array.isArray(output.data)) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
const errorData = output.data.filter(
|
|
49
|
+
(error) => !error.type.includes("ancestor"),
|
|
50
|
+
);
|
|
51
|
+
if (errorData.length === 0) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
return { errorData };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (isTracebackMime(output.mimetype)) {
|
|
58
|
+
if (typeof output.data !== "string" || output.data.length === 0) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
return { errorData: [], tracebackHtml: output.data };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function errorDataHasTraceback(errorData: MarimoError[]): boolean {
|
|
68
|
+
return errorData.some((error) => "traceback" in error && error.traceback);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function getTracebackFromConsole(
|
|
72
|
+
consoleOutputs: OutputMessage[] | undefined,
|
|
73
|
+
): string | undefined {
|
|
74
|
+
const tracebackOutput = consoleOutputs?.find((output) =>
|
|
75
|
+
isTracebackMime(output.mimetype),
|
|
76
|
+
);
|
|
77
|
+
if (
|
|
78
|
+
!tracebackOutput ||
|
|
79
|
+
typeof tracebackOutput.data !== "string" ||
|
|
80
|
+
tracebackOutput.data.length === 0
|
|
81
|
+
) {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
return tracebackOutput.data;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function resolveTracebackHtml(
|
|
88
|
+
parsed: Pick<CellErrorEntry, "errorData" | "tracebackHtml">,
|
|
89
|
+
consoleOutputs: OutputMessage[] | undefined,
|
|
90
|
+
): string | undefined {
|
|
91
|
+
if (parsed.tracebackHtml) {
|
|
92
|
+
return parsed.tracebackHtml;
|
|
93
|
+
}
|
|
94
|
+
if (errorDataHasTraceback(parsed.errorData)) {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
return getTracebackFromConsole(consoleOutputs);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function getCellErrorEntries(store: JotaiStore): CellErrorEntry[] {
|
|
101
|
+
const { cellIds, cellRuntime, cellData } = store.get(notebookAtom);
|
|
102
|
+
const entries: CellErrorEntry[] = [];
|
|
103
|
+
|
|
104
|
+
for (const [cellIndex, cellId] of cellIds.inOrderIds.entries()) {
|
|
105
|
+
const cell = cellRuntime[cellId];
|
|
106
|
+
const output = cell.output;
|
|
107
|
+
if (!output || !isErrorMime(output.mimetype)) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const parsed = parseCellErrorOutput(output);
|
|
112
|
+
if (!parsed) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const cellName = displayCellName(cellData[cellId].name, cellIndex);
|
|
117
|
+
// Prefer the code from the last execution: runtime errors correspond to
|
|
118
|
+
// the previous run, while `code` may already contain unsaved edits.
|
|
119
|
+
const cellCode = cellData[cellId].lastCodeRun ?? cellData[cellId].code;
|
|
120
|
+
|
|
121
|
+
entries.push({
|
|
122
|
+
cellId,
|
|
123
|
+
cellName,
|
|
124
|
+
cellCode,
|
|
125
|
+
errorData: parsed.errorData,
|
|
126
|
+
tracebackHtml: resolveTracebackHtml(parsed, cell.consoleOutputs),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return entries;
|
|
24
131
|
}
|
|
25
132
|
|
|
26
133
|
function describeError(error: MarimoError): string {
|
|
@@ -67,6 +174,77 @@ function describeError(error: MarimoError): string {
|
|
|
67
174
|
return "Unknown error";
|
|
68
175
|
}
|
|
69
176
|
|
|
177
|
+
function formatSingleError(error: MarimoError): string {
|
|
178
|
+
let text = describeError(error);
|
|
179
|
+
if ("traceback" in error && error.traceback) {
|
|
180
|
+
text += `\n\nTraceback:\n${parseHtmlContent(error.traceback)}`;
|
|
181
|
+
}
|
|
182
|
+
return text;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function formatCellErrorDetails(
|
|
186
|
+
entry: CellErrorEntry,
|
|
187
|
+
store: JotaiStore,
|
|
188
|
+
): string {
|
|
189
|
+
const parts: string[] = [`Code:\n${entry.cellCode}`];
|
|
190
|
+
|
|
191
|
+
if (entry.tracebackHtml) {
|
|
192
|
+
parts.push(`Traceback:\n${parseHtmlContent(entry.tracebackHtml)}`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (entry.errorData.length > 0) {
|
|
196
|
+
parts.push(
|
|
197
|
+
entry.errorData.map((error) => formatSingleError(error)).join("\n\n"),
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (entry.errorData.some((error) => error.type === "sql-error")) {
|
|
202
|
+
const datasourceContext = formatDatasourceContextForCell(
|
|
203
|
+
entry.cellId,
|
|
204
|
+
store,
|
|
205
|
+
);
|
|
206
|
+
if (datasourceContext) {
|
|
207
|
+
parts.push(`Database schema:\n${datasourceContext}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return parts.join("\n\n");
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function formatCellErrorXml(entry: CellErrorEntry, store: JotaiStore): string {
|
|
215
|
+
return contextToXml({
|
|
216
|
+
type: "error",
|
|
217
|
+
data: {
|
|
218
|
+
name: entry.cellName || `Cell ${entry.cellId}`,
|
|
219
|
+
cellId: entry.cellId,
|
|
220
|
+
},
|
|
221
|
+
details: formatCellErrorDetails(entry, store),
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function summarizeCellError(entry: CellErrorEntry): string {
|
|
226
|
+
if (entry.errorData.length === 1) {
|
|
227
|
+
return describeError(entry.errorData[0]);
|
|
228
|
+
}
|
|
229
|
+
if (entry.errorData.length > 1) {
|
|
230
|
+
return `${entry.errorData.length} errors`;
|
|
231
|
+
}
|
|
232
|
+
if (entry.tracebackHtml) {
|
|
233
|
+
const text = parseHtmlContent(entry.tracebackHtml).trim();
|
|
234
|
+
const firstLine = text
|
|
235
|
+
.split("\n")
|
|
236
|
+
.find((line) => line.trim())
|
|
237
|
+
?.trim();
|
|
238
|
+
return firstLine || "traceback";
|
|
239
|
+
}
|
|
240
|
+
return "error";
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function errorContextName(entry: CellErrorEntry): string {
|
|
244
|
+
const cellName = entry.cellName || `Cell ${entry.cellId}`;
|
|
245
|
+
return `Error: ${cellName}`;
|
|
246
|
+
}
|
|
247
|
+
|
|
70
248
|
const errorsTxt = new PluralWord("error", "errors");
|
|
71
249
|
export class ErrorContextProvider extends AIContextProvider<ErrorContextItem> {
|
|
72
250
|
readonly title = "Errors";
|
|
@@ -80,39 +258,50 @@ export class ErrorContextProvider extends AIContextProvider<ErrorContextItem> {
|
|
|
80
258
|
}
|
|
81
259
|
|
|
82
260
|
getItems(): ErrorContextItem[] {
|
|
83
|
-
const errors = this.store
|
|
261
|
+
const errors = getCellErrorEntries(this.store);
|
|
262
|
+
|
|
84
263
|
if (errors.length === 0) {
|
|
85
264
|
return [];
|
|
86
265
|
}
|
|
87
266
|
|
|
88
|
-
|
|
267
|
+
const items: ErrorContextItem[] = [
|
|
89
268
|
{
|
|
90
269
|
uri: this.asURI("all"),
|
|
91
270
|
name: "Errors",
|
|
92
271
|
type: this.contextType,
|
|
93
272
|
data: {
|
|
94
273
|
type: "all-errors",
|
|
95
|
-
errors
|
|
96
|
-
cellId: error.cellId,
|
|
97
|
-
cellName: error.cellName,
|
|
98
|
-
errorData: error.output.data,
|
|
99
|
-
})),
|
|
274
|
+
errors,
|
|
100
275
|
},
|
|
101
276
|
description: "All errors in the notebook",
|
|
102
277
|
},
|
|
103
278
|
];
|
|
104
279
|
|
|
105
|
-
|
|
280
|
+
for (const error of errors) {
|
|
281
|
+
items.push({
|
|
282
|
+
uri: this.asURI(error.cellId),
|
|
283
|
+
name: errorContextName(error),
|
|
284
|
+
type: this.contextType,
|
|
285
|
+
data: {
|
|
286
|
+
type: "cell-error",
|
|
287
|
+
error,
|
|
288
|
+
},
|
|
289
|
+
description: summarizeCellError(error),
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return items;
|
|
106
294
|
}
|
|
107
295
|
|
|
108
296
|
formatCompletion(item: ErrorContextItem): Completion {
|
|
109
297
|
if (item.data.type === "all-errors") {
|
|
298
|
+
const errorCount = item.data.errors.length;
|
|
110
299
|
return {
|
|
111
300
|
label: "@Errors",
|
|
112
301
|
displayLabel: "Errors",
|
|
113
|
-
detail: `${
|
|
302
|
+
detail: `${errorCount} ${errorsTxt.pluralize(errorCount)}`,
|
|
114
303
|
type: "error",
|
|
115
|
-
apply: "@
|
|
304
|
+
apply: "@error://all",
|
|
116
305
|
section: Sections.ERROR,
|
|
117
306
|
info: () => {
|
|
118
307
|
const infoContainer = document.createElement("div");
|
|
@@ -136,7 +325,7 @@ export class ErrorContextProvider extends AIContextProvider<ErrorContextItem> {
|
|
|
136
325
|
|
|
137
326
|
const descriptionDiv = document.createElement("div");
|
|
138
327
|
descriptionDiv.classList.add("text-sm", "text-muted-foreground");
|
|
139
|
-
descriptionDiv.textContent = `${
|
|
328
|
+
descriptionDiv.textContent = `${errorCount} ${errorsTxt.pluralize(errorCount)}`;
|
|
140
329
|
headerDiv.append(descriptionDiv);
|
|
141
330
|
|
|
142
331
|
infoContainer.append(headerDiv);
|
|
@@ -146,6 +335,17 @@ export class ErrorContextProvider extends AIContextProvider<ErrorContextItem> {
|
|
|
146
335
|
};
|
|
147
336
|
}
|
|
148
337
|
|
|
338
|
+
if (item.data.type === "cell-error") {
|
|
339
|
+
const { error } = item.data;
|
|
340
|
+
return {
|
|
341
|
+
...this.createBasicCompletion(item, {
|
|
342
|
+
detail: summarizeCellError(error),
|
|
343
|
+
type: "error",
|
|
344
|
+
}),
|
|
345
|
+
section: Sections.ERROR,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
149
349
|
return {
|
|
150
350
|
label: "Error",
|
|
151
351
|
displayLabel: "Error",
|
|
@@ -154,21 +354,11 @@ export class ErrorContextProvider extends AIContextProvider<ErrorContextItem> {
|
|
|
154
354
|
}
|
|
155
355
|
|
|
156
356
|
formatContext(item: ErrorContextItem): string {
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
const xmls = data.errors.map((err) => {
|
|
160
|
-
return contextToXml({
|
|
161
|
-
type: this.contextType,
|
|
162
|
-
data: {
|
|
163
|
-
name: err.cellName || `Cell ${err.cellId}`,
|
|
164
|
-
description: err.errorData
|
|
165
|
-
.map((err) => describeError(err))
|
|
166
|
-
.join("\n"),
|
|
167
|
-
},
|
|
168
|
-
});
|
|
169
|
-
});
|
|
357
|
+
const entries =
|
|
358
|
+
item.data.type === "all-errors" ? item.data.errors : [item.data.error];
|
|
170
359
|
|
|
171
|
-
|
|
172
|
-
|
|
360
|
+
return entries
|
|
361
|
+
.map((entry) => formatCellErrorXml(entry, this.store))
|
|
362
|
+
.join("\n\n");
|
|
173
363
|
}
|
|
174
364
|
}
|
|
@@ -9,11 +9,19 @@ import { MultiMap } from "@/utils/multi-map";
|
|
|
9
9
|
import type { TypedString } from "@/utils/typed";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* Unique identifier for a context item in the format "type
|
|
12
|
+
* Unique identifier for a context item in the format "type://id"
|
|
13
13
|
* e.g., "variable://my_var", "data://users", "file://config.py"
|
|
14
14
|
*/
|
|
15
15
|
export type ContextLocatorId = TypedString<"ContextLocatorId">;
|
|
16
16
|
|
|
17
|
+
function parseContextType(uri: ContextLocatorId): string | undefined {
|
|
18
|
+
const separator = uri.indexOf("://");
|
|
19
|
+
if (separator === -1) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
return uri.slice(0, separator);
|
|
23
|
+
}
|
|
24
|
+
|
|
17
25
|
/**
|
|
18
26
|
* Base interface for context items that can be mentioned in AI prompts
|
|
19
27
|
*/
|
|
@@ -142,40 +150,80 @@ export class AIContextRegistry<T extends AIContextItem> {
|
|
|
142
150
|
);
|
|
143
151
|
}
|
|
144
152
|
|
|
153
|
+
private findProviderForUri(
|
|
154
|
+
uri: ContextLocatorId,
|
|
155
|
+
): AIContextProvider | undefined {
|
|
156
|
+
const type = parseContextType(uri);
|
|
157
|
+
if (!type) {
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
return [...this.providers].find(
|
|
161
|
+
(provider) =>
|
|
162
|
+
provider.contextType === type &&
|
|
163
|
+
provider.getItems().some((item) => item.uri === uri),
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
145
167
|
/**
|
|
146
|
-
*
|
|
168
|
+
* Resolve only the requested context items, querying each matching provider
|
|
147
169
|
*/
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
170
|
+
resolveItems(contextIds: ContextLocatorId[]): T[] {
|
|
171
|
+
if (contextIds.length === 0) {
|
|
172
|
+
return [];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const idsByType = new MultiMap<string, ContextLocatorId>();
|
|
176
|
+
for (const contextId of contextIds) {
|
|
177
|
+
const type = parseContextType(contextId);
|
|
178
|
+
if (type) {
|
|
179
|
+
idsByType.add(type, contextId);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const itemsById = new Map<ContextLocatorId, T>();
|
|
184
|
+
for (const [type, ids] of idsByType.entries()) {
|
|
185
|
+
const providers = [...this.providers].filter(
|
|
186
|
+
(provider) => provider.contextType === type,
|
|
187
|
+
);
|
|
188
|
+
for (const provider of providers) {
|
|
189
|
+
const itemsByUri = new Map<ContextLocatorId, T>(
|
|
190
|
+
provider
|
|
191
|
+
.getItems()
|
|
192
|
+
.map((item) => [item.uri as ContextLocatorId, item as T]),
|
|
193
|
+
);
|
|
194
|
+
for (const id of ids) {
|
|
195
|
+
const item = itemsByUri.get(id);
|
|
196
|
+
if (item) {
|
|
197
|
+
itemsById.set(id, item);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
153
202
|
|
|
203
|
+
// Preserve the order in which the ids were requested, so formatted context
|
|
204
|
+
// matches the order the user mentioned them in the prompt.
|
|
205
|
+
const results: T[] = [];
|
|
154
206
|
for (const contextId of contextIds) {
|
|
155
|
-
const item =
|
|
207
|
+
const item = itemsById.get(contextId);
|
|
156
208
|
if (item) {
|
|
157
|
-
|
|
209
|
+
results.push(item);
|
|
158
210
|
}
|
|
159
211
|
}
|
|
212
|
+
return results;
|
|
213
|
+
}
|
|
160
214
|
|
|
161
|
-
|
|
215
|
+
/**
|
|
216
|
+
* Get context information for mentioned items
|
|
217
|
+
*/
|
|
218
|
+
getContextInfo(contextIds: ContextLocatorId[]): T[] {
|
|
219
|
+
return this.resolveItems(contextIds);
|
|
162
220
|
}
|
|
163
221
|
|
|
164
222
|
/**
|
|
165
223
|
* Format context for AI prompt inclusion
|
|
166
224
|
*/
|
|
167
225
|
formatContextForAI(contextIds: ContextLocatorId[]): string {
|
|
168
|
-
const
|
|
169
|
-
this.getAllItems().map((item) => [item.uri as ContextLocatorId, item]),
|
|
170
|
-
);
|
|
171
|
-
|
|
172
|
-
const contextInfo: T[] = [];
|
|
173
|
-
for (const contextId of contextIds) {
|
|
174
|
-
const item = allItems.get(contextId);
|
|
175
|
-
if (item) {
|
|
176
|
-
contextInfo.push(item);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
226
|
+
const contextInfo = this.resolveItems(contextIds);
|
|
179
227
|
|
|
180
228
|
if (contextInfo.length === 0) {
|
|
181
229
|
return "";
|
|
@@ -183,7 +231,7 @@ export class AIContextRegistry<T extends AIContextItem> {
|
|
|
183
231
|
|
|
184
232
|
return contextInfo
|
|
185
233
|
.map((item) => {
|
|
186
|
-
const provider = this.
|
|
234
|
+
const provider = this.findProviderForUri(item.uri as ContextLocatorId);
|
|
187
235
|
return provider?.formatContext(item) || "";
|
|
188
236
|
})
|
|
189
237
|
.join("\n\n");
|
|
@@ -195,36 +243,22 @@ export class AIContextRegistry<T extends AIContextItem> {
|
|
|
195
243
|
async getAttachmentsForContext(
|
|
196
244
|
contextIds: ContextLocatorId[],
|
|
197
245
|
): Promise<FileUIPart[]> {
|
|
198
|
-
const
|
|
199
|
-
this.getAllItems().map((item) => [item.uri as ContextLocatorId, item]),
|
|
200
|
-
);
|
|
201
|
-
|
|
202
|
-
const contextInfo: T[] = [];
|
|
203
|
-
for (const contextId of contextIds) {
|
|
204
|
-
const item = allItems.get(contextId);
|
|
205
|
-
if (item) {
|
|
206
|
-
contextInfo.push(item);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
246
|
+
const contextInfo = this.resolveItems(contextIds);
|
|
209
247
|
|
|
210
248
|
if (contextInfo.length === 0) {
|
|
211
249
|
return [];
|
|
212
250
|
}
|
|
213
251
|
|
|
214
|
-
|
|
215
|
-
const itemsByProvider = new MultiMap<string, T>();
|
|
252
|
+
const itemsByProvider = new MultiMap<AIContextProvider, T>();
|
|
216
253
|
for (const item of contextInfo) {
|
|
217
|
-
const
|
|
218
|
-
|
|
254
|
+
const provider = this.findProviderForUri(item.uri as ContextLocatorId);
|
|
255
|
+
if (provider) {
|
|
256
|
+
itemsByProvider.add(provider, item);
|
|
257
|
+
}
|
|
219
258
|
}
|
|
220
259
|
|
|
221
|
-
// Collect attachments from all providers
|
|
222
260
|
const attachmentPromises = [...itemsByProvider.entries()].map(
|
|
223
|
-
async ([
|
|
224
|
-
const provider = this.getProvider(providerType);
|
|
225
|
-
if (!provider) {
|
|
226
|
-
return [];
|
|
227
|
-
}
|
|
261
|
+
async ([provider, items]) => {
|
|
228
262
|
try {
|
|
229
263
|
return await provider.getAttachments(items);
|
|
230
264
|
} catch (error) {
|
package/src/core/ai/state.ts
CHANGED
|
@@ -21,6 +21,17 @@ export interface AiCompletionCell {
|
|
|
21
21
|
|
|
22
22
|
export const aiCompletionCellAtom = atom<AiCompletionCell | null>(null);
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* A prompt queued to be delivered to the AI assistant panel.
|
|
26
|
+
* The active panel consumes this on mount/when ready, then clears it.
|
|
27
|
+
*/
|
|
28
|
+
export interface PendingAiPrompt {
|
|
29
|
+
prompt: string;
|
|
30
|
+
submit: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const pendingAiPromptAtom = atom<PendingAiPrompt | null>(null);
|
|
34
|
+
|
|
24
35
|
const INCLUDE_OTHER_CELLS_KEY = "marimo:ai:includeOtherCells";
|
|
25
36
|
export const includeOtherCellsAtom = atomWithStorage<boolean>(
|
|
26
37
|
INCLUDE_OTHER_CELLS_KEY,
|
|
@@ -218,3 +218,42 @@ describe("transitionCell serialization", () => {
|
|
|
218
218
|
);
|
|
219
219
|
});
|
|
220
220
|
});
|
|
221
|
+
|
|
222
|
+
describe("transitionCell stdin resolution", () => {
|
|
223
|
+
function stdinOutput(): OutputMessage {
|
|
224
|
+
return {
|
|
225
|
+
channel: "stdin",
|
|
226
|
+
mimetype: "text/plain",
|
|
227
|
+
data: "(Pdb) ",
|
|
228
|
+
timestamp: Date.now() as Seconds,
|
|
229
|
+
} as OutputMessage;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
it("resolves a dangling pdb prompt when the cell goes idle", () => {
|
|
233
|
+
// A paused-pdb cell shows an unanswered stdin prompt (the debugger box);
|
|
234
|
+
// once it finishes (e.g. after quitting with `q`), the box is removed.
|
|
235
|
+
const cell = createCellRuntimeState({
|
|
236
|
+
status: "running",
|
|
237
|
+
debuggerActive: true,
|
|
238
|
+
consoleOutputs: [stdinOutput()],
|
|
239
|
+
});
|
|
240
|
+
const next = transitionCell(cell, {
|
|
241
|
+
cell_id: "cell-1",
|
|
242
|
+
status: "idle",
|
|
243
|
+
} as CellMessage);
|
|
244
|
+
expect(next.debuggerActive).toBe(false);
|
|
245
|
+
expect(next.consoleOutputs[0].response).toBe("");
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it("leaves an unanswered prompt interactive while still running", () => {
|
|
249
|
+
const cell = createCellRuntimeState({
|
|
250
|
+
status: "running",
|
|
251
|
+
consoleOutputs: [stdinOutput()],
|
|
252
|
+
});
|
|
253
|
+
const next = transitionCell(cell, {
|
|
254
|
+
cell_id: "cell-1",
|
|
255
|
+
status: null,
|
|
256
|
+
} as CellMessage);
|
|
257
|
+
expect(next.consoleOutputs[0].response).toBeUndefined();
|
|
258
|
+
});
|
|
259
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { getReadonlyCodeDisplay } from "../readonly-code-display";
|
|
5
|
+
|
|
6
|
+
describe("getReadonlyCodeDisplay", () => {
|
|
7
|
+
it("unwraps SQL cells to their inner query and marks them as sql", () => {
|
|
8
|
+
const result = getReadonlyCodeDisplay(
|
|
9
|
+
'my_table = mo.sql("""SELECT 1 AS id""")',
|
|
10
|
+
);
|
|
11
|
+
expect(result.language).toBe("sql");
|
|
12
|
+
expect(result.code).toBe("SELECT 1 AS id");
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("unwraps f-string SQL cells", () => {
|
|
16
|
+
const result = getReadonlyCodeDisplay(
|
|
17
|
+
'my_table = mo.sql(f"""SELECT 1 AS id""")',
|
|
18
|
+
);
|
|
19
|
+
expect(result.language).toBe("sql");
|
|
20
|
+
expect(result.code).toBe("SELECT 1 AS id");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("leaves plain Python cells untouched", () => {
|
|
24
|
+
const code = "x = 1 + 2\nprint(x)";
|
|
25
|
+
const result = getReadonlyCodeDisplay(code);
|
|
26
|
+
expect(result.language).toBe("python");
|
|
27
|
+
expect(result.code).toBe(code);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("unwraps markdown cells to their inner content", () => {
|
|
31
|
+
const result = getReadonlyCodeDisplay('mo.md("""## Heading""")');
|
|
32
|
+
expect(result.language).toBe("markdown");
|
|
33
|
+
expect(result.code).toBe("## Heading");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("treats empty or whitespace-only code as python", () => {
|
|
37
|
+
expect(getReadonlyCodeDisplay("")).toEqual({
|
|
38
|
+
code: "",
|
|
39
|
+
language: "python",
|
|
40
|
+
});
|
|
41
|
+
expect(getReadonlyCodeDisplay(" \n\t ")).toEqual({
|
|
42
|
+
code: " \n\t ",
|
|
43
|
+
language: "python",
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
});
|
package/src/core/cells/cell.ts
CHANGED
|
@@ -118,9 +118,11 @@ export function transitionCell(
|
|
|
118
118
|
// Coalesce console outputs, which are streamed during execution.
|
|
119
119
|
let consoleOutputs = cell.consoleOutputs;
|
|
120
120
|
|
|
121
|
-
//
|
|
122
|
-
//
|
|
123
|
-
|
|
121
|
+
// When the cell is interrupted or finishes (idle), there is no live
|
|
122
|
+
// stdin/pdb session, so resolve any dangling stdin prompt. This removes the
|
|
123
|
+
// debugger box — e.g. after quitting pdb with `q`, which stops the cell
|
|
124
|
+
// (idle) rather than interrupting it.
|
|
125
|
+
if (didInterruptFromThisMessage || message.status === "idle") {
|
|
124
126
|
nextCell.debuggerActive = false;
|
|
125
127
|
consoleOutputs = consoleOutputs.map((output) => {
|
|
126
128
|
if (output.channel === "stdin") {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { MarkdownParser, SQLParser } from "@marimo-team/smart-cells";
|
|
4
|
+
import type { LanguageAdapterType } from "@/core/codemirror/language/types";
|
|
5
|
+
|
|
6
|
+
export interface ReadonlyCodeDisplay {
|
|
7
|
+
code: string;
|
|
8
|
+
language: LanguageAdapterType;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const markdownParser = new MarkdownParser();
|
|
12
|
+
const sqlParser = new SQLParser();
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Unwrap SQL and markdown cells so read-only views show inner content with the
|
|
16
|
+
* correct syntax highlighting instead of the raw Python wrapper.
|
|
17
|
+
*/
|
|
18
|
+
export function getReadonlyCodeDisplay(code: string): ReadonlyCodeDisplay {
|
|
19
|
+
const trimmed = code.trim();
|
|
20
|
+
if (!trimmed) {
|
|
21
|
+
return { code, language: "python" };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (markdownParser.isSupported(trimmed)) {
|
|
25
|
+
return {
|
|
26
|
+
code: markdownParser.transformIn(trimmed).code,
|
|
27
|
+
language: "markdown",
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (sqlParser.isSupported(trimmed)) {
|
|
32
|
+
return { code: sqlParser.transformIn(trimmed).code, language: "sql" };
|
|
33
|
+
}
|
|
34
|
+
return { code, language: "python" };
|
|
35
|
+
}
|