@marimo-team/frontend 0.14.18-dev38 → 0.14.18-dev39

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.
Files changed (107) hide show
  1. package/dist/assets/{ConnectedDataExplorerComponent-Dv68DrL_.js → ConnectedDataExplorerComponent-Dluf8clT.js} +1 -1
  2. package/dist/assets/{ImageComparisonComponent-B1uRvjCG.js → ImageComparisonComponent-CK_fmBK6.js} +1 -1
  3. package/dist/assets/{VegaLite-DcJJGHdT.js → VegaLite-GAUrkYuV.js} +1 -1
  4. package/dist/assets/{_baseEach-DuV3zbFg.js → _baseEach-C0Sqwj1Y.js} +1 -1
  5. package/dist/assets/_baseMap-wbAXOiz5.js +1 -0
  6. package/dist/assets/{_baseUniq-B4fO5WxV.js → _baseUniq-C6CtZ9jJ.js} +1 -1
  7. package/dist/assets/{_createAggregator-B1gGJnz5.js → _createAggregator-DCg3aG5g.js} +1 -1
  8. package/dist/assets/{any-language-editor-gUm8Pr5k.js → any-language-editor-C1hAvQU_.js} +1 -1
  9. package/dist/assets/{architectureDiagram-SUXI7LT5-D8JW0vTZ.js → architectureDiagram-SUXI7LT5-CY6ZXfK5.js} +1 -1
  10. package/dist/assets/{blockDiagram-6J76NXCF-pje0e_9-.js → blockDiagram-6J76NXCF-BfT_qzzN.js} +1 -1
  11. package/dist/assets/{c4Diagram-6F6E4RAY-pRJXc-X6.js → c4Diagram-6F6E4RAY-BNmm8mbN.js} +1 -1
  12. package/dist/assets/channel-p2n1Ehs7.js +1 -0
  13. package/dist/assets/{chunk-353BL4L5-DWP0EAjK.js → chunk-353BL4L5-BD1eYYEv.js} +1 -1
  14. package/dist/assets/{chunk-67H74DCK-CxePEeQq.js → chunk-67H74DCK-D32ZcVIe.js} +1 -1
  15. package/dist/assets/{chunk-AACKK3MU-CyndqHmU.js → chunk-AACKK3MU-VRjqE2eS.js} +1 -1
  16. package/dist/assets/{chunk-BFAMUDN2-BE9bwlib.js → chunk-BFAMUDN2-C-cJmQLf.js} +1 -1
  17. package/dist/assets/{chunk-E2GYISFI-B85SWyoP.js → chunk-E2GYISFI-BVyg2Fo6.js} +1 -1
  18. package/dist/assets/{chunk-OW32GOEJ-C5Ag_xqO.js → chunk-OW32GOEJ-CJAy1Ibj.js} +1 -1
  19. package/dist/assets/{chunk-SKB7J2MH-DkUhr5YQ.js → chunk-SKB7J2MH-CNnoGuDT.js} +1 -1
  20. package/dist/assets/{chunk-SZ463SBG-Bz17SFYY.js → chunk-SZ463SBG-B-DyRJ-t.js} +1 -1
  21. package/dist/assets/{circle-play-BgNmoXvo.js → circle-play-DlcQmjy-.js} +1 -1
  22. package/dist/assets/classDiagram-M3E45YP4-CvCuraNp.js +1 -0
  23. package/dist/assets/classDiagram-v2-YAWTLIQI-CvCuraNp.js +1 -0
  24. package/dist/assets/clone-CglRxjPg.js +1 -0
  25. package/dist/assets/{compile-CHyG-xBK.js → compile-Da_Fjkgs.js} +1 -1
  26. package/dist/assets/{dagre-JOIXM2OF-tScjgBWM.js → dagre-JOIXM2OF-B6XR4pJX.js} +1 -1
  27. package/dist/assets/{data-grid-overlay-editor-CI501ZK0.js → data-grid-overlay-editor-v4eNCtyj.js} +1 -1
  28. package/dist/assets/{diagram-5UYTHUR4-1UwgEQLe.js → diagram-5UYTHUR4-fJ5HfR4m.js} +1 -1
  29. package/dist/assets/{diagram-VMROVX33-hKKw3I1i.js → diagram-VMROVX33-j3xejCo0.js} +1 -1
  30. package/dist/assets/{diagram-ZTM2IBQH-CyEG2YHp.js → diagram-ZTM2IBQH-dkgnTPsZ.js} +1 -1
  31. package/dist/assets/{edit-page-D9vV7W7m.js → edit-page-CmCSSEmo.js} +38 -38
  32. package/dist/assets/{erDiagram-3M52JZNH-a-tNhsYp.js → erDiagram-3M52JZNH-pjMXU-CR.js} +1 -1
  33. package/dist/assets/{flowDiagram-KYDEHFYC-CnZqSO3t.js → flowDiagram-KYDEHFYC-RFXKcK0k.js} +1 -1
  34. package/dist/assets/{ganttDiagram-EK5VF46D-6Pfp7XZd.js → ganttDiagram-EK5VF46D-BgmOCQC2.js} +1 -1
  35. package/dist/assets/{gitGraphDiagram-GW3U2K7C-CD6KQaJF.js → gitGraphDiagram-GW3U2K7C-DQPA3IzE.js} +1 -1
  36. package/dist/assets/{glide-data-editor-Bk9UyPiz.js → glide-data-editor-DWBodoS3.js} +4 -4
  37. package/dist/assets/{graph-BLBzFGRL.js → graph-DacIO-UV.js} +1 -1
  38. package/dist/assets/{home-page-Dtk3jnN0.js → home-page-CRXrPrxk.js} +1 -1
  39. package/dist/assets/{index-CA2h-g7e.js → index-8RrfVV3p.js} +1 -1
  40. package/dist/assets/{index-CofDTD9Q.js → index-BC3mHcN4.js} +1 -1
  41. package/dist/assets/{index-BVeys4As.js → index-BDUp_sLz.js} +1 -1
  42. package/dist/assets/{index-CFCxalSV.js → index-BHV0wUT7.js} +181 -180
  43. package/dist/assets/{index-Cdq0JLN7.js → index-BQF1L3gh.js} +1 -1
  44. package/dist/assets/{index-BMYfIFnR.js → index-BRCKCYPN.js} +1 -1
  45. package/dist/assets/{index-lRvedUef.js → index-BXCylVut.js} +1 -1
  46. package/dist/assets/{index-C4P8DJgN.js → index-Bg24ojRl.js} +1 -1
  47. package/dist/assets/{index-CCBKzLLu.js → index-BoaOUG3q.js} +1 -1
  48. package/dist/assets/{index-DLvVH5yg.js → index-BxSZURSP.js} +1 -1
  49. package/dist/assets/{index-CSP0KaW6.js → index-BxjU6sz1.js} +1 -1
  50. package/dist/assets/{index-Dke4Ikso.js → index-C8_gM-Yh.js} +1 -1
  51. package/dist/assets/{index-DiD7NRwC.js → index-CRoIS0e_.js} +1 -1
  52. package/dist/assets/index-CrH6PPjG.css +1 -0
  53. package/dist/assets/{index-BZMexvZ6.js → index-D-j7DlKu.js} +1 -1
  54. package/dist/assets/{index-IRmeC8mO.js → index-D6Npa4_9.js} +1 -1
  55. package/dist/assets/{index-D1i6-Vzt.js → index-M577h3xO.js} +1 -1
  56. package/dist/assets/{index-DVotKDCm.js → index-PXlQy2IY.js} +1 -1
  57. package/dist/assets/{index-FRgaLm_h.js → index-dlPsVpRk.js} +1 -1
  58. package/dist/assets/{index-DEhXWjAF.js → index-i68zQEWD.js} +1 -1
  59. package/dist/assets/{index-DfkIq4yn.js → index-txOE5caF.js} +1 -1
  60. package/dist/assets/infoDiagram-LHK5PUON-BQo83J9q.js +2 -0
  61. package/dist/assets/{journeyDiagram-EWQZEKCU-BJDGTPdN.js → journeyDiagram-EWQZEKCU--vn5uP9G.js} +1 -1
  62. package/dist/assets/{kanban-definition-ZSS6B67P-CvXSD86T.js → kanban-definition-ZSS6B67P-BsAtaVBp.js} +1 -1
  63. package/dist/assets/{layout-CAJqQFqW.js → layout-D89ZmFuH.js} +1 -1
  64. package/dist/assets/{linear-B1-iCo54.js → linear--2JfAOS_.js} +1 -1
  65. package/dist/assets/links-DHuoq13s.js +17 -0
  66. package/dist/assets/{mermaid-CoTltW6T.js → mermaid-BbIb2iAQ.js} +4 -4
  67. package/dist/assets/{min-CAtH1e7k.js → min-B719_H4j.js} +1 -1
  68. package/dist/assets/{mindmap-definition-6CBA2TL7-8_3hl_9G.js → mindmap-definition-6CBA2TL7-DbrwDtvS.js} +1 -1
  69. package/dist/assets/{number-overlay-editor-_U6Bi7tP.js → number-overlay-editor-VjmSOKWj.js} +1 -1
  70. package/dist/assets/{pieDiagram-NIOCPIFQ-5Piswj_j.js → pieDiagram-NIOCPIFQ-DDr_8e6N.js} +1 -1
  71. package/dist/assets/{quadrantDiagram-2OG54O6I-B-eCUAoN.js → quadrantDiagram-2OG54O6I-TFsW3qDt.js} +1 -1
  72. package/dist/assets/{react-plotly-4mSeoo5U.js → react-plotly-BJBcptDR.js} +1 -1
  73. package/dist/assets/{requirementDiagram-QOLK2EJ7-DcePxcNR.js → requirementDiagram-QOLK2EJ7-D5BaSClp.js} +1 -1
  74. package/dist/assets/{run-page-CojJ7qSs.js → run-page-D1TeIIYq.js} +1 -1
  75. package/dist/assets/{sankeyDiagram-4UZDY2LN-CFtqqzJ-.js → sankeyDiagram-4UZDY2LN-B-zmPQdl.js} +1 -1
  76. package/dist/assets/{sequenceDiagram-SKLFT4DO-B2b0ayzr.js → sequenceDiagram-SKLFT4DO-Dw--eukV.js} +1 -1
  77. package/dist/assets/{slides-component-Bo-bOPD4.js → slides-component-CUV-ryNw.js} +1 -1
  78. package/dist/assets/{sortBy-CQ7cYGOi.js → sortBy-C9K5SL89.js} +1 -1
  79. package/dist/assets/{stateDiagram-MI5ZYTHO-CraGSGCe.js → stateDiagram-MI5ZYTHO-WTQoQzCe.js} +1 -1
  80. package/dist/assets/stateDiagram-v2-5AN5P6BG-CoRRGWzU.js +1 -0
  81. package/dist/assets/{storage-B7zCO6St.js → storage-BgxGjrlK.js} +3 -3
  82. package/dist/assets/{terminal-tKrSa7wD.js → terminal-BRsJ5bg0.js} +1 -1
  83. package/dist/assets/{time-DfceXfii.js → time-Wo8mRr4h.js} +1 -1
  84. package/dist/assets/{timeline-definition-MYPXXCX6-mETqJEkh.js → timeline-definition-MYPXXCX6-DUiBvKXW.js} +1 -1
  85. package/dist/assets/{tracing-Df7FGhtp.js → tracing-DkTh5bjk.js} +2 -2
  86. package/dist/assets/{trash-D_witDuD.js → trash-_cwoDPy1.js} +1 -1
  87. package/dist/assets/{treemap-75Q7IDZK-2zY68G8S.js → treemap-75Q7IDZK-DndwJIAY.js} +1 -1
  88. package/dist/assets/{vega-component-BriH_8ZE.js → vega-component-BQrEMxr7.js} +1 -1
  89. package/dist/assets/{xychartDiagram-H2YORKM3-CBxGxXLd.js → xychartDiagram-H2YORKM3-Cn7nHPc6.js} +1 -1
  90. package/dist/index.html +2 -2
  91. package/package.json +1 -1
  92. package/src/components/ai/ai-model-dropdown.tsx +1 -1
  93. package/src/components/chat/chat-panel.tsx +8 -51
  94. package/src/components/editor/ai/add-cell-with-ai.tsx +114 -60
  95. package/src/components/editor/ai/ai-completion-editor.tsx +27 -15
  96. package/src/core/ai/config.ts +76 -0
  97. package/src/core/config/config-schema.ts +18 -12
  98. package/src/plugins/impl/chat/chat-ui.tsx +0 -3
  99. package/dist/assets/_baseMap-CXKOjduW.js +0 -1
  100. package/dist/assets/channel-DSfjdvRx.js +0 -1
  101. package/dist/assets/classDiagram-M3E45YP4-W7ZwJ9Nk.js +0 -1
  102. package/dist/assets/classDiagram-v2-YAWTLIQI-W7ZwJ9Nk.js +0 -1
  103. package/dist/assets/clone-D0s-H6Md.js +0 -1
  104. package/dist/assets/index-BH8vrBOf.css +0 -1
  105. package/dist/assets/infoDiagram-LHK5PUON-BcKRYZqP.js +0 -2
  106. package/dist/assets/links-Df4dtLc_.js +0 -18
  107. package/dist/assets/stateDiagram-v2-5AN5P6BG-B6V5_20O.js +0 -1
@@ -36,7 +36,7 @@ import {
36
36
  SelectValue,
37
37
  } from "@/components/ui/select";
38
38
  import { addMessageToChat } from "@/core/ai/chat-utils";
39
- import type { QualifiedModelId } from "@/core/ai/ids/ids";
39
+ import { useModelChange } from "@/core/ai/config";
40
40
  import {
41
41
  activeChatAtom,
42
42
  type Chat,
@@ -44,13 +44,12 @@ import {
44
44
  chatStateAtom,
45
45
  } from "@/core/ai/state";
46
46
  import { getCodes } from "@/core/codemirror/copilot/getCodes";
47
- import { aiAtom, aiEnabledAtom, userConfigAtom } from "@/core/config/config";
48
- import { DEFAULT_AI_MODEL, type UserConfig } from "@/core/config/config-schema";
47
+ import { aiAtom, aiEnabledAtom } from "@/core/config/config";
48
+ import { DEFAULT_AI_MODEL } from "@/core/config/config-schema";
49
49
  import { FeatureFlagged } from "@/core/config/feature-flag";
50
50
  import { useRequestClient } from "@/core/network/requests";
51
51
  import { useRuntimeManager } from "@/core/runtime/config";
52
52
  import { ErrorBanner } from "@/plugins/impl/common/error-banner";
53
- import { type ResolvedTheme, useTheme } from "@/theme/useTheme";
54
53
  import { cn } from "@/utils/cn";
55
54
  import { timeAgo } from "@/utils/dates";
56
55
  import { Logger } from "@/utils/Logger";
@@ -147,7 +146,6 @@ const ChatHeader: React.FC<ChatHeaderProps> = ({
147
146
  interface ChatMessageProps {
148
147
  message: Message;
149
148
  index: number;
150
- theme: ResolvedTheme;
151
149
  onEdit: (index: number, newValue: string) => void;
152
150
  setChatState: Dispatch<SetStateAction<ChatState>>;
153
151
  chatState: ChatState;
@@ -156,7 +154,7 @@ interface ChatMessageProps {
156
154
  }
157
155
 
158
156
  const ChatMessage: React.FC<ChatMessageProps> = memo(
159
- ({ message, index, theme, onEdit, isStreamingReasoning, totalMessages }) => (
157
+ ({ message, index, onEdit, isStreamingReasoning, totalMessages }) => (
160
158
  <div
161
159
  className={cn(
162
160
  "flex group relative",
@@ -168,7 +166,6 @@ const ChatMessage: React.FC<ChatMessageProps> = memo(
168
166
  <PromptInput
169
167
  key={message.id}
170
168
  value={message.content}
171
- theme={theme}
172
169
  placeholder="Type your message..."
173
170
  onChange={() => {
174
171
  // noop
@@ -248,10 +245,9 @@ const DEFAULT_MODE = "manual";
248
245
  const ChatInputFooter: React.FC<ChatInputFooterProps> = memo(
249
246
  ({ input, onSendClick, isLoading, onStop }) => {
250
247
  const ai = useAtomValue(aiAtom);
251
- const [userConfig, setUserConfig] = useAtom(userConfigAtom);
252
248
  const currentMode = ai?.mode || DEFAULT_MODE;
253
249
  const currentModel = ai?.models?.chat_model || DEFAULT_AI_MODEL;
254
- const { saveUserConfig } = useRequestClient();
250
+ const { saveModeChange, saveModelChange } = useModelChange();
255
251
 
256
252
  const modeOptions = [
257
253
  {
@@ -266,44 +262,11 @@ const ChatInputFooter: React.FC<ChatInputFooterProps> = memo(
266
262
  },
267
263
  ];
268
264
 
269
- const handleModeChange = async (newMode: "ask" | "manual") => {
270
- const newConfig: UserConfig = {
271
- ...userConfig,
272
- ai: {
273
- ...userConfig.ai,
274
- mode: newMode,
275
- },
276
- };
277
- saveConfig(newConfig);
278
- };
279
-
280
- const handleModelChange = async (newModel: QualifiedModelId) => {
281
- const newConfig: UserConfig = {
282
- ...userConfig,
283
- ai: {
284
- ...userConfig.ai,
285
- models: {
286
- custom_models: [],
287
- displayed_models: [],
288
- ...userConfig.ai?.models,
289
- chat_model: newModel,
290
- },
291
- },
292
- };
293
- saveConfig(newConfig);
294
- };
295
-
296
- const saveConfig = async (newConfig: UserConfig) => {
297
- await saveUserConfig({ config: newConfig }).then(() => {
298
- setUserConfig(newConfig);
299
- });
300
- };
301
-
302
265
  return (
303
266
  <div className="px-3 py-2 border-t border-border/20 flex items-center justify-between">
304
267
  <div className="flex items-center gap-2">
305
268
  <FeatureFlagged feature="mcp_docs">
306
- <Select value={currentMode} onValueChange={handleModeChange}>
269
+ <Select value={currentMode} onValueChange={saveModeChange}>
307
270
  <SelectTrigger className="h-6 text-xs border-border shadow-none! ring-0! bg-muted hover:bg-muted/30 py-0 px-2 gap-1">
308
271
  <SelectValue placeholder="manual" />
309
272
  </SelectTrigger>
@@ -328,7 +291,7 @@ const ChatInputFooter: React.FC<ChatInputFooterProps> = memo(
328
291
  <AIModelDropdown
329
292
  value={currentModel}
330
293
  placeholder="Model"
331
- onSelect={handleModelChange}
294
+ onSelect={(model) => saveModelChange(model, "chat")}
332
295
  triggerClassName="h-6 text-xs shadow-none! ring-0! bg-muted hover:bg-muted/30 rounded-sm"
333
296
  iconSize="small"
334
297
  showAddCustomModelDocs={true}
@@ -359,14 +322,13 @@ interface ChatInputProps {
359
322
  input: string;
360
323
  setInput: (value: string) => void;
361
324
  onSubmit: (e: KeyboardEvent | undefined, value: string) => void;
362
- theme: ResolvedTheme;
363
325
  inputRef: React.RefObject<ReactCodeMirrorRef | null>;
364
326
  isLoading: boolean;
365
327
  onStop: () => void;
366
328
  }
367
329
 
368
330
  const ChatInput: React.FC<ChatInputProps> = memo(
369
- ({ input, setInput, onSubmit, theme, inputRef, isLoading, onStop }) => {
331
+ ({ input, setInput, onSubmit, inputRef, isLoading, onStop }) => {
370
332
  const handleSendClick = () => {
371
333
  if (input.trim()) {
372
334
  onSubmit(undefined, input);
@@ -381,7 +343,6 @@ const ChatInput: React.FC<ChatInputProps> = memo(
381
343
  onChange={setInput}
382
344
  onSubmit={onSubmit}
383
345
  onClose={() => inputRef.current?.editor?.blur()}
384
- theme={theme}
385
346
  placeholder="Type your message..."
386
347
  />
387
348
  </div>
@@ -428,7 +389,6 @@ const ChatPanelBody = () => {
428
389
  const newMessageInputRef = useRef<ReactCodeMirrorRef>(null);
429
390
  const scrollContainerRef = useRef<HTMLDivElement>(null);
430
391
  const messagesEndRef = useRef<HTMLDivElement>(null);
431
- const { theme } = useTheme();
432
392
  const runtimeManager = useRuntimeManager();
433
393
  const { invokeAiTool } = useRequestClient();
434
394
 
@@ -687,7 +647,6 @@ const ChatPanelBody = () => {
687
647
  key="new-thread-input"
688
648
  value={newThreadInput}
689
649
  placeholder="Ask anything, @ to include context about tables or dataframes"
690
- theme={theme}
691
650
  onClose={handleOnCloseThread}
692
651
  onChange={setNewThreadInput}
693
652
  onSubmit={handleNewThreadSubmit}
@@ -707,7 +666,6 @@ const ChatPanelBody = () => {
707
666
  key={idx}
708
667
  message={message}
709
668
  index={idx}
710
- theme={theme}
711
669
  onEdit={handleMessageEdit}
712
670
  setChatState={setChatState}
713
671
  chatState={chatState}
@@ -747,7 +705,6 @@ const ChatPanelBody = () => {
747
705
  input={input}
748
706
  setInput={setInput}
749
707
  onSubmit={handleChatInputSubmit}
750
- theme={theme}
751
708
  inputRef={newMessageInputRef}
752
709
  isLoading={isLoading}
753
710
  onStop={stop}
@@ -16,10 +16,11 @@ import ReactCodeMirror, {
16
16
  type ReactCodeMirrorRef,
17
17
  } from "@uiw/react-codemirror";
18
18
  import { useCompletion } from "ai/react";
19
- import { useAtom, useStore } from "jotai";
19
+ import { useAtom, useAtomValue, useStore } from "jotai";
20
20
  import { atomWithStorage } from "jotai/utils";
21
21
  import {
22
22
  ChevronsUpDown,
23
+ DatabaseIcon,
23
24
  Loader2Icon,
24
25
  SendHorizontal,
25
26
  SparklesIcon,
@@ -27,22 +28,28 @@ import {
27
28
  } from "lucide-react";
28
29
  import { useMemo, useState } from "react";
29
30
  import useEvent from "react-use-event-hook";
31
+ import { AIModelDropdown } from "@/components/ai/ai-model-dropdown";
30
32
  import { Button } from "@/components/ui/button";
31
33
  import {
32
34
  DropdownMenu,
33
35
  DropdownMenuContent,
34
36
  DropdownMenuItem,
37
+ DropdownMenuSeparator,
35
38
  DropdownMenuTrigger,
36
39
  } from "@/components/ui/dropdown-menu";
37
40
  import { toast } from "@/components/ui/use-toast";
41
+ import { useModelChange } from "@/core/ai/config";
38
42
  import { resourceExtension } from "@/core/codemirror/ai/resources";
39
43
  import { customPythonLanguageSupport } from "@/core/codemirror/language/languages/python";
40
44
  import { SQLLanguageAdapter } from "@/core/codemirror/language/languages/sql/sql";
45
+ import { aiAtom } from "@/core/config/config";
46
+ import { DEFAULT_AI_MODEL } from "@/core/config/config-schema";
41
47
  import { useRuntimeManager } from "@/core/runtime/config";
42
- import { type ResolvedTheme, useTheme } from "@/theme/useTheme";
48
+ import { useTheme } from "@/theme/useTheme";
43
49
  import { cn } from "@/utils/cn";
44
50
  import { prettyError } from "@/utils/errors";
45
51
  import { useCellActions } from "../../../core/cells/cells";
52
+ import { PythonIcon } from "../cell/code/icons";
46
53
  import {
47
54
  getAICompletionBody,
48
55
  mentionsCompletionSource,
@@ -72,6 +79,10 @@ export const AddCellWithAI: React.FC<{
72
79
  const { theme } = useTheme();
73
80
  const runtimeManager = useRuntimeManager();
74
81
 
82
+ const ai = useAtomValue(aiAtom);
83
+ const editModel = ai?.models?.edit_model || DEFAULT_AI_MODEL;
84
+ const { saveModelChange } = useModelChange();
85
+
75
86
  const {
76
87
  completion,
77
88
  input,
@@ -109,32 +120,50 @@ export const AddCellWithAI: React.FC<{
109
120
  }
110
121
  };
111
122
 
123
+ const pythonIcon = (
124
+ <>
125
+ <PythonIcon className="size-4 mr-2" />
126
+ Python
127
+ </>
128
+ );
129
+
130
+ const sqlIcon = (
131
+ <>
132
+ <DatabaseIcon className="size-4 mr-2" />
133
+ SQL
134
+ </>
135
+ );
136
+
137
+ const languageDropdown = (
138
+ <DropdownMenu modal={false}>
139
+ <DropdownMenuTrigger asChild={true}>
140
+ <Button
141
+ variant="text"
142
+ className="ml-2"
143
+ size="xs"
144
+ data-testid="language-button"
145
+ >
146
+ {language === "python" ? pythonIcon : sqlIcon}
147
+ <ChevronsUpDown className="ml-1 h-3.5 w-3.5 text-muted-foreground/70" />
148
+ </Button>
149
+ </DropdownMenuTrigger>
150
+ <DropdownMenuContent align="center">
151
+ <div className="px-2 py-1 font-semibold">Select language</div>
152
+ <DropdownMenuSeparator />
153
+ <DropdownMenuItem onClick={() => setLanguage("python")}>
154
+ {pythonIcon}
155
+ </DropdownMenuItem>
156
+ <DropdownMenuItem onClick={() => setLanguage("sql")}>
157
+ {sqlIcon}
158
+ </DropdownMenuItem>
159
+ </DropdownMenuContent>
160
+ </DropdownMenu>
161
+ );
162
+
112
163
  const inputComponent = (
113
164
  <div className="flex items-center px-3">
114
- <SparklesIcon className="size-4 text-(--blue-11)" />
115
- <DropdownMenu modal={false}>
116
- <DropdownMenuTrigger asChild={true}>
117
- <Button
118
- variant="text"
119
- className="ml-2"
120
- size="xs"
121
- data-testid="language-button"
122
- >
123
- {language === "python" ? "Python" : "SQL"}
124
- <ChevronsUpDown className="ml-1 h-3.5 w-3.5 text-muted-foreground/70" />
125
- </Button>
126
- </DropdownMenuTrigger>
127
- <DropdownMenuContent align="center">
128
- <DropdownMenuItem onClick={() => setLanguage("python")}>
129
- Python
130
- </DropdownMenuItem>
131
- <DropdownMenuItem onClick={() => setLanguage("sql")}>
132
- SQL
133
- </DropdownMenuItem>
134
- </DropdownMenuContent>
135
- </DropdownMenu>
165
+ <SparklesIcon className="size-4 text-(--blue-11) mr-2" />
136
166
  <PromptInput
137
- theme={theme}
138
167
  onClose={() => {
139
168
  setCompletion("");
140
169
  onClose();
@@ -158,33 +187,10 @@ export const AddCellWithAI: React.FC<{
158
187
  Stop
159
188
  </Button>
160
189
  )}
161
- {!isLoading && completion && (
162
- <Button
163
- data-testid="accept-completion-button"
164
- variant="text"
165
- size="sm"
166
- className="mb-0"
167
- disabled={isLoading}
168
- onClick={() => {
169
- createNewCell({
170
- cellId: "__end__",
171
- before: false,
172
- code:
173
- language === "python"
174
- ? completion
175
- : SQLLanguageAdapter.fromQuery(completion),
176
- });
177
- setCompletion("");
178
- onClose();
179
- }}
180
- >
181
- <span className="text-(--grass-11) opacity-100">Accept</span>
182
- </Button>
183
- )}
184
190
  <Button variant="text" size="sm" onClick={submit} title="Submit">
185
191
  <SendHorizontal className="size-4" />
186
192
  </Button>
187
- <Button variant="text" size="sm" className="mb-0" onClick={onClose}>
193
+ <Button variant="text" size="sm" className="mb-0 px-1" onClick={onClose}>
188
194
  <XIcon className="size-4" />
189
195
  </Button>
190
196
  </div>
@@ -193,16 +199,65 @@ export const AddCellWithAI: React.FC<{
193
199
  return (
194
200
  <div className={cn("flex flex-col w-full gap-2 py-2")}>
195
201
  {inputComponent}
196
- {!completion && (
197
- <span className="text-xs text-muted-foreground px-3 flex flex-col gap-1">
198
- <span>
199
- You can mention <span className="text-(--cyan-11)">@dataframe</span>{" "}
200
- or <span className="text-(--cyan-11)">@sql_table</span> to pull
201
- additional context such as column names.
202
+ <div className="flex flex-row justify-between -mt-1 ml-1 mr-3">
203
+ {!completion && (
204
+ <span className="text-xs text-muted-foreground px-3 flex flex-col gap-1">
205
+ <span>
206
+ You can mention{" "}
207
+ <span className="text-(--cyan-11)">@dataframe</span> or{" "}
208
+ <span className="text-(--cyan-11)">@sql_table</span> to pull
209
+ additional context such as column names.
210
+ </span>
211
+ <span>Code from other cells is automatically included.</span>
202
212
  </span>
203
- <span>Code from other cells is automatically included.</span>
204
- </span>
205
- )}
213
+ )}
214
+ {completion && (
215
+ <>
216
+ <Button
217
+ data-testid="accept-completion-button"
218
+ variant="text"
219
+ size="sm"
220
+ className="mb-0"
221
+ onClick={() => {
222
+ createNewCell({
223
+ cellId: "__end__",
224
+ before: false,
225
+ code:
226
+ language === "python"
227
+ ? completion
228
+ : SQLLanguageAdapter.fromQuery(completion),
229
+ });
230
+ setCompletion("");
231
+ onClose();
232
+ }}
233
+ >
234
+ <span className="text-(--grass-11)">Accept</span>
235
+ </Button>
236
+ <Button
237
+ data-testid="decline-completion-button"
238
+ variant="text"
239
+ size="sm"
240
+ className="mb-0 pl-1"
241
+ onClick={() => setCompletion("")}
242
+ >
243
+ <span className="text-(--red-10)">Reject</span>
244
+ </Button>
245
+ </>
246
+ )}
247
+ <div className="ml-auto flex items-center gap-1">
248
+ {languageDropdown}
249
+ <AIModelDropdown
250
+ value={editModel}
251
+ onSelect={(model) => {
252
+ saveModelChange(model, "edit");
253
+ }}
254
+ triggerClassName="h-7 text-xs max-w-64"
255
+ iconSize="small"
256
+ forRole="edit"
257
+ />
258
+ </div>
259
+ </div>
260
+
206
261
  {completion && (
207
262
  <ReactCodeMirror
208
263
  value={completion}
@@ -230,7 +285,6 @@ interface PromptInputProps {
230
285
  onChange: (value: string) => void;
231
286
  onSubmit: (e: KeyboardEvent | undefined, value: string) => void;
232
287
  additionalCompletions?: AdditionalCompletions;
233
- theme: ResolvedTheme;
234
288
  maxHeight?: string;
235
289
  }
236
290
 
@@ -249,12 +303,12 @@ export const PromptInput = ({
249
303
  onSubmit,
250
304
  onClose,
251
305
  additionalCompletions,
252
- theme,
253
306
  maxHeight,
254
307
  }: PromptInputProps) => {
255
308
  const handleSubmit = onSubmit;
256
309
  const handleEscape = onClose;
257
310
  const store = useStore();
311
+ const { theme } = useTheme();
258
312
 
259
313
  const additionalCompletionsSource: CompletionSource = useEvent(
260
314
  (context: CompletionContext) => {
@@ -150,7 +150,6 @@ export const AiCompletionEditor: React.FC<Props> = ({
150
150
  <SparklesIcon className="text-(--blue-10) shrink-0" size={16} />
151
151
  <PromptInput
152
152
  inputRef={inputRef}
153
- theme={theme}
154
153
  onClose={() => {
155
154
  declineChange();
156
155
  setCompletion("");
@@ -178,20 +177,33 @@ export const AiCompletionEditor: React.FC<Props> = ({
178
177
  Stop
179
178
  </Button>
180
179
  )}
181
- {!isLoading && completion && (
182
- <Button
183
- data-testid="accept-completion-button"
184
- variant="text"
185
- size="xs"
186
- className="mb-0"
187
- disabled={isLoading}
188
- onClick={() => {
189
- acceptChange(completion);
190
- setCompletion("");
191
- }}
192
- >
193
- <span className="text-(--grass-11) opacity-100">Accept</span>
194
- </Button>
180
+ {completion && (
181
+ <>
182
+ <Button
183
+ data-testid="accept-completion-button"
184
+ variant="text"
185
+ size="xs"
186
+ className="mb-0"
187
+ disabled={isLoading}
188
+ onClick={() => {
189
+ acceptChange(completion);
190
+ setCompletion("");
191
+ }}
192
+ >
193
+ <span className="text-(--grass-11) opacity-100">Accept</span>
194
+ </Button>
195
+ <Button
196
+ data-testid="decline-completion-button"
197
+ variant="text"
198
+ size="xs"
199
+ className="mb-0 pl-1"
200
+ onClick={() => {
201
+ setCompletion("");
202
+ }}
203
+ >
204
+ <span className="text-(--red-10)">Reject</span>
205
+ </Button>
206
+ </>
195
207
  )}
196
208
  <div className="h-full w-px bg-border mx-2" />
197
209
  <Tooltip content="Include code from other cells">
@@ -0,0 +1,76 @@
1
+ /* Copyright 2024 Marimo. All rights reserved. */
2
+
3
+ import type { Role } from "@marimo-team/llm-info";
4
+ import { useAtom } from "jotai";
5
+ import type { QualifiedModelId } from "@/core/ai/ids/ids";
6
+ import { userConfigAtom } from "@/core/config/config";
7
+ import type { AIModelKey, UserConfig } from "@/core/config/config-schema";
8
+ import { useRequestClient } from "@/core/network/requests";
9
+
10
+ // Extract only the supported roles from the Role type
11
+ type SupportedRole = Extract<Role, "chat" | "autocomplete" | "edit">;
12
+
13
+ const getModelKeyForRole = (forRole: SupportedRole): AIModelKey | null => {
14
+ switch (forRole) {
15
+ case "chat":
16
+ return "chat_model";
17
+ case "autocomplete":
18
+ return "autocomplete_model";
19
+ case "edit":
20
+ return "edit_model";
21
+ }
22
+ };
23
+
24
+ /**
25
+ * Hook for saving model and mode changes.
26
+ */
27
+ export const useModelChange = () => {
28
+ const [userConfig, setUserConfig] = useAtom(userConfigAtom);
29
+ const { saveUserConfig } = useRequestClient();
30
+
31
+ const saveConfig = async (newConfig: UserConfig) => {
32
+ await saveUserConfig({ config: newConfig }).then(() => {
33
+ setUserConfig(newConfig);
34
+ });
35
+ };
36
+
37
+ const saveModelChange = async (
38
+ model: QualifiedModelId,
39
+ forRole: SupportedRole,
40
+ ) => {
41
+ const modelKey = getModelKeyForRole(forRole);
42
+
43
+ if (!modelKey) {
44
+ return;
45
+ }
46
+
47
+ const newConfig: UserConfig = {
48
+ ...userConfig,
49
+ ai: {
50
+ ...userConfig.ai,
51
+ models: {
52
+ custom_models: userConfig.ai?.models?.custom_models ?? [],
53
+ displayed_models: userConfig.ai?.models?.displayed_models ?? [],
54
+ ...userConfig.ai?.models,
55
+ [modelKey]: model,
56
+ },
57
+ },
58
+ };
59
+
60
+ saveConfig(newConfig);
61
+ };
62
+
63
+ const saveModeChange = async (newMode: "ask" | "manual") => {
64
+ const newConfig: UserConfig = {
65
+ ...userConfig,
66
+ ai: {
67
+ ...userConfig.ai,
68
+ mode: newMode,
69
+ },
70
+ };
71
+
72
+ saveConfig(newConfig);
73
+ };
74
+
75
+ return { saveModelChange, saveModeChange };
76
+ };
@@ -51,6 +51,20 @@ const AiConfigSchema = z
51
51
  })
52
52
  .passthrough();
53
53
 
54
+ const AiModelsSchema = z.object({
55
+ chat_model: z.string().nullish(),
56
+ edit_model: z.string().nullish(),
57
+ autocomplete_model: z.string().nullish(),
58
+ displayed_models: z.array(z.string()).default([]),
59
+ custom_models: z.array(z.string()).default([]),
60
+ });
61
+
62
+ // Extract the model key type from the schema
63
+ export type AIModelKey = keyof Pick<
64
+ z.infer<typeof AiModelsSchema>,
65
+ "chat_model" | "edit_model" | "autocomplete_model"
66
+ >;
67
+
54
68
  export const UserConfigSchema = z
55
69
  .object({
56
70
  completion: z
@@ -158,18 +172,10 @@ export const UserConfigSchema = z
158
172
  aws_secret_access_key: z.string().optional(),
159
173
  })
160
174
  .optional(),
161
- models: z
162
- .object({
163
- chat_model: z.string().nullish(),
164
- edit_model: z.string().nullish(),
165
- autocomplete_model: z.string().nullish(),
166
- displayed_models: z.array(z.string()).default([]),
167
- custom_models: z.array(z.string()).default([]),
168
- })
169
- .default({
170
- displayed_models: [],
171
- custom_models: [],
172
- }),
175
+ models: AiModelsSchema.default({
176
+ displayed_models: [],
177
+ custom_models: [],
178
+ }),
173
179
  })
174
180
  .passthrough()
175
181
  .default({}),
@@ -43,7 +43,6 @@ import { toast } from "@/components/ui/use-toast";
43
43
  import { moveToEndOfEditor } from "@/core/codemirror/utils";
44
44
  import { useAsyncData } from "@/hooks/useAsyncData";
45
45
  import { renderHTML } from "@/plugins/core/RenderHTML";
46
- import { useTheme } from "@/theme/useTheme";
47
46
  import { cn } from "@/utils/cn";
48
47
  import { copyToClipboard } from "@/utils/copy";
49
48
  import { Logger } from "@/utils/Logger";
@@ -74,7 +73,6 @@ export const Chatbot: React.FC<Props> = (props) => {
74
73
  const formRef = useRef<HTMLFormElement>(null);
75
74
  const codeMirrorInputRef = useRef<ReactCodeMirrorRef>(null);
76
75
  const scrollContainerRef = useRef<HTMLDivElement>(null);
77
- const { theme } = useTheme();
78
76
 
79
77
  const { data: initialMessages } = useAsyncData(async () => {
80
78
  const chatMessages = await props.get_chat_history({});
@@ -374,7 +372,6 @@ export const Chatbot: React.FC<Props> = (props) => {
374
372
  placeholder={promptInputPlaceholder}
375
373
  value={input}
376
374
  inputRef={codeMirrorInputRef}
377
- theme={theme}
378
375
  maxHeight={props.maxHeight ? `${props.maxHeight / 2}px` : undefined}
379
376
  onChange={setInput}
380
377
  onSubmit={(_evt, newValue) => {
@@ -1 +0,0 @@
1
- import{b as m}from"./_baseEach-DuV3zbFg.js";import{x as s}from"./index-CFCxalSV.js";function e(r,o){var a=-1,t=s(r)?Array(r.length):[];return m(r,function(n,f,i){t[++a]=o(n,f,i)}),t}export{e as b};
@@ -1 +0,0 @@
1
- import{U as s,C as o}from"./mermaid-CoTltW6T.js";const n=(a,r)=>s.lang.round(o.parse(a)[r]);export{n as c};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as t,C as o}from"./chunk-SZ463SBG-Bz17SFYY.js";import{_ as e}from"./mermaid-CoTltW6T.js";import"./transform-B8bpuzxV.js";import"./chunk-E2GYISFI-B85SWyoP.js";import"./chunk-BFAMUDN2-BE9bwlib.js";import"./chunk-SKB7J2MH-DkUhr5YQ.js";import"./index-CFCxalSV.js";import"./step-BwsUM5iJ.js";import"./timer-BwIYMJWC.js";var i={parser:t,get db(){return new o},renderer:s,styles:a,init:e(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{i as diagram};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as t,C as o}from"./chunk-SZ463SBG-Bz17SFYY.js";import{_ as e}from"./mermaid-CoTltW6T.js";import"./transform-B8bpuzxV.js";import"./chunk-E2GYISFI-B85SWyoP.js";import"./chunk-BFAMUDN2-BE9bwlib.js";import"./chunk-SKB7J2MH-DkUhr5YQ.js";import"./index-CFCxalSV.js";import"./step-BwsUM5iJ.js";import"./timer-BwIYMJWC.js";var i={parser:t,get db(){return new o},renderer:s,styles:a,init:e(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{i as diagram};
@@ -1 +0,0 @@
1
- import{b as n}from"./_baseUniq-B4fO5WxV.js";function o(r){return n(r,4)}export{o as c};