@marimo-team/islands 0.23.9-dev34 → 0.23.9-dev37
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-MJy-Ll40.js → ConnectedDataExplorerComponent-BQBH2XAd.js} +4 -4
- package/dist/assets/__vite-browser-external-TaZstNaH.js +1 -0
- package/dist/assets/{worker-BoAkAmaG.js → worker-CZaLU0G8.js} +2 -2
- package/dist/{chat-ui-CpX2YcGy.js → chat-ui-BQqY0W74.js} +60 -60
- package/dist/{code-visibility-y3APpJ-N.js → code-visibility-CHwUF5vX.js} +675 -556
- package/dist/{formats-BIKFEOlR.js → formats-B7_JC7Ba.js} +1 -1
- package/dist/{glide-data-editor-DjQd6fKp.js → glide-data-editor-BmM4MCbn.js} +2 -2
- package/dist/{html-to-image-QL7QveRm.js → html-to-image-BAPmFVwS.js} +2139 -2152
- package/dist/{input-Dh0iMVFM.js → input-Ld3tUgdF.js} +1 -1
- package/dist/main.js +110 -109
- package/dist/{mermaid-CAibas-0.js → mermaid-BrUZ2PpQ.js} +2 -2
- package/dist/{process-output-C657UH7t.js → process-output-B55jxGI5.js} +1 -1
- package/dist/{reveal-component-Cbw9hzrS.js → reveal-component-DQF8h6lC.js} +5 -5
- package/dist/{spec-BKuFJIDz.js → spec-nqxKYdNH.js} +1 -1
- package/dist/{toDate-BeKbrOvs.js → toDate-DLCQY32Y.js} +1 -1
- package/dist/{useAsyncData-yp6n17kh.js → useAsyncData-3f5sSgzf.js} +1 -1
- package/dist/{useDeepCompareMemoize-DJvAHUIC.js → useDeepCompareMemoize-Cu37j2QD.js} +1 -1
- package/dist/{useLifecycle-CsYXf0Ln.js → useLifecycle-DVkMZA_I.js} +1 -1
- package/dist/{useTheme-CK_R9Mn8.js → useTheme-DNcgchnA.js} +11 -2
- package/dist/{vega-component-ikfBfkZO.js → vega-component-7odw1pLZ.js} +5 -5
- package/package.json +1 -1
- package/src/components/app-config/ai-config.tsx +74 -15
- package/src/components/chat/chat-panel.tsx +2 -2
- package/src/components/data-table/__tests__/header-items.test.tsx +220 -10
- package/src/components/data-table/column-header.tsx +17 -12
- package/src/components/data-table/export-actions.tsx +19 -12
- package/src/components/data-table/header-items.tsx +40 -16
- package/src/components/data-table/schemas.ts +2 -2
- package/src/components/editor/actions/useCellActionButton.tsx +3 -3
- package/src/components/editor/cell/code/cell-editor.tsx +7 -4
- package/src/components/editor/chrome/types.ts +13 -6
- package/src/components/editor/chrome/wrapper/app-chrome.tsx +6 -4
- package/src/components/editor/chrome/wrapper/footer-items/ai-status.tsx +10 -1
- package/src/components/editor/chrome/wrapper/sidebar.tsx +7 -5
- package/src/components/editor/errors/auto-fix.tsx +3 -3
- package/src/components/editor/navigation/__tests__/navigation.test.ts +15 -0
- package/src/components/editor/navigation/navigation.ts +5 -0
- package/src/components/editor/output/MarimoTracebackOutput.tsx +4 -3
- package/src/components/editor/renderers/cell-array.tsx +27 -24
- package/src/core/config/__tests__/config-schema.test.ts +2 -0
- package/src/core/config/config-schema.ts +1 -0
- package/src/core/config/config.ts +16 -0
- package/src/utils/__tests__/json-parser.test.ts +1 -69
- package/src/utils/json/json-parser.ts +0 -30
- package/dist/assets/__vite-browser-external-BBEFRPue.js +0 -1
|
@@ -29,10 +29,13 @@ import { formattingExample } from "./column-formatting/feature";
|
|
|
29
29
|
import { formatOptions } from "./column-formatting/types";
|
|
30
30
|
import { NAMELESS_COLUMN_PREFIX } from "./columns";
|
|
31
31
|
|
|
32
|
-
export function
|
|
33
|
-
column
|
|
34
|
-
locale
|
|
35
|
-
|
|
32
|
+
export function FormatOptions<TData, TValue>({
|
|
33
|
+
column,
|
|
34
|
+
locale,
|
|
35
|
+
}: {
|
|
36
|
+
column: Column<TData, TValue>;
|
|
37
|
+
locale: string;
|
|
38
|
+
}) {
|
|
36
39
|
const dataType: DataType | undefined = column.columnDef.meta?.dataType;
|
|
37
40
|
const columnFormatOptions = dataType ? formatOptions[dataType] : [];
|
|
38
41
|
|
|
@@ -83,9 +86,11 @@ export function renderFormatOptions<TData, TValue>(
|
|
|
83
86
|
);
|
|
84
87
|
}
|
|
85
88
|
|
|
86
|
-
export function
|
|
87
|
-
column
|
|
88
|
-
|
|
89
|
+
export function ColumnWrapping<TData, TValue>({
|
|
90
|
+
column,
|
|
91
|
+
}: {
|
|
92
|
+
column: Column<TData, TValue>;
|
|
93
|
+
}) {
|
|
89
94
|
if (!column.getCanWrap?.() || !column.getColumnWrapping) {
|
|
90
95
|
return null;
|
|
91
96
|
}
|
|
@@ -108,9 +113,11 @@ export function renderColumnWrapping<TData, TValue>(
|
|
|
108
113
|
);
|
|
109
114
|
}
|
|
110
115
|
|
|
111
|
-
export function
|
|
112
|
-
column
|
|
113
|
-
|
|
116
|
+
export function ColumnPinning<TData, TValue>({
|
|
117
|
+
column,
|
|
118
|
+
}: {
|
|
119
|
+
column: Column<TData, TValue>;
|
|
120
|
+
}) {
|
|
114
121
|
if (!column.getCanPin?.() || !column.getIsPinned) {
|
|
115
122
|
return null;
|
|
116
123
|
}
|
|
@@ -157,7 +164,11 @@ export function HideColumn<TData, TValue>({
|
|
|
157
164
|
);
|
|
158
165
|
}
|
|
159
166
|
|
|
160
|
-
export function
|
|
167
|
+
export function CopyColumn<TData, TValue>({
|
|
168
|
+
column,
|
|
169
|
+
}: {
|
|
170
|
+
column: Column<TData, TValue>;
|
|
171
|
+
}) {
|
|
161
172
|
if (!column.getCanCopy?.()) {
|
|
162
173
|
return null;
|
|
163
174
|
}
|
|
@@ -177,10 +188,19 @@ export function renderCopyColumn<TData, TValue>(column: Column<TData, TValue>) {
|
|
|
177
188
|
const AscIcon = ArrowUpNarrowWideIcon;
|
|
178
189
|
const DescIcon = ArrowDownWideNarrowIcon;
|
|
179
190
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
)
|
|
191
|
+
/**
|
|
192
|
+
* `table` is optional: it is only needed to detect multi-column sorting and
|
|
193
|
+
* offer "Clear all sorts". Call sites that build their header inside column
|
|
194
|
+
* definitions (where the table instance isn't yet in scope) omit it and fall
|
|
195
|
+
* back to single-column "Clear sort".
|
|
196
|
+
*/
|
|
197
|
+
export function Sorts<TData, TValue>({
|
|
198
|
+
column,
|
|
199
|
+
table,
|
|
200
|
+
}: {
|
|
201
|
+
column: Column<TData, TValue>;
|
|
202
|
+
table?: Table<TData>;
|
|
203
|
+
}) {
|
|
184
204
|
if (!column.getCanSort()) {
|
|
185
205
|
return null;
|
|
186
206
|
}
|
|
@@ -271,7 +291,11 @@ export function renderSortIcon<TData, TValue>(column: Column<TData, TValue>) {
|
|
|
271
291
|
return <Icon className="h-3 w-3" />;
|
|
272
292
|
}
|
|
273
293
|
|
|
274
|
-
export function
|
|
294
|
+
export function DataType<TData, TValue>({
|
|
295
|
+
column,
|
|
296
|
+
}: {
|
|
297
|
+
column: Column<TData, TValue>;
|
|
298
|
+
}) {
|
|
275
299
|
const dtype: string | undefined = column.columnDef.meta?.dtype;
|
|
276
300
|
if (!dtype) {
|
|
277
301
|
return null;
|
|
@@ -4,7 +4,7 @@ import z from "zod";
|
|
|
4
4
|
import { rpc } from "@/plugins/core/rpc";
|
|
5
5
|
|
|
6
6
|
export type DownloadAsArgs = (req: {
|
|
7
|
-
format: "csv" | "json" | "parquet";
|
|
7
|
+
format: "csv" | "json" | "parquet" | "tsv";
|
|
8
8
|
}) => Promise<{
|
|
9
9
|
url: string;
|
|
10
10
|
filename: string;
|
|
@@ -15,7 +15,7 @@ export type DownloadAsArgs = (req: {
|
|
|
15
15
|
export const DownloadAsSchema = rpc
|
|
16
16
|
.input(
|
|
17
17
|
z.object({
|
|
18
|
-
format: z.enum(["csv", "json", "parquet"]),
|
|
18
|
+
format: z.enum(["csv", "json", "parquet", "tsv"]),
|
|
19
19
|
}),
|
|
20
20
|
)
|
|
21
21
|
.output(
|
|
@@ -48,7 +48,7 @@ import {
|
|
|
48
48
|
import { switchLanguage } from "@/core/codemirror/language/extension";
|
|
49
49
|
import { MARKDOWN_INITIAL_HIDE_CODE } from "@/core/codemirror/language/languages/markdown";
|
|
50
50
|
import {
|
|
51
|
-
|
|
51
|
+
aiFeaturesEnabledAtom,
|
|
52
52
|
appWidthAtom,
|
|
53
53
|
autoInstantiateAtom,
|
|
54
54
|
} from "@/core/config/config";
|
|
@@ -100,7 +100,7 @@ export function useCellActionButtons({ cell, closePopover }: Props) {
|
|
|
100
100
|
const deleteCell = useDeleteCellCallback();
|
|
101
101
|
const { openModal } = useImperativeModal();
|
|
102
102
|
const setAiCompletionCell = useSetAtom(aiCompletionCellAtom);
|
|
103
|
-
const
|
|
103
|
+
const aiFeaturesEnabled = useAtomValue(aiFeaturesEnabledAtom);
|
|
104
104
|
const autoInstantiate = useAtomValue(autoInstantiateAtom);
|
|
105
105
|
const kioskMode = useAtomValue(kioskModeAtom);
|
|
106
106
|
const appWidth = useAtomValue(appWidthAtom);
|
|
@@ -162,7 +162,7 @@ export function useCellActionButtons({ cell, closePopover }: Props) {
|
|
|
162
162
|
{
|
|
163
163
|
icon: <SparklesIcon size={13} strokeWidth={1.5} />,
|
|
164
164
|
label: "Refactor with AI",
|
|
165
|
-
hidden: !
|
|
165
|
+
hidden: !aiFeaturesEnabled,
|
|
166
166
|
handle: () => {
|
|
167
167
|
setAiCompletionCell((current) =>
|
|
168
168
|
current?.cellId === cellId ? null : { cellId },
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
connectedDocAtom,
|
|
27
27
|
realTimeCollaboration,
|
|
28
28
|
} from "@/core/codemirror/rtc/extension";
|
|
29
|
-
import { autoInstantiateAtom,
|
|
29
|
+
import { autoInstantiateAtom, isAiFeatureEnabled } from "@/core/config/config";
|
|
30
30
|
import type { UserConfig } from "@/core/config/config-schema";
|
|
31
31
|
import { OverridingHotkeyProvider } from "@/core/hotkeys/hotkeys";
|
|
32
32
|
import { connectionAtom } from "@/core/network/connection";
|
|
@@ -173,13 +173,13 @@ const CellEditorInternal = ({
|
|
|
173
173
|
});
|
|
174
174
|
});
|
|
175
175
|
|
|
176
|
-
const
|
|
176
|
+
const aiFeaturesEnabled = isAiFeatureEnabled(userConfig);
|
|
177
177
|
|
|
178
178
|
const extensions = useMemo(() => {
|
|
179
179
|
const extensions = setupCodeMirror({
|
|
180
180
|
cellId,
|
|
181
181
|
showPlaceholder,
|
|
182
|
-
enableAI:
|
|
182
|
+
enableAI: aiFeaturesEnabled,
|
|
183
183
|
cellActions: {
|
|
184
184
|
...cellActions,
|
|
185
185
|
afterToggleMarkdown,
|
|
@@ -201,6 +201,9 @@ const CellEditorInternal = ({
|
|
|
201
201
|
splitCell,
|
|
202
202
|
toggleHideCode,
|
|
203
203
|
aiCellCompletion: () => {
|
|
204
|
+
if (!aiFeaturesEnabled) {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
204
207
|
let closed = false;
|
|
205
208
|
setAiCompletionCell((v) => {
|
|
206
209
|
// Toggle close
|
|
@@ -271,7 +274,7 @@ const CellEditorInternal = ({
|
|
|
271
274
|
userConfig.display,
|
|
272
275
|
userConfig.diagnostics,
|
|
273
276
|
userConfig.ai?.inline_tooltip,
|
|
274
|
-
|
|
277
|
+
aiFeaturesEnabled,
|
|
275
278
|
theme,
|
|
276
279
|
showPlaceholder,
|
|
277
280
|
cellActions,
|
|
@@ -201,16 +201,23 @@ export const PANEL_MAP = new Map<PanelType, PanelDescriptor>(
|
|
|
201
201
|
);
|
|
202
202
|
|
|
203
203
|
/**
|
|
204
|
-
* Check if a panel should be hidden based on its
|
|
205
|
-
* and `requiredCapability`.
|
|
204
|
+
* Check if a panel should be hidden based on its descriptor and runtime state.
|
|
206
205
|
*/
|
|
207
|
-
export function isPanelHidden(
|
|
208
|
-
panel
|
|
209
|
-
capabilities
|
|
210
|
-
|
|
206
|
+
export function isPanelHidden({
|
|
207
|
+
panel,
|
|
208
|
+
capabilities,
|
|
209
|
+
aiEnabled,
|
|
210
|
+
}: {
|
|
211
|
+
panel: PanelDescriptor;
|
|
212
|
+
capabilities: Capabilities;
|
|
213
|
+
aiEnabled: boolean;
|
|
214
|
+
}): boolean {
|
|
211
215
|
if (panel.hidden) {
|
|
212
216
|
return true;
|
|
213
217
|
}
|
|
218
|
+
if (panel.type === "ai" && !aiEnabled) {
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
214
221
|
if (panel.requiredCapability && !capabilities[panel.requiredCapability]) {
|
|
215
222
|
return true;
|
|
216
223
|
}
|
|
@@ -27,6 +27,7 @@ import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
|
27
27
|
import { LazyActivity } from "@/components/utils/lazy-mount";
|
|
28
28
|
import { cellErrorCount } from "@/core/cells/cells";
|
|
29
29
|
import { capabilitiesAtom } from "@/core/config/capabilities";
|
|
30
|
+
import { aiEnabledAtom } from "@/core/config/config";
|
|
30
31
|
import { getFeatureFlag } from "@/core/config/feature-flag";
|
|
31
32
|
import { cn } from "@/utils/cn";
|
|
32
33
|
import { ErrorBoundary } from "../../boundary/ErrorBoundary";
|
|
@@ -93,18 +94,19 @@ export const AppChrome: React.FC<PropsWithChildren> = ({ children }) => {
|
|
|
93
94
|
const [panelLayout, setPanelLayout] = useAtom(panelLayoutAtom);
|
|
94
95
|
// Subscribe to capabilities to re-render when they change (e.g., terminal capability)
|
|
95
96
|
const capabilities = useAtomValue(capabilitiesAtom);
|
|
97
|
+
const aiEnabled = useAtomValue(aiEnabledAtom);
|
|
96
98
|
|
|
97
99
|
// Convert current developer panel items to PanelDescriptors
|
|
98
100
|
// Filter out hidden panels (e.g., terminal when capability is not available)
|
|
99
101
|
const devPanelItems = useMemo(() => {
|
|
100
102
|
return panelLayout.developerPanel.flatMap((id) => {
|
|
101
103
|
const panel = PANEL_MAP.get(id);
|
|
102
|
-
if (!panel || isPanelHidden(panel, capabilities)) {
|
|
104
|
+
if (!panel || isPanelHidden({ panel, capabilities, aiEnabled })) {
|
|
103
105
|
return [];
|
|
104
106
|
}
|
|
105
107
|
return [panel];
|
|
106
108
|
});
|
|
107
|
-
}, [panelLayout.developerPanel, capabilities]);
|
|
109
|
+
}, [panelLayout.developerPanel, capabilities, aiEnabled]);
|
|
108
110
|
|
|
109
111
|
const handleSetDevPanelItems = (items: PanelDescriptor[]) => {
|
|
110
112
|
setPanelLayout((prev) => ({
|
|
@@ -141,7 +143,7 @@ export const AppChrome: React.FC<PropsWithChildren> = ({ children }) => {
|
|
|
141
143
|
const availableDevPanels = useMemo(() => {
|
|
142
144
|
const sidebarIds = new Set(panelLayout.sidebar);
|
|
143
145
|
return PANELS.filter((p) => {
|
|
144
|
-
if (isPanelHidden(p, capabilities)) {
|
|
146
|
+
if (isPanelHidden({ panel: p, capabilities, aiEnabled })) {
|
|
145
147
|
return false;
|
|
146
148
|
}
|
|
147
149
|
// Exclude panels that are in the sidebar
|
|
@@ -150,7 +152,7 @@ export const AppChrome: React.FC<PropsWithChildren> = ({ children }) => {
|
|
|
150
152
|
}
|
|
151
153
|
return true;
|
|
152
154
|
});
|
|
153
|
-
}, [panelLayout.sidebar, capabilities]);
|
|
155
|
+
}, [panelLayout.sidebar, capabilities, aiEnabled]);
|
|
154
156
|
|
|
155
157
|
const emitResizeEvent = useEvent(() => {
|
|
156
158
|
// HACK: Unfortunately, we have to do this twice to make sure the
|
|
@@ -4,18 +4,27 @@ import { useAtomValue } from "jotai";
|
|
|
4
4
|
import { SparklesIcon } from "lucide-react";
|
|
5
5
|
import React from "react";
|
|
6
6
|
import { useOpenSettingsToTab } from "@/components/app-config/state";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
aiAtom,
|
|
9
|
+
aiEnabledAtom,
|
|
10
|
+
aiModelConfiguredAtom,
|
|
11
|
+
} from "@/core/config/config";
|
|
8
12
|
import { DEFAULT_AI_MODEL } from "@/core/config/config-schema";
|
|
9
13
|
import { FooterItem } from "../footer-item";
|
|
10
14
|
|
|
11
15
|
export const AIStatusIcon: React.FC = () => {
|
|
12
16
|
const ai = useAtomValue(aiAtom);
|
|
13
17
|
const aiEnabled = useAtomValue(aiEnabledAtom);
|
|
18
|
+
const aiModelConfigured = useAtomValue(aiModelConfiguredAtom);
|
|
14
19
|
const chatModel = ai?.models?.chat_model || DEFAULT_AI_MODEL;
|
|
15
20
|
const editModel = ai?.models?.edit_model || chatModel;
|
|
16
21
|
const { handleClick } = useOpenSettingsToTab();
|
|
17
22
|
|
|
18
23
|
if (!aiEnabled) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!aiModelConfigured) {
|
|
19
28
|
return (
|
|
20
29
|
<FooterItem
|
|
21
30
|
tooltip="Assist is disabled"
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
notebookQueuedOrRunningCountAtom,
|
|
13
13
|
} from "@/core/cells/cells";
|
|
14
14
|
import { capabilitiesAtom } from "@/core/config/capabilities";
|
|
15
|
+
import { aiEnabledAtom } from "@/core/config/config";
|
|
15
16
|
import { cn } from "@/utils/cn";
|
|
16
17
|
import { FeedbackButton } from "../components/feedback-button";
|
|
17
18
|
import { panelLayoutAtom, useChromeActions, useChromeState } from "../state";
|
|
@@ -30,6 +31,7 @@ export const Sidebar: React.FC = () => {
|
|
|
30
31
|
const [panelLayout, setPanelLayout] = useAtom(panelLayoutAtom);
|
|
31
32
|
// Subscribe to capabilities to re-render when they change
|
|
32
33
|
const capabilities = useAtomValue(capabilitiesAtom);
|
|
34
|
+
const aiEnabled = useAtomValue(aiEnabledAtom);
|
|
33
35
|
|
|
34
36
|
const renderIcon = ({ Icon }: PanelDescriptor, className?: string) => {
|
|
35
37
|
return <Icon className={cn("h-5 w-5", className)} />;
|
|
@@ -40,7 +42,7 @@ export const Sidebar: React.FC = () => {
|
|
|
40
42
|
const availableSidebarPanels = useMemo(() => {
|
|
41
43
|
const devPanelIds = new Set(panelLayout.developerPanel);
|
|
42
44
|
return PANELS.filter((p) => {
|
|
43
|
-
if (isPanelHidden(p, capabilities)) {
|
|
45
|
+
if (isPanelHidden({ panel: p, capabilities, aiEnabled })) {
|
|
44
46
|
return false;
|
|
45
47
|
}
|
|
46
48
|
// Exclude panels that are in the developer panel
|
|
@@ -49,19 +51,19 @@ export const Sidebar: React.FC = () => {
|
|
|
49
51
|
}
|
|
50
52
|
return true;
|
|
51
53
|
});
|
|
52
|
-
}, [panelLayout.developerPanel, capabilities]);
|
|
54
|
+
}, [panelLayout.developerPanel, capabilities, aiEnabled]);
|
|
53
55
|
|
|
54
56
|
// Convert current sidebar items to PanelDescriptors
|
|
55
57
|
// Filter out hidden panels (e.g., when capability is not available)
|
|
56
58
|
const sidebarItems = useMemo(() => {
|
|
57
59
|
return panelLayout.sidebar.flatMap((id) => {
|
|
58
60
|
const panel = PANEL_MAP.get(id);
|
|
59
|
-
if (!panel || isPanelHidden(panel, capabilities)) {
|
|
61
|
+
if (!panel || isPanelHidden({ panel, capabilities, aiEnabled })) {
|
|
60
62
|
return [];
|
|
61
63
|
}
|
|
62
64
|
return [panel];
|
|
63
65
|
});
|
|
64
|
-
}, [panelLayout.sidebar, capabilities]);
|
|
66
|
+
}, [panelLayout.sidebar, capabilities, aiEnabled]);
|
|
65
67
|
|
|
66
68
|
const handleSetSidebarItems = (items: PanelDescriptor[]) => {
|
|
67
69
|
setPanelLayout((prev) => ({
|
|
@@ -218,7 +220,7 @@ const SidebarItem: React.FC<
|
|
|
218
220
|
// Render as div when not clickable (e.g., inside ReorderableList)
|
|
219
221
|
// This avoids nested interactive elements which break react-aria's drag behavior
|
|
220
222
|
const content = onClick ? (
|
|
221
|
-
<button className={itemClassName} onClick={onClick}>
|
|
223
|
+
<button type="button" className={itemClassName} onClick={onClick}>
|
|
222
224
|
{children}
|
|
223
225
|
</button>
|
|
224
226
|
) : (
|
|
@@ -13,7 +13,7 @@ import { Tooltip } from "@/components/ui/tooltip";
|
|
|
13
13
|
import { aiCompletionCellAtom } from "@/core/ai/state";
|
|
14
14
|
import { notebookAtom, useCellActions } from "@/core/cells/cells";
|
|
15
15
|
import type { CellId } from "@/core/cells/ids";
|
|
16
|
-
import {
|
|
16
|
+
import { aiFeaturesEnabledAtom } from "@/core/config/config";
|
|
17
17
|
import { getAutoFixes } from "@/core/errors/errors";
|
|
18
18
|
import type { MarimoError } from "@/core/kernel/messages";
|
|
19
19
|
import { cn } from "@/utils/cn";
|
|
@@ -30,9 +30,9 @@ export const AutoFixButton = ({
|
|
|
30
30
|
}) => {
|
|
31
31
|
const store = useStore();
|
|
32
32
|
const { createNewCell } = useCellActions();
|
|
33
|
-
const
|
|
33
|
+
const aiFeaturesEnabled = useAtomValue(aiFeaturesEnabledAtom);
|
|
34
34
|
const autoFixes = errors.flatMap((error) =>
|
|
35
|
-
getAutoFixes(error, { aiEnabled }),
|
|
35
|
+
getAutoFixes(error, { aiEnabled: aiFeaturesEnabled }),
|
|
36
36
|
);
|
|
37
37
|
const setAiCompletionCell = useSetAtom(aiCompletionCellAtom);
|
|
38
38
|
|
|
@@ -1207,6 +1207,21 @@ describe("useCellNavigationProps", () => {
|
|
|
1207
1207
|
});
|
|
1208
1208
|
|
|
1209
1209
|
describe("AI completion functionality", () => {
|
|
1210
|
+
beforeEach(() => {
|
|
1211
|
+
const config = defaultUserConfig();
|
|
1212
|
+
store.set(userConfigAtom, {
|
|
1213
|
+
...config,
|
|
1214
|
+
ai: {
|
|
1215
|
+
...config.ai,
|
|
1216
|
+
models: {
|
|
1217
|
+
displayed_models: [],
|
|
1218
|
+
custom_models: [],
|
|
1219
|
+
edit_model: "openai/gpt-4o",
|
|
1220
|
+
},
|
|
1221
|
+
},
|
|
1222
|
+
});
|
|
1223
|
+
});
|
|
1224
|
+
|
|
1210
1225
|
it("should toggle AI completion when shortcut is pressed", () => {
|
|
1211
1226
|
const { result } = renderWithProvider(() =>
|
|
1212
1227
|
useCellNavigationProps(cellId1, optionsWithMockEditor),
|
|
@@ -24,6 +24,7 @@ import { usePendingDeleteService } from "@/core/cells/pending-delete-service";
|
|
|
24
24
|
import { scrollCellIntoView } from "@/core/cells/scrollCellIntoView";
|
|
25
25
|
import {
|
|
26
26
|
hotkeysAtom,
|
|
27
|
+
isAiFeatureEnabled,
|
|
27
28
|
keymapPresetAtom,
|
|
28
29
|
userConfigAtom,
|
|
29
30
|
} from "@/core/config/config";
|
|
@@ -195,6 +196,7 @@ export function useCellNavigationProps(
|
|
|
195
196
|
const pendingDeleteService = usePendingDeleteService();
|
|
196
197
|
const deleteCells = useDeleteManyCellsCallback();
|
|
197
198
|
const userConfig = useAtomValue(userConfigAtom);
|
|
199
|
+
const aiFeaturesEnabled = isAiFeatureEnabled(userConfig);
|
|
198
200
|
|
|
199
201
|
// Wrap selection actions to clear pending cells on any selection change
|
|
200
202
|
const selectionActions = {
|
|
@@ -496,6 +498,9 @@ export function useCellNavigationProps(
|
|
|
496
498
|
return true;
|
|
497
499
|
}),
|
|
498
500
|
"cell.aiCompletion": (cellId) => {
|
|
501
|
+
if (!aiFeaturesEnabled) {
|
|
502
|
+
return false;
|
|
503
|
+
}
|
|
499
504
|
let closed = false;
|
|
500
505
|
setAiCompletionCell((v) => {
|
|
501
506
|
// Toggle close
|
|
@@ -25,7 +25,7 @@ import { getCellEditorView } from "@/core/cells/cells";
|
|
|
25
25
|
import type { CellId } from "@/core/cells/ids";
|
|
26
26
|
import { SCRATCH_CELL_ID } from "@/core/cells/ids";
|
|
27
27
|
import { insertDebuggerAtLine } from "@/core/codemirror/editing/debugging";
|
|
28
|
-
import {
|
|
28
|
+
import { aiFeaturesEnabledAtom } from "@/core/config/config";
|
|
29
29
|
import { getRequestClient } from "@/core/network/requests";
|
|
30
30
|
import { isStaticNotebook } from "@/core/static/static-state";
|
|
31
31
|
import { isWasm } from "@/core/wasm/utils";
|
|
@@ -70,7 +70,7 @@ export const MarimoTracebackOutput = ({
|
|
|
70
70
|
});
|
|
71
71
|
|
|
72
72
|
const lastTracebackLine = lastLine(traceback);
|
|
73
|
-
const
|
|
73
|
+
const aiFeaturesEnabled = useAtomValue(aiFeaturesEnabledAtom);
|
|
74
74
|
|
|
75
75
|
// Get last traceback info
|
|
76
76
|
const tracebackInfo = extractAllTracebackInfo(traceback)?.at(0);
|
|
@@ -83,7 +83,8 @@ export const MarimoTracebackOutput = ({
|
|
|
83
83
|
!isStaticNotebook() &&
|
|
84
84
|
cellId !== SCRATCH_CELL_ID;
|
|
85
85
|
|
|
86
|
-
const showAIFix =
|
|
86
|
+
const showAIFix =
|
|
87
|
+
onRefactorWithAI && aiFeaturesEnabled && !isStaticNotebook();
|
|
87
88
|
|
|
88
89
|
const showSearch = !isStaticNotebook();
|
|
89
90
|
|
|
@@ -24,7 +24,7 @@ import { maybeAddMarimoImport } from "@/core/cells/add-missing-import";
|
|
|
24
24
|
import { SETUP_CELL_ID } from "@/core/cells/ids";
|
|
25
25
|
import { LanguageAdapters } from "@/core/codemirror/language/LanguageAdapters";
|
|
26
26
|
import { MARKDOWN_INITIAL_HIDE_CODE } from "@/core/codemirror/language/languages/markdown";
|
|
27
|
-
import { aiEnabledAtom } from "@/core/config/config";
|
|
27
|
+
import { aiEnabledAtom, aiFeaturesEnabledAtom } from "@/core/config/config";
|
|
28
28
|
import { canInteractWithAppAtom } from "@/core/network/connection";
|
|
29
29
|
import { useBoolean } from "@/hooks/useBoolean";
|
|
30
30
|
import { cn } from "@/utils/cn";
|
|
@@ -261,6 +261,7 @@ const AddCellButtons: React.FC<{
|
|
|
261
261
|
const { createNewCell } = useCellActions();
|
|
262
262
|
const [isAiButtonOpen, isAiButtonOpenActions] = useBoolean(false);
|
|
263
263
|
const aiEnabled = useAtomValue(aiEnabledAtom);
|
|
264
|
+
const aiFeaturesEnabled = useAtomValue(aiFeaturesEnabledAtom);
|
|
264
265
|
const canInteractWithApp = useAtomValue(canInteractWithAppAtom);
|
|
265
266
|
const { handleClick } = useOpenSettingsToTab();
|
|
266
267
|
|
|
@@ -270,7 +271,7 @@ const AddCellButtons: React.FC<{
|
|
|
270
271
|
);
|
|
271
272
|
|
|
272
273
|
const renderBody = () => {
|
|
273
|
-
if (isAiButtonOpen) {
|
|
274
|
+
if (aiEnabled && isAiButtonOpen) {
|
|
274
275
|
return <AddCellWithAI onClose={isAiButtonOpenActions.toggle} />;
|
|
275
276
|
}
|
|
276
277
|
|
|
@@ -328,30 +329,32 @@ const AddCellButtons: React.FC<{
|
|
|
328
329
|
<DatabaseIcon className="mr-2 size-4 shrink-0" />
|
|
329
330
|
SQL
|
|
330
331
|
</Button>
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
delayDuration={100}
|
|
338
|
-
asChild={false}
|
|
339
|
-
>
|
|
340
|
-
<Button
|
|
341
|
-
className={buttonClass}
|
|
342
|
-
variant="text"
|
|
343
|
-
size="sm"
|
|
344
|
-
disabled={!canInteractWithApp}
|
|
345
|
-
onClick={
|
|
346
|
-
aiEnabled
|
|
347
|
-
? isAiButtonOpenActions.toggle
|
|
348
|
-
: () => handleClick("ai", "ai-providers")
|
|
332
|
+
{aiEnabled && (
|
|
333
|
+
<Tooltip
|
|
334
|
+
content={
|
|
335
|
+
aiFeaturesEnabled ? null : (
|
|
336
|
+
<span>AI provider not found or Edit model not selected</span>
|
|
337
|
+
)
|
|
349
338
|
}
|
|
339
|
+
delayDuration={100}
|
|
340
|
+
asChild={false}
|
|
350
341
|
>
|
|
351
|
-
<
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
342
|
+
<Button
|
|
343
|
+
className={buttonClass}
|
|
344
|
+
variant="text"
|
|
345
|
+
size="sm"
|
|
346
|
+
disabled={!canInteractWithApp}
|
|
347
|
+
onClick={
|
|
348
|
+
aiFeaturesEnabled
|
|
349
|
+
? isAiButtonOpenActions.toggle
|
|
350
|
+
: () => handleClick("ai", "ai-providers")
|
|
351
|
+
}
|
|
352
|
+
>
|
|
353
|
+
<SparklesIcon className="mr-2 size-4 shrink-0" />
|
|
354
|
+
Generate with AI
|
|
355
|
+
</Button>
|
|
356
|
+
</Tooltip>
|
|
357
|
+
)}
|
|
355
358
|
</>
|
|
356
359
|
);
|
|
357
360
|
};
|
|
@@ -46,6 +46,7 @@ test("default UserConfig - empty", () => {
|
|
|
46
46
|
{
|
|
47
47
|
"ai": {
|
|
48
48
|
"custom_providers": {},
|
|
49
|
+
"enabled": true,
|
|
49
50
|
"inline_tooltip": false,
|
|
50
51
|
"mode": "manual",
|
|
51
52
|
"models": {
|
|
@@ -118,6 +119,7 @@ test("default UserConfig - one level", () => {
|
|
|
118
119
|
{
|
|
119
120
|
"ai": {
|
|
120
121
|
"custom_providers": {},
|
|
122
|
+
"enabled": true,
|
|
121
123
|
"inline_tooltip": false,
|
|
122
124
|
"mode": "manual",
|
|
123
125
|
"models": {
|
|
@@ -158,6 +158,7 @@ export const UserConfigSchema = z
|
|
|
158
158
|
.prefault({}),
|
|
159
159
|
ai: z
|
|
160
160
|
.looseObject({
|
|
161
|
+
enabled: z.boolean().prefault(true),
|
|
161
162
|
rules: z.string().prefault(""),
|
|
162
163
|
max_tokens: z.number().int().positive().nullable().optional(),
|
|
163
164
|
mode: z.enum(COPILOT_MODES).prefault("manual"),
|
|
@@ -78,6 +78,14 @@ export const aiEnabledAtom = atom<boolean>((get) => {
|
|
|
78
78
|
return isAiEnabled(get(resolvedMarimoConfigAtom));
|
|
79
79
|
});
|
|
80
80
|
|
|
81
|
+
export const aiModelConfiguredAtom = atom<boolean>((get) => {
|
|
82
|
+
return isAiModelConfigured(get(resolvedMarimoConfigAtom));
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
export const aiFeaturesEnabledAtom = atom<boolean>((get) => {
|
|
86
|
+
return isAiFeatureEnabled(get(resolvedMarimoConfigAtom));
|
|
87
|
+
});
|
|
88
|
+
|
|
81
89
|
export const editorFontSizeAtom = atom<number>((get) => {
|
|
82
90
|
return get(resolvedMarimoConfigAtom).display.code_editor_font_size;
|
|
83
91
|
});
|
|
@@ -87,6 +95,10 @@ export const localeAtom = atom<string | null | undefined>((get) => {
|
|
|
87
95
|
});
|
|
88
96
|
|
|
89
97
|
export function isAiEnabled(config: UserConfig) {
|
|
98
|
+
return config.ai?.enabled !== false;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function isAiModelConfigured(config: UserConfig) {
|
|
90
102
|
return (
|
|
91
103
|
Boolean(config.ai?.models?.chat_model) ||
|
|
92
104
|
Boolean(config.ai?.models?.edit_model) ||
|
|
@@ -94,6 +106,10 @@ export function isAiEnabled(config: UserConfig) {
|
|
|
94
106
|
);
|
|
95
107
|
}
|
|
96
108
|
|
|
109
|
+
export function isAiFeatureEnabled(config: UserConfig) {
|
|
110
|
+
return isAiEnabled(config) && isAiModelConfigured(config);
|
|
111
|
+
}
|
|
112
|
+
|
|
97
113
|
/**
|
|
98
114
|
* Atom for storing the app config.
|
|
99
115
|
*/
|