@marimo-team/islands 0.23.14-dev6 → 0.23.14-dev7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ConnectedDataExplorerComponent-Du3_nUzI.js → ConnectedDataExplorerComponent-DXBx_nQg.js} +4 -4
- package/dist/{chat-ui-BZxLHwyD.js → chat-ui-DYBcNEdd.js} +81 -81
- package/dist/{code-visibility-C90Am97q.js → code-visibility-AEdE3uR_.js} +1030 -837
- package/dist/{formats-d6MhLuQ9.js → formats-WsOgyW_K.js} +1 -1
- package/dist/{glide-data-editor-DkzAInWG.js → glide-data-editor-qpmKyAPn.js} +2 -2
- package/dist/{html-to-image-CGp_08St.js → html-to-image-MqcD07Bw.js} +73 -72
- package/dist/{input-CbEz_aj_.js → input-BSdZp5Ng.js} +1 -1
- package/dist/main.js +254 -254
- package/dist/{mermaid-CJW9vIyO.js → mermaid-D-HYBMEV.js} +2 -2
- package/dist/{process-output-R6JsYrv3.js → process-output-Dt3icftd.js} +1 -1
- package/dist/{reveal-component-C_u9FY4_.js → reveal-component-C1Y6sY7N.js} +5 -5
- package/dist/{spec-Bv-XlYiv.js → spec-CnTgI25l.js} +1 -1
- package/dist/{toDate-D-l5s8nn.js → toDate-D1Z7ZXWh.js} +1 -1
- package/dist/{useAsyncData-1Dhzjfwf.js → useAsyncData-BMc8itk2.js} +1 -1
- package/dist/{useDeepCompareMemoize-CDWT3BDz.js → useDeepCompareMemoize-ZwmDBRDY.js} +1 -1
- package/dist/{useLifecycle-AHlswLw-.js → useLifecycle-CxffarYV.js} +1 -1
- package/dist/{useTheme-BrYvK-_A.js → useTheme-yGsGEk82.js} +26 -24
- package/dist/{vega-component-Pk6lyc_a.js → vega-component-BFJTyykA.js} +5 -5
- package/package.json +1 -1
- package/src/components/chat/acp/agent-panel.tsx +35 -1
- package/src/components/chat/chat-panel.tsx +68 -29
- package/src/components/editor/chrome/wrapper/__tests__/useOpenAiAssistant.test.ts +36 -0
- package/src/components/editor/chrome/wrapper/useAiPanel.ts +3 -1
- package/src/components/editor/chrome/wrapper/useOpenAiAssistant.ts +88 -0
- package/src/components/editor/errors/__tests__/auto-fix.test.ts +119 -0
- package/src/components/editor/errors/auto-fix.tsx +108 -34
- package/src/components/editor/errors/fix-mode.ts +1 -1
- package/src/components/editor/output/MarimoTracebackOutput.tsx +10 -1
- package/src/components/editor/output/__tests__/traceback.test.tsx +14 -6
- package/src/core/ai/config.ts +2 -2
- package/src/core/ai/state.ts +11 -0
- package/src/core/codemirror/utils.ts +15 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { cellId } from "@/__tests__/branded";
|
|
5
|
+
import type { MarimoError } from "@/core/kernel/messages";
|
|
6
|
+
|
|
7
|
+
const getDatasourceContext = vi.fn<(id: unknown) => string | null>(() => null);
|
|
8
|
+
|
|
9
|
+
vi.mock("@/core/ai/context/providers/datasource", () => ({
|
|
10
|
+
getDatasourceContext: (id: unknown) => getDatasourceContext(id),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
const { buildFixPrompt, buildFixPromptFromText } = await import("../auto-fix");
|
|
14
|
+
|
|
15
|
+
describe("buildFixPromptFromText", () => {
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
getDatasourceContext.mockReset();
|
|
18
|
+
getDatasourceContext.mockReturnValue(null);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("includes the cell id in the header when provided", () => {
|
|
22
|
+
const prompt = buildFixPromptFromText("boom", cellId("cell-1"));
|
|
23
|
+
expect(prompt).toBe(
|
|
24
|
+
"My cell (id: cell-1) produced the following error. Please fix it:\n\nboom",
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("uses a generic header when no cell id is provided", () => {
|
|
29
|
+
const prompt = buildFixPromptFromText("boom");
|
|
30
|
+
expect(prompt).toBe(
|
|
31
|
+
"My code gives the following error. Please fix it:\n\nboom",
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("appends datasource context when explicitly requested for the cell", () => {
|
|
36
|
+
getDatasourceContext.mockReturnValue("@datasource://my_db");
|
|
37
|
+
const prompt = buildFixPromptFromText("boom", cellId("cell-1"), {
|
|
38
|
+
includeDatasourceContext: true,
|
|
39
|
+
});
|
|
40
|
+
expect(prompt).toBe(
|
|
41
|
+
"My cell (id: cell-1) produced the following error. Please fix it:\n\nboom\n\nDatabase schema: @datasource://my_db",
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("does not append datasource context by default", () => {
|
|
46
|
+
getDatasourceContext.mockReturnValue("@datasource://my_db");
|
|
47
|
+
const prompt = buildFixPromptFromText("boom", cellId("cell-1"));
|
|
48
|
+
expect(prompt).toBe(
|
|
49
|
+
"My cell (id: cell-1) produced the following error. Please fix it:\n\nboom",
|
|
50
|
+
);
|
|
51
|
+
expect(getDatasourceContext).not.toHaveBeenCalled();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("does not look up datasource context without a cell id", () => {
|
|
55
|
+
buildFixPromptFromText("boom", undefined, {
|
|
56
|
+
includeDatasourceContext: true,
|
|
57
|
+
});
|
|
58
|
+
expect(getDatasourceContext).not.toHaveBeenCalled();
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe("buildFixPrompt", () => {
|
|
63
|
+
beforeEach(() => {
|
|
64
|
+
getDatasourceContext.mockReset();
|
|
65
|
+
getDatasourceContext.mockReturnValue(null);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("uses the error message when present", () => {
|
|
69
|
+
const errors: MarimoError[] = [
|
|
70
|
+
{ type: "sql-error", msg: "syntax error", sql_statement: "SELECT" },
|
|
71
|
+
];
|
|
72
|
+
expect(buildFixPrompt(errors, cellId("cell-1"))).toBe(
|
|
73
|
+
"My cell (id: cell-1) produced the following error. Please fix it:\n\nsyntax error",
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("joins multiple errors with newlines", () => {
|
|
78
|
+
const errors: MarimoError[] = [
|
|
79
|
+
{ type: "syntax", msg: "bad syntax" },
|
|
80
|
+
{
|
|
81
|
+
type: "exception",
|
|
82
|
+
exception_type: "ValueError",
|
|
83
|
+
msg: "bad value",
|
|
84
|
+
raising_cell: null,
|
|
85
|
+
},
|
|
86
|
+
];
|
|
87
|
+
expect(buildFixPrompt(errors, cellId("cell-1"))).toBe(
|
|
88
|
+
"My cell (id: cell-1) produced the following error. Please fix it:\n\nbad syntax\nbad value",
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("falls back to the error type when there is no message", () => {
|
|
93
|
+
const errors: MarimoError[] = [
|
|
94
|
+
{ type: "multiple-defs", name: "foo", cells: [cellId("foo")] },
|
|
95
|
+
];
|
|
96
|
+
expect(buildFixPrompt(errors, cellId("cell-1"))).toBe(
|
|
97
|
+
"My cell (id: cell-1) produced the following error. Please fix it:\n\nmultiple-defs",
|
|
98
|
+
);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("appends datasource context for SQL errors", () => {
|
|
102
|
+
getDatasourceContext.mockReturnValue("@datasource://my_db");
|
|
103
|
+
const errors: MarimoError[] = [
|
|
104
|
+
{ type: "sql-error", msg: "syntax error", sql_statement: "SELECT" },
|
|
105
|
+
];
|
|
106
|
+
expect(buildFixPrompt(errors, cellId("cell-1"))).toBe(
|
|
107
|
+
"My cell (id: cell-1) produced the following error. Please fix it:\n\nsyntax error\n\nDatabase schema: @datasource://my_db",
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("does not append datasource context for non-SQL errors", () => {
|
|
112
|
+
getDatasourceContext.mockReturnValue("@datasource://my_db");
|
|
113
|
+
const errors: MarimoError[] = [{ type: "syntax", msg: "bad syntax" }];
|
|
114
|
+
expect(buildFixPrompt(errors, cellId("cell-1"))).toBe(
|
|
115
|
+
"My cell (id: cell-1) produced the following error. Please fix it:\n\nbad syntax",
|
|
116
|
+
);
|
|
117
|
+
expect(getDatasourceContext).not.toHaveBeenCalled();
|
|
118
|
+
});
|
|
119
|
+
});
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
3
|
import { useAtomValue, useSetAtom, useStore } from "jotai";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
CheckIcon,
|
|
6
|
+
ChevronDownIcon,
|
|
7
|
+
HatGlasses,
|
|
8
|
+
type LucideIcon,
|
|
9
|
+
SparklesIcon,
|
|
10
|
+
WrenchIcon,
|
|
11
|
+
} from "lucide-react";
|
|
5
12
|
import { Button } from "@/components/ui/button";
|
|
6
13
|
import {
|
|
7
14
|
DropdownMenu,
|
|
@@ -14,11 +21,46 @@ import { aiCompletionCellAtom } from "@/core/ai/state";
|
|
|
14
21
|
import { notebookAtom, useCellActions } from "@/core/cells/cells";
|
|
15
22
|
import type { CellId } from "@/core/cells/ids";
|
|
16
23
|
import { aiFeaturesEnabledAtom } from "@/core/config/config";
|
|
24
|
+
import { getDatasourceContext } from "@/core/ai/context/providers/datasource";
|
|
17
25
|
import { getAutoFixes } from "@/core/errors/errors";
|
|
18
26
|
import type { MarimoError } from "@/core/kernel/messages";
|
|
19
27
|
import { cn } from "@/utils/cn";
|
|
28
|
+
import { useOpenAiAssistant } from "../chrome/wrapper/useOpenAiAssistant";
|
|
20
29
|
import { type FixMode, useFixMode } from "./fix-mode";
|
|
21
30
|
|
|
31
|
+
export function buildFixPromptFromText(
|
|
32
|
+
errorText: string,
|
|
33
|
+
cellId?: CellId,
|
|
34
|
+
{
|
|
35
|
+
includeDatasourceContext = false,
|
|
36
|
+
}: { includeDatasourceContext?: boolean } = {},
|
|
37
|
+
): string {
|
|
38
|
+
const header =
|
|
39
|
+
cellId != null
|
|
40
|
+
? `My cell (id: ${cellId}) produced the following error. Please fix it:`
|
|
41
|
+
: "My code gives the following error. Please fix it:";
|
|
42
|
+
let prompt = `${header}\n\n${errorText}`;
|
|
43
|
+
if (cellId != null && includeDatasourceContext) {
|
|
44
|
+
const datasourceContext = getDatasourceContext(cellId);
|
|
45
|
+
if (datasourceContext) {
|
|
46
|
+
prompt += `\n\nDatabase schema: ${datasourceContext}`;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return prompt;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function buildFixPrompt(errors: MarimoError[], cellId: CellId): string {
|
|
53
|
+
const errorText = errors
|
|
54
|
+
.map((error) => ("msg" in error && error.msg ? error.msg : error.type))
|
|
55
|
+
.join("\n");
|
|
56
|
+
const includeDatasourceContext = errors.some(
|
|
57
|
+
(error) => error.type === "sql-error",
|
|
58
|
+
);
|
|
59
|
+
return buildFixPromptFromText(errorText, cellId, {
|
|
60
|
+
includeDatasourceContext,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
22
64
|
export const AutoFixButton = ({
|
|
23
65
|
errors,
|
|
24
66
|
cellId,
|
|
@@ -35,6 +77,7 @@ export const AutoFixButton = ({
|
|
|
35
77
|
getAutoFixes(error, { aiEnabled: aiFeaturesEnabled }),
|
|
36
78
|
);
|
|
37
79
|
const setAiCompletionCell = useSetAtom(aiCompletionCellAtom);
|
|
80
|
+
const openAiAssistant = useOpenAiAssistant();
|
|
38
81
|
|
|
39
82
|
if (autoFixes.length === 0) {
|
|
40
83
|
return null;
|
|
@@ -67,6 +110,12 @@ export const AutoFixButton = ({
|
|
|
67
110
|
editorView?.focus();
|
|
68
111
|
};
|
|
69
112
|
|
|
113
|
+
const openAISidebar = () => {
|
|
114
|
+
openAiAssistant({
|
|
115
|
+
prompt: buildFixPrompt(errors, cellId),
|
|
116
|
+
});
|
|
117
|
+
};
|
|
118
|
+
|
|
70
119
|
return (
|
|
71
120
|
<div className={cn("my-2", className)}>
|
|
72
121
|
{firstFix.fixType === "ai" ? (
|
|
@@ -74,6 +123,7 @@ export const AutoFixButton = ({
|
|
|
74
123
|
tooltip={firstFix.description}
|
|
75
124
|
openPrompt={() => handleFix(false)}
|
|
76
125
|
applyAutofix={() => handleFix(true)}
|
|
126
|
+
openChat={openAISidebar}
|
|
77
127
|
/>
|
|
78
128
|
) : (
|
|
79
129
|
<Tooltip content={firstFix.description} align="start">
|
|
@@ -92,23 +142,53 @@ export const AutoFixButton = ({
|
|
|
92
142
|
);
|
|
93
143
|
};
|
|
94
144
|
|
|
95
|
-
|
|
96
|
-
|
|
145
|
+
interface FixModeConfig {
|
|
146
|
+
Icon: LucideIcon;
|
|
147
|
+
title: string;
|
|
148
|
+
description: string;
|
|
149
|
+
}
|
|
97
150
|
|
|
98
|
-
const
|
|
99
|
-
|
|
151
|
+
const MODE_CONFIG: Record<FixMode, FixModeConfig> = {
|
|
152
|
+
autofix: {
|
|
153
|
+
Icon: WrenchIcon,
|
|
154
|
+
title: "Inline AI Fix",
|
|
155
|
+
description: "Apply AI fixes inline in the cell",
|
|
156
|
+
},
|
|
157
|
+
chat: {
|
|
158
|
+
Icon: HatGlasses,
|
|
159
|
+
title: "Fix with AI assistant",
|
|
160
|
+
description: "Open the AI sidebar to fix",
|
|
161
|
+
},
|
|
162
|
+
prompt: {
|
|
163
|
+
Icon: SparklesIcon,
|
|
164
|
+
title: "Suggest a prompt",
|
|
165
|
+
description: "Edit the prompt before applying",
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const FIX_MODES: FixMode[] = ["autofix", "prompt", "chat"];
|
|
100
170
|
|
|
101
171
|
export const AIFixButton = ({
|
|
102
172
|
tooltip,
|
|
103
173
|
openPrompt,
|
|
104
174
|
applyAutofix,
|
|
175
|
+
openChat,
|
|
105
176
|
}: {
|
|
106
177
|
tooltip: string;
|
|
107
178
|
openPrompt: () => void;
|
|
108
179
|
applyAutofix: () => void;
|
|
180
|
+
openChat: () => void;
|
|
109
181
|
}) => {
|
|
110
182
|
const { fixMode, setFixMode } = useFixMode();
|
|
111
183
|
|
|
184
|
+
let onAction = openPrompt;
|
|
185
|
+
if (fixMode === "chat") {
|
|
186
|
+
onAction = openChat;
|
|
187
|
+
} else if (fixMode === "autofix") {
|
|
188
|
+
onAction = applyAutofix;
|
|
189
|
+
}
|
|
190
|
+
const { Icon, title } = MODE_CONFIG[fixMode];
|
|
191
|
+
|
|
112
192
|
return (
|
|
113
193
|
<div className="flex">
|
|
114
194
|
<Tooltip content={tooltip} align="start">
|
|
@@ -116,14 +196,10 @@ export const AIFixButton = ({
|
|
|
116
196
|
size="xs"
|
|
117
197
|
variant="outline"
|
|
118
198
|
className="font-normal rounded-r-none border-r-0"
|
|
119
|
-
onClick={
|
|
199
|
+
onClick={onAction}
|
|
120
200
|
>
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
) : (
|
|
124
|
-
<AutofixIcon className="h-3 w-3 mr-2 mb-0.5" />
|
|
125
|
-
)}
|
|
126
|
-
{fixMode === "prompt" ? PromptTitle : AutofixTitle}
|
|
201
|
+
<Icon className="h-3 w-3 mr-2 mb-0.5" />
|
|
202
|
+
{title}
|
|
127
203
|
</Button>
|
|
128
204
|
</Tooltip>
|
|
129
205
|
<DropdownMenu>
|
|
@@ -138,40 +214,38 @@ export const AIFixButton = ({
|
|
|
138
214
|
</Button>
|
|
139
215
|
</DropdownMenuTrigger>
|
|
140
216
|
<DropdownMenuContent align="end" className="w-56">
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
217
|
+
{FIX_MODES.map((mode) => (
|
|
218
|
+
<DropdownMenuItem
|
|
219
|
+
key={mode}
|
|
220
|
+
className="flex items-center gap-2"
|
|
221
|
+
onClick={() => setFixMode(mode)}
|
|
222
|
+
>
|
|
223
|
+
<AiModeItem mode={mode} selected={mode === fixMode} />
|
|
224
|
+
</DropdownMenuItem>
|
|
225
|
+
))}
|
|
149
226
|
</DropdownMenuContent>
|
|
150
227
|
</DropdownMenu>
|
|
151
228
|
</div>
|
|
152
229
|
);
|
|
153
230
|
};
|
|
154
231
|
|
|
155
|
-
const AiModeItem = ({
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const
|
|
163
|
-
const description =
|
|
164
|
-
mode === "prompt"
|
|
165
|
-
? "Edit the prompt before applying"
|
|
166
|
-
: "Apply AI fixes automatically";
|
|
232
|
+
const AiModeItem = ({
|
|
233
|
+
mode,
|
|
234
|
+
selected,
|
|
235
|
+
}: {
|
|
236
|
+
mode: FixMode;
|
|
237
|
+
selected: boolean;
|
|
238
|
+
}) => {
|
|
239
|
+
const { Icon, title, description } = MODE_CONFIG[mode];
|
|
167
240
|
|
|
168
241
|
return (
|
|
169
|
-
<div className="flex items-center gap-2">
|
|
170
|
-
|
|
242
|
+
<div className="flex items-center gap-2 w-full">
|
|
243
|
+
<Icon className="h-4 w-4 shrink-0" />
|
|
171
244
|
<div className="flex flex-col">
|
|
172
245
|
<span className="font-medium">{title}</span>
|
|
173
246
|
<span className="text-xs text-muted-foreground">{description}</span>
|
|
174
247
|
</div>
|
|
248
|
+
{selected && <CheckIcon className="h-4 w-4 ml-auto shrink-0" />}
|
|
175
249
|
</div>
|
|
176
250
|
);
|
|
177
251
|
};
|
|
@@ -4,7 +4,7 @@ import { useAtom } from "jotai";
|
|
|
4
4
|
import { atomWithStorage } from "jotai/utils";
|
|
5
5
|
import { jotaiJsonStorage } from "@/utils/storage/jotai";
|
|
6
6
|
|
|
7
|
-
export type FixMode = "prompt" | "autofix";
|
|
7
|
+
export type FixMode = "prompt" | "autofix" | "chat";
|
|
8
8
|
|
|
9
9
|
const BASE_KEY = "marimo:ai-autofix-mode";
|
|
10
10
|
|
|
@@ -41,7 +41,8 @@ import {
|
|
|
41
41
|
extractAllTracebackInfo,
|
|
42
42
|
getTracebackInfo,
|
|
43
43
|
} from "@/utils/traceback";
|
|
44
|
-
import {
|
|
44
|
+
import { useOpenAiAssistant } from "../chrome/wrapper/useOpenAiAssistant";
|
|
45
|
+
import { AIFixButton, buildFixPromptFromText } from "../errors/auto-fix";
|
|
45
46
|
import { MangledSegments } from "../errors/mangled-local-chip";
|
|
46
47
|
import { CellLinkTraceback } from "../links/cell-link";
|
|
47
48
|
import type { OnRefactorWithAI } from "../Output";
|
|
@@ -71,6 +72,7 @@ export const MarimoTracebackOutput = ({
|
|
|
71
72
|
|
|
72
73
|
const lastTracebackLine = lastLine(traceback);
|
|
73
74
|
const aiFeaturesEnabled = useAtomValue(aiFeaturesEnabledAtom);
|
|
75
|
+
const openAiAssistant = useOpenAiAssistant();
|
|
74
76
|
|
|
75
77
|
// Get last traceback info
|
|
76
78
|
const tracebackInfo = extractAllTracebackInfo(traceback)?.at(0);
|
|
@@ -97,6 +99,12 @@ export const MarimoTracebackOutput = ({
|
|
|
97
99
|
});
|
|
98
100
|
};
|
|
99
101
|
|
|
102
|
+
const openAISidebar = () => {
|
|
103
|
+
openAiAssistant({
|
|
104
|
+
prompt: buildFixPromptFromText(lastTracebackLine, cellId),
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
|
|
100
108
|
return (
|
|
101
109
|
<div className="flex flex-col gap-2 min-w-full w-fit">
|
|
102
110
|
<button
|
|
@@ -126,6 +134,7 @@ export const MarimoTracebackOutput = ({
|
|
|
126
134
|
tooltip="Fix with AI"
|
|
127
135
|
openPrompt={() => handleRefactorWithAI(false)}
|
|
128
136
|
applyAutofix={() => handleRefactorWithAI(true)}
|
|
137
|
+
openChat={openAISidebar}
|
|
129
138
|
/>
|
|
130
139
|
)}
|
|
131
140
|
{showDebugger && (
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
3
|
import { render } from "@testing-library/react";
|
|
4
|
+
import { Provider } from "jotai";
|
|
4
5
|
import { beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
|
|
6
|
+
import { MockRequestClient } from "@/__mocks__/requests";
|
|
5
7
|
import { Tracebacks } from "@/__mocks__/tracebacks";
|
|
6
8
|
import { cellId } from "@/__tests__/branded";
|
|
7
9
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
8
10
|
import { initialModeAtom } from "@/core/mode";
|
|
11
|
+
import { requestClientAtom } from "@/core/network/requests";
|
|
9
12
|
import { store } from "@/core/state/jotai";
|
|
10
13
|
import { renderHTML } from "@/plugins/core/RenderHTML";
|
|
11
14
|
import {
|
|
@@ -20,13 +23,16 @@ describe("traceback component", () => {
|
|
|
20
23
|
beforeEach(() => {
|
|
21
24
|
vi.resetAllMocks();
|
|
22
25
|
store.set(initialModeAtom, "edit");
|
|
26
|
+
store.set(requestClientAtom, MockRequestClient.create());
|
|
23
27
|
});
|
|
24
28
|
|
|
25
29
|
test("extracts cell-link", () => {
|
|
26
30
|
const traceback = (
|
|
27
|
-
<
|
|
28
|
-
<
|
|
29
|
-
|
|
31
|
+
<Provider store={store}>
|
|
32
|
+
<TooltipProvider>
|
|
33
|
+
<MarimoTracebackOutput traceback={Tracebacks.raw} cellId={cid} />
|
|
34
|
+
</TooltipProvider>
|
|
35
|
+
</Provider>
|
|
30
36
|
);
|
|
31
37
|
const { unmount, getAllByRole } = render(traceback);
|
|
32
38
|
|
|
@@ -44,9 +50,11 @@ describe("traceback component", () => {
|
|
|
44
50
|
|
|
45
51
|
test("renames File to Cell for relevant lines", () => {
|
|
46
52
|
const traceback = (
|
|
47
|
-
<
|
|
48
|
-
<
|
|
49
|
-
|
|
53
|
+
<Provider store={store}>
|
|
54
|
+
<TooltipProvider>
|
|
55
|
+
<MarimoTracebackOutput traceback={Tracebacks.raw} cellId={cid} />
|
|
56
|
+
</TooltipProvider>
|
|
57
|
+
</Provider>
|
|
50
58
|
);
|
|
51
59
|
const { unmount, container } = render(traceback);
|
|
52
60
|
|
package/src/core/ai/config.ts
CHANGED
|
@@ -60,7 +60,7 @@ export const useModelChange = () => {
|
|
|
60
60
|
},
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
-
saveConfig(newConfig);
|
|
63
|
+
await saveConfig(newConfig);
|
|
64
64
|
};
|
|
65
65
|
|
|
66
66
|
const saveModeChange = async (newMode: CopilotMode) => {
|
|
@@ -71,7 +71,7 @@ export const useModelChange = () => {
|
|
|
71
71
|
},
|
|
72
72
|
};
|
|
73
73
|
|
|
74
|
-
saveConfig(newConfig);
|
|
74
|
+
await saveConfig(newConfig);
|
|
75
75
|
};
|
|
76
76
|
|
|
77
77
|
return { saveModelChange, saveModeChange };
|
package/src/core/ai/state.ts
CHANGED
|
@@ -21,6 +21,17 @@ export interface AiCompletionCell {
|
|
|
21
21
|
|
|
22
22
|
export const aiCompletionCellAtom = atom<AiCompletionCell | null>(null);
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* A prompt queued to be delivered to the AI assistant panel.
|
|
26
|
+
* The active panel consumes this on mount/when ready, then clears it.
|
|
27
|
+
*/
|
|
28
|
+
export interface PendingAiPrompt {
|
|
29
|
+
prompt: string;
|
|
30
|
+
submit: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const pendingAiPromptAtom = atom<PendingAiPrompt | null>(null);
|
|
34
|
+
|
|
24
35
|
const INCLUDE_OTHER_CELLS_KEY = "marimo:ai:includeOtherCells";
|
|
25
36
|
export const includeOtherCellsAtom = atomWithStorage<boolean>(
|
|
26
37
|
INCLUDE_OTHER_CELLS_KEY,
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import type { EditorState, Transaction } from "@codemirror/state";
|
|
3
3
|
import type { EditorView, ViewUpdate } from "@codemirror/view";
|
|
4
4
|
import { getCM } from "@replit/codemirror-vim";
|
|
5
|
+
import type { ReactCodeMirrorRef } from "@uiw/react-codemirror";
|
|
5
6
|
|
|
6
7
|
export function isAtStartOfEditor(ev: { state: EditorState }) {
|
|
7
8
|
const main = ev.state.selection.main;
|
|
@@ -37,6 +38,20 @@ export function moveToEndOfEditor(ev: EditorView | undefined) {
|
|
|
37
38
|
});
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
/** We delay the focus and move to end of the editor until React has rendered the prefilled value. */
|
|
42
|
+
export function focusInputAndMoveToEnd(
|
|
43
|
+
ref: React.RefObject<ReactCodeMirrorRef | null>,
|
|
44
|
+
) {
|
|
45
|
+
requestAnimationFrame(() => {
|
|
46
|
+
const view = ref.current?.view;
|
|
47
|
+
if (!view) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
view.focus();
|
|
51
|
+
moveToEndOfEditor(view);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
40
55
|
export function isInVimMode(ev: EditorView): boolean {
|
|
41
56
|
return getCM(ev)?.state.vim != null;
|
|
42
57
|
}
|