@marimo-team/islands 0.25.3-dev265 → 0.25.3-dev267
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-C_hj7Poi.js → ConnectedDataExplorerComponent-C4yCN38h.js} +1 -1
- package/dist/{ErrorBoundary-DzYPyiOs.js → ErrorBoundary-CewXPyL5.js} +64 -69
- package/dist/{abnfDiagram-VCTEODGH-DzrZZXRD.js → abnfDiagram-VCTEODGH-o6Ay6Q63.js} +2 -2
- package/dist/{any-language-editor-CFeWfC3o.js → any-language-editor-DnG26QXh.js} +1 -1
- package/dist/{architectureDiagram-5GKGNRK7-iF_snyFL.js → architectureDiagram-5GKGNRK7-BhiwwlVg.js} +1 -1
- package/dist/{chat-ui-DFxNgzXF.js → chat-ui-CbgR5ivy.js} +1964 -1740
- package/dist/{chunk-IMKFNOWR-B7xytD4B.js → chunk-IMKFNOWR-WMSMf4Nj.js} +1 -1
- package/dist/{chunk-SHT3W25Y-I8ZFP5L1.js → chunk-SHT3W25Y-BRfADrzD.js} +1 -1
- package/dist/{chunk-SVP7TREG-Di-uAPdT.js → chunk-SVP7TREG-CuK5QFre.js} +1 -1
- package/dist/{chunk-TICWLB2K-BFpUy0ud.js → chunk-TICWLB2K-Bcun_jhJ.js} +1 -1
- package/dist/{classDiagram-ZZMXUADV-Cqm4JGpx.js → classDiagram-ZZMXUADV-CgWDbUDT.js} +2 -2
- package/dist/{classDiagram-v2-VYDZK3BY-DFHRJpN3.js → classDiagram-v2-VYDZK3BY-DYmc9SFU.js} +2 -2
- package/dist/{code-visibility-CJExrSFe.js → code-visibility-BKBl9Rh6.js} +9 -9
- package/dist/{common-5mM7a8cm.js → common-N3Jd_6m9.js} +5944 -5626
- package/dist/{cynefinDiagram-5FMLGOSQ-cuGOeTBC.js → cynefinDiagram-5FMLGOSQ-PPKWipjT.js} +1 -1
- package/dist/{dagre-GXQ25YYZ-DJ76aC4k.js → dagre-GXQ25YYZ-niRBZh--.js} +1 -1
- package/dist/{data-grid-overlay-editor--Un2Lk6m.js → data-grid-overlay-editor-Cp59CrqZ.js} +1 -1
- package/dist/{diagram-S7CK7UJ4-DLLc0sw7.js → diagram-S7CK7UJ4-DPiP9rot.js} +1 -1
- package/dist/{diagram-UQ7AKVKN-Tz_d6qmk.js → diagram-UQ7AKVKN-B0WZuaxV.js} +1 -1
- package/dist/{diagram-VX7I27RA-D7SzPrDN.js → diagram-VX7I27RA-Rn42WBuN.js} +1 -1
- package/dist/{diagram-Z3DM3KII-qr37XV58.js → diagram-Z3DM3KII-BB93--ry.js} +1 -1
- package/dist/{dropdown-menu-CkJ7ErY6.js → dropdown-menu-BpImEfn3.js} +1211 -1205
- package/dist/{ebnfDiagram-PWID7BFC-BKOQb4J0.js → ebnfDiagram-PWID7BFC-V4t5h6mZ.js} +2 -2
- package/dist/{erDiagram-RLTQ6QDP-CM15Vq68.js → erDiagram-RLTQ6QDP-C1MsAKXd.js} +1 -1
- package/dist/{error-banner-DbRFOd2O.js → error-banner-DPc9eIw5.js} +3 -3
- package/dist/{error-entries-CMoaUX9V.js → error-entries-CJGdD2ky.js} +49 -42
- package/dist/{feedback-button-C2NUXbzh.js → feedback-button-Qc1f9iPq.js} +70 -70
- package/dist/{flowDiagram-HODETNUW-Dt3N5yFT.js → flowDiagram-HODETNUW-DHjeJP96.js} +2 -2
- package/dist/{glide-data-editor-Dj9U-NK9.js → glide-data-editor-hCq3K0sd.js} +2042 -2041
- package/dist/{glide-utils-Ba9WD2IN.js → glide-utils-DC3sk6EX.js} +28 -34
- package/dist/{globals-CkbsxIrq.js → globals-B2_uMYDY.js} +1 -1
- package/dist/{html-to-image-D_5CMreP.js → html-to-image-S7spRQCi.js} +5526 -5828
- package/dist/{infoDiagram-27XIBGKW-C7axJMCl.js → infoDiagram-27XIBGKW-Dovv6iKx.js} +1 -1
- package/dist/{ishikawaDiagram-5VMMS53U-CdC9Eur7.js → ishikawaDiagram-5VMMS53U-D2nyygsc.js} +1 -1
- package/dist/{kanban-definition-UXKFOSKX-BHnMIgD7.js → kanban-definition-UXKFOSKX-KyVx0uTY.js} +1 -1
- package/dist/main.js +1288 -1287
- package/dist/{mermaid-DFYN_PXd.js → mermaid-B-jKSwGt.js} +33 -33
- package/dist/{mindmap-definition-YA3MSWOX-BagHoCGU.js → mindmap-definition-YA3MSWOX-Bz9tKQFl.js} +1 -1
- package/dist/{number-overlay-editor-H8QAAmdr.js → number-overlay-editor-CfNYvbdz.js} +1 -1
- package/dist/{pegDiagram-XKGWAZYB-8IIZczip.js → pegDiagram-XKGWAZYB-C_qpq6Tv.js} +2 -2
- package/dist/pencil-DcT8AE0h.js +10 -0
- package/dist/{pieDiagram-E7YTZNPT-CE6dKBIr.js → pieDiagram-E7YTZNPT-Bp9ucgI4.js} +1 -1
- package/dist/{process-output-CGFNVC60.js → process-output-f34bkXTm.js} +2 -2
- package/dist/{railroadDiagram-O6MQD6OU-CkexPq3b.js → railroadDiagram-O6MQD6OU-tq-VEH6j.js} +2 -2
- package/dist/{requirementDiagram-BXWQKSXE-B0-OkuAe.js → requirementDiagram-BXWQKSXE-DW-vHadb.js} +1 -1
- package/dist/{reveal-component-BperrCFw.js → reveal-component-CUxv-kkP.js} +9 -9
- package/dist/{sequenceDiagram-WJ2MYXX4-Dxysg8DX.js → sequenceDiagram-WJ2MYXX4-Dv9U9qRP.js} +1 -1
- package/dist/{stateDiagram-D77RDMKH-DTEN3-OJ.js → stateDiagram-D77RDMKH-DTmPQ0EB.js} +2 -2
- package/dist/{stateDiagram-v2-MP3YSRHH-vktk3Xc5.js → stateDiagram-v2-MP3YSRHH-CkCif0Iw.js} +2 -2
- package/dist/style.css +1 -1
- package/dist/{swimlanes-42K2YHIH-Cjveh9OM.js → swimlanes-42K2YHIH-21kCq6l8.js} +1 -1
- package/dist/{swimlanesDiagram-VR7AAH4N-Bdr7Ev_2.js → swimlanesDiagram-VR7AAH4N-BpRJP6KU.js} +2 -2
- package/dist/{timeline-definition-24CTP7MA-KIuDcefn.js → timeline-definition-24CTP7MA-Ckgh4lS4.js} +1 -1
- package/dist/{toast-CY1_Qv41.js → toast-DpczUryP.js} +1 -1
- package/dist/{vega-component-C-lqjV0q.js → vega-component-CF9kI6C4.js} +1 -1
- package/dist/{vennDiagram-4TSXK5OY-DrIOUHfb.js → vennDiagram-4TSXK5OY-CDei2Bt3.js} +1 -1
- package/dist/{wardleyDiagram-VM6X3IG4-CJNjf4OH.js → wardleyDiagram-VM6X3IG4-CXavT601.js} +1 -1
- package/dist/{xychartDiagram-S5SC5T6Z-DEshFho-.js → xychartDiagram-S5SC5T6Z-CvJhcZri.js} +1 -1
- package/package.json +1 -1
- package/src/components/chat/__tests__/chat-display.test.tsx +157 -86
- package/src/components/chat/__tests__/chat-utils.test.ts +42 -0
- package/src/components/chat/__tests__/turn-timing.test.ts +84 -0
- package/src/components/chat/__tests__/use-assistant-turn.test.ts +95 -0
- package/src/components/chat/__tests__/user-message.test.tsx +27 -0
- package/src/components/chat/acp/agent-panel.tsx +2 -0
- package/src/components/chat/agent-activity.tsx +132 -0
- package/src/components/chat/chat-display.tsx +125 -96
- package/src/components/chat/chat-panel.tsx +123 -106
- package/src/components/chat/chat-utils.ts +12 -24
- package/src/components/chat/tool-call/__tests__/tool-labels.test.ts +76 -0
- package/src/components/chat/tool-call/__tests__/tool-step.test.tsx +57 -0
- package/src/components/chat/tool-call/shared.ts +6 -0
- package/src/components/chat/tool-call/tool-call-view.tsx +22 -76
- package/src/components/chat/tool-call/tool-labels.ts +172 -0
- package/src/components/chat/tool-call/tool-step.tsx +145 -0
- package/src/components/chat/turn-timing.ts +73 -0
- package/src/components/chat/use-assistant-turn.ts +65 -0
- package/src/components/chat/use-chat-generation.ts +5 -4
- package/src/components/chat/user-message.tsx +145 -0
- package/src/components/editor/actions/__tests__/useCellActionButton.test.tsx +18 -1
- package/src/components/editor/actions/useCellActionButton.tsx +3 -0
- package/src/components/editor/ai/completion-handlers.tsx +0 -37
- package/src/components/editor/cell/StagedAICell.tsx +51 -144
- package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +193 -32
- package/src/components/editor/chrome/wrapper/app-chrome.tsx +2 -2
- package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +145 -81
- package/src/components/editor/chrome/wrapper/pending-ai-changes-badge.tsx +25 -0
- package/src/components/editor/chrome/wrapper/sidebar.tsx +6 -0
- package/src/components/editor/links/cell-link.tsx +4 -4
- package/src/components/editor/notebook-cell.tsx +3 -11
- package/src/components/ui/toast.tsx +1 -1
- package/src/core/ai/__tests__/staged-cells.test.ts +74 -126
- package/src/core/ai/staged-cells.ts +48 -38
- package/src/core/cells/scrollCellIntoView.ts +6 -0
- package/src/core/codemirror/placeholder/__tests__/extensions.test.ts +59 -0
- package/src/core/codemirror/placeholder/extensions.ts +23 -0
- package/src/core/session/__tests__/useNotebookSession.hook.test.tsx +34 -0
- package/src/core/session/useNotebookSession.tsx +7 -2
- package/src/css/app/Cell.css +7 -36
- package/src/plugins/impl/chat/chat-ui.tsx +2 -3
- package/src/plugins/impl/common/error-banner.tsx +5 -3
- package/src/components/chat/reasoning-accordion.tsx +0 -61
- package/src/components/chat/tool-call/tool-activity-group.tsx +0 -49
- package/src/components/chat/tool-call/tool-error-card.tsx +0 -81
- package/src/components/chat/tool-call/tool-history-row.tsx +0 -147
- package/src/components/icons/loading-ellipsis.tsx +0 -46
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import type { ReactCodeMirrorRef } from "@uiw/react-codemirror";
|
|
4
|
+
import type { FileUIPart, TextUIPart, UIMessage } from "ai";
|
|
5
|
+
import { PencilIcon } from "lucide-react";
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { Button } from "@/components/ui/button";
|
|
8
|
+
import { Tooltip } from "@/components/ui/tooltip";
|
|
9
|
+
import { focusInputAndMoveToEnd } from "@/core/codemirror/utils";
|
|
10
|
+
import { PromptInput } from "../editor/ai/prompt-input";
|
|
11
|
+
import { CopyClipboardIcon } from "../icons/copy-icon";
|
|
12
|
+
import { AttachmentRenderer } from "./chat-components";
|
|
13
|
+
|
|
14
|
+
interface UserMessageProps {
|
|
15
|
+
message: UIMessage;
|
|
16
|
+
canEdit: boolean;
|
|
17
|
+
onEdit: (newValue: string) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const UserMessage: React.FC<UserMessageProps> = ({
|
|
21
|
+
message,
|
|
22
|
+
canEdit,
|
|
23
|
+
onEdit,
|
|
24
|
+
}) => {
|
|
25
|
+
const content = message.parts
|
|
26
|
+
.filter((p): p is TextUIPart => p.type === "text")
|
|
27
|
+
.map((p) => p.text)
|
|
28
|
+
.join("\n");
|
|
29
|
+
const attachments = message.parts
|
|
30
|
+
.filter((p): p is FileUIPart => p.type === "file")
|
|
31
|
+
.map((part, idx) => (
|
|
32
|
+
// Sent parts never reorder, and a file attached twice repeats its URL.
|
|
33
|
+
// react-doctor-disable-next-line react-doctor/no-array-index-as-key
|
|
34
|
+
<AttachmentRenderer attachment={part} key={idx} />
|
|
35
|
+
));
|
|
36
|
+
|
|
37
|
+
const [draft, setDraft] = React.useState<string | null>(null);
|
|
38
|
+
const inputRef = React.useRef<ReactCodeMirrorRef>(null);
|
|
39
|
+
const editButtonRef = React.useRef<HTMLButtonElement>(null);
|
|
40
|
+
const editing = draft != null;
|
|
41
|
+
|
|
42
|
+
const startEditing = () => {
|
|
43
|
+
setDraft(content);
|
|
44
|
+
focusInputAndMoveToEnd(inputRef);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const cancel = () => {
|
|
48
|
+
setDraft(null);
|
|
49
|
+
requestAnimationFrame(() => editButtonRef.current?.focus());
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const submit = (value: string) => {
|
|
53
|
+
if (!canEdit || !value.trim()) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
onEdit(value);
|
|
57
|
+
setDraft(null);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
if (editing) {
|
|
61
|
+
return (
|
|
62
|
+
<div className="w-full rounded-[10px] border border-border bg-background">
|
|
63
|
+
{attachments}
|
|
64
|
+
<div className="px-2 py-1.5">
|
|
65
|
+
<PromptInput
|
|
66
|
+
inputRef={inputRef}
|
|
67
|
+
className="max-h-[400px]"
|
|
68
|
+
value={draft}
|
|
69
|
+
onChange={setDraft}
|
|
70
|
+
onSubmit={(_e, value) => submit(value)}
|
|
71
|
+
onClose={cancel}
|
|
72
|
+
placeholder="Type your message. Use @ to reference variables and cells."
|
|
73
|
+
/>
|
|
74
|
+
</div>
|
|
75
|
+
<div className="flex items-center gap-2 border-t border-border/20 px-3 py-1.5">
|
|
76
|
+
<span className="mr-auto text-xs text-muted-foreground">
|
|
77
|
+
Responses after this message will be replaced.
|
|
78
|
+
</span>
|
|
79
|
+
<Button variant="text" size="xs" onClick={cancel}>
|
|
80
|
+
Cancel
|
|
81
|
+
</Button>
|
|
82
|
+
<Button
|
|
83
|
+
size="xs"
|
|
84
|
+
disabled={!canEdit || !draft.trim()}
|
|
85
|
+
onClick={() => submit(draft)}
|
|
86
|
+
>
|
|
87
|
+
Send
|
|
88
|
+
</Button>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<div className="flex max-w-[90%] flex-col items-end gap-1">
|
|
96
|
+
{attachments}
|
|
97
|
+
<div className="rounded-[10px] bg-(--slate-3) px-3 py-1.5 whitespace-pre-wrap wrap-break-word">
|
|
98
|
+
<MentionText text={content} />
|
|
99
|
+
</div>
|
|
100
|
+
<div className="flex items-center gap-1 opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100">
|
|
101
|
+
<Tooltip content="Edit">
|
|
102
|
+
<Button
|
|
103
|
+
ref={editButtonRef}
|
|
104
|
+
variant="text"
|
|
105
|
+
size="icon"
|
|
106
|
+
aria-label="Edit message"
|
|
107
|
+
className="chat-control"
|
|
108
|
+
disabled={!canEdit}
|
|
109
|
+
onClick={startEditing}
|
|
110
|
+
>
|
|
111
|
+
<PencilIcon className="size-3" />
|
|
112
|
+
</Button>
|
|
113
|
+
</Tooltip>
|
|
114
|
+
<CopyClipboardIcon
|
|
115
|
+
value={content}
|
|
116
|
+
className="size-3"
|
|
117
|
+
buttonClassName="chat-control flex size-6 items-center justify-center rounded-md"
|
|
118
|
+
/>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const MENTION = /([@#])[\w-]+:\/\/([\w./-]+)/g;
|
|
125
|
+
|
|
126
|
+
const MentionText: React.FC<{ text: string }> = ({ text }) => {
|
|
127
|
+
const nodes: React.ReactNode[] = [];
|
|
128
|
+
let last = 0;
|
|
129
|
+
for (const match of text.matchAll(MENTION)) {
|
|
130
|
+
nodes.push(text.slice(last, match.index));
|
|
131
|
+
nodes.push(
|
|
132
|
+
<span
|
|
133
|
+
key={match.index}
|
|
134
|
+
title={match[0].slice(1)}
|
|
135
|
+
className="rounded-sm bg-(--slate-5) px-1 font-mono text-[0.85em]"
|
|
136
|
+
>
|
|
137
|
+
{match[1]}
|
|
138
|
+
{match[2]}
|
|
139
|
+
</span>,
|
|
140
|
+
);
|
|
141
|
+
last = match.index + match[0].length;
|
|
142
|
+
}
|
|
143
|
+
nodes.push(text.slice(last));
|
|
144
|
+
return nodes;
|
|
145
|
+
};
|
|
@@ -10,6 +10,7 @@ import { partial } from "@/__mocks__/partial";
|
|
|
10
10
|
import { MockRequestClient } from "@/__mocks__/requests";
|
|
11
11
|
import { ModalProvider } from "@/components/modal/ImperativeModal";
|
|
12
12
|
import { toast } from "@/components/ui/use-toast";
|
|
13
|
+
import { type Edit, stagedAICellsAtom } from "@/core/ai/staged-cells";
|
|
13
14
|
import { CellId } from "@/core/cells/ids";
|
|
14
15
|
import { requestClientAtom } from "@/core/network/requests";
|
|
15
16
|
import type { NotebookReplica } from "@/core/notebook/replica";
|
|
@@ -21,8 +22,11 @@ import { useCellActionButtons } from "../useCellActionButton";
|
|
|
21
22
|
vi.mock("@/utils/copy", () => ({ copyToClipboard: vi.fn() }));
|
|
22
23
|
vi.mock("@/components/ui/use-toast", () => ({ toast: vi.fn() }));
|
|
23
24
|
|
|
24
|
-
function renderActions(cellId: CellId, name: string) {
|
|
25
|
+
function renderActions(cellId: CellId, name: string, edit?: Edit) {
|
|
25
26
|
const store = createStore();
|
|
27
|
+
if (edit) {
|
|
28
|
+
store.set(stagedAICellsAtom, new Map([[cellId, edit]]));
|
|
29
|
+
}
|
|
26
30
|
const { model, documentAtom, stop } = mockDocumentModel(store);
|
|
27
31
|
onTestFinished(stop);
|
|
28
32
|
store.set(requestClientAtom, MockRequestClient.create());
|
|
@@ -111,3 +115,16 @@ describe("useCellActionButtons", () => {
|
|
|
111
115
|
expect(getAction(groups, "Copy cell ID").disabled).toBeUndefined();
|
|
112
116
|
});
|
|
113
117
|
});
|
|
118
|
+
|
|
119
|
+
it("offers individual AI review actions in the cell menu only for pending changes", () => {
|
|
120
|
+
const id = CellId.create();
|
|
121
|
+
const pending = renderActions(id, "_", { type: "add_cell" });
|
|
122
|
+
expect(pending[0].map((action) => action.label)).toEqual([
|
|
123
|
+
"Keep cell",
|
|
124
|
+
"Discard cell",
|
|
125
|
+
]);
|
|
126
|
+
const ordinary = renderActions(id, "_");
|
|
127
|
+
expect(ordinary.flat().some((action) => action.label === "Keep cell")).toBe(
|
|
128
|
+
false,
|
|
129
|
+
);
|
|
130
|
+
});
|
|
@@ -63,6 +63,7 @@ import { copyToClipboard } from "@/utils/copy";
|
|
|
63
63
|
import { downloadCellOutputAsImage } from "@/utils/download";
|
|
64
64
|
import { MarkdownIcon, PythonIcon } from "../cell/code/icons";
|
|
65
65
|
import { useDeleteCellCallback } from "../cell/useDeleteCell";
|
|
66
|
+
import { useStagedAICellActions } from "../cell/StagedAICell";
|
|
66
67
|
import { useRunCell } from "../cell/useRunCells";
|
|
67
68
|
import { useSplitCellCallback } from "../cell/useSplitCell";
|
|
68
69
|
import { NameCellInput } from "./name-cell-input";
|
|
@@ -93,6 +94,7 @@ export function useCellActionButtons({ cell, closePopover }: Props) {
|
|
|
93
94
|
clearCellOutput,
|
|
94
95
|
createCell,
|
|
95
96
|
} = useCellActions();
|
|
97
|
+
const stagedActions = useStagedAICellActions(cell?.cellId);
|
|
96
98
|
const showHiddenCode = useSetAtom(showHiddenCodeAtom);
|
|
97
99
|
const edit = useNotebookDocumentEdit();
|
|
98
100
|
const splitCell = useSplitCellCallback();
|
|
@@ -144,6 +146,7 @@ export function useCellActionButtons({ cell, closePopover }: Props) {
|
|
|
144
146
|
|
|
145
147
|
// Actions
|
|
146
148
|
const actions: ActionButton[][] = [
|
|
149
|
+
stagedActions,
|
|
147
150
|
[
|
|
148
151
|
{
|
|
149
152
|
icon: <PlayIcon size={13} strokeWidth={1.5} />,
|
|
@@ -46,43 +46,6 @@ export const createAiCompletionOnKeydown = (opts: {
|
|
|
46
46
|
};
|
|
47
47
|
};
|
|
48
48
|
|
|
49
|
-
export const CompletionActionsCellFooter: React.FC<{
|
|
50
|
-
isLoading: boolean;
|
|
51
|
-
onAccept: () => void;
|
|
52
|
-
onDecline: () => void;
|
|
53
|
-
size?: "xs" | "sm";
|
|
54
|
-
multipleCompletions?: boolean;
|
|
55
|
-
runCell?: () => void;
|
|
56
|
-
acceptLabel?: string;
|
|
57
|
-
declineLabel?: string;
|
|
58
|
-
}> = ({
|
|
59
|
-
isLoading,
|
|
60
|
-
onAccept,
|
|
61
|
-
onDecline,
|
|
62
|
-
runCell,
|
|
63
|
-
acceptLabel,
|
|
64
|
-
declineLabel,
|
|
65
|
-
size,
|
|
66
|
-
}) => {
|
|
67
|
-
return (
|
|
68
|
-
<>
|
|
69
|
-
<AcceptCompletionButton
|
|
70
|
-
isLoading={isLoading}
|
|
71
|
-
onAccept={onAccept}
|
|
72
|
-
size={size}
|
|
73
|
-
runCell={runCell}
|
|
74
|
-
label={acceptLabel}
|
|
75
|
-
/>
|
|
76
|
-
<RejectCompletionButton
|
|
77
|
-
onDecline={onDecline}
|
|
78
|
-
size={size}
|
|
79
|
-
label={declineLabel}
|
|
80
|
-
disabled={isLoading}
|
|
81
|
-
/>
|
|
82
|
-
</>
|
|
83
|
-
);
|
|
84
|
-
};
|
|
85
|
-
|
|
86
49
|
export const AcceptCompletionButton: React.FC<{
|
|
87
50
|
isLoading: boolean;
|
|
88
51
|
onAccept: () => void;
|
|
@@ -1,24 +1,17 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
-
import { useAtomValue, useStore } from "jotai";
|
|
4
|
-
import {
|
|
5
|
-
import { Tooltip } from "@/components/ui/tooltip";
|
|
3
|
+
import { atom, useAtomValue, useStore } from "jotai";
|
|
4
|
+
import { useMemo } from "react";
|
|
6
5
|
import {
|
|
7
6
|
type Edit,
|
|
7
|
+
stagedAICellsAtom,
|
|
8
8
|
useStagedAICell,
|
|
9
9
|
useStagedCells,
|
|
10
10
|
} from "@/core/ai/staged-cells";
|
|
11
11
|
import { chatGenerationInProgressAtom } from "@/core/ai/state";
|
|
12
|
-
import { getCellEditorView } from "@/core/cells/cells";
|
|
13
12
|
import type { CellId } from "@/core/cells/ids";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import { Logger } from "@/utils/Logger";
|
|
17
|
-
import {
|
|
18
|
-
AI_EDIT_ACTION_LABELS,
|
|
19
|
-
CompletionActionsCellFooter,
|
|
20
|
-
} from "../ai/completion-handlers";
|
|
21
|
-
import { useRunCell } from "./useRunCells";
|
|
13
|
+
import { AI_EDIT_ACTION_LABELS } from "../ai/completion-handlers";
|
|
14
|
+
import type { ActionButton } from "../actions/types";
|
|
22
15
|
|
|
23
16
|
const actionLabels: Record<Edit["type"], { accept: string; decline: string }> =
|
|
24
17
|
{
|
|
@@ -27,142 +20,56 @@ const actionLabels: Record<Edit["type"], { accept: string; decline: string }> =
|
|
|
27
20
|
delete_cell: { accept: "Delete cell", decline: "Keep cell" },
|
|
28
21
|
};
|
|
29
22
|
|
|
30
|
-
export const
|
|
31
|
-
cellId: CellId;
|
|
32
|
-
className?: string;
|
|
33
|
-
}> = ({ cellId, className }) => {
|
|
34
|
-
const stagedCell = useStagedAICell(cellId);
|
|
35
|
-
|
|
36
|
-
if (!stagedCell) {
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const cellClass =
|
|
41
|
-
stagedCell.type === "delete_cell"
|
|
42
|
-
? "mo-ai-deleted-cell"
|
|
43
|
-
: "mo-ai-generated-cell";
|
|
44
|
-
|
|
45
|
-
return <div className={cn(cellClass, className)} />;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
export const StagedAICellFooter: React.FC<{ cellId: CellId }> = ({
|
|
23
|
+
export const StagedAIDeletionMarker: React.FC<{ cellId: CellId }> = ({
|
|
49
24
|
cellId,
|
|
50
|
-
}) =>
|
|
25
|
+
}) =>
|
|
26
|
+
useStagedAICell(cellId)?.type === "delete_cell" ? (
|
|
27
|
+
<div className="mo-ai-deleted-cell" />
|
|
28
|
+
) : null;
|
|
29
|
+
|
|
30
|
+
export function useStagedAICellActions(
|
|
31
|
+
cellId: CellId | undefined,
|
|
32
|
+
): ActionButton[] {
|
|
51
33
|
const store = useStore();
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const handleCompletion = (type: "accept" | "reject") => {
|
|
66
|
-
const completionFunc =
|
|
67
|
-
type === "accept" ? acceptStagedCell : rejectStagedCell;
|
|
68
|
-
completionFunc(cellId, stagedAiCell, removeStagedCell, deleteStagedCell);
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
const tooltipContent =
|
|
72
|
-
stagedAiCell.type === "add_cell"
|
|
73
|
-
? "AI-generated cell"
|
|
74
|
-
: isDeletion
|
|
75
|
-
? "AI suggests deleting this cell"
|
|
76
|
-
: "AI changed this cell";
|
|
77
|
-
|
|
78
|
-
return (
|
|
79
|
-
<div className="mo-ai-cell-footer">
|
|
80
|
-
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
|
81
|
-
<Tooltip content={tooltipContent}>
|
|
82
|
-
<span
|
|
83
|
-
className="inline-flex items-center gap-1.5"
|
|
84
|
-
aria-label={tooltipContent}
|
|
85
|
-
>
|
|
86
|
-
<SparklesIcon
|
|
87
|
-
aria-hidden="true"
|
|
88
|
-
className="size-3.5 text-(--blue-11)"
|
|
89
|
-
/>
|
|
90
|
-
<span>AI</span>
|
|
91
|
-
</span>
|
|
92
|
-
</Tooltip>
|
|
93
|
-
</div>
|
|
94
|
-
<div className="flex items-center gap-1.5">
|
|
95
|
-
<CompletionActionsCellFooter
|
|
96
|
-
isLoading={isGenerating}
|
|
97
|
-
onAccept={() => handleCompletion("accept")}
|
|
98
|
-
onDecline={() => handleCompletion("reject")}
|
|
99
|
-
size="xs"
|
|
100
|
-
runCell={isDeletion ? undefined : runCell}
|
|
101
|
-
acceptLabel={labels.accept}
|
|
102
|
-
declineLabel={labels.decline}
|
|
103
|
-
/>
|
|
104
|
-
</div>
|
|
105
|
-
</div>
|
|
34
|
+
const edit = useStagedAICell(cellId);
|
|
35
|
+
// Every cell calls this; only cells with a pending edit care about the lock.
|
|
36
|
+
const isLocked = useAtomValue(
|
|
37
|
+
useMemo(
|
|
38
|
+
() =>
|
|
39
|
+
atom(
|
|
40
|
+
(get) =>
|
|
41
|
+
cellId !== undefined &&
|
|
42
|
+
get(stagedAICellsAtom).has(cellId) &&
|
|
43
|
+
get(chatGenerationInProgressAtom),
|
|
44
|
+
),
|
|
45
|
+
[cellId],
|
|
46
|
+
),
|
|
106
47
|
);
|
|
107
|
-
};
|
|
48
|
+
const { acceptStagedCell, rejectStagedCell } = useStagedCells();
|
|
108
49
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
*/
|
|
112
|
-
export function acceptStagedCell(
|
|
113
|
-
cellId: CellId,
|
|
114
|
-
edit: Edit,
|
|
115
|
-
removeStagedCell: (cellId: CellId) => void,
|
|
116
|
-
deleteStagedCell: (cellId: CellId) => void,
|
|
117
|
-
): void {
|
|
118
|
-
switch (edit.type) {
|
|
119
|
-
case "delete_cell":
|
|
120
|
-
// For delete cells, the cell is deleted when the completion is accepted
|
|
121
|
-
deleteStagedCell(cellId);
|
|
122
|
-
break;
|
|
123
|
-
default:
|
|
124
|
-
removeStagedCell(cellId);
|
|
125
|
-
break;
|
|
50
|
+
if (!edit || cellId === undefined) {
|
|
51
|
+
return [];
|
|
126
52
|
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Reject a staged cell and revert the changes.
|
|
131
|
-
*/
|
|
132
|
-
export function rejectStagedCell(
|
|
133
|
-
cellId: CellId,
|
|
134
|
-
edit: Edit,
|
|
135
|
-
removeStagedCell: (cellId: CellId) => void,
|
|
136
|
-
deleteStagedCell: (cellId: CellId) => void,
|
|
137
|
-
): void {
|
|
138
|
-
switch (edit.type) {
|
|
139
|
-
case "update_cell": {
|
|
140
|
-
// Revert cell code
|
|
141
|
-
const editorView = getCellEditorView(cellId);
|
|
142
|
-
if (!editorView) {
|
|
143
|
-
Logger.error("Editor for this cell not found", { cellId });
|
|
144
|
-
break;
|
|
145
|
-
}
|
|
146
53
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
54
|
+
const labels = actionLabels[edit.type];
|
|
55
|
+
return [
|
|
56
|
+
{
|
|
57
|
+
label: labels.accept,
|
|
58
|
+
disabled: isLocked,
|
|
59
|
+
handle: () => {
|
|
60
|
+
if (!store.get(chatGenerationInProgressAtom)) {
|
|
61
|
+
acceptStagedCell(cellId, edit);
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
label: labels.decline,
|
|
67
|
+
disabled: isLocked,
|
|
68
|
+
handle: () => {
|
|
69
|
+
if (!store.get(chatGenerationInProgressAtom)) {
|
|
70
|
+
rejectStagedCell(cellId, edit);
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
];
|
|
168
75
|
}
|