@marimo-team/islands 0.25.3-dev250 → 0.25.3-dev251
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-BBihxHrr.js → ConnectedDataExplorerComponent-DGOAQFtf.js} +11 -11
- package/dist/{ErrorBoundary-Ks8L_DFj.js → ErrorBoundary-CZ19wGWT.js} +7 -7
- package/dist/{abnfDiagram-VCTEODGH-DBcJVxYP.js → abnfDiagram-VCTEODGH-DzrZZXRD.js} +2 -2
- package/dist/{any-language-editor-jGaR0EAo.js → any-language-editor-Be7dA2VK.js} +3 -3
- package/dist/{architectureDiagram-5GKGNRK7-BcHZXF4S.js → architectureDiagram-5GKGNRK7-iF_snyFL.js} +1 -1
- package/dist/{button-B6RjDKJx.js → button-0-RqY1DW.js} +2 -1
- package/dist/{capabilities-CkVzWJEX.js → capabilities-B6yy_CBN.js} +1 -1
- package/dist/{chat-ui-DgaVxFrc.js → chat-ui-zbfp80GW.js} +1547 -1482
- package/dist/{check-Z_vtI3OL.js → check-CSvxZk5I.js} +1 -1
- package/dist/{chunk-IMKFNOWR-CTZlwBAA.js → chunk-IMKFNOWR-B7xytD4B.js} +1 -1
- package/dist/{chunk-SHT3W25Y-53g_RZHm.js → chunk-SHT3W25Y-I8ZFP5L1.js} +1 -1
- package/dist/{chunk-SVP7TREG-P6abNGV_.js → chunk-SVP7TREG-Di-uAPdT.js} +1 -1
- package/dist/{chunk-TICWLB2K-BTJJqkyF.js → chunk-TICWLB2K-BFpUy0ud.js} +1 -1
- package/dist/{classDiagram-ZZMXUADV-BUGqOrTm.js → classDiagram-ZZMXUADV-Cqm4JGpx.js} +2 -2
- package/dist/{classDiagram-v2-VYDZK3BY-CXpQvO-s.js → classDiagram-v2-VYDZK3BY-DFHRJpN3.js} +2 -2
- package/dist/{code-visibility-CCCqFZYh.js → code-visibility-D2D_sL1T.js} +4 -4
- package/dist/{common-JXDK9IjO.js → common-DSIM5DHJ.js} +262 -262
- package/dist/{config-DbjzjwxX.js → config-mwEjAmvP.js} +2 -2
- package/dist/{copy-icon-CG57cxwb.js → copy-icon-DD8bdw4L.js} +3 -3
- package/dist/{createReducer-DePYJ0F3.js → createReducer-Bx81cw8_.js} +1 -1
- package/dist/{cynefinDiagram-5FMLGOSQ-DOcnl8wW.js → cynefinDiagram-5FMLGOSQ-cuGOeTBC.js} +1 -1
- package/dist/{dagre-GXQ25YYZ-CuHR9QfG.js → dagre-GXQ25YYZ-DJ76aC4k.js} +1 -1
- package/dist/{data-grid-overlay-editor-QAJSeJpH.js → data-grid-overlay-editor-ByfOIO6H.js} +1 -1
- package/dist/{diagram-S7CK7UJ4-BPiG4kso.js → diagram-S7CK7UJ4-DLLc0sw7.js} +1 -1
- package/dist/{diagram-UQ7AKVKN-_piBOvOC.js → diagram-UQ7AKVKN-Tz_d6qmk.js} +1 -1
- package/dist/{diagram-VX7I27RA-DCzVeJ8M.js → diagram-VX7I27RA-D7SzPrDN.js} +1 -1
- package/dist/{diagram-Z3DM3KII-D64CF1jQ.js → diagram-Z3DM3KII-qr37XV58.js} +1 -1
- package/dist/{dialog-DsZ49mmU.js → dialog-BHYPX87J.js} +3 -3
- package/dist/{dropdown-menu-BRiE7Vnp.js → dropdown-menu-Blu7V6Mk.js} +6 -6
- package/dist/{ebnfDiagram-PWID7BFC-BapQPML-.js → ebnfDiagram-PWID7BFC-BKOQb4J0.js} +2 -2
- package/dist/{erDiagram-RLTQ6QDP-CNQvC2_Q.js → erDiagram-RLTQ6QDP-CM15Vq68.js} +1 -1
- package/dist/{error-banner-IsonnvA0.js → error-banner-DbRFOd2O.js} +2 -2
- package/dist/{error-entries-CRPfxMqm.js → error-entries-BuIE3Ius.js} +9 -9
- package/dist/{feedback-button-Bg7zGcid.js → feedback-button-VT03EJ1h.js} +7 -7
- package/dist/{flowDiagram-HODETNUW-BBt6S6oe.js → flowDiagram-HODETNUW-Dt3N5yFT.js} +2 -2
- package/dist/{formats-BK4VefeF.js → formats-gGospKWJ.js} +2 -2
- package/dist/{glide-data-editor-BuFe7QfV.js → glide-data-editor-C94G7kD7.js} +10 -10
- package/dist/{glide-utils-CQddGpa-.js → glide-utils-qbkRZpEy.js} +3 -3
- package/dist/{globals-8EK96XwF.js → globals-D7JZ5hgJ.js} +2 -2
- package/dist/{html-to-image-Gx4q3ZoD.js → html-to-image-BXrAd5q8.js} +460 -455
- package/dist/{infoDiagram-27XIBGKW-Hd8Ifz82.js → infoDiagram-27XIBGKW-C7axJMCl.js} +1 -1
- package/dist/{ishikawaDiagram-5VMMS53U-CP7hnHWx.js → ishikawaDiagram-5VMMS53U-CdC9Eur7.js} +1 -1
- package/dist/{kanban-definition-UXKFOSKX-CX91sa9b.js → kanban-definition-UXKFOSKX-BHnMIgD7.js} +1 -1
- package/dist/{loader-BisjOLiW.js → loader-BVEtQ4F3.js} +1 -1
- package/dist/main.js +236 -236
- package/dist/{mermaid-Djb3Ni9t.js → mermaid-DFYN_PXd.js} +35 -35
- package/dist/{mindmap-definition-YA3MSWOX-DgCNo07O.js → mindmap-definition-YA3MSWOX-BagHoCGU.js} +1 -1
- package/dist/{number-overlay-editor-BDJF1c8h.js → number-overlay-editor-CdcscCHp.js} +1 -1
- package/dist/{pegDiagram-XKGWAZYB-SGJhnIKo.js → pegDiagram-XKGWAZYB-8IIZczip.js} +2 -2
- package/dist/{pieDiagram-E7YTZNPT-1stXIWCQ.js → pieDiagram-E7YTZNPT-CE6dKBIr.js} +1 -1
- package/dist/{process-output-D2ISIyIs.js → process-output-D2ZjxrHe.js} +4 -4
- package/dist/{railroadDiagram-O6MQD6OU-DY1mojzB.js → railroadDiagram-O6MQD6OU-CkexPq3b.js} +2 -2
- package/dist/{requirementDiagram-BXWQKSXE-gHzhUp0D.js → requirementDiagram-BXWQKSXE-B0-OkuAe.js} +1 -1
- package/dist/{reveal-component-B_hNJfj-.js → reveal-component-Cr9euMM2.js} +8 -8
- package/dist/{select-BSxlITBo.js → select-CcidPqlM.js} +5 -5
- package/dist/{sequenceDiagram-WJ2MYXX4-rjPtD9fB.js → sequenceDiagram-WJ2MYXX4-Dxysg8DX.js} +1 -1
- package/dist/{spec-Dq8RNplp.js → spec-ZwfZcgsm.js} +4 -4
- package/dist/{stateDiagram-D77RDMKH-DMNxjUxV.js → stateDiagram-D77RDMKH-DTEN3-OJ.js} +2 -2
- package/dist/{stateDiagram-v2-MP3YSRHH-CumAaqGo.js → stateDiagram-v2-MP3YSRHH-vktk3Xc5.js} +2 -2
- package/dist/{strings-BGW-fXVJ.js → strings-BYJNyKmg.js} +3 -3
- package/dist/style.css +1 -1
- package/dist/{swimlanes-42K2YHIH-BU14_Tej.js → swimlanes-42K2YHIH-Cjveh9OM.js} +1 -1
- package/dist/{swimlanesDiagram-VR7AAH4N-CieAnPqJ.js → swimlanesDiagram-VR7AAH4N-Bdr7Ev_2.js} +2 -2
- package/dist/{swiper-component-Dir1o8ml.js → swiper-component-Dvj-goaD.js} +2 -2
- package/dist/{timeline-definition-24CTP7MA-CrVj4E1K.js → timeline-definition-24CTP7MA-KIuDcefn.js} +1 -1
- package/dist/{toast-BWIPi3aF.js → toast-CkB5Jq9K.js} +4 -4
- package/dist/{tooltip-C3UnvsRe.js → tooltip-Bzl4k00l.js} +2 -2
- package/dist/{use-restore-focus-BWO0z1lo.js → use-restore-focus-CmflVLWJ.js} +1 -1
- package/dist/{useDateFormatter-ChB4eyim.js → useDateFormatter-CYo5fm8s.js} +2 -2
- package/dist/{useIframeCapabilities-cMNQ80CK.js → useIframeCapabilities-BIolGsF1.js} +1 -1
- package/dist/{useLifecycle-BquH9ET-.js → useLifecycle-GNgvNwI-.js} +2 -2
- package/dist/{useTheme-DgOLVISn.js → useTheme-BZ2GGLgm.js} +141 -139
- package/dist/{vega-component-Bwg_Kuh-.js → vega-component-DOtem7Hf.js} +7 -7
- package/dist/{vennDiagram-4TSXK5OY-ZLxQ8yhm.js → vennDiagram-4TSXK5OY-DrIOUHfb.js} +1 -1
- package/dist/{wardleyDiagram-VM6X3IG4-D88pcLY1.js → wardleyDiagram-VM6X3IG4-CJNjf4OH.js} +1 -1
- package/dist/{xychartDiagram-S5SC5T6Z-_13d4g8g.js → xychartDiagram-S5SC5T6Z-DEshFho-.js} +1 -1
- package/package.json +1 -1
- package/src/__tests__/fixtures/pair-config.ts +1 -1
- package/src/components/app-config/__tests__/state.test.tsx +50 -0
- package/src/components/app-config/ai-config.tsx +1 -1
- package/src/components/chat/__tests__/capabilities-popover.test.tsx +0 -1
- package/src/components/chat/__tests__/chat-display.test.tsx +125 -0
- package/src/components/chat/__tests__/chat-markdown.test.tsx +43 -0
- package/src/components/chat/__tests__/chat-utils.test.ts +51 -1
- package/src/components/chat/acp/__tests__/agent-panel-initialization.test.tsx +1 -1
- package/src/components/chat/acp/__tests__/agent-panel.test.tsx +22 -17
- package/src/components/chat/acp/agent-panel.tsx +4 -3
- package/src/components/chat/acp/blocks.tsx +10 -10
- package/src/components/chat/acp/common.tsx +4 -5
- package/src/components/chat/acp/model-selector.tsx +1 -1
- package/src/components/chat/capabilities-popover.tsx +1 -6
- package/src/components/chat/chat-components.tsx +7 -4
- package/src/components/chat/chat-display.tsx +51 -6
- package/src/components/chat/chat-history-popover.tsx +6 -1
- package/src/components/chat/chat-markdown.tsx +15 -0
- package/src/components/chat/chat-panel.tsx +105 -119
- package/src/components/chat/chat-typography.css +108 -0
- package/src/components/chat/chat-utils.ts +8 -3
- package/src/components/chat/notebook-access-selector.tsx +1 -1
- package/src/components/chat/reasoning-accordion.tsx +3 -3
- package/src/components/chat/tool-call/tool-activity-group.tsx +49 -0
- package/src/components/chat/tool-call/tool-history-row.tsx +3 -30
- package/src/components/chat/tool-call/tool-result.tsx +1 -1
- package/src/components/chat/use-chat-generation.ts +19 -0
- package/src/components/editor/actions/__tests__/pair-with-agent-commands.test.ts +18 -37
- package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +5 -8
- package/src/components/editor/actions/__tests__/pair-with-agent.test.tsx +5 -19
- package/src/components/editor/actions/pair-with-agent-commands.ts +0 -1
- package/src/components/editor/actions/pair-with-agent-modal.tsx +7 -11
- package/src/components/editor/actions/pair-with-agent.tsx +3 -31
- package/src/components/editor/actions/useCellActionButton.tsx +1 -1
- package/src/components/editor/ai/ai-completion-editor.tsx +1 -1
- package/src/components/editor/ai/prompt-input.tsx +189 -0
- package/src/components/editor/cell/StagedAICell.tsx +3 -3
- package/src/components/editor/cell/code/cell-editor.tsx +14 -1
- package/src/components/editor/chrome/panels/panel-header.tsx +13 -0
- package/src/components/editor/chrome/types.ts +1 -1
- package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +53 -45
- package/src/components/editor/chrome/wrapper/__tests__/useAskAiAboutCell.test.tsx +58 -0
- package/src/components/editor/chrome/wrapper/app-chrome.tsx +89 -72
- package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +5 -8
- package/src/components/editor/chrome/wrapper/useAskAiAboutCell.ts +36 -0
- package/src/components/editor/renderers/__tests__/add-cell-buttons.test.tsx +18 -0
- package/src/components/editor/renderers/cell-array.tsx +10 -67
- package/src/components/markdown/markdown-renderer.tsx +35 -20
- package/src/components/mcp/mcp-status-indicator.tsx +18 -14
- package/src/core/ai/__tests__/staged-cells.test.ts +1 -467
- package/src/core/ai/context/context.ts +2 -0
- package/src/core/ai/context/providers/__tests__/cell.test.ts +49 -0
- package/src/core/ai/context/providers/cell.ts +54 -0
- package/src/core/ai/staged-cells.ts +4 -280
- package/src/core/ai/state.ts +3 -0
- package/src/core/codemirror/__tests__/setup.test.ts +32 -0
- package/src/core/codemirror/cells/state.ts +1 -0
- package/src/core/codemirror/cm.ts +5 -1
- package/src/core/codemirror/config/types.ts +1 -1
- package/src/core/codemirror/language/languages/python.ts +3 -3
- package/src/core/codemirror/placeholder/__tests__/extensions.test.ts +16 -0
- package/src/core/codemirror/placeholder/extensions.ts +57 -19
- package/src/core/hotkeys/hotkeys.ts +2 -1
- package/src/core/notebook/document/__tests__/codemirror.test.ts +1 -0
- package/src/plugins/impl/chat/__tests__/chat-prompts.test.tsx +1 -1
- package/src/plugins/impl/chat/chat-ui.tsx +1 -1
- package/src/components/editor/ai/__tests__/staged-cell-submission.test.ts +0 -106
- package/src/components/editor/ai/add-cell-with-ai.tsx +0 -506
- package/src/components/editor/ai/staged-cell-submission.ts +0 -46
- package/src/components/editor/cell/__tests__/StagedAICell.test.tsx +0 -66
|
@@ -6,19 +6,14 @@ import { createElement, type ReactNode } from "react";
|
|
|
6
6
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
7
7
|
import { cellId } from "@/__tests__/branded";
|
|
8
8
|
import { CellId } from "@/core/cells/ids";
|
|
9
|
-
import { getCellEditorView } from "../../cells/cells";
|
|
10
|
-
import { prepareCellCodeUpdate } from "../../codemirror/language/cell-code";
|
|
11
9
|
import {
|
|
12
10
|
stagedAICellsAtom,
|
|
13
|
-
stagedGenerationInProgressAtom,
|
|
14
11
|
useStagedAICell,
|
|
15
|
-
useStagedCellGeneration,
|
|
16
12
|
useStagedCells,
|
|
17
13
|
visibleForTesting,
|
|
18
14
|
} from "../staged-cells";
|
|
19
15
|
|
|
20
|
-
const {
|
|
21
|
-
visibleForTesting;
|
|
16
|
+
const { createActions, reducer, initialState } = visibleForTesting;
|
|
22
17
|
|
|
23
18
|
// Mock the dependencies
|
|
24
19
|
const mockCreateCell = vi.fn();
|
|
@@ -83,7 +78,6 @@ describe("staged-cells", () => {
|
|
|
83
78
|
|
|
84
79
|
// Reset the atom state
|
|
85
80
|
store.set(stagedAICellsAtom, new Map());
|
|
86
|
-
store.set(stagedGenerationAtom, null);
|
|
87
81
|
});
|
|
88
82
|
|
|
89
83
|
describe("reducer and actions", () => {
|
|
@@ -379,463 +373,3 @@ describe("staged-cells", () => {
|
|
|
379
373
|
});
|
|
380
374
|
});
|
|
381
375
|
});
|
|
382
|
-
|
|
383
|
-
describe("staged cell generation", () => {
|
|
384
|
-
let store: ReturnType<typeof getDefaultStore>;
|
|
385
|
-
const renderGenerationHook = () =>
|
|
386
|
-
renderHook(() => useStagedCellGeneration(store));
|
|
387
|
-
|
|
388
|
-
beforeEach(() => {
|
|
389
|
-
store = getDefaultStore();
|
|
390
|
-
vi.clearAllMocks();
|
|
391
|
-
vi.mocked(CellId.create).mockReset();
|
|
392
|
-
store.set(stagedAICellsAtom, new Map());
|
|
393
|
-
store.set(stagedGenerationAtom, null);
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
it("should begin generation", () => {
|
|
397
|
-
const { result } = renderGenerationHook();
|
|
398
|
-
result.current.beginStagedCellGeneration();
|
|
399
|
-
|
|
400
|
-
// No cell or cell update should have been called
|
|
401
|
-
expect(mockCreateCell).not.toHaveBeenCalled();
|
|
402
|
-
expect(prepareCellCodeUpdate).not.toHaveBeenCalled();
|
|
403
|
-
expect(mockSetCellCode).not.toHaveBeenCalled();
|
|
404
|
-
expect(store.get(stagedGenerationInProgressAtom)).toBe(true);
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
it("should mark generation complete after a successful finish", () => {
|
|
408
|
-
const { result } = renderGenerationHook();
|
|
409
|
-
result.current.beginStagedCellGeneration();
|
|
410
|
-
result.current.finishStagedCellGeneration(true);
|
|
411
|
-
|
|
412
|
-
expect(store.get(stagedGenerationInProgressAtom)).toBe(false);
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
it("should not couple unrelated staged-cell removal to generation state", () => {
|
|
416
|
-
const { result: stagedCells } = renderHook(() => useStagedCells(store));
|
|
417
|
-
const { result } = renderGenerationHook();
|
|
418
|
-
result.current.beginStagedCellGeneration();
|
|
419
|
-
|
|
420
|
-
const chatCellId = cellId("chat-edited-cell");
|
|
421
|
-
stagedCells.current.addStagedCell({
|
|
422
|
-
cellId: chatCellId,
|
|
423
|
-
edit: { type: "update_cell", previousCode: "before" },
|
|
424
|
-
});
|
|
425
|
-
stagedCells.current.removeStagedCell(chatCellId);
|
|
426
|
-
|
|
427
|
-
expect(store.get(stagedGenerationInProgressAtom)).toBe(true);
|
|
428
|
-
});
|
|
429
|
-
|
|
430
|
-
it("should only accept cells owned by the completed generation", () => {
|
|
431
|
-
const firstGeneration = renderGenerationHook();
|
|
432
|
-
firstGeneration.result.current.beginStagedCellGeneration();
|
|
433
|
-
|
|
434
|
-
vi.mocked(CellId.create).mockReturnValueOnce(cellId("superseded-cell"));
|
|
435
|
-
firstGeneration.result.current.onData({
|
|
436
|
-
type: "data-notebook-cells-completion",
|
|
437
|
-
data: {
|
|
438
|
-
cells: [{ language: "python", code: "old" }],
|
|
439
|
-
},
|
|
440
|
-
});
|
|
441
|
-
firstGeneration.result.current.finishStagedCellGeneration(true);
|
|
442
|
-
|
|
443
|
-
const activeGeneration = renderGenerationHook();
|
|
444
|
-
activeGeneration.result.current.beginStagedCellGeneration();
|
|
445
|
-
vi.mocked(CellId.create).mockReturnValueOnce(cellId("active-cell"));
|
|
446
|
-
activeGeneration.result.current.onData({
|
|
447
|
-
type: "data-notebook-cells-completion",
|
|
448
|
-
data: {
|
|
449
|
-
cells: [{ language: "python", code: "active" }],
|
|
450
|
-
},
|
|
451
|
-
});
|
|
452
|
-
activeGeneration.result.current.finishStagedCellGeneration(true);
|
|
453
|
-
|
|
454
|
-
expect(firstGeneration.result.current.hasOwnedStagedCells()).toBe(false);
|
|
455
|
-
expect(firstGeneration.result.current.acceptOwnedStagedCells()).toBe(false);
|
|
456
|
-
expect(store.get(stagedAICellsAtom)).toEqual(
|
|
457
|
-
new Map([
|
|
458
|
-
[cellId("superseded-cell"), { type: "add_cell" }],
|
|
459
|
-
[cellId("active-cell"), { type: "add_cell" }],
|
|
460
|
-
]),
|
|
461
|
-
);
|
|
462
|
-
|
|
463
|
-
expect(activeGeneration.result.current.hasOwnedStagedCells()).toBe(true);
|
|
464
|
-
expect(activeGeneration.result.current.acceptOwnedStagedCells()).toBe(true);
|
|
465
|
-
expect(store.get(stagedAICellsAtom)).toEqual(
|
|
466
|
-
new Map([[cellId("superseded-cell"), { type: "add_cell" }]]),
|
|
467
|
-
);
|
|
468
|
-
});
|
|
469
|
-
|
|
470
|
-
it("should replace only the previous generation owned by this hook", () => {
|
|
471
|
-
const { result: stagedCells } = renderHook(() => useStagedCells(store));
|
|
472
|
-
const { result } = renderGenerationHook();
|
|
473
|
-
stagedCells.current.addStagedCell({
|
|
474
|
-
cellId: cellId("chat-edited-cell"),
|
|
475
|
-
edit: { type: "update_cell", previousCode: "before" },
|
|
476
|
-
});
|
|
477
|
-
result.current.beginStagedCellGeneration();
|
|
478
|
-
|
|
479
|
-
vi.mocked(CellId.create).mockReturnValueOnce(cellId("generated-cell"));
|
|
480
|
-
result.current.onData({
|
|
481
|
-
type: "data-notebook-cells-completion",
|
|
482
|
-
data: {
|
|
483
|
-
cells: [{ language: "python", code: "generated" }],
|
|
484
|
-
},
|
|
485
|
-
});
|
|
486
|
-
result.current.finishStagedCellGeneration(true);
|
|
487
|
-
mockDeleteCellCallback.mockClear();
|
|
488
|
-
|
|
489
|
-
result.current.beginStagedCellGeneration();
|
|
490
|
-
|
|
491
|
-
expect(mockDeleteCellCallback).toHaveBeenCalledExactlyOnceWith({
|
|
492
|
-
cellId: cellId("generated-cell"),
|
|
493
|
-
});
|
|
494
|
-
expect(store.get(stagedAICellsAtom)).toEqual(
|
|
495
|
-
new Map([
|
|
496
|
-
[
|
|
497
|
-
cellId("chat-edited-cell"),
|
|
498
|
-
{ type: "update_cell", previousCode: "before" },
|
|
499
|
-
],
|
|
500
|
-
]),
|
|
501
|
-
);
|
|
502
|
-
expect(store.get(stagedGenerationInProgressAtom)).toBe(true);
|
|
503
|
-
});
|
|
504
|
-
|
|
505
|
-
it("should leave staged cells from another workflow untouched when accepting", () => {
|
|
506
|
-
const { result: stagedCells } = renderHook(() => useStagedCells(store));
|
|
507
|
-
const { result } = renderGenerationHook();
|
|
508
|
-
result.current.beginStagedCellGeneration();
|
|
509
|
-
|
|
510
|
-
vi.mocked(CellId.create).mockReturnValueOnce(cellId("generated-cell"));
|
|
511
|
-
result.current.onData({
|
|
512
|
-
type: "data-notebook-cells-completion",
|
|
513
|
-
data: {
|
|
514
|
-
cells: [{ language: "python", code: "generated" }],
|
|
515
|
-
},
|
|
516
|
-
});
|
|
517
|
-
result.current.finishStagedCellGeneration(true);
|
|
518
|
-
stagedCells.current.addStagedCell({
|
|
519
|
-
cellId: cellId("chat-edited-cell"),
|
|
520
|
-
edit: { type: "update_cell", previousCode: "before" },
|
|
521
|
-
});
|
|
522
|
-
|
|
523
|
-
expect(result.current.acceptOwnedStagedCells()).toBe(true);
|
|
524
|
-
expect(store.get(stagedAICellsAtom)).toEqual(
|
|
525
|
-
new Map([
|
|
526
|
-
[
|
|
527
|
-
cellId("chat-edited-cell"),
|
|
528
|
-
{ type: "update_cell", previousCode: "before" },
|
|
529
|
-
],
|
|
530
|
-
]),
|
|
531
|
-
);
|
|
532
|
-
});
|
|
533
|
-
|
|
534
|
-
it("should only discard the generation's remaining staged cells", () => {
|
|
535
|
-
const { result: stagedCells } = renderHook(() => useStagedCells(store));
|
|
536
|
-
const { result } = renderGenerationHook();
|
|
537
|
-
result.current.beginStagedCellGeneration();
|
|
538
|
-
|
|
539
|
-
vi.mocked(CellId.create)
|
|
540
|
-
.mockReturnValueOnce(cellId("accepted-cell"))
|
|
541
|
-
.mockReturnValueOnce(cellId("pending-cell"));
|
|
542
|
-
result.current.onData({
|
|
543
|
-
type: "data-notebook-cells-completion",
|
|
544
|
-
data: {
|
|
545
|
-
cells: [
|
|
546
|
-
{ language: "python", code: "accepted" },
|
|
547
|
-
{ language: "python", code: "pending" },
|
|
548
|
-
],
|
|
549
|
-
},
|
|
550
|
-
});
|
|
551
|
-
result.current.finishStagedCellGeneration(true);
|
|
552
|
-
stagedCells.current.removeStagedCell(cellId("accepted-cell"));
|
|
553
|
-
stagedCells.current.addStagedCell({
|
|
554
|
-
cellId: cellId("chat-edited-cell"),
|
|
555
|
-
edit: { type: "update_cell", previousCode: "before" },
|
|
556
|
-
});
|
|
557
|
-
|
|
558
|
-
expect(result.current.discardOwnedStagedCells()).toBe(true);
|
|
559
|
-
expect(mockDeleteCellCallback).not.toHaveBeenCalledWith({
|
|
560
|
-
cellId: cellId("accepted-cell"),
|
|
561
|
-
});
|
|
562
|
-
expect(mockDeleteCellCallback).toHaveBeenCalledWith({
|
|
563
|
-
cellId: cellId("pending-cell"),
|
|
564
|
-
});
|
|
565
|
-
expect(mockDeleteCellCallback).not.toHaveBeenCalledWith({
|
|
566
|
-
cellId: cellId("chat-edited-cell"),
|
|
567
|
-
});
|
|
568
|
-
expect(store.get(stagedAICellsAtom)).toEqual(
|
|
569
|
-
new Map([
|
|
570
|
-
[
|
|
571
|
-
cellId("chat-edited-cell"),
|
|
572
|
-
{ type: "update_cell", previousCode: "before" },
|
|
573
|
-
],
|
|
574
|
-
]),
|
|
575
|
-
);
|
|
576
|
-
});
|
|
577
|
-
|
|
578
|
-
it.each([
|
|
579
|
-
{ name: "no cells", cells: [] },
|
|
580
|
-
{
|
|
581
|
-
name: "an empty cell",
|
|
582
|
-
cells: [{ language: "python" as const, code: "" }],
|
|
583
|
-
},
|
|
584
|
-
])("should reject $name", ({ cells }) => {
|
|
585
|
-
const { result } = renderGenerationHook();
|
|
586
|
-
result.current.beginStagedCellGeneration();
|
|
587
|
-
|
|
588
|
-
expect(() =>
|
|
589
|
-
result.current.onData({
|
|
590
|
-
type: "data-notebook-cells-completion",
|
|
591
|
-
data: { cells },
|
|
592
|
-
}),
|
|
593
|
-
).toThrow(/cells/i);
|
|
594
|
-
});
|
|
595
|
-
|
|
596
|
-
it("should create cells from a validated completion", () => {
|
|
597
|
-
const { result } = renderGenerationHook();
|
|
598
|
-
result.current.beginStagedCellGeneration();
|
|
599
|
-
|
|
600
|
-
// Mock CellId.create to return a predictable ID
|
|
601
|
-
const mockCellId = cellId("mock-cell-id");
|
|
602
|
-
vi.mocked(CellId.create).mockReturnValue(mockCellId);
|
|
603
|
-
|
|
604
|
-
result.current.onData({
|
|
605
|
-
type: "data-notebook-cells-completion",
|
|
606
|
-
data: {
|
|
607
|
-
cells: [{ language: "python", code: "print('```')" }],
|
|
608
|
-
},
|
|
609
|
-
});
|
|
610
|
-
|
|
611
|
-
expect(mockCreateCell).toHaveBeenCalledWith({
|
|
612
|
-
code: "print('```')",
|
|
613
|
-
id: "mock-cell-id",
|
|
614
|
-
});
|
|
615
|
-
});
|
|
616
|
-
|
|
617
|
-
it("should create multiple cells", () => {
|
|
618
|
-
const { result } = renderGenerationHook();
|
|
619
|
-
result.current.beginStagedCellGeneration();
|
|
620
|
-
|
|
621
|
-
vi.mocked(CellId.create)
|
|
622
|
-
.mockReturnValueOnce(cellId("first-cell"))
|
|
623
|
-
.mockReturnValueOnce(cellId("second-cell"));
|
|
624
|
-
|
|
625
|
-
result.current.onData({
|
|
626
|
-
type: "data-notebook-cells-completion",
|
|
627
|
-
data: {
|
|
628
|
-
cells: [
|
|
629
|
-
{ language: "python", code: "value = 1" },
|
|
630
|
-
{ language: "python", code: "value" },
|
|
631
|
-
],
|
|
632
|
-
},
|
|
633
|
-
});
|
|
634
|
-
|
|
635
|
-
expect(mockCreateCell).toHaveBeenCalledWith(
|
|
636
|
-
expect.objectContaining({
|
|
637
|
-
code: "value = 1",
|
|
638
|
-
id: "first-cell",
|
|
639
|
-
}),
|
|
640
|
-
);
|
|
641
|
-
expect(mockCreateCell).toHaveBeenCalledWith(
|
|
642
|
-
expect.objectContaining({
|
|
643
|
-
code: "value",
|
|
644
|
-
id: "second-cell",
|
|
645
|
-
}),
|
|
646
|
-
);
|
|
647
|
-
});
|
|
648
|
-
|
|
649
|
-
it("should apply cumulative snapshots without recreating cells", () => {
|
|
650
|
-
const { result } = renderGenerationHook();
|
|
651
|
-
result.current.beginStagedCellGeneration();
|
|
652
|
-
|
|
653
|
-
vi.mocked(CellId.create)
|
|
654
|
-
.mockReturnValueOnce(cellId("first-cell"))
|
|
655
|
-
.mockReturnValueOnce(cellId("second-cell"));
|
|
656
|
-
|
|
657
|
-
result.current.onData({
|
|
658
|
-
type: "data-notebook-cells-completion",
|
|
659
|
-
data: {
|
|
660
|
-
cells: [{ language: "python", code: "value" }],
|
|
661
|
-
},
|
|
662
|
-
});
|
|
663
|
-
result.current.onData({
|
|
664
|
-
type: "data-notebook-cells-completion",
|
|
665
|
-
data: {
|
|
666
|
-
cells: [
|
|
667
|
-
{ language: "python", code: "value = 1" },
|
|
668
|
-
{ language: "python", code: "value" },
|
|
669
|
-
],
|
|
670
|
-
},
|
|
671
|
-
});
|
|
672
|
-
result.current.onData({
|
|
673
|
-
type: "data-notebook-cells-completion",
|
|
674
|
-
data: {
|
|
675
|
-
cells: [
|
|
676
|
-
{ language: "python", code: "value = 1" },
|
|
677
|
-
{ language: "python", code: "value + 1" },
|
|
678
|
-
],
|
|
679
|
-
},
|
|
680
|
-
});
|
|
681
|
-
|
|
682
|
-
expect(mockCreateCell).toHaveBeenCalledTimes(2);
|
|
683
|
-
expect(store.get(stagedAICellsAtom).has(cellId("first-cell"))).toBe(true);
|
|
684
|
-
expect(store.get(stagedAICellsAtom).has(cellId("second-cell"))).toBe(true);
|
|
685
|
-
expect(prepareCellCodeUpdate).toHaveBeenCalledTimes(2);
|
|
686
|
-
expect(
|
|
687
|
-
mockCellHandle.current.editorViewOrNull.dispatch,
|
|
688
|
-
).toHaveBeenCalledTimes(2);
|
|
689
|
-
expect(mockSetCellCode).not.toHaveBeenCalled();
|
|
690
|
-
});
|
|
691
|
-
|
|
692
|
-
it("writes through the document when the cell editor is not mounted", () => {
|
|
693
|
-
const { result } = renderGenerationHook();
|
|
694
|
-
result.current.beginStagedCellGeneration();
|
|
695
|
-
|
|
696
|
-
vi.mocked(CellId.create).mockReturnValueOnce(cellId("unmounted-cell"));
|
|
697
|
-
result.current.onData({
|
|
698
|
-
type: "data-notebook-cells-completion",
|
|
699
|
-
data: {
|
|
700
|
-
cells: [{ language: "python", code: "value" }],
|
|
701
|
-
},
|
|
702
|
-
});
|
|
703
|
-
|
|
704
|
-
vi.mocked(getCellEditorView).mockReturnValueOnce(null);
|
|
705
|
-
result.current.onData({
|
|
706
|
-
type: "data-notebook-cells-completion",
|
|
707
|
-
data: {
|
|
708
|
-
cells: [{ language: "python", code: "value = 1" }],
|
|
709
|
-
},
|
|
710
|
-
});
|
|
711
|
-
|
|
712
|
-
expect(mockSetCellCode).toHaveBeenCalledWith({
|
|
713
|
-
cellId: cellId("unmounted-cell"),
|
|
714
|
-
code: "value = 1",
|
|
715
|
-
});
|
|
716
|
-
});
|
|
717
|
-
|
|
718
|
-
it("should remove trailing cells when a retry produces a shorter snapshot", () => {
|
|
719
|
-
const { result } = renderGenerationHook();
|
|
720
|
-
result.current.beginStagedCellGeneration();
|
|
721
|
-
|
|
722
|
-
vi.mocked(CellId.create)
|
|
723
|
-
.mockReturnValueOnce(cellId("first-cell"))
|
|
724
|
-
.mockReturnValueOnce(cellId("second-cell"));
|
|
725
|
-
|
|
726
|
-
result.current.onData({
|
|
727
|
-
type: "data-notebook-cells-completion",
|
|
728
|
-
data: {
|
|
729
|
-
cells: [
|
|
730
|
-
{ language: "python", code: "value = 1" },
|
|
731
|
-
{ language: "python", code: "value" },
|
|
732
|
-
],
|
|
733
|
-
},
|
|
734
|
-
});
|
|
735
|
-
result.current.onData({
|
|
736
|
-
type: "data-notebook-cells-completion",
|
|
737
|
-
data: {
|
|
738
|
-
cells: [{ language: "python", code: "replacement = 2" }],
|
|
739
|
-
},
|
|
740
|
-
});
|
|
741
|
-
|
|
742
|
-
expect(mockDeleteCellCallback).toHaveBeenCalledWith({
|
|
743
|
-
cellId: cellId("second-cell"),
|
|
744
|
-
});
|
|
745
|
-
expect(store.get(stagedAICellsAtom).has(cellId("first-cell"))).toBe(true);
|
|
746
|
-
expect(store.get(stagedAICellsAtom).has(cellId("second-cell"))).toBe(false);
|
|
747
|
-
});
|
|
748
|
-
|
|
749
|
-
it("should remove a provisional marimo import when a retry no longer needs it", () => {
|
|
750
|
-
const { result } = renderGenerationHook();
|
|
751
|
-
result.current.beginStagedCellGeneration();
|
|
752
|
-
|
|
753
|
-
vi.mocked(CellId.create)
|
|
754
|
-
.mockReturnValueOnce(cellId("generated-cell"))
|
|
755
|
-
.mockReturnValueOnce(cellId("marimo-import"));
|
|
756
|
-
|
|
757
|
-
result.current.onData({
|
|
758
|
-
type: "data-notebook-cells-completion",
|
|
759
|
-
data: {
|
|
760
|
-
cells: [{ language: "sql", code: "result = mo.sql('SELECT 1')" }],
|
|
761
|
-
},
|
|
762
|
-
});
|
|
763
|
-
result.current.onData({
|
|
764
|
-
type: "data-notebook-cells-completion",
|
|
765
|
-
data: {
|
|
766
|
-
cells: [{ language: "python", code: "result = 1" }],
|
|
767
|
-
},
|
|
768
|
-
});
|
|
769
|
-
|
|
770
|
-
expect(mockDeleteCellCallback).toHaveBeenCalledWith({
|
|
771
|
-
cellId: cellId("marimo-import"),
|
|
772
|
-
});
|
|
773
|
-
expect(store.get(stagedAICellsAtom).has(cellId("marimo-import"))).toBe(
|
|
774
|
-
false,
|
|
775
|
-
);
|
|
776
|
-
});
|
|
777
|
-
|
|
778
|
-
it("should discard provisional cells when generation fails", () => {
|
|
779
|
-
const { result } = renderGenerationHook();
|
|
780
|
-
result.current.beginStagedCellGeneration();
|
|
781
|
-
|
|
782
|
-
vi.mocked(CellId.create).mockReturnValue(cellId("partial-cell"));
|
|
783
|
-
result.current.onData({
|
|
784
|
-
type: "data-notebook-cells-completion",
|
|
785
|
-
data: {
|
|
786
|
-
cells: [{ language: "python", code: "partial" }],
|
|
787
|
-
},
|
|
788
|
-
});
|
|
789
|
-
result.current.finishStagedCellGeneration(false);
|
|
790
|
-
|
|
791
|
-
expect(mockDeleteCellCallback).toHaveBeenCalledWith({
|
|
792
|
-
cellId: cellId("partial-cell"),
|
|
793
|
-
});
|
|
794
|
-
expect(store.get(stagedAICellsAtom)).toEqual(new Map());
|
|
795
|
-
expect(store.get(stagedGenerationInProgressAtom)).toBe(false);
|
|
796
|
-
});
|
|
797
|
-
|
|
798
|
-
it("should not let a superseded generation mutate active cells", () => {
|
|
799
|
-
const firstGeneration = renderGenerationHook();
|
|
800
|
-
firstGeneration.result.current.beginStagedCellGeneration();
|
|
801
|
-
|
|
802
|
-
vi.mocked(CellId.create).mockReturnValueOnce(cellId("superseded-cell"));
|
|
803
|
-
firstGeneration.result.current.onData({
|
|
804
|
-
type: "data-notebook-cells-completion",
|
|
805
|
-
data: {
|
|
806
|
-
cells: [{ language: "python", code: "old" }],
|
|
807
|
-
},
|
|
808
|
-
});
|
|
809
|
-
|
|
810
|
-
const activeGeneration = renderGenerationHook();
|
|
811
|
-
activeGeneration.result.current.beginStagedCellGeneration();
|
|
812
|
-
expect(mockDeleteCellCallback).toHaveBeenCalledWith({
|
|
813
|
-
cellId: cellId("superseded-cell"),
|
|
814
|
-
});
|
|
815
|
-
mockDeleteCellCallback.mockClear();
|
|
816
|
-
|
|
817
|
-
vi.mocked(CellId.create).mockReturnValueOnce(cellId("active-cell"));
|
|
818
|
-
activeGeneration.result.current.onData({
|
|
819
|
-
type: "data-notebook-cells-completion",
|
|
820
|
-
data: {
|
|
821
|
-
cells: [{ language: "python", code: "active" }],
|
|
822
|
-
},
|
|
823
|
-
});
|
|
824
|
-
|
|
825
|
-
firstGeneration.result.current.finishStagedCellGeneration(true);
|
|
826
|
-
firstGeneration.result.current.onData({
|
|
827
|
-
type: "data-notebook-cells-completion",
|
|
828
|
-
data: {
|
|
829
|
-
cells: [{ language: "python", code: "stale" }],
|
|
830
|
-
},
|
|
831
|
-
});
|
|
832
|
-
|
|
833
|
-
expect(mockDeleteCellCallback).not.toHaveBeenCalledWith({
|
|
834
|
-
cellId: cellId("active-cell"),
|
|
835
|
-
});
|
|
836
|
-
expect(store.get(stagedAICellsAtom)).toEqual(
|
|
837
|
-
new Map([[cellId("active-cell"), { type: "add_cell" }]]),
|
|
838
|
-
);
|
|
839
|
-
expect(store.get(stagedGenerationInProgressAtom)).toBe(true);
|
|
840
|
-
});
|
|
841
|
-
});
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
import { getRequestClient } from "@/core/network/requests";
|
|
8
8
|
import type { JotaiStore } from "@/core/state/jotai";
|
|
9
9
|
import { variablesAtom } from "@/core/variables/state";
|
|
10
|
+
import { CellContextProvider } from "./providers/cell";
|
|
10
11
|
import { CellOutputContextProvider } from "./providers/cell-output";
|
|
11
12
|
import { DatasourceContextProvider } from "./providers/datasource";
|
|
12
13
|
import { ErrorContextProvider } from "./providers/error";
|
|
@@ -25,6 +26,7 @@ export function getAIContextRegistry(store: JotaiStore) {
|
|
|
25
26
|
.register(new VariableContextProvider(variables, tablesMap))
|
|
26
27
|
.register(new ErrorContextProvider(store))
|
|
27
28
|
.register(new CellOutputContextProvider(store))
|
|
29
|
+
.register(new CellContextProvider(store))
|
|
28
30
|
.register(
|
|
29
31
|
new DatasourceContextProvider(datasource.connectionsMap, tablesMap),
|
|
30
32
|
);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { createStore } from "jotai";
|
|
4
|
+
import { expect, it } from "vitest";
|
|
5
|
+
import { MockNotebook } from "@/__mocks__/notebook";
|
|
6
|
+
import { notebookAtom } from "@/core/cells/cells";
|
|
7
|
+
import { CellId } from "@/core/cells/ids";
|
|
8
|
+
import { getAIContextRegistry } from "../../context";
|
|
9
|
+
import { CellContextProvider } from "../cell";
|
|
10
|
+
|
|
11
|
+
it("resolves empty cells and reads their latest code when preparing context", () => {
|
|
12
|
+
const store = createStore();
|
|
13
|
+
const cellId = CellId.create();
|
|
14
|
+
const provider = new CellContextProvider(store);
|
|
15
|
+
store.set(
|
|
16
|
+
notebookAtom,
|
|
17
|
+
MockNotebook.notebookState({ cellData: { [cellId]: { code: "" } } }),
|
|
18
|
+
);
|
|
19
|
+
const uri = provider.asURI(cellId);
|
|
20
|
+
expect(getAIContextRegistry(store).formatContextForAI([uri])).toContain(
|
|
21
|
+
"This cell is empty.",
|
|
22
|
+
);
|
|
23
|
+
store.set(
|
|
24
|
+
notebookAtom,
|
|
25
|
+
MockNotebook.notebookState({ cellData: { [cellId]: { code: "x = 1" } } }),
|
|
26
|
+
);
|
|
27
|
+
expect(getAIContextRegistry(store).formatContextForAI([uri])).toContain(
|
|
28
|
+
"x = 1",
|
|
29
|
+
);
|
|
30
|
+
store.set(notebookAtom, MockNotebook.notebookState());
|
|
31
|
+
expect(getAIContextRegistry(store).formatContextForAI([uri])).toBe("");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("completes cell names while inserting stable cell references", () => {
|
|
35
|
+
const store = createStore();
|
|
36
|
+
const cellId = CellId.create();
|
|
37
|
+
store.set(
|
|
38
|
+
notebookAtom,
|
|
39
|
+
MockNotebook.notebookState({
|
|
40
|
+
cellData: { [cellId]: { name: "my_analysis", code: "x = 1" } },
|
|
41
|
+
}),
|
|
42
|
+
);
|
|
43
|
+
const provider = new CellContextProvider(store);
|
|
44
|
+
expect(provider.formatCompletion(provider.getItems()[0])).toMatchObject({
|
|
45
|
+
label: "@my_analysis",
|
|
46
|
+
displayLabel: "my_analysis",
|
|
47
|
+
apply: `@cell://${cellId}`,
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { notebookAtom } from "@/core/cells/cells";
|
|
4
|
+
import { displayCellName } from "@/core/cells/names";
|
|
5
|
+
import type { JotaiStore } from "@/core/state/jotai";
|
|
6
|
+
import { AIContextProvider, type AIContextItem } from "../registry";
|
|
7
|
+
import { contextToXml } from "../utils";
|
|
8
|
+
|
|
9
|
+
interface CellContextItem extends AIContextItem {
|
|
10
|
+
type: "cell";
|
|
11
|
+
data: { cellId: string; code: string };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class CellContextProvider extends AIContextProvider<CellContextItem> {
|
|
15
|
+
public readonly title = "Cells";
|
|
16
|
+
public readonly mentionPrefix = "@";
|
|
17
|
+
public readonly contextType = "cell";
|
|
18
|
+
|
|
19
|
+
private readonly store: JotaiStore;
|
|
20
|
+
|
|
21
|
+
public constructor(store: JotaiStore) {
|
|
22
|
+
super();
|
|
23
|
+
this.store = store;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public getItems(): CellContextItem[] {
|
|
27
|
+
const notebook = this.store.get(notebookAtom);
|
|
28
|
+
return notebook.cellIds.map((cellId, index) => {
|
|
29
|
+
const cell = notebook.cellData[cellId];
|
|
30
|
+
return {
|
|
31
|
+
uri: this.asURI(cellId),
|
|
32
|
+
name: displayCellName(cell.name, index),
|
|
33
|
+
type: this.contextType,
|
|
34
|
+
description: "Cell code",
|
|
35
|
+
data: { cellId, code: cell.code },
|
|
36
|
+
};
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public formatCompletion(item: CellContextItem) {
|
|
41
|
+
return {
|
|
42
|
+
...this.createBasicCompletion(item),
|
|
43
|
+
label: `${this.mentionPrefix}${item.name}`,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public formatContext(item: CellContextItem) {
|
|
48
|
+
return contextToXml({
|
|
49
|
+
type: "cell",
|
|
50
|
+
data: { cellId: item.data.cellId, name: item.name },
|
|
51
|
+
details: item.data.code || "This cell is empty.",
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|