@marimo-team/islands 0.25.3-dev248 → 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-CD1s2-yk.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-BLiMCTCu.js → code-visibility-D2D_sL1T.js} +4 -4
- package/dist/{common-H6uadBsj.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-Ba360Exn.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-Cfjlex_J.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-CREvupa1.js → glide-data-editor-C94G7kD7.js} +10 -10
- package/dist/{glide-utils-CQddGpa-.js → glide-utils-qbkRZpEy.js} +3 -3
- package/dist/{globals-DUIx9HDW.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-B8TTAovO.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-CmZQtS97.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
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
autocompletion,
|
|
5
|
+
type Completion,
|
|
6
|
+
type CompletionContext,
|
|
7
|
+
type CompletionSource,
|
|
8
|
+
} from "@codemirror/autocomplete";
|
|
9
|
+
import { markdown } from "@codemirror/lang-markdown";
|
|
10
|
+
import { Prec } from "@codemirror/state";
|
|
11
|
+
import { promptHistory } from "@marimo-team/codemirror-ai";
|
|
12
|
+
import ReactCodeMirror, {
|
|
13
|
+
EditorView,
|
|
14
|
+
keymap,
|
|
15
|
+
minimalSetup,
|
|
16
|
+
type ReactCodeMirrorRef,
|
|
17
|
+
} from "@uiw/react-codemirror";
|
|
18
|
+
import { useStore } from "jotai";
|
|
19
|
+
import { useMemo } from "react";
|
|
20
|
+
import useEvent from "react-use-event-hook";
|
|
21
|
+
import { z } from "zod";
|
|
22
|
+
import { clickablePlaceholderExtension } from "@/core/codemirror/placeholder/extensions";
|
|
23
|
+
import { resourceExtension } from "@/core/codemirror/ai/resources";
|
|
24
|
+
import { useTheme } from "@/theme/useTheme";
|
|
25
|
+
import { cn } from "@/utils/cn";
|
|
26
|
+
import { ZodLocalStorage } from "@/utils/storage/typed";
|
|
27
|
+
import { CONTEXT_TRIGGER, mentionsCompletionSource } from "./completion-utils";
|
|
28
|
+
|
|
29
|
+
const KEY = "marimo:ai-prompt-history";
|
|
30
|
+
const promptHistoryStorage = new ZodLocalStorage(z.array(z.string()), () => []);
|
|
31
|
+
|
|
32
|
+
export interface AdditionalCompletions {
|
|
33
|
+
triggerCompletionRegex: RegExp;
|
|
34
|
+
completions: Completion[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface PromptInputProps {
|
|
38
|
+
inputRef?: React.RefObject<ReactCodeMirrorRef | null>;
|
|
39
|
+
placeholder?: string;
|
|
40
|
+
placeholderAction?: Parameters<typeof clickablePlaceholderExtension>[0];
|
|
41
|
+
value: string;
|
|
42
|
+
className?: string;
|
|
43
|
+
onKeyDown?: (e: React.KeyboardEvent<HTMLDivElement>) => void;
|
|
44
|
+
onClose: () => void;
|
|
45
|
+
onChange: (value: string) => void;
|
|
46
|
+
onSubmit: (e: KeyboardEvent | undefined, value: string) => void;
|
|
47
|
+
additionalCompletions?: AdditionalCompletions;
|
|
48
|
+
maxHeight?: string;
|
|
49
|
+
onAddFiles?: (files: File[]) => void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* CodeMirror-based input for the AI prompt.
|
|
54
|
+
*
|
|
55
|
+
* This is just text (no language support), but we use codemirror to get autocomplete
|
|
56
|
+
* for @dataframe and @sql_table.
|
|
57
|
+
*/
|
|
58
|
+
export const PromptInput = ({
|
|
59
|
+
value,
|
|
60
|
+
placeholder,
|
|
61
|
+
placeholderAction,
|
|
62
|
+
inputRef,
|
|
63
|
+
className,
|
|
64
|
+
onChange,
|
|
65
|
+
onSubmit,
|
|
66
|
+
onKeyDown,
|
|
67
|
+
onClose,
|
|
68
|
+
onAddFiles,
|
|
69
|
+
additionalCompletions,
|
|
70
|
+
maxHeight,
|
|
71
|
+
}: PromptInputProps) => {
|
|
72
|
+
const handleSubmit = useEvent(onSubmit);
|
|
73
|
+
const handleEscape = useEvent(onClose);
|
|
74
|
+
const store = useStore();
|
|
75
|
+
const { theme } = useTheme();
|
|
76
|
+
|
|
77
|
+
const additionalCompletionsSource: CompletionSource = useEvent(
|
|
78
|
+
(context: CompletionContext) => {
|
|
79
|
+
if (!additionalCompletions) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return mentionsCompletionSource(
|
|
84
|
+
[additionalCompletions.triggerCompletionRegex],
|
|
85
|
+
additionalCompletions.completions,
|
|
86
|
+
)(context);
|
|
87
|
+
},
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// Changing extensions can be expensive, so
|
|
91
|
+
// it is worth making sure this is memoized well.
|
|
92
|
+
const extensions = useMemo(() => {
|
|
93
|
+
const markdownLanguage = markdown();
|
|
94
|
+
return [
|
|
95
|
+
...(placeholderAction
|
|
96
|
+
? clickablePlaceholderExtension(placeholderAction)
|
|
97
|
+
: []),
|
|
98
|
+
autocompletion({}),
|
|
99
|
+
markdownLanguage,
|
|
100
|
+
resourceExtension({
|
|
101
|
+
language: markdownLanguage.language,
|
|
102
|
+
store,
|
|
103
|
+
onAddFiles,
|
|
104
|
+
}),
|
|
105
|
+
markdownLanguage.language.data.of({
|
|
106
|
+
autocomplete: additionalCompletionsSource,
|
|
107
|
+
}),
|
|
108
|
+
promptHistory({
|
|
109
|
+
storage: {
|
|
110
|
+
load: () => promptHistoryStorage.get(KEY),
|
|
111
|
+
save: (prompts) => promptHistoryStorage.set(KEY, prompts),
|
|
112
|
+
},
|
|
113
|
+
}),
|
|
114
|
+
EditorView.lineWrapping,
|
|
115
|
+
minimalSetup(),
|
|
116
|
+
Prec.highest(
|
|
117
|
+
keymap.of([
|
|
118
|
+
{
|
|
119
|
+
preventDefault: true,
|
|
120
|
+
stopPropagation: true,
|
|
121
|
+
any: (view, event) => {
|
|
122
|
+
const pressedModOrShift =
|
|
123
|
+
event.metaKey || event.ctrlKey || event.shiftKey;
|
|
124
|
+
// If no mod key is pressed, submit
|
|
125
|
+
if (event.key === "Enter" && !pressedModOrShift) {
|
|
126
|
+
handleSubmit(event, view.state.doc.toString());
|
|
127
|
+
event.preventDefault();
|
|
128
|
+
event.stopPropagation();
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
// Mod+Enter does add a new line already by codemirror
|
|
132
|
+
// But Shift+Enter does not, so we need to handle it manually
|
|
133
|
+
if (event.key === "Enter" && event.shiftKey) {
|
|
134
|
+
view.dispatch(view.state.replaceSelection("\n"));
|
|
135
|
+
event.preventDefault();
|
|
136
|
+
event.stopPropagation();
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return false;
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
]),
|
|
144
|
+
),
|
|
145
|
+
keymap.of([
|
|
146
|
+
{
|
|
147
|
+
key: "Escape",
|
|
148
|
+
preventDefault: true,
|
|
149
|
+
stopPropagation: true,
|
|
150
|
+
run: () => {
|
|
151
|
+
handleEscape();
|
|
152
|
+
return true;
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
]),
|
|
156
|
+
];
|
|
157
|
+
}, [
|
|
158
|
+
store,
|
|
159
|
+
placeholderAction,
|
|
160
|
+
onAddFiles,
|
|
161
|
+
additionalCompletionsSource,
|
|
162
|
+
handleSubmit,
|
|
163
|
+
handleEscape,
|
|
164
|
+
]);
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
<ReactCodeMirror
|
|
168
|
+
ref={inputRef}
|
|
169
|
+
className={cn(
|
|
170
|
+
"mo-ai-prompt flex-1 font-sans text-sm leading-6 overflow-auto my-1",
|
|
171
|
+
className,
|
|
172
|
+
)}
|
|
173
|
+
width="100%"
|
|
174
|
+
maxHeight={maxHeight}
|
|
175
|
+
value={value}
|
|
176
|
+
basicSetup={false}
|
|
177
|
+
extensions={extensions}
|
|
178
|
+
onChange={onChange}
|
|
179
|
+
onKeyDown={onKeyDown}
|
|
180
|
+
theme={theme === "dark" ? "dark" : "light"}
|
|
181
|
+
placeholder={
|
|
182
|
+
placeholderAction
|
|
183
|
+
? undefined
|
|
184
|
+
: placeholder ||
|
|
185
|
+
`Generate with AI, ${CONTEXT_TRIGGER} to include context`
|
|
186
|
+
}
|
|
187
|
+
/>
|
|
188
|
+
);
|
|
189
|
+
};
|
|
@@ -5,10 +5,10 @@ import { SparklesIcon } from "lucide-react";
|
|
|
5
5
|
import { Tooltip } from "@/components/ui/tooltip";
|
|
6
6
|
import {
|
|
7
7
|
type Edit,
|
|
8
|
-
stagedGenerationInProgressAtom,
|
|
9
8
|
useStagedAICell,
|
|
10
9
|
useStagedCells,
|
|
11
10
|
} from "@/core/ai/staged-cells";
|
|
11
|
+
import { chatGenerationInProgressAtom } from "@/core/ai/state";
|
|
12
12
|
import { getCellEditorView } from "@/core/cells/cells";
|
|
13
13
|
import type { CellId } from "@/core/cells/ids";
|
|
14
14
|
import { prepareCellCodeUpdate } from "@/core/codemirror/language/cell-code";
|
|
@@ -49,8 +49,8 @@ export const StagedAICellFooter: React.FC<{ cellId: CellId }> = ({
|
|
|
49
49
|
cellId,
|
|
50
50
|
}) => {
|
|
51
51
|
const store = useStore();
|
|
52
|
-
const generationInProgress = useAtomValue(stagedGenerationInProgressAtom);
|
|
53
52
|
const stagedAiCell = useStagedAICell(cellId);
|
|
53
|
+
const isGenerating = useAtomValue(chatGenerationInProgressAtom);
|
|
54
54
|
const runCell = useRunCell(cellId);
|
|
55
55
|
|
|
56
56
|
const { deleteStagedCell, removeStagedCell } = useStagedCells(store);
|
|
@@ -93,7 +93,7 @@ export const StagedAICellFooter: React.FC<{ cellId: CellId }> = ({
|
|
|
93
93
|
</div>
|
|
94
94
|
<div className="flex items-center gap-1.5">
|
|
95
95
|
<CompletionActionsCellFooter
|
|
96
|
-
isLoading={
|
|
96
|
+
isLoading={isGenerating}
|
|
97
97
|
onAccept={() => handleCompletion("accept")}
|
|
98
98
|
onDecline={() => handleCompletion("reject")}
|
|
99
99
|
size="xs"
|
|
@@ -31,7 +31,11 @@ import {
|
|
|
31
31
|
} from "@/core/codemirror/language/extension";
|
|
32
32
|
import { MARKDOWN_INITIAL_HIDE_CODE } from "@/core/codemirror/language/languages/markdown";
|
|
33
33
|
import type { LanguageAdapterType } from "@/core/codemirror/language/types";
|
|
34
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
autoInstantiateAtom,
|
|
36
|
+
isAiEnabled,
|
|
37
|
+
isAiFeatureEnabled,
|
|
38
|
+
} from "@/core/config/config";
|
|
35
39
|
import type { UserConfig } from "@/core/config/config-schema";
|
|
36
40
|
import { OverridingHotkeyProvider } from "@/core/hotkeys/hotkeys";
|
|
37
41
|
import { useSaveNotebook } from "@/core/saving/save-component";
|
|
@@ -39,6 +43,7 @@ import type { Theme } from "@/theme/useTheme";
|
|
|
39
43
|
import { cn } from "@/utils/cn";
|
|
40
44
|
import { invariant } from "@/utils/invariant";
|
|
41
45
|
import { mergeRefs } from "@/utils/mergeRefs";
|
|
46
|
+
import { useAskAiAboutCell } from "../../chrome/wrapper/useAskAiAboutCell";
|
|
42
47
|
import { AiCompletionEditor } from "../../ai/ai-completion-editor";
|
|
43
48
|
import {
|
|
44
49
|
closeSignatureHelp,
|
|
@@ -189,12 +194,15 @@ const CellEditorInternal = ({
|
|
|
189
194
|
});
|
|
190
195
|
|
|
191
196
|
const aiFeaturesEnabled = isAiFeatureEnabled(userConfig);
|
|
197
|
+
const askAiAboutCell = useAskAiAboutCell();
|
|
198
|
+
const aiChatEnabled = isAiEnabled(userConfig);
|
|
192
199
|
|
|
193
200
|
const extensions = useMemo(() => {
|
|
194
201
|
const extensions = setupCodeMirror({
|
|
195
202
|
cellId,
|
|
196
203
|
showPlaceholder,
|
|
197
204
|
enableAI: aiFeaturesEnabled,
|
|
205
|
+
enableAIChat: aiChatEnabled,
|
|
198
206
|
cellActions: {
|
|
199
207
|
...cellActions,
|
|
200
208
|
afterToggleMarkdown,
|
|
@@ -213,6 +221,9 @@ const CellEditorInternal = ({
|
|
|
213
221
|
},
|
|
214
222
|
splitCell,
|
|
215
223
|
toggleHideCode,
|
|
224
|
+
askAiAboutCell: () => {
|
|
225
|
+
void askAiAboutCell(cellId);
|
|
226
|
+
},
|
|
216
227
|
aiCellCompletion: () => {
|
|
217
228
|
if (!aiFeaturesEnabled) {
|
|
218
229
|
return false;
|
|
@@ -296,6 +307,8 @@ const CellEditorInternal = ({
|
|
|
296
307
|
inlineAiTooltip,
|
|
297
308
|
tooltipParentSelector,
|
|
298
309
|
aiFeaturesEnabled,
|
|
310
|
+
aiChatEnabled,
|
|
311
|
+
askAiAboutCell,
|
|
299
312
|
theme,
|
|
300
313
|
showPlaceholder,
|
|
301
314
|
cellActions,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { createContext, useContext, type PropsWithChildren } from "react";
|
|
4
|
+
import { createPortal } from "react-dom";
|
|
5
|
+
|
|
6
|
+
export const PanelHeaderActionsContext = createContext<HTMLElement | null>(
|
|
7
|
+
null,
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
export function PanelHeaderActions({ children }: PropsWithChildren) {
|
|
11
|
+
const target = useContext(PanelHeaderActionsContext);
|
|
12
|
+
return target ? createPortal(children, target) : children;
|
|
13
|
+
}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
4
4
|
import { createStore, Provider } from "jotai";
|
|
5
5
|
import { describe, expect, it, vi } from "vitest";
|
|
6
|
+
import type { ChatStatus } from "ai";
|
|
7
|
+
import { useChatGeneration } from "@/components/chat/use-chat-generation";
|
|
6
8
|
import { cellId } from "@/__tests__/branded";
|
|
7
9
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
8
|
-
import { stagedAICellsAtom
|
|
10
|
+
import { stagedAICellsAtom } from "@/core/ai/staged-cells";
|
|
9
11
|
import { scrollAndHighlightCell } from "../../../links/cell-link";
|
|
12
|
+
import { StagedAICellFooter } from "../../../cell/StagedAICell";
|
|
10
13
|
import { PendingAICells } from "../pending-ai-cells";
|
|
11
14
|
|
|
12
15
|
vi.mock("@/components/editor/cell/useDeleteCell", () => ({
|
|
@@ -25,6 +28,7 @@ vi.mock("@/core/cells/actions", () => ({
|
|
|
25
28
|
|
|
26
29
|
vi.mock("@/components/editor/cell/useRunCells", () => ({
|
|
27
30
|
useRunCells: () => vi.fn(),
|
|
31
|
+
useRunCell: () => vi.fn(),
|
|
28
32
|
}));
|
|
29
33
|
|
|
30
34
|
vi.mock("@/components/editor/links/cell-link", () => ({
|
|
@@ -87,50 +91,54 @@ describe("PendingAICells", () => {
|
|
|
87
91
|
expect(scrollAndHighlightCell).toHaveBeenLastCalledWith(third, "focus");
|
|
88
92
|
expect(screen.getByText("3 / 3")).toBeInTheDocument();
|
|
89
93
|
});
|
|
94
|
+
});
|
|
90
95
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
96
|
+
function ChatGeneration({ status }: { status: ChatStatus }) {
|
|
97
|
+
useChatGeneration(status);
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
it("locks staged edits during generation and unlocks after completion, stop, error, or unmount", () => {
|
|
102
|
+
const store = createStore();
|
|
103
|
+
const pendingCell = cellId("pending-cell");
|
|
104
|
+
store.set(stagedAICellsAtom, new Map([[pendingCell, { type: "add_cell" }]]));
|
|
105
|
+
const ui = (status?: ChatStatus) => (
|
|
106
|
+
<Provider store={store}>
|
|
107
|
+
<TooltipProvider>
|
|
108
|
+
{status && <ChatGeneration status={status} />}
|
|
109
|
+
<PendingAICells />
|
|
110
|
+
<StagedAICellFooter cellId={pendingCell} />
|
|
111
|
+
</TooltipProvider>
|
|
112
|
+
</Provider>
|
|
113
|
+
);
|
|
114
|
+
const { rerender } = render(ui("ready"));
|
|
115
|
+
const keep = screen.getByRole("button", { name: "Keep all" });
|
|
116
|
+
const discard = screen.getByRole("button", { name: "Discard all" });
|
|
117
|
+
const keepCell = screen.getByRole("button", { name: "Keep cell" });
|
|
118
|
+
const discardCell = screen.getByRole("button", { name: "Discard cell" });
|
|
119
|
+
const keepAndRun = keep.parentElement!.querySelectorAll("button")[1];
|
|
120
|
+
for (const status of [
|
|
121
|
+
"submitted",
|
|
122
|
+
"streaming",
|
|
123
|
+
"ready",
|
|
124
|
+
"submitted",
|
|
125
|
+
"ready",
|
|
126
|
+
"submitted",
|
|
127
|
+
"error",
|
|
128
|
+
"streaming",
|
|
129
|
+
undefined,
|
|
130
|
+
] as const) {
|
|
131
|
+
rerender(ui(status));
|
|
132
|
+
const isGenerating = status === "submitted" || status === "streaming";
|
|
133
|
+
for (const button of [keep, discard, keepAndRun, keepCell, discardCell]) {
|
|
134
|
+
expect(button.hasAttribute("disabled")).toBe(isGenerating);
|
|
135
|
+
if (isGenerating) {
|
|
136
|
+
fireEvent.click(button);
|
|
137
|
+
}
|
|
120
138
|
}
|
|
121
|
-
expect(
|
|
139
|
+
expect(store.get(stagedAICellsAtom).has(pendingCell)).toBe(true);
|
|
140
|
+
}
|
|
122
141
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
id: generationId,
|
|
126
|
-
status: "complete",
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
rerender(view);
|
|
130
|
-
|
|
131
|
-
for (const button of acceptButtons) {
|
|
132
|
-
expect(button).toBeEnabled();
|
|
133
|
-
}
|
|
134
|
-
expect(screen.getByRole("button", { name: "Discard all" })).toBeEnabled();
|
|
135
|
-
});
|
|
142
|
+
fireEvent.click(keep);
|
|
143
|
+
expect(store.get(stagedAICellsAtom).size).toBe(0);
|
|
136
144
|
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { act, renderHook } from "@testing-library/react";
|
|
4
|
+
import { createStore, Provider } from "jotai";
|
|
5
|
+
import type { PropsWithChildren } from "react";
|
|
6
|
+
import { beforeEach, expect, it, vi } from "vitest";
|
|
7
|
+
import { pendingAiPromptAtom } from "@/core/ai/state";
|
|
8
|
+
import { CellId } from "@/core/cells/ids";
|
|
9
|
+
import { aiPanelTabAtom } from "../useAiPanel";
|
|
10
|
+
import { useAskAiAboutCell } from "../useAskAiAboutCell";
|
|
11
|
+
|
|
12
|
+
const { aiAtom, openApplication, saveModeChange } = await vi.hoisted(
|
|
13
|
+
async () => {
|
|
14
|
+
const { atom } = await import("jotai");
|
|
15
|
+
return {
|
|
16
|
+
aiAtom: atom<{ mode?: "ask" | "agent" | "code_mode" }>({}),
|
|
17
|
+
openApplication: vi.fn(),
|
|
18
|
+
saveModeChange: vi.fn().mockResolvedValue(undefined),
|
|
19
|
+
};
|
|
20
|
+
},
|
|
21
|
+
);
|
|
22
|
+
vi.mock("@/core/config/config", async (importOriginal) => ({
|
|
23
|
+
...(await importOriginal<object>()),
|
|
24
|
+
aiAtom,
|
|
25
|
+
}));
|
|
26
|
+
vi.mock("@/core/ai/config", () => ({
|
|
27
|
+
useAIConfigActions: () => ({ saveModeChange }),
|
|
28
|
+
}));
|
|
29
|
+
vi.mock("../../state", () => ({
|
|
30
|
+
useChromeActions: () => ({ openApplication }),
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
beforeEach(() => vi.clearAllMocks());
|
|
34
|
+
it.each([undefined, "ask", "agent", "code_mode"] as const)(
|
|
35
|
+
"opens chat from %s mode without submitting or replacing the draft",
|
|
36
|
+
async (mode) => {
|
|
37
|
+
const store = createStore();
|
|
38
|
+
store.set(aiAtom, { mode });
|
|
39
|
+
store.set(aiPanelTabAtom, "agents");
|
|
40
|
+
const cellId = CellId.create();
|
|
41
|
+
const { result } = renderHook(useAskAiAboutCell, {
|
|
42
|
+
wrapper: ({ children }: PropsWithChildren) => (
|
|
43
|
+
<Provider store={store}>{children}</Provider>
|
|
44
|
+
),
|
|
45
|
+
});
|
|
46
|
+
await act(() => result.current(cellId));
|
|
47
|
+
expect(openApplication).toHaveBeenCalledWith("ai");
|
|
48
|
+
expect(store.get(aiPanelTabAtom)).toBe("chat");
|
|
49
|
+
expect(saveModeChange.mock.calls).toEqual(
|
|
50
|
+
mode === "agent" || mode === "code_mode" ? [] : [["agent"]],
|
|
51
|
+
);
|
|
52
|
+
expect(store.get(pendingAiPromptAtom)).toEqual({
|
|
53
|
+
prompt: `@cell://${cellId} `,
|
|
54
|
+
submit: false,
|
|
55
|
+
append: true,
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
);
|