@marimo-team/islands 0.23.17-dev43 → 0.23.17-dev45
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/{common-CSgt0ouV.js → common-BpaVk2oS.js} +702 -709
- package/dist/main.js +2 -2
- package/dist/{reveal-component-CtWp2grT.js → reveal-component-DwO6vFzC.js} +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/ai/ai-model-dropdown.tsx +2 -2
- package/src/components/chat/__tests__/capabilities-popover.test.tsx +38 -0
- package/src/components/chat/capabilities-popover.tsx +68 -0
- package/src/components/chat/chat-panel.tsx +17 -12
- package/src/components/editor/chrome/wrapper/useOpenAiAssistant.ts +2 -2
- package/src/core/ai/__tests__/config.test.tsx +96 -0
- package/src/core/ai/config.ts +16 -22
- package/src/core/ai/state.ts +10 -0
- package/src/core/network/types.ts +1 -0
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
CircleHelpIcon,
|
|
10
10
|
} from "lucide-react";
|
|
11
11
|
import React from "react";
|
|
12
|
-
import { type SupportedRole,
|
|
12
|
+
import { type SupportedRole, useAIConfigActions } from "@/core/ai/config";
|
|
13
13
|
import {
|
|
14
14
|
AiModelId,
|
|
15
15
|
isKnownAIProvider,
|
|
@@ -64,7 +64,7 @@ export const AIModelDropdown = ({
|
|
|
64
64
|
|
|
65
65
|
const ai = useAtomValue(aiAtom);
|
|
66
66
|
const completion = useAtomValue(completionAtom);
|
|
67
|
-
const { saveModelChange } =
|
|
67
|
+
const { saveModelChange } = useAIConfigActions();
|
|
68
68
|
const { handleClick } = useOpenSettingsToTab();
|
|
69
69
|
|
|
70
70
|
// Only include autocompleteModel if copilot is set to "custom"
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
4
|
+
import { createStore, Provider } from "jotai";
|
|
5
|
+
import { describe, expect, it } from "vitest";
|
|
6
|
+
import { chatOptionsAtom } from "@/core/ai/state";
|
|
7
|
+
import { CapabilitiesPopover } from "../capabilities-popover";
|
|
8
|
+
|
|
9
|
+
describe("CapabilitiesPopover", () => {
|
|
10
|
+
it("updates ephemeral chat options", () => {
|
|
11
|
+
const store = createStore();
|
|
12
|
+
|
|
13
|
+
render(
|
|
14
|
+
<Provider store={store}>
|
|
15
|
+
<CapabilitiesPopover />
|
|
16
|
+
</Provider>,
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
const trigger = screen.getByRole("button", { name: "Capabilities" });
|
|
20
|
+
expect(trigger).not.toHaveAttribute("data-active");
|
|
21
|
+
|
|
22
|
+
fireEvent.click(trigger);
|
|
23
|
+
const webSearch = screen.getByRole("switch", { name: "Web search" });
|
|
24
|
+
expect(webSearch).not.toBeChecked();
|
|
25
|
+
|
|
26
|
+
fireEvent.click(webSearch);
|
|
27
|
+
|
|
28
|
+
expect(webSearch).toBeChecked();
|
|
29
|
+
expect(trigger).toHaveAttribute("data-active", "true");
|
|
30
|
+
expect(trigger).toHaveClass("bg-primary/15", "text-primary", "ring-1");
|
|
31
|
+
expect(trigger).toHaveAttribute(
|
|
32
|
+
"title",
|
|
33
|
+
"Capabilities (web search enabled)",
|
|
34
|
+
);
|
|
35
|
+
expect(store.get(chatOptionsAtom)).toEqual({ webSearch: true });
|
|
36
|
+
expect(createStore().get(chatOptionsAtom)).toEqual({ webSearch: false });
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { useAtom } from "jotai";
|
|
4
|
+
import { SlidersHorizontalIcon } from "lucide-react";
|
|
5
|
+
import type React from "react";
|
|
6
|
+
import { Button } from "@/components/ui/button";
|
|
7
|
+
import {
|
|
8
|
+
Popover,
|
|
9
|
+
PopoverContent,
|
|
10
|
+
PopoverTrigger,
|
|
11
|
+
} from "@/components/ui/popover";
|
|
12
|
+
import { ExternalLink } from "@/components/ui/links";
|
|
13
|
+
import { Switch } from "@/components/ui/switch";
|
|
14
|
+
import { chatOptionsAtom } from "@/core/ai/state";
|
|
15
|
+
import { cn } from "@/utils/cn";
|
|
16
|
+
|
|
17
|
+
export const CapabilitiesPopover: React.FC = () => {
|
|
18
|
+
const [chatOptions, setChatOptions] = useAtom(chatOptionsAtom);
|
|
19
|
+
const webSearchOn = chatOptions.webSearch;
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<Popover>
|
|
23
|
+
<PopoverTrigger asChild={true}>
|
|
24
|
+
<Button
|
|
25
|
+
aria-label="Capabilities"
|
|
26
|
+
title={
|
|
27
|
+
webSearchOn ? "Capabilities (web search enabled)" : "Capabilities"
|
|
28
|
+
}
|
|
29
|
+
data-active={webSearchOn || undefined}
|
|
30
|
+
variant="text"
|
|
31
|
+
size="icon"
|
|
32
|
+
className={cn(
|
|
33
|
+
"h-6 w-6 shrink-0 bg-muted hover:bg-muted/30",
|
|
34
|
+
webSearchOn &&
|
|
35
|
+
"bg-primary/15 text-primary ring-1 ring-primary/30 opacity-100 hover:bg-primary/20",
|
|
36
|
+
)}
|
|
37
|
+
>
|
|
38
|
+
<SlidersHorizontalIcon className="h-3.5 w-3.5" />
|
|
39
|
+
</Button>
|
|
40
|
+
</PopoverTrigger>
|
|
41
|
+
<PopoverContent className="w-76 p-3" align="start" side="top">
|
|
42
|
+
<label
|
|
43
|
+
htmlFor="web-search-toggle"
|
|
44
|
+
className="flex items-start justify-between gap-3 cursor-pointer"
|
|
45
|
+
>
|
|
46
|
+
<div className="flex flex-col gap-0.5">
|
|
47
|
+
<span className="text-sm font-semibold">Web search</span>
|
|
48
|
+
<span className="text-xs text-muted-foreground">
|
|
49
|
+
Search the web when the model supports it.{" "}
|
|
50
|
+
<ExternalLink href="https://docs.marimo.io/guides/editor_features/tools#web-search-and-fetch">
|
|
51
|
+
Learn more
|
|
52
|
+
</ExternalLink>
|
|
53
|
+
</span>
|
|
54
|
+
</div>
|
|
55
|
+
<Switch
|
|
56
|
+
id="web-search-toggle"
|
|
57
|
+
aria-label="Web search"
|
|
58
|
+
size="sm"
|
|
59
|
+
checked={webSearchOn}
|
|
60
|
+
onCheckedChange={(webSearch) =>
|
|
61
|
+
setChatOptions((options) => ({ ...options, webSearch }))
|
|
62
|
+
}
|
|
63
|
+
/>
|
|
64
|
+
</label>
|
|
65
|
+
</PopoverContent>
|
|
66
|
+
</Popover>
|
|
67
|
+
);
|
|
68
|
+
};
|
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
SelectTrigger,
|
|
37
37
|
} from "@/components/ui/select";
|
|
38
38
|
import { replaceMessagesInChat } from "@/core/ai/chat-utils";
|
|
39
|
-
import {
|
|
39
|
+
import { useAIConfigActions } from "@/core/ai/config";
|
|
40
40
|
import { AI_SDK_UI_THROTTLE_MS } from "@/core/ai/constants";
|
|
41
41
|
import { AiModelId } from "@/core/ai/ids/ids";
|
|
42
42
|
import { useStagedAICellsActions } from "@/core/ai/staged-cells";
|
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
activeChatAtom,
|
|
45
45
|
type Chat,
|
|
46
46
|
type ChatId,
|
|
47
|
+
chatOptionsAtom,
|
|
47
48
|
chatStateAtom,
|
|
48
49
|
pendingAiPromptAtom,
|
|
49
50
|
} from "@/core/ai/state";
|
|
@@ -91,6 +92,7 @@ import {
|
|
|
91
92
|
} from "./chat-abort";
|
|
92
93
|
import { renderUIMessage } from "./chat-display";
|
|
93
94
|
import { ChatHistoryPopover } from "./chat-history-popover";
|
|
95
|
+
import { CapabilitiesPopover } from "./capabilities-popover";
|
|
94
96
|
import {
|
|
95
97
|
type ChatMessagePart,
|
|
96
98
|
convertToFileUIPart,
|
|
@@ -298,7 +300,7 @@ const ChatInputFooter: React.FC<ChatInputFooterProps> = memo(
|
|
|
298
300
|
const currentModel = ai?.models?.chat_model || DEFAULT_AI_MODEL;
|
|
299
301
|
const currentProvider = AiModelId.parse(currentModel).providerId;
|
|
300
302
|
|
|
301
|
-
const { saveModeChange } =
|
|
303
|
+
const { saveModeChange } = useAIConfigActions();
|
|
302
304
|
|
|
303
305
|
const modeOptions: {
|
|
304
306
|
value: CopilotMode;
|
|
@@ -326,8 +328,9 @@ const ChatInputFooter: React.FC<ChatInputFooterProps> = memo(
|
|
|
326
328
|
},
|
|
327
329
|
{
|
|
328
330
|
value: "code_mode",
|
|
329
|
-
label: "Code Mode
|
|
330
|
-
subtitle:
|
|
331
|
+
label: "Code Mode",
|
|
332
|
+
subtitle:
|
|
333
|
+
"AI with access to the notebook's kernel. Can overwrite changes.",
|
|
331
334
|
Icon: CodeIcon,
|
|
332
335
|
},
|
|
333
336
|
];
|
|
@@ -341,12 +344,12 @@ const ChatInputFooter: React.FC<ChatInputFooterProps> = memo(
|
|
|
341
344
|
|
|
342
345
|
return (
|
|
343
346
|
<TooltipProvider>
|
|
344
|
-
<div className="px-3 py-2 border-t border-border/20 flex flex-
|
|
345
|
-
<div className="flex items-center gap-
|
|
347
|
+
<div className="px-3 py-2 border-t border-border/20 flex flex-wrap items-center gap-1">
|
|
348
|
+
<div className="flex flex-wrap items-center gap-1">
|
|
346
349
|
<Select value={currentMode} onValueChange={saveModeChange}>
|
|
347
|
-
<SelectTrigger className="h-6 text-xs border-border shadow-none! ring-0! bg-muted hover:bg-muted/30 py-0 px-2 gap-1.5">
|
|
350
|
+
<SelectTrigger className="h-6 text-xs border-border shadow-none! ring-0! bg-muted hover:bg-muted/30 py-0 px-2 gap-1.5 shrink-0">
|
|
348
351
|
{CurrentModeIcon && <CurrentModeIcon className="h-3 w-3" />}
|
|
349
|
-
<span>{CurrentModeLabel}</span>
|
|
352
|
+
<span className="text-nowrap">{CurrentModeLabel}</span>
|
|
350
353
|
</SelectTrigger>
|
|
351
354
|
<SelectContent>
|
|
352
355
|
<SelectGroup>
|
|
@@ -377,13 +380,14 @@ const ChatInputFooter: React.FC<ChatInputFooterProps> = memo(
|
|
|
377
380
|
</Select>
|
|
378
381
|
<AIModelDropdown
|
|
379
382
|
placeholder="Model"
|
|
380
|
-
triggerClassName="h-6 text-xs shadow-none! ring-0! bg-muted hover:bg-muted/30 rounded-sm"
|
|
383
|
+
triggerClassName="h-6 text-xs shadow-none! ring-0! bg-muted hover:bg-muted/30 rounded-sm max-w-[200px]"
|
|
381
384
|
iconSize="small"
|
|
382
385
|
showAddCustomModelDocs={true}
|
|
383
386
|
forRole="chat"
|
|
384
387
|
/>
|
|
388
|
+
<CapabilitiesPopover />
|
|
385
389
|
</div>
|
|
386
|
-
<div className="flex flex-row">
|
|
390
|
+
<div className="flex flex-row ml-auto">
|
|
387
391
|
<AddContextButton
|
|
388
392
|
handleAddContext={onAddContext}
|
|
389
393
|
isLoading={isLoading}
|
|
@@ -445,8 +449,8 @@ const ChatInput: React.FC<ChatInputProps> = memo(
|
|
|
445
449
|
});
|
|
446
450
|
|
|
447
451
|
return (
|
|
448
|
-
<div className="relative shrink-0
|
|
449
|
-
<div className={cn("px-2 py-
|
|
452
|
+
<div className="relative shrink-0 flex flex-col border-t">
|
|
453
|
+
<div className={cn("px-2 py-1.5", inputClassName)}>
|
|
450
454
|
<PromptInput
|
|
451
455
|
className="max-h-[400px]"
|
|
452
456
|
inputRef={inputRef}
|
|
@@ -598,6 +602,7 @@ const ChatPanelBody = () => {
|
|
|
598
602
|
const completionBody = {
|
|
599
603
|
uiMessages: options.messages,
|
|
600
604
|
includeOtherCode: getCodes(""),
|
|
605
|
+
options: store.get(chatOptionsAtom),
|
|
601
606
|
};
|
|
602
607
|
|
|
603
608
|
// Call this here to ensure the value is not stale
|
|
@@ -4,7 +4,7 @@ import { useSetAtom, useStore } from "jotai";
|
|
|
4
4
|
import useEvent from "react-use-event-hook";
|
|
5
5
|
import { agentSessionStateAtom } from "@/components/chat/acp/state";
|
|
6
6
|
import { toast } from "@/components/ui/use-toast";
|
|
7
|
-
import {
|
|
7
|
+
import { useAIConfigActions } from "@/core/ai/config";
|
|
8
8
|
import { pendingAiPromptAtom } from "@/core/ai/state";
|
|
9
9
|
import type { CopilotMode } from "@/core/ai/tools/registry";
|
|
10
10
|
import { aiModelConfiguredAtom } from "@/core/config/config";
|
|
@@ -43,7 +43,7 @@ export function resolveAiPanelTab(
|
|
|
43
43
|
export function useOpenAiAssistant() {
|
|
44
44
|
const { openApplication } = useChromeActions();
|
|
45
45
|
const { setAiPanelTab } = useAiPanelTab();
|
|
46
|
-
const { saveModeChange } =
|
|
46
|
+
const { saveModeChange } = useAIConfigActions();
|
|
47
47
|
const setPendingPrompt = useSetAtom(pendingAiPromptAtom);
|
|
48
48
|
const store = useStore();
|
|
49
49
|
|
|
@@ -0,0 +1,96 @@
|
|
|
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 { ReactNode } from "react";
|
|
6
|
+
import { describe, expect, it, vi } from "vitest";
|
|
7
|
+
import { userConfigAtom } from "@/core/config/config";
|
|
8
|
+
import { defaultUserConfig } from "@/core/config/config-schema";
|
|
9
|
+
import { requestClientAtom } from "@/core/network/requests";
|
|
10
|
+
import type { EditRequests, RunRequests } from "@/core/network/types";
|
|
11
|
+
import { AiModelId } from "../ids/ids";
|
|
12
|
+
import { useAIConfigActions } from "../config";
|
|
13
|
+
|
|
14
|
+
describe("useAIConfigActions", () => {
|
|
15
|
+
it("persists a model change without overwriting newer AI config", async () => {
|
|
16
|
+
const store = createStore();
|
|
17
|
+
const saveUserConfig = vi.fn().mockResolvedValue(null);
|
|
18
|
+
store.set(userConfigAtom, defaultUserConfig());
|
|
19
|
+
store.set(requestClientAtom, {
|
|
20
|
+
saveUserConfig,
|
|
21
|
+
} as unknown as EditRequests & RunRequests);
|
|
22
|
+
|
|
23
|
+
const wrapper = ({ children }: { children: ReactNode }) => (
|
|
24
|
+
<Provider store={store}>{children}</Provider>
|
|
25
|
+
);
|
|
26
|
+
const { result } = renderHook(() => useAIConfigActions(), { wrapper });
|
|
27
|
+
|
|
28
|
+
store.set(userConfigAtom, (config) => ({
|
|
29
|
+
...config,
|
|
30
|
+
ai: {
|
|
31
|
+
...config.ai,
|
|
32
|
+
mode: "agent",
|
|
33
|
+
},
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
await act(async () => {
|
|
37
|
+
await result.current.saveModelChange(
|
|
38
|
+
AiModelId.parse("openai/gpt-4o").id,
|
|
39
|
+
"chat",
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
expect(saveUserConfig).toHaveBeenCalledWith({
|
|
44
|
+
config: { ai: { models: { chat_model: "openai/gpt-4o" } } },
|
|
45
|
+
});
|
|
46
|
+
expect(store.get(userConfigAtom).ai).toMatchObject({
|
|
47
|
+
mode: "agent",
|
|
48
|
+
models: { chat_model: "openai/gpt-4o" },
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("preserves concurrent AI changes when saves resolve out of order", async () => {
|
|
53
|
+
const store = createStore();
|
|
54
|
+
const resolvers: Array<() => void> = [];
|
|
55
|
+
const saveUserConfig = vi.fn(
|
|
56
|
+
() =>
|
|
57
|
+
new Promise<null>((resolve) => {
|
|
58
|
+
resolvers.push(() => resolve(null));
|
|
59
|
+
}),
|
|
60
|
+
);
|
|
61
|
+
store.set(userConfigAtom, defaultUserConfig());
|
|
62
|
+
store.set(requestClientAtom, {
|
|
63
|
+
saveUserConfig,
|
|
64
|
+
} as unknown as EditRequests & RunRequests);
|
|
65
|
+
|
|
66
|
+
const wrapper = ({ children }: { children: ReactNode }) => (
|
|
67
|
+
<Provider store={store}>{children}</Provider>
|
|
68
|
+
);
|
|
69
|
+
const { result } = renderHook(() => useAIConfigActions(), { wrapper });
|
|
70
|
+
|
|
71
|
+
const modelChange = result.current.saveModelChange(
|
|
72
|
+
AiModelId.parse("openai/gpt-4o").id,
|
|
73
|
+
"chat",
|
|
74
|
+
);
|
|
75
|
+
const modeChange = result.current.saveModeChange("agent");
|
|
76
|
+
|
|
77
|
+
expect(saveUserConfig).toHaveBeenNthCalledWith(1, {
|
|
78
|
+
config: { ai: { models: { chat_model: "openai/gpt-4o" } } },
|
|
79
|
+
});
|
|
80
|
+
expect(saveUserConfig).toHaveBeenNthCalledWith(2, {
|
|
81
|
+
config: { ai: { mode: "agent" } },
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
await act(async () => {
|
|
85
|
+
resolvers[1]();
|
|
86
|
+
await modeChange;
|
|
87
|
+
resolvers[0]();
|
|
88
|
+
await modelChange;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
expect(store.get(userConfigAtom).ai).toMatchObject({
|
|
92
|
+
mode: "agent",
|
|
93
|
+
models: { chat_model: "openai/gpt-4o" },
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
});
|
package/src/core/ai/config.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
3
|
import type { Role } from "@marimo-team/llm-info";
|
|
4
|
-
import {
|
|
4
|
+
import { useSetAtom } from "jotai";
|
|
5
|
+
import { merge } from "lodash-es";
|
|
5
6
|
import type { QualifiedModelId } from "@/core/ai/ids/ids";
|
|
6
7
|
import { userConfigAtom } from "@/core/config/config";
|
|
7
8
|
import type {
|
|
@@ -14,6 +15,11 @@ import { useRequestClient } from "@/core/network/requests";
|
|
|
14
15
|
// Extract only the supported roles from the Role type
|
|
15
16
|
export type SupportedRole = Extract<Role, "chat" | "autocomplete" | "edit">;
|
|
16
17
|
|
|
18
|
+
interface AiConfigPatch {
|
|
19
|
+
mode?: CopilotMode;
|
|
20
|
+
models?: Partial<NonNullable<NonNullable<UserConfig["ai"]>["models"]>>;
|
|
21
|
+
}
|
|
22
|
+
|
|
17
23
|
const getModelKeyForRole = (forRole: SupportedRole): AIModelKey | null => {
|
|
18
24
|
switch (forRole) {
|
|
19
25
|
case "chat":
|
|
@@ -26,15 +32,16 @@ const getModelKeyForRole = (forRole: SupportedRole): AIModelKey | null => {
|
|
|
26
32
|
};
|
|
27
33
|
|
|
28
34
|
/**
|
|
29
|
-
* Hook for saving
|
|
35
|
+
* Hook for saving AI config changes
|
|
30
36
|
*/
|
|
31
|
-
export const
|
|
32
|
-
const
|
|
37
|
+
export const useAIConfigActions = () => {
|
|
38
|
+
const setUserConfig = useSetAtom(userConfigAtom);
|
|
33
39
|
const { saveUserConfig } = useRequestClient();
|
|
34
40
|
|
|
35
|
-
const saveConfig = async (
|
|
41
|
+
const saveConfig = async (aiConfig: AiConfigPatch) => {
|
|
42
|
+
const newConfig = { ai: aiConfig };
|
|
36
43
|
await saveUserConfig({ config: newConfig }).then(() => {
|
|
37
|
-
setUserConfig((prev) => ({
|
|
44
|
+
setUserConfig((prev) => merge({}, prev, newConfig));
|
|
38
45
|
});
|
|
39
46
|
};
|
|
40
47
|
|
|
@@ -48,28 +55,15 @@ export const useModelChange = () => {
|
|
|
48
55
|
return;
|
|
49
56
|
}
|
|
50
57
|
|
|
51
|
-
const newConfig:
|
|
52
|
-
|
|
53
|
-
...userConfig.ai,
|
|
54
|
-
models: {
|
|
55
|
-
custom_models: userConfig.ai?.models?.custom_models ?? [],
|
|
56
|
-
displayed_models: userConfig.ai?.models?.displayed_models ?? [],
|
|
57
|
-
...userConfig.ai?.models,
|
|
58
|
-
[modelKey]: model,
|
|
59
|
-
},
|
|
60
|
-
},
|
|
58
|
+
const newConfig: AiConfigPatch = {
|
|
59
|
+
models: { [modelKey]: model },
|
|
61
60
|
};
|
|
62
61
|
|
|
63
62
|
await saveConfig(newConfig);
|
|
64
63
|
};
|
|
65
64
|
|
|
66
65
|
const saveModeChange = async (newMode: CopilotMode) => {
|
|
67
|
-
const newConfig:
|
|
68
|
-
ai: {
|
|
69
|
-
...userConfig.ai,
|
|
70
|
-
mode: newMode,
|
|
71
|
-
},
|
|
72
|
-
};
|
|
66
|
+
const newConfig: AiConfigPatch = { mode: newMode };
|
|
73
67
|
|
|
74
68
|
await saveConfig(newConfig);
|
|
75
69
|
};
|
package/src/core/ai/state.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { uniqueBy } from "@/utils/arrays";
|
|
|
8
8
|
import { adaptForLocalStorage, jotaiJsonStorage } from "@/utils/storage/jotai";
|
|
9
9
|
import type { TypedString } from "@/utils/typed";
|
|
10
10
|
import type { CellId } from "../cells/ids";
|
|
11
|
+
import type { ChatOptions } from "../network/types";
|
|
11
12
|
|
|
12
13
|
const KEY = "marimo:ai:chatState:v5";
|
|
13
14
|
|
|
@@ -32,6 +33,15 @@ export interface PendingAiPrompt {
|
|
|
32
33
|
|
|
33
34
|
export const pendingAiPromptAtom = atom<PendingAiPrompt | null>(null);
|
|
34
35
|
|
|
36
|
+
const CHAT_OPTIONS_KEY = "marimo:ai:chatOptions";
|
|
37
|
+
export const chatOptionsAtom = atomWithStorage<Required<ChatOptions>>(
|
|
38
|
+
CHAT_OPTIONS_KEY,
|
|
39
|
+
{
|
|
40
|
+
webSearch: false,
|
|
41
|
+
},
|
|
42
|
+
jotaiJsonStorage,
|
|
43
|
+
);
|
|
44
|
+
|
|
35
45
|
const INCLUDE_OTHER_CELLS_KEY = "marimo:ai:includeOtherCells";
|
|
36
46
|
export const includeOtherCellsAtom = atomWithStorage<boolean>(
|
|
37
47
|
INCLUDE_OTHER_CELLS_KEY,
|
|
@@ -131,6 +131,7 @@ export type OpenTutorialRequest = schemas["OpenTutorialRequest"];
|
|
|
131
131
|
export type TutorialId = OpenTutorialRequest["tutorialId"];
|
|
132
132
|
export type InvokeAiToolRequest = schemas["InvokeAiToolRequest"];
|
|
133
133
|
export type InvokeAiToolResponse = schemas["InvokeAiToolResponse"];
|
|
134
|
+
export type ChatOptions = schemas["ChatOptions"];
|
|
134
135
|
export type ClearCacheRequest = schemas["ClearCacheRequest"];
|
|
135
136
|
export type GetCacheInfoRequest = schemas["GetCacheInfoRequest"];
|
|
136
137
|
export type LspHealthResponse = schemas["LspHealthResponse"];
|