@marimo-team/islands 0.25.3-dev250 → 0.25.3-dev252
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-By2DNeOL.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-Byb3BN4h.js} +4 -4
- package/dist/{common-JXDK9IjO.js → common-CgWyYqpC.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-BP_fYnBa.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-DPWFUVWl.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-GvhMU_kg.js} +10 -10
- package/dist/{glide-utils-CQddGpa-.js → glide-utils-qbkRZpEy.js} +3 -3
- package/dist/{globals-8EK96XwF.js → globals-RlhgvYCL.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-Dg1EfcEs.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-UPs4Rd1M.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
|
@@ -1,30 +1,19 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { useAtomValue } from "jotai";
|
|
4
4
|
import { selectAtom } from "jotai/utils";
|
|
5
|
-
import { useMemo
|
|
5
|
+
import { useMemo } from "react";
|
|
6
6
|
import { useDeleteCellCallback } from "@/components/editor/cell/useDeleteCell";
|
|
7
7
|
import { CellId } from "@/core/cells/ids";
|
|
8
8
|
import { createReducerAndAtoms } from "@/utils/createReducer";
|
|
9
9
|
import { Logger } from "@/utils/Logger";
|
|
10
|
-
import {
|
|
11
|
-
type CompletionDataPart,
|
|
12
|
-
NOTEBOOK_CELLS_COMPLETION_DATA_TYPE,
|
|
13
|
-
type GeneratedCell,
|
|
14
|
-
notebookCellsCompletionSchema,
|
|
15
|
-
} from "./completion-output";
|
|
16
|
-
import { maybeAddMarimoImport } from "../cells/add-missing-import";
|
|
17
10
|
import { getCellEditorView } from "../cells/cells";
|
|
18
|
-
import {
|
|
11
|
+
import { useCellActions } from "../cells/actions";
|
|
19
12
|
import { prepareCellCodeUpdate } from "../codemirror/language/cell-code";
|
|
20
13
|
import type { JotaiStore } from "../state/jotai";
|
|
21
14
|
import type { EditType } from "./tools/edit-notebook-tool";
|
|
22
15
|
|
|
23
|
-
/**
|
|
24
|
-
* Pending AI edits shared by completion, chat, and agent workflows.
|
|
25
|
-
* Generation ownership keeps one workflow from accepting or discarding another's
|
|
26
|
-
* staged cells.
|
|
27
|
-
*/
|
|
16
|
+
/** Pending AI edits shared by chat and agent workflows. */
|
|
28
17
|
|
|
29
18
|
export type Edit =
|
|
30
19
|
| { type: Extract<EditType, "update_cell">; previousCode: string }
|
|
@@ -33,25 +22,6 @@ export type Edit =
|
|
|
33
22
|
|
|
34
23
|
export type StagedAICells = Map<CellId, Edit>;
|
|
35
24
|
|
|
36
|
-
type StagedGeneration =
|
|
37
|
-
| {
|
|
38
|
-
id: symbol;
|
|
39
|
-
status: "in_progress";
|
|
40
|
-
cellIds: readonly CellId[];
|
|
41
|
-
}
|
|
42
|
-
| { id: symbol; status: "complete" };
|
|
43
|
-
|
|
44
|
-
interface OwnedStagedGeneration {
|
|
45
|
-
id: symbol;
|
|
46
|
-
status: "in_progress" | "complete";
|
|
47
|
-
reconciler: StagedCellReconciler;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const stagedGenerationAtom = atom<StagedGeneration | null>(null);
|
|
51
|
-
export const stagedGenerationInProgressAtom = atom(
|
|
52
|
-
(get) => get(stagedGenerationAtom)?.status === "in_progress",
|
|
53
|
-
);
|
|
54
|
-
|
|
55
25
|
const initialState = (): StagedAICells => {
|
|
56
26
|
return new Map();
|
|
57
27
|
};
|
|
@@ -143,255 +113,9 @@ export function useStagedCells(store: JotaiStore) {
|
|
|
143
113
|
};
|
|
144
114
|
}
|
|
145
115
|
|
|
146
|
-
/** Owns the lifecycle of cells created by one structured completion flow. */
|
|
147
|
-
export function useStagedCellGeneration(store: JotaiStore) {
|
|
148
|
-
const stagedCellActions = useStagedCells(store);
|
|
149
|
-
const { createCell } = useCellActions();
|
|
150
|
-
const ownedGeneration = useRef<OwnedStagedGeneration | null>(null);
|
|
151
|
-
// Ownership lives in a ref, so subscribe to its backing atoms to rerender
|
|
152
|
-
// consumers when completion or per-cell acceptance changes.
|
|
153
|
-
useAtomValue(stagedGenerationAtom, { store });
|
|
154
|
-
useAtomValue(stagedAICellsAtom, { store });
|
|
155
|
-
|
|
156
|
-
const discardStagedCellIds = (cellIds: readonly CellId[]) => {
|
|
157
|
-
const stagedAICells = store.get(stagedAICellsAtom);
|
|
158
|
-
for (const cellId of cellIds.toReversed()) {
|
|
159
|
-
if (stagedAICells.has(cellId)) {
|
|
160
|
-
stagedCellActions.deleteStagedCell(cellId);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
const beginStagedCellGeneration = () => {
|
|
166
|
-
const activeGeneration = store.get(stagedGenerationAtom);
|
|
167
|
-
if (activeGeneration?.status === "in_progress") {
|
|
168
|
-
discardStagedCellIds(activeGeneration.cellIds);
|
|
169
|
-
}
|
|
170
|
-
ownedGeneration.current?.reconciler.discard(store.get(stagedAICellsAtom));
|
|
171
|
-
const generationId = Symbol("staged-cell-generation");
|
|
172
|
-
ownedGeneration.current = {
|
|
173
|
-
id: generationId,
|
|
174
|
-
status: "in_progress",
|
|
175
|
-
reconciler: new StagedCellReconciler({
|
|
176
|
-
...stagedCellActions,
|
|
177
|
-
createCell,
|
|
178
|
-
}),
|
|
179
|
-
};
|
|
180
|
-
store.set(stagedGenerationAtom, {
|
|
181
|
-
id: generationId,
|
|
182
|
-
status: "in_progress",
|
|
183
|
-
cellIds: [],
|
|
184
|
-
});
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
const finishStagedCellGeneration = (successful: boolean) => {
|
|
188
|
-
const generation = ownedGeneration.current;
|
|
189
|
-
if (generation?.status !== "in_progress") {
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const stagedGeneration = store.get(stagedGenerationAtom);
|
|
194
|
-
const ownsActiveGeneration =
|
|
195
|
-
stagedGeneration?.id === generation.id &&
|
|
196
|
-
stagedGeneration.status === "in_progress";
|
|
197
|
-
if (!ownsActiveGeneration || !successful) {
|
|
198
|
-
generation.reconciler.discard(store.get(stagedAICellsAtom));
|
|
199
|
-
if (ownsActiveGeneration) {
|
|
200
|
-
store.set(stagedGenerationAtom, null);
|
|
201
|
-
}
|
|
202
|
-
ownedGeneration.current = null;
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
generation.status = "complete";
|
|
207
|
-
store.set(stagedGenerationAtom, {
|
|
208
|
-
id: generation.id,
|
|
209
|
-
status: "complete",
|
|
210
|
-
});
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
const hasOwnedStagedCells = () => {
|
|
214
|
-
const generation = ownedGeneration.current;
|
|
215
|
-
const stagedGeneration = store.get(stagedGenerationAtom);
|
|
216
|
-
return (
|
|
217
|
-
generation !== null &&
|
|
218
|
-
generation.status === "complete" &&
|
|
219
|
-
stagedGeneration?.status === "complete" &&
|
|
220
|
-
stagedGeneration.id === generation.id &&
|
|
221
|
-
generation.reconciler.hasStagedCells(store.get(stagedAICellsAtom))
|
|
222
|
-
);
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
const acceptOwnedStagedCells = () => {
|
|
226
|
-
if (!hasOwnedStagedCells()) {
|
|
227
|
-
return false;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
ownedGeneration.current?.reconciler.accept();
|
|
231
|
-
store.set(stagedGenerationAtom, null);
|
|
232
|
-
ownedGeneration.current = null;
|
|
233
|
-
return true;
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
const discardOwnedStagedCells = () => {
|
|
237
|
-
const generation = ownedGeneration.current;
|
|
238
|
-
const stagedGeneration = store.get(stagedGenerationAtom);
|
|
239
|
-
if (generation === null || stagedGeneration?.id !== generation.id) {
|
|
240
|
-
return false;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
generation.reconciler.discard(store.get(stagedAICellsAtom));
|
|
244
|
-
store.set(stagedGenerationAtom, null);
|
|
245
|
-
ownedGeneration.current = null;
|
|
246
|
-
return true;
|
|
247
|
-
};
|
|
248
|
-
|
|
249
|
-
const onData = (part: CompletionDataPart) => {
|
|
250
|
-
if (part.type !== NOTEBOOK_CELLS_COMPLETION_DATA_TYPE) {
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
const generation = ownedGeneration.current;
|
|
254
|
-
const stagedGeneration = store.get(stagedGenerationAtom);
|
|
255
|
-
if (
|
|
256
|
-
generation?.status !== "in_progress" ||
|
|
257
|
-
stagedGeneration?.id !== generation.id ||
|
|
258
|
-
stagedGeneration.status !== "in_progress"
|
|
259
|
-
) {
|
|
260
|
-
return;
|
|
261
|
-
}
|
|
262
|
-
const completion = notebookCellsCompletionSchema.parse(part.data);
|
|
263
|
-
generation.reconciler.reconcile(completion.cells);
|
|
264
|
-
store.set(stagedGenerationAtom, {
|
|
265
|
-
...stagedGeneration,
|
|
266
|
-
cellIds: generation.reconciler.stagedCellIds(),
|
|
267
|
-
});
|
|
268
|
-
};
|
|
269
|
-
|
|
270
|
-
return {
|
|
271
|
-
beginStagedCellGeneration,
|
|
272
|
-
finishStagedCellGeneration,
|
|
273
|
-
hasOwnedStagedCells,
|
|
274
|
-
acceptOwnedStagedCells,
|
|
275
|
-
discardOwnedStagedCells,
|
|
276
|
-
onData,
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
|
|
280
116
|
export { stagedAICellsAtom };
|
|
281
117
|
export const visibleForTesting = {
|
|
282
|
-
stagedGenerationAtom,
|
|
283
118
|
createActions,
|
|
284
119
|
reducer,
|
|
285
120
|
initialState,
|
|
286
121
|
};
|
|
287
|
-
|
|
288
|
-
interface CreatedCell {
|
|
289
|
-
cellId: CellId;
|
|
290
|
-
code: string;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
interface StagedCellReconcilerActions {
|
|
294
|
-
createStagedCell: (code: string) => CellId;
|
|
295
|
-
updateStagedCell: (opts: UpdateStagedCellAction) => void;
|
|
296
|
-
deleteStagedCell: (cellId: CellId) => void;
|
|
297
|
-
removeStagedCell: (cellId: CellId) => void;
|
|
298
|
-
addStagedCell: (payload: { cellId: CellId; edit: Edit }) => void;
|
|
299
|
-
createCell: CreateCell;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
class StagedCellReconciler {
|
|
303
|
-
private createdCells: CreatedCell[] = [];
|
|
304
|
-
private readonly actions: StagedCellReconcilerActions;
|
|
305
|
-
private marimoImportCellId: CellId | null = null;
|
|
306
|
-
|
|
307
|
-
public constructor(actions: StagedCellReconcilerActions) {
|
|
308
|
-
this.actions = actions;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
public reconcile(completionCells: GeneratedCell[]) {
|
|
312
|
-
for (const [idx, cell] of completionCells.entries()) {
|
|
313
|
-
if (idx < this.createdCells.length) {
|
|
314
|
-
const existingCell = this.createdCells[idx];
|
|
315
|
-
const codeChanged = existingCell.code !== cell.code;
|
|
316
|
-
this.createdCells[idx] = { ...existingCell, code: cell.code };
|
|
317
|
-
if (!codeChanged) {
|
|
318
|
-
continue;
|
|
319
|
-
}
|
|
320
|
-
this.actions.updateStagedCell({
|
|
321
|
-
cellId: existingCell.cellId,
|
|
322
|
-
code: cell.code,
|
|
323
|
-
});
|
|
324
|
-
} else {
|
|
325
|
-
const newCellId = this.actions.createStagedCell(cell.code);
|
|
326
|
-
this.createdCells.push({ cellId: newCellId, code: cell.code });
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
const removedCells = this.createdCells.splice(completionCells.length);
|
|
331
|
-
for (const { cellId } of removedCells.toReversed()) {
|
|
332
|
-
this.actions.deleteStagedCell(cellId);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
this.syncMarimoImport(completionCells);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
public discard(stagedCells: StagedAICells) {
|
|
339
|
-
for (const { cellId } of this.createdCells.toReversed()) {
|
|
340
|
-
if (stagedCells.has(cellId)) {
|
|
341
|
-
this.actions.deleteStagedCell(cellId);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
this.createdCells = [];
|
|
345
|
-
|
|
346
|
-
if (this.marimoImportCellId && stagedCells.has(this.marimoImportCellId)) {
|
|
347
|
-
this.actions.deleteStagedCell(this.marimoImportCellId);
|
|
348
|
-
}
|
|
349
|
-
this.marimoImportCellId = null;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
public accept() {
|
|
353
|
-
for (const cellId of this.stagedCellIds()) {
|
|
354
|
-
this.actions.removeStagedCell(cellId);
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
public hasStagedCells(stagedCells: StagedAICells) {
|
|
359
|
-
return this.stagedCellIds().some((cellId) => stagedCells.has(cellId));
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
public stagedCellIds() {
|
|
363
|
-
const cellIds = this.createdCells.map(({ cellId }) => cellId);
|
|
364
|
-
if (this.marimoImportCellId) {
|
|
365
|
-
cellIds.push(this.marimoImportCellId);
|
|
366
|
-
}
|
|
367
|
-
return cellIds;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
/** Keep the generated marimo import consistent with the latest snapshot. */
|
|
371
|
-
private syncMarimoImport(completionCells: GeneratedCell[]) {
|
|
372
|
-
const requiresMarimo = completionCells.some(
|
|
373
|
-
(cell) => cell.language !== "python",
|
|
374
|
-
);
|
|
375
|
-
if (!requiresMarimo) {
|
|
376
|
-
if (this.marimoImportCellId) {
|
|
377
|
-
this.actions.deleteStagedCell(this.marimoImportCellId);
|
|
378
|
-
this.marimoImportCellId = null;
|
|
379
|
-
}
|
|
380
|
-
return;
|
|
381
|
-
}
|
|
382
|
-
if (this.marimoImportCellId) {
|
|
383
|
-
return;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
const cellId = maybeAddMarimoImport({
|
|
387
|
-
autoInstantiate: false,
|
|
388
|
-
createCell: this.actions.createCell,
|
|
389
|
-
fromCellId: this.createdCells[0]?.cellId,
|
|
390
|
-
before: true,
|
|
391
|
-
});
|
|
392
|
-
if (cellId) {
|
|
393
|
-
this.actions.addStagedCell({ cellId, edit: { type: "add_cell" } });
|
|
394
|
-
this.marimoImportCellId = cellId;
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
}
|
package/src/core/ai/state.ts
CHANGED
|
@@ -31,10 +31,13 @@ export const aiCompletionCellAtom = atom<AiCompletionCell | null>(null);
|
|
|
31
31
|
export interface PendingAiPrompt {
|
|
32
32
|
prompt: string;
|
|
33
33
|
submit: boolean;
|
|
34
|
+
append?: boolean;
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
export const pendingAiPromptAtom = atom<PendingAiPrompt | null>(null);
|
|
37
38
|
|
|
39
|
+
export const chatGenerationInProgressAtom = atom(false);
|
|
40
|
+
|
|
38
41
|
const CHAT_OPTIONS_KEY = "marimo:ai:chatOptions";
|
|
39
42
|
export const chatOptionsAtom = atomWithStorage<Required<ChatOptions>>(
|
|
40
43
|
CHAT_OPTIONS_KEY,
|
|
@@ -40,6 +40,7 @@ function getOpts() {
|
|
|
40
40
|
enableAI: false,
|
|
41
41
|
cellActions: {
|
|
42
42
|
toggleHideCode: namedFunction("toggleHideCode"),
|
|
43
|
+
askAiAboutCell: namedFunction("askAiAboutCell"),
|
|
43
44
|
aiCellCompletion: namedFunction("aiCellCompletion"),
|
|
44
45
|
createManyBelow: namedFunction("createManyBelow"),
|
|
45
46
|
onRun: namedFunction("onRun"),
|
|
@@ -240,3 +241,34 @@ test("ai adds more extensions", () => {
|
|
|
240
241
|
.flat();
|
|
241
242
|
expect(withAI.length - 2).toBe(withoutAI.length);
|
|
242
243
|
});
|
|
244
|
+
|
|
245
|
+
test("empty-cell AI action opens chat without invoking the local refactor prompt", () => {
|
|
246
|
+
const askAiAboutCell = vi.fn();
|
|
247
|
+
const aiCellCompletion = vi.fn();
|
|
248
|
+
const view = new EditorView({
|
|
249
|
+
state: EditorState.create({
|
|
250
|
+
doc: "",
|
|
251
|
+
extensions: setup({
|
|
252
|
+
showPlaceholder: true,
|
|
253
|
+
enableAI: false,
|
|
254
|
+
enableAIChat: true,
|
|
255
|
+
cellActions: {
|
|
256
|
+
...getOpts().cellActions,
|
|
257
|
+
askAiAboutCell,
|
|
258
|
+
aiCellCompletion,
|
|
259
|
+
},
|
|
260
|
+
}),
|
|
261
|
+
}),
|
|
262
|
+
});
|
|
263
|
+
try {
|
|
264
|
+
const button = view.dom.querySelector<HTMLButtonElement>(
|
|
265
|
+
".cm-clickable-placeholder",
|
|
266
|
+
);
|
|
267
|
+
expect(button?.textContent).toBe("generate with AI");
|
|
268
|
+
button?.click();
|
|
269
|
+
expect(askAiAboutCell).toHaveBeenCalledOnce();
|
|
270
|
+
expect(aiCellCompletion).not.toHaveBeenCalled();
|
|
271
|
+
} finally {
|
|
272
|
+
view.destroy();
|
|
273
|
+
}
|
|
274
|
+
});
|
|
@@ -6,6 +6,7 @@ import type { CellId } from "@/core/cells/ids";
|
|
|
6
6
|
|
|
7
7
|
export interface CodemirrorCellActions extends CellActions {
|
|
8
8
|
toggleHideCode: () => boolean;
|
|
9
|
+
askAiAboutCell: () => void;
|
|
9
10
|
aiCellCompletion: () => boolean;
|
|
10
11
|
createManyBelow: (content: string[]) => void;
|
|
11
12
|
onRun: () => void;
|
|
@@ -74,6 +74,7 @@ export interface CodeMirrorSetupOpts {
|
|
|
74
74
|
cellId: CellId;
|
|
75
75
|
showPlaceholder: boolean;
|
|
76
76
|
enableAI: boolean;
|
|
77
|
+
enableAIChat?: boolean;
|
|
77
78
|
acceptCompletionOnEnter?: boolean;
|
|
78
79
|
cellActions: CodemirrorCellActions;
|
|
79
80
|
completionConfig: CompletionConfig;
|
|
@@ -92,7 +93,10 @@ export interface CodeMirrorSetupOpts {
|
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
function getPlaceholderType(opts: CodeMirrorSetupOpts) {
|
|
95
|
-
const { showPlaceholder, enableAI } = opts;
|
|
96
|
+
const { showPlaceholder, enableAI, enableAIChat } = opts;
|
|
97
|
+
if (enableAIChat) {
|
|
98
|
+
return "ai";
|
|
99
|
+
}
|
|
96
100
|
return showPlaceholder ? "marimo-import" : enableAI ? "ai" : "none";
|
|
97
101
|
}
|
|
98
102
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* The type of placeholder to use for the editor.
|
|
5
5
|
*
|
|
6
6
|
* - "marimo-import" -> Shows `import marimo as mo`
|
|
7
|
-
* - "ai" -> Shows `Start coding or
|
|
7
|
+
* - "ai" -> Shows `Start coding or generate with AI`
|
|
8
8
|
* - "none" -> No placeholder
|
|
9
9
|
*/
|
|
10
10
|
export type PlaceholderType = "marimo-import" | "ai" | "none";
|
|
@@ -422,11 +422,11 @@ function getPlaceholderExtension(placeholderType: PlaceholderType): Extension {
|
|
|
422
422
|
if (placeholderType === "ai") {
|
|
423
423
|
return clickablePlaceholderExtension({
|
|
424
424
|
beforeText: "Start coding or ",
|
|
425
|
-
linkText: "generate",
|
|
426
|
-
afterText: "
|
|
425
|
+
linkText: "generate with AI",
|
|
426
|
+
afterText: ".",
|
|
427
427
|
onClick: (ev) => {
|
|
428
428
|
const cellActions = ev.state.facet(cellActionsState);
|
|
429
|
-
cellActions.
|
|
429
|
+
cellActions.askAiAboutCell();
|
|
430
430
|
},
|
|
431
431
|
});
|
|
432
432
|
}
|
|
@@ -81,16 +81,32 @@ describe("clickablePlaceholderExtension", () => {
|
|
|
81
81
|
|
|
82
82
|
// Check text content
|
|
83
83
|
expect(placeholder.textContent).toBe("Click here to continue");
|
|
84
|
+
expect(
|
|
85
|
+
[...placeholder.querySelectorAll('[aria-hidden="true"]')].map(
|
|
86
|
+
(node) => node.textContent,
|
|
87
|
+
),
|
|
88
|
+
).toEqual(["Click ", " to continue"]);
|
|
89
|
+
expect(
|
|
90
|
+
placeholder.querySelector("button")?.closest('[aria-hidden="true"]'),
|
|
91
|
+
).toBeNull();
|
|
84
92
|
|
|
85
93
|
// Find and check the clickable link
|
|
86
94
|
const link = placeholder.querySelector(".cm-clickable-placeholder")!;
|
|
87
95
|
expect(link).toBeTruthy();
|
|
88
96
|
expect(link.textContent).toBe("here");
|
|
97
|
+
expect(link.tagName).toBe("BUTTON");
|
|
98
|
+
expect(link.closest('[aria-hidden="true"]')).toBeNull();
|
|
99
|
+
expect(link.getAttribute("type")).toBe("button");
|
|
89
100
|
|
|
90
101
|
// Simulate click
|
|
91
102
|
(link as HTMLElement).click();
|
|
92
103
|
expect(onClick).toHaveBeenCalled();
|
|
93
104
|
|
|
105
|
+
view.dispatch({ changes: { from: 0, insert: "Hello" } });
|
|
106
|
+
expect(view.dom.querySelector(".cm-clickable-placeholder")).toBeNull();
|
|
107
|
+
view.dispatch({ changes: { from: 0, to: 5 } });
|
|
108
|
+
expect(view.dom.querySelector("button")?.textContent).toBe("here");
|
|
109
|
+
|
|
94
110
|
// Check theme classes
|
|
95
111
|
expect(link.classList.contains("cm-clickable-placeholder")).toBe(true);
|
|
96
112
|
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
import type { Extension } from "@codemirror/state";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
Decoration,
|
|
5
|
+
EditorView,
|
|
6
|
+
keymap,
|
|
7
|
+
placeholder,
|
|
8
|
+
WidgetType,
|
|
9
|
+
} from "@codemirror/view";
|
|
4
10
|
import { isEditorReadonly } from "../readonly/extension";
|
|
5
11
|
|
|
6
12
|
/**
|
|
@@ -60,37 +66,69 @@ export function clickablePlaceholderExtension(opts: {
|
|
|
60
66
|
}): Extension[] {
|
|
61
67
|
const { beforeText, linkText, afterText, onClick } = opts;
|
|
62
68
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
69
|
+
class ActionPlaceholder extends WidgetType {
|
|
70
|
+
public toDOM(ev: EditorView) {
|
|
71
|
+
const wrapper = document.createElement("span");
|
|
72
|
+
wrapper.className = "cm-placeholder";
|
|
73
|
+
wrapper.contentEditable = "false";
|
|
74
|
+
const before = document.createElement("span");
|
|
75
|
+
before.textContent = beforeText;
|
|
76
|
+
before.setAttribute("aria-hidden", "true");
|
|
77
|
+
wrapper.append(before);
|
|
78
|
+
const button = document.createElement("button");
|
|
79
|
+
button.type = "button";
|
|
80
|
+
button.textContent = linkText;
|
|
81
|
+
button.className = "cm-clickable-placeholder";
|
|
82
|
+
button.onclick = (event) => {
|
|
83
|
+
event.stopPropagation();
|
|
84
|
+
onClick(ev);
|
|
85
|
+
};
|
|
86
|
+
const after = document.createElement("span");
|
|
87
|
+
after.textContent = afterText;
|
|
88
|
+
after.setAttribute("aria-hidden", "true");
|
|
89
|
+
wrapper.append(button, after);
|
|
90
|
+
return wrapper;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public override ignoreEvent(event: Event) {
|
|
94
|
+
return (
|
|
95
|
+
event.target instanceof Element &&
|
|
96
|
+
event.target.closest(".cm-clickable-placeholder") !== null
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
80
100
|
|
|
81
101
|
return [
|
|
82
|
-
|
|
102
|
+
EditorView.decorations.compute(["doc"], (state) =>
|
|
103
|
+
state.doc.length === 0
|
|
104
|
+
? Decoration.set([
|
|
105
|
+
Decoration.widget({
|
|
106
|
+
widget: new ActionPlaceholder(),
|
|
107
|
+
side: 1,
|
|
108
|
+
}).range(0),
|
|
109
|
+
])
|
|
110
|
+
: Decoration.none,
|
|
111
|
+
),
|
|
83
112
|
EditorView.theme({
|
|
84
113
|
".cm-placeholder": {
|
|
85
114
|
color: "var(--slate-8)",
|
|
86
115
|
},
|
|
87
116
|
".cm-clickable-placeholder": {
|
|
88
117
|
cursor: "pointer !important",
|
|
118
|
+
font: "inherit",
|
|
119
|
+
background: "transparent",
|
|
120
|
+
border: "none",
|
|
121
|
+
padding: "0",
|
|
89
122
|
pointerEvents: "auto",
|
|
90
123
|
color: "var(--slate-9)",
|
|
91
124
|
textDecoration: "underline",
|
|
92
125
|
textUnderlineOffset: "0.2em",
|
|
93
126
|
},
|
|
127
|
+
".cm-clickable-placeholder:focus-visible": {
|
|
128
|
+
outline: "1px solid var(--muted-foreground)",
|
|
129
|
+
outlineOffset: "2px",
|
|
130
|
+
borderRadius: "2px",
|
|
131
|
+
},
|
|
94
132
|
".cm-clickable-placeholder:hover": {
|
|
95
133
|
color: "var(--sky-11)",
|
|
96
134
|
},
|
|
@@ -7,7 +7,7 @@ import type { PopoverContent } from "@/components/ui/popover";
|
|
|
7
7
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
8
8
|
import { Chatbot } from "../chat-ui";
|
|
9
9
|
|
|
10
|
-
vi.mock("@/components/editor/ai/
|
|
10
|
+
vi.mock("@/components/editor/ai/prompt-input", () => ({
|
|
11
11
|
PromptInput: ({ value }: { value: string }) => (
|
|
12
12
|
<textarea aria-label="Chat prompt" value={value} readOnly={true} />
|
|
13
13
|
),
|
|
@@ -39,7 +39,7 @@ import {
|
|
|
39
39
|
import {
|
|
40
40
|
type AdditionalCompletions,
|
|
41
41
|
PromptInput,
|
|
42
|
-
} from "@/components/editor/ai/
|
|
42
|
+
} from "@/components/editor/ai/prompt-input";
|
|
43
43
|
import { CopyClipboardIcon } from "@/components/icons/copy-icon";
|
|
44
44
|
import { Spinner } from "@/components/icons/spinner";
|
|
45
45
|
import { Button } from "@/components/ui/button";
|