@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
|
@@ -11,19 +11,22 @@ interface SlideContentProps extends Pick<
|
|
|
11
11
|
"output" | "status"
|
|
12
12
|
> {
|
|
13
13
|
cellId: CellId;
|
|
14
|
+
stale: boolean;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
export const Slide = memo(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
17
|
+
export const Slide = memo(
|
|
18
|
+
({ output, cellId, status, stale }: SlideContentProps) => {
|
|
19
|
+
const loading = outputIsLoading(status);
|
|
20
|
+
return (
|
|
21
|
+
<OutputArea
|
|
22
|
+
className="contents"
|
|
23
|
+
allowExpand={false}
|
|
24
|
+
output={output}
|
|
25
|
+
cellId={cellId}
|
|
26
|
+
stale={stale}
|
|
27
|
+
loading={loading}
|
|
28
|
+
/>
|
|
29
|
+
);
|
|
30
|
+
},
|
|
31
|
+
);
|
|
29
32
|
Slide.displayName = "Slide";
|
|
@@ -28,9 +28,10 @@ import {
|
|
|
28
28
|
} from "@/core/cells/runs";
|
|
29
29
|
import { type ResolvedTheme, useTheme } from "@/theme/useTheme";
|
|
30
30
|
import { cn } from "@/utils/cn";
|
|
31
|
+
import { formatElapsedTime } from "@/utils/time";
|
|
31
32
|
import { ClearButton } from "../buttons/clear-button";
|
|
32
33
|
import type { SignalListener } from "../charts/types";
|
|
33
|
-
import { ElapsedTime
|
|
34
|
+
import { ElapsedTime } from "../editor/cell/CellStatus";
|
|
34
35
|
import { PanelEmptyState } from "../editor/chrome/panels/empty-state";
|
|
35
36
|
import { usePanelSection } from "../editor/chrome/panels/panel-context";
|
|
36
37
|
import { CellLink } from "../editor/links/cell-link";
|
package/src/core/ai/config.ts
CHANGED
|
@@ -60,7 +60,7 @@ export const useModelChange = () => {
|
|
|
60
60
|
},
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
-
saveConfig(newConfig);
|
|
63
|
+
await saveConfig(newConfig);
|
|
64
64
|
};
|
|
65
65
|
|
|
66
66
|
const saveModeChange = async (newMode: CopilotMode) => {
|
|
@@ -71,7 +71,7 @@ export const useModelChange = () => {
|
|
|
71
71
|
},
|
|
72
72
|
};
|
|
73
73
|
|
|
74
|
-
saveConfig(newConfig);
|
|
74
|
+
await saveConfig(newConfig);
|
|
75
75
|
};
|
|
76
76
|
|
|
77
77
|
return { saveModelChange, saveModeChange };
|
|
@@ -176,6 +176,33 @@ class FileContextProvider extends AIContextProvider<FileContextItem> {
|
|
|
176
176
|
}
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
+
// Second mock provider with the same context type as MockContextProvider
|
|
180
|
+
class SecondaryMockProvider extends AIContextProvider<MockContextItem> {
|
|
181
|
+
readonly title = "Secondary Mock Items";
|
|
182
|
+
readonly mentionPrefix = "@";
|
|
183
|
+
readonly contextType = "mock";
|
|
184
|
+
|
|
185
|
+
getItems(): MockContextItem[] {
|
|
186
|
+
return [
|
|
187
|
+
{
|
|
188
|
+
type: "mock",
|
|
189
|
+
uri: this.asURI("secondary-item"),
|
|
190
|
+
name: "Secondary Item",
|
|
191
|
+
description: "From the second mock provider",
|
|
192
|
+
data: { value: "secondary" },
|
|
193
|
+
},
|
|
194
|
+
];
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
formatContext(item: MockContextItem): string {
|
|
198
|
+
return `Secondary: ${item.name}`;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
formatCompletion(item: MockContextItem): Completion {
|
|
202
|
+
return this.createBasicCompletion(item);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
179
206
|
describe("AIContextProvider", () => {
|
|
180
207
|
let provider: MockContextProvider;
|
|
181
208
|
|
|
@@ -411,6 +438,95 @@ describe("AIContextRegistry", () => {
|
|
|
411
438
|
});
|
|
412
439
|
});
|
|
413
440
|
|
|
441
|
+
describe("resolveItems", () => {
|
|
442
|
+
let mockGetItems: ReturnType<typeof vi.spyOn>;
|
|
443
|
+
let fileGetItems: ReturnType<typeof vi.spyOn>;
|
|
444
|
+
|
|
445
|
+
beforeEach(() => {
|
|
446
|
+
registry.register(mockProvider);
|
|
447
|
+
registry.register(fileProvider);
|
|
448
|
+
mockGetItems = vi.spyOn(mockProvider, "getItems");
|
|
449
|
+
fileGetItems = vi.spyOn(fileProvider, "getItems");
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
it("should resolve valid IDs from the matching providers only", () => {
|
|
453
|
+
const resolved = registry.resolveItems([
|
|
454
|
+
"mock://item1",
|
|
455
|
+
"file://config.py",
|
|
456
|
+
] as ContextLocatorId[]);
|
|
457
|
+
|
|
458
|
+
expect(resolved).toHaveLength(2);
|
|
459
|
+
expect(resolved.map((item) => item.uri)).toEqual([
|
|
460
|
+
"mock://item1",
|
|
461
|
+
"file://config.py",
|
|
462
|
+
]);
|
|
463
|
+
expect(mockGetItems).toHaveBeenCalledTimes(1);
|
|
464
|
+
expect(fileGetItems).toHaveBeenCalledTimes(1);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
it("should not call unrelated providers", () => {
|
|
468
|
+
registry.resolveItems(["mock://item1"] as ContextLocatorId[]);
|
|
469
|
+
|
|
470
|
+
expect(mockGetItems).toHaveBeenCalledTimes(1);
|
|
471
|
+
expect(fileGetItems).not.toHaveBeenCalled();
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
it("should ignore unknown or invalid URIs", () => {
|
|
475
|
+
const resolved = registry.resolveItems([
|
|
476
|
+
"mock://nonexistent",
|
|
477
|
+
"unknown://item",
|
|
478
|
+
"malformed" as ContextLocatorId,
|
|
479
|
+
] as ContextLocatorId[]);
|
|
480
|
+
|
|
481
|
+
expect(resolved).toEqual([]);
|
|
482
|
+
expect(mockGetItems).toHaveBeenCalledTimes(1);
|
|
483
|
+
expect(fileGetItems).not.toHaveBeenCalled();
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
it("should return empty array for empty input", () => {
|
|
487
|
+
expect(registry.resolveItems([])).toEqual([]);
|
|
488
|
+
expect(mockGetItems).not.toHaveBeenCalled();
|
|
489
|
+
expect(fileGetItems).not.toHaveBeenCalled();
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
it("should preserve requested order when providers are interleaved", () => {
|
|
493
|
+
const resolved = registry.resolveItems([
|
|
494
|
+
"mock://item1",
|
|
495
|
+
"file://config.py",
|
|
496
|
+
"mock://item2",
|
|
497
|
+
"file://utils/helpers.py",
|
|
498
|
+
] as ContextLocatorId[]);
|
|
499
|
+
|
|
500
|
+
expect(resolved.map((item) => item.uri)).toEqual([
|
|
501
|
+
"mock://item1",
|
|
502
|
+
"file://config.py",
|
|
503
|
+
"mock://item2",
|
|
504
|
+
"file://utils/helpers.py",
|
|
505
|
+
]);
|
|
506
|
+
// Each provider is still queried only once, regardless of interleaving.
|
|
507
|
+
expect(mockGetItems).toHaveBeenCalledTimes(1);
|
|
508
|
+
expect(fileGetItems).toHaveBeenCalledTimes(1);
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
it("should resolve items from all providers that share a context type", () => {
|
|
512
|
+
const secondaryProvider = new SecondaryMockProvider();
|
|
513
|
+
const secondaryGetItems = vi.spyOn(secondaryProvider, "getItems");
|
|
514
|
+
registry.register(secondaryProvider);
|
|
515
|
+
|
|
516
|
+
const resolved = registry.resolveItems([
|
|
517
|
+
"mock://item1",
|
|
518
|
+
"mock://secondary-item",
|
|
519
|
+
] as ContextLocatorId[]);
|
|
520
|
+
|
|
521
|
+
expect(resolved.map((item) => item.uri)).toEqual([
|
|
522
|
+
"mock://item1",
|
|
523
|
+
"mock://secondary-item",
|
|
524
|
+
]);
|
|
525
|
+
expect(mockGetItems).toHaveBeenCalledTimes(1);
|
|
526
|
+
expect(secondaryGetItems).toHaveBeenCalledTimes(1);
|
|
527
|
+
});
|
|
528
|
+
});
|
|
529
|
+
|
|
414
530
|
describe("getContextInfo", () => {
|
|
415
531
|
beforeEach(() => {
|
|
416
532
|
registry.register(mockProvider);
|
|
@@ -531,6 +647,19 @@ describe("AIContextRegistry", () => {
|
|
|
531
647
|
expect(formatted).toContain("Mock: Item 1 (value1)");
|
|
532
648
|
expect(formatted.split("\n\n")).toHaveLength(1);
|
|
533
649
|
});
|
|
650
|
+
|
|
651
|
+
it("should format items from the provider that owns them", () => {
|
|
652
|
+
registry.register(new SecondaryMockProvider());
|
|
653
|
+
|
|
654
|
+
const formatted = registry.formatContextForAI([
|
|
655
|
+
"mock://item1",
|
|
656
|
+
"mock://secondary-item",
|
|
657
|
+
] as ContextLocatorId[]);
|
|
658
|
+
|
|
659
|
+
expect(formatted).toBe(
|
|
660
|
+
"Mock: Item 1 (value1)\n\nSecondary: Secondary Item",
|
|
661
|
+
);
|
|
662
|
+
});
|
|
534
663
|
});
|
|
535
664
|
|
|
536
665
|
describe("edge cases and error handling", () => {
|
|
@@ -621,7 +750,7 @@ describe("AIContextRegistry", () => {
|
|
|
621
750
|
expect(provider).toBe(errorProvider);
|
|
622
751
|
|
|
623
752
|
const items = registry.getAllItems();
|
|
624
|
-
expect(items).toHaveLength(
|
|
753
|
+
expect(items).toHaveLength(3);
|
|
625
754
|
expect(items[0].type).toBe("error");
|
|
626
755
|
expect(items[0].name).toBe("Errors");
|
|
627
756
|
});
|
|
@@ -654,10 +783,11 @@ describe("AIContextRegistry", () => {
|
|
|
654
783
|
expect(provider).toBeDefined();
|
|
655
784
|
|
|
656
785
|
const items = provider!.getItems();
|
|
657
|
-
expect(items).toHaveLength(
|
|
786
|
+
expect(items).toHaveLength(3);
|
|
658
787
|
|
|
659
788
|
const completion = provider!.formatCompletion(items[0]);
|
|
660
789
|
expect(completion.label).toBe("@Errors");
|
|
790
|
+
expect(completion.apply).toBe("@error://all");
|
|
661
791
|
expect(completion.type).toBe("error");
|
|
662
792
|
});
|
|
663
793
|
|
|
@@ -690,7 +820,7 @@ describe("AIContextRegistry", () => {
|
|
|
690
820
|
const errorItems = items.filter((item) => item.type === "error");
|
|
691
821
|
const mockItems = items.filter((item) => item.type === "mock");
|
|
692
822
|
|
|
693
|
-
expect(errorItems).toHaveLength(
|
|
823
|
+
expect(errorItems).toHaveLength(3);
|
|
694
824
|
expect(mockItems).toHaveLength(3);
|
|
695
825
|
});
|
|
696
826
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
/* oxlint-disable typescript/no-explicit-any */
|
|
3
3
|
|
|
4
|
+
import { createStore } from "jotai";
|
|
4
5
|
import { beforeEach, describe, expect, it } from "vitest";
|
|
5
6
|
import { cellId, variableName } from "@/__tests__/branded";
|
|
6
7
|
import type {
|
|
@@ -634,7 +635,8 @@ describe("DatasourceContextProvider", () => {
|
|
|
634
635
|
|
|
635
636
|
describe("getDatasourceContext", () => {
|
|
636
637
|
it("should return null if no cell ID is found", () => {
|
|
637
|
-
const
|
|
638
|
+
const store = createStore();
|
|
639
|
+
const context = getDatasourceContext(cellId("1"), store);
|
|
638
640
|
expect(context).toBeNull();
|
|
639
641
|
});
|
|
640
642
|
});
|
|
@@ -57,7 +57,7 @@ describe("ErrorContextProvider", () => {
|
|
|
57
57
|
|
|
58
58
|
const items = provider.getItems();
|
|
59
59
|
|
|
60
|
-
expect(items).toHaveLength(
|
|
60
|
+
expect(items).toHaveLength(3);
|
|
61
61
|
expect(items[0]).toMatchObject({
|
|
62
62
|
name: "Errors",
|
|
63
63
|
type: "error",
|
|
@@ -78,6 +78,18 @@ describe("ErrorContextProvider", () => {
|
|
|
78
78
|
],
|
|
79
79
|
},
|
|
80
80
|
});
|
|
81
|
+
expect(items[1]).toMatchObject({
|
|
82
|
+
uri: `error://${cellId1}`,
|
|
83
|
+
name: "Error: Cell 1",
|
|
84
|
+
description: "Invalid syntax",
|
|
85
|
+
data: { type: "cell-error", error: { cellId: cellId1 } },
|
|
86
|
+
});
|
|
87
|
+
expect(items[2]).toMatchObject({
|
|
88
|
+
uri: `error://${cellId2}`,
|
|
89
|
+
name: "Error: Cell 2",
|
|
90
|
+
description: "Runtime error",
|
|
91
|
+
data: { type: "cell-error", error: { cellId: cellId2 } },
|
|
92
|
+
});
|
|
81
93
|
});
|
|
82
94
|
|
|
83
95
|
it("should handle cells without names", () => {
|
|
@@ -92,7 +104,12 @@ describe("ErrorContextProvider", () => {
|
|
|
92
104
|
]);
|
|
93
105
|
|
|
94
106
|
const items = provider.getItems();
|
|
95
|
-
expect(items[
|
|
107
|
+
expect(items[1].data.type).toBe("cell-error");
|
|
108
|
+
if (items[1].data.type === "cell-error") {
|
|
109
|
+
expect(items[1].data.error.cellName).toBe("cell-0");
|
|
110
|
+
}
|
|
111
|
+
expect(items[1].name).toBe("Error: cell-0");
|
|
112
|
+
expect(items[1].description).toBe("Invalid syntax");
|
|
96
113
|
});
|
|
97
114
|
});
|
|
98
115
|
|
|
@@ -119,7 +136,7 @@ describe("ErrorContextProvider", () => {
|
|
|
119
136
|
|
|
120
137
|
expect(completion).toMatchInlineSnapshot(`
|
|
121
138
|
{
|
|
122
|
-
"apply": "@
|
|
139
|
+
"apply": "@error://all",
|
|
123
140
|
"detail": "2 errors",
|
|
124
141
|
"displayLabel": "Errors",
|
|
125
142
|
"info": [Function],
|
|
@@ -170,6 +187,24 @@ describe("ErrorContextProvider", () => {
|
|
|
170
187
|
expect(completion.detail).toBe("1 error");
|
|
171
188
|
});
|
|
172
189
|
|
|
190
|
+
it("formats completion for a single cell error", () => {
|
|
191
|
+
const cellId = CellIdClass.create();
|
|
192
|
+
|
|
193
|
+
createMockNotebookWithErrors([
|
|
194
|
+
{
|
|
195
|
+
cellId,
|
|
196
|
+
cellName: "Cell 1",
|
|
197
|
+
errorData: [MockNotebook.errors.syntax("Invalid syntax")],
|
|
198
|
+
},
|
|
199
|
+
]);
|
|
200
|
+
|
|
201
|
+
const items = provider.getItems();
|
|
202
|
+
const completion = provider.formatCompletion(items[1]);
|
|
203
|
+
expect(completion.apply).toBe(`@error://${cellId}`);
|
|
204
|
+
expect(completion.displayLabel).toBe("Error: Cell 1");
|
|
205
|
+
expect(items[1].name).toBe("Error: Cell 1");
|
|
206
|
+
});
|
|
207
|
+
|
|
173
208
|
it("should handle fallback for unknown error types", () => {
|
|
174
209
|
const item = {
|
|
175
210
|
uri: "error://unknown",
|
|
@@ -210,7 +245,8 @@ describe("ErrorContextProvider", () => {
|
|
|
210
245
|
|
|
211
246
|
const items = provider.getItems();
|
|
212
247
|
const context = provider.formatContext(items[0]);
|
|
213
|
-
expect(context).
|
|
248
|
+
expect(context).toContain("Invalid syntax");
|
|
249
|
+
expect(context).toContain("Code:");
|
|
214
250
|
});
|
|
215
251
|
|
|
216
252
|
it("should format context for multiple error types", () => {
|
|
@@ -239,14 +275,12 @@ describe("ErrorContextProvider", () => {
|
|
|
239
275
|
const items = provider.getItems();
|
|
240
276
|
const context = provider.formatContext(items[0]);
|
|
241
277
|
|
|
242
|
-
|
|
243
|
-
expect(context).
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
The variable 'variable_x' was defined by another cell"></error>"
|
|
249
|
-
`);
|
|
278
|
+
expect(context).toContain("Invalid syntax");
|
|
279
|
+
expect(context).toContain("Runtime error");
|
|
280
|
+
expect(context).toContain("This cell is in a cycle");
|
|
281
|
+
expect(context).toContain(
|
|
282
|
+
"The variable 'variable_x' was defined by another cell",
|
|
283
|
+
);
|
|
250
284
|
});
|
|
251
285
|
|
|
252
286
|
it("should handle cells without names", () => {
|
|
@@ -261,8 +295,158 @@ describe("ErrorContextProvider", () => {
|
|
|
261
295
|
]);
|
|
262
296
|
|
|
263
297
|
const items = provider.getItems();
|
|
264
|
-
const context = provider.formatContext(items[
|
|
265
|
-
expect(context).toContain(`
|
|
298
|
+
const context = provider.formatContext(items[1]);
|
|
299
|
+
expect(context).toContain(`cellId="${cellId}"`);
|
|
300
|
+
expect(context).toContain("Invalid syntax");
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it("includes exception tracebacks in formatted context", () => {
|
|
304
|
+
const cellId = CellIdClass.create();
|
|
305
|
+
|
|
306
|
+
createMockNotebookWithErrors([
|
|
307
|
+
{
|
|
308
|
+
cellId,
|
|
309
|
+
cellName: "Cell 1",
|
|
310
|
+
errorData: [
|
|
311
|
+
{
|
|
312
|
+
type: "exception",
|
|
313
|
+
msg: "boom",
|
|
314
|
+
exception_type: "ValueError",
|
|
315
|
+
raising_cell: null,
|
|
316
|
+
traceback: "<pre>line 1\nline 2</pre>",
|
|
317
|
+
},
|
|
318
|
+
],
|
|
319
|
+
},
|
|
320
|
+
]);
|
|
321
|
+
|
|
322
|
+
const items = provider.getItems();
|
|
323
|
+
const context = provider.formatContext(items[1]);
|
|
324
|
+
expect(context).toContain("boom");
|
|
325
|
+
expect(context).toContain("line 1");
|
|
326
|
+
expect(context).toContain("line 2");
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it("includes traceback-only cell outputs", () => {
|
|
330
|
+
const cellId = CellIdClass.create();
|
|
331
|
+
const notebookState = MockNotebook.notebookState({
|
|
332
|
+
cellData: {
|
|
333
|
+
[cellId]: {
|
|
334
|
+
name: "Cell 1",
|
|
335
|
+
code: "raise ValueError('boom')",
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
});
|
|
339
|
+
notebookState.cellRuntime[cellId] = {
|
|
340
|
+
...notebookState.cellRuntime[cellId],
|
|
341
|
+
output: {
|
|
342
|
+
channel: "marimo-error",
|
|
343
|
+
data: "<pre>ValueError: boom\n line 1</pre>",
|
|
344
|
+
mimetype: "application/vnd.marimo+traceback",
|
|
345
|
+
timestamp: Date.now(),
|
|
346
|
+
},
|
|
347
|
+
};
|
|
348
|
+
store.set(notebookAtom, notebookState);
|
|
349
|
+
|
|
350
|
+
const items = provider.getItems();
|
|
351
|
+
expect(items).toHaveLength(2);
|
|
352
|
+
expect(items[1].description).toBe("ValueError: boom");
|
|
353
|
+
const context = provider.formatContext(items[1]);
|
|
354
|
+
expect(context).toContain("raise ValueError('boom')");
|
|
355
|
+
expect(context).toContain("ValueError: boom");
|
|
356
|
+
expect(context).toContain("line 1");
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it("includes console tracebacks for marimo error outputs", () => {
|
|
360
|
+
const cellId = CellIdClass.create();
|
|
361
|
+
const notebookState = MockNotebook.notebookState({
|
|
362
|
+
cellData: {
|
|
363
|
+
[cellId]: {
|
|
364
|
+
name: "Cell 1",
|
|
365
|
+
code: "raise ValueError('boom')",
|
|
366
|
+
},
|
|
367
|
+
},
|
|
368
|
+
});
|
|
369
|
+
notebookState.cellRuntime[cellId] = {
|
|
370
|
+
...notebookState.cellRuntime[cellId],
|
|
371
|
+
output: {
|
|
372
|
+
channel: "marimo-error",
|
|
373
|
+
data: [
|
|
374
|
+
{
|
|
375
|
+
type: "exception",
|
|
376
|
+
msg: "boom",
|
|
377
|
+
exception_type: "ValueError",
|
|
378
|
+
raising_cell: null,
|
|
379
|
+
},
|
|
380
|
+
],
|
|
381
|
+
mimetype: "application/vnd.marimo+error",
|
|
382
|
+
timestamp: Date.now(),
|
|
383
|
+
},
|
|
384
|
+
consoleOutputs: [
|
|
385
|
+
{
|
|
386
|
+
channel: "stderr",
|
|
387
|
+
data: '<pre>ValueError: boom\n File "notebook.py", line 1</pre>',
|
|
388
|
+
mimetype: "application/vnd.marimo+traceback",
|
|
389
|
+
timestamp: Date.now(),
|
|
390
|
+
},
|
|
391
|
+
],
|
|
392
|
+
};
|
|
393
|
+
store.set(notebookAtom, notebookState);
|
|
394
|
+
|
|
395
|
+
const items = provider.getItems();
|
|
396
|
+
const context = provider.formatContext(items[1]);
|
|
397
|
+
expect(context).toContain("boom");
|
|
398
|
+
expect(context).toContain("ValueError: boom");
|
|
399
|
+
expect(context).toContain('File "notebook.py", line 1');
|
|
400
|
+
expect(items[1].description).toBe("boom");
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it("does not duplicate console traceback when error already has one", () => {
|
|
404
|
+
const cellId = CellIdClass.create();
|
|
405
|
+
const notebookState = MockNotebook.notebookState({
|
|
406
|
+
cellData: {
|
|
407
|
+
[cellId]: {
|
|
408
|
+
name: "Cell 1",
|
|
409
|
+
code: "raise ValueError('boom')",
|
|
410
|
+
},
|
|
411
|
+
},
|
|
412
|
+
});
|
|
413
|
+
notebookState.cellRuntime[cellId] = {
|
|
414
|
+
...notebookState.cellRuntime[cellId],
|
|
415
|
+
output: {
|
|
416
|
+
channel: "marimo-error",
|
|
417
|
+
data: [
|
|
418
|
+
{
|
|
419
|
+
type: "exception",
|
|
420
|
+
msg: "boom",
|
|
421
|
+
exception_type: "ValueError",
|
|
422
|
+
raising_cell: null,
|
|
423
|
+
traceback: "<pre>embedded line 1</pre>",
|
|
424
|
+
},
|
|
425
|
+
],
|
|
426
|
+
mimetype: "application/vnd.marimo+error",
|
|
427
|
+
timestamp: Date.now(),
|
|
428
|
+
},
|
|
429
|
+
consoleOutputs: [
|
|
430
|
+
{
|
|
431
|
+
channel: "stderr",
|
|
432
|
+
data: "<pre>console line 1</pre>",
|
|
433
|
+
mimetype: "application/vnd.marimo+traceback",
|
|
434
|
+
timestamp: Date.now(),
|
|
435
|
+
},
|
|
436
|
+
],
|
|
437
|
+
};
|
|
438
|
+
store.set(notebookAtom, notebookState);
|
|
439
|
+
|
|
440
|
+
const items = provider.getItems();
|
|
441
|
+
if (items[1].data.type !== "cell-error") {
|
|
442
|
+
throw new Error("Expected cell-error item");
|
|
443
|
+
}
|
|
444
|
+
expect(items[1].data.error.tracebackHtml).toBeUndefined();
|
|
445
|
+
|
|
446
|
+
const context = provider.formatContext(items[1]);
|
|
447
|
+
expect(context).toContain("embedded line 1");
|
|
448
|
+
expect(context).not.toContain("console line 1");
|
|
449
|
+
expect(items[1].description).toBe("boom");
|
|
266
450
|
});
|
|
267
451
|
});
|
|
268
452
|
|
|
@@ -319,7 +503,8 @@ describe("ErrorContextProvider", () => {
|
|
|
319
503
|
testStore.set(notebookAtom, notebookState);
|
|
320
504
|
|
|
321
505
|
const items = testProvider.getItems();
|
|
322
|
-
|
|
506
|
+
// One "all-errors" item plus one per-cell error item.
|
|
507
|
+
expect(items).toHaveLength(2);
|
|
323
508
|
const context = testProvider.formatContext(items[0]);
|
|
324
509
|
expect(context).toContain(expected);
|
|
325
510
|
}
|
|
@@ -8,6 +8,7 @@ import type { CellId } from "@/core/cells/ids";
|
|
|
8
8
|
import { LanguageAdapters } from "@/core/codemirror/language/LanguageAdapters";
|
|
9
9
|
import { renderDatasourceInfo } from "@/core/codemirror/language/languages/sql/renderers";
|
|
10
10
|
import {
|
|
11
|
+
allTablesAtom,
|
|
11
12
|
type ConnectionsMap,
|
|
12
13
|
type DatasetTablesMap,
|
|
13
14
|
dataSourceConnectionsAtom,
|
|
@@ -18,7 +19,7 @@ import {
|
|
|
18
19
|
INTERNAL_SQL_ENGINES,
|
|
19
20
|
} from "@/core/datasets/engines";
|
|
20
21
|
import type { DataSourceConnection, DataTable } from "@/core/kernel/messages";
|
|
21
|
-
import {
|
|
22
|
+
import type { JotaiStore } from "@/core/state/jotai";
|
|
22
23
|
import type { AIContextItem } from "../registry";
|
|
23
24
|
import { AIContextProvider } from "../registry";
|
|
24
25
|
import { contextToXml } from "../utils";
|
|
@@ -148,7 +149,10 @@ export class DatasourceContextProvider extends AIContextProvider<DatasourceConte
|
|
|
148
149
|
}
|
|
149
150
|
}
|
|
150
151
|
|
|
151
|
-
export function getDatasourceContext(
|
|
152
|
+
export function getDatasourceContext(
|
|
153
|
+
cellId: CellId,
|
|
154
|
+
store: JotaiStore,
|
|
155
|
+
): string | null {
|
|
152
156
|
const cellData = store.get(cellDataAtom(cellId));
|
|
153
157
|
const code = cellData?.code;
|
|
154
158
|
if (!code || code.trim() === "") {
|
|
@@ -164,3 +168,24 @@ export function getDatasourceContext(cellId: CellId): string | null {
|
|
|
164
168
|
}
|
|
165
169
|
return null;
|
|
166
170
|
}
|
|
171
|
+
|
|
172
|
+
export function formatDatasourceContextForCell(
|
|
173
|
+
cellId: CellId,
|
|
174
|
+
store: JotaiStore,
|
|
175
|
+
): string | null {
|
|
176
|
+
const mention = getDatasourceContext(cellId, store);
|
|
177
|
+
if (!mention) {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const connectionsMap = store.get(dataSourceConnectionsAtom).connectionsMap;
|
|
182
|
+
const tablesMap = store.get(allTablesAtom);
|
|
183
|
+
const provider = new DatasourceContextProvider(connectionsMap, tablesMap);
|
|
184
|
+
const uri = mention.slice(1);
|
|
185
|
+
const item = provider.getItems().find((candidate) => candidate.uri === uri);
|
|
186
|
+
if (!item) {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return provider.formatContext(item);
|
|
191
|
+
}
|