@marimo-team/islands 0.19.8-dev41 → 0.19.8-dev48
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/assets/__vite-browser-external-WSlCcXn_.js +1 -0
- package/dist/assets/worker-DUYMdbtA.js +73 -0
- package/dist/main.js +1740 -1691
- package/dist/style.css +1 -1
- package/dist/{useDeepCompareMemoize-CMGprt3H.js → useDeepCompareMemoize-BhZZsis0.js} +7 -3
- package/dist/{vega-component-DU3aSp4m.js → vega-component-DCxUyPnb.js} +1 -1
- package/package.json +1 -1
- package/src/components/app-config/optional-features.tsx +1 -1
- package/src/components/chat/__tests__/useFileState.test.tsx +93 -0
- package/src/components/chat/acp/agent-panel.tsx +26 -77
- package/src/components/chat/chat-components.tsx +114 -1
- package/src/components/chat/chat-panel.tsx +32 -104
- package/src/components/chat/chat-utils.ts +42 -0
- package/src/components/editor/ai/add-cell-with-ai.tsx +85 -53
- package/src/components/editor/ai/ai-completion-editor.tsx +15 -38
- package/src/components/editor/chrome/panels/packages-panel.tsx +12 -9
- package/src/core/islands/__tests__/bridge.test.ts +7 -2
- package/src/core/islands/bridge.ts +1 -1
- package/src/core/islands/main.ts +7 -0
- package/src/core/network/types.ts +2 -2
- package/src/core/wasm/bridge.ts +1 -1
- package/src/core/websocket/useMarimoKernelConnection.tsx +5 -15
- package/src/plugins/impl/anywidget/AnyWidgetPlugin.tsx +86 -167
- package/src/plugins/impl/anywidget/__tests__/AnyWidgetPlugin.test.tsx +37 -123
- package/src/plugins/impl/anywidget/__tests__/model.test.ts +128 -122
- package/src/{utils/__tests__/data-views.test.ts → plugins/impl/anywidget/__tests__/serialization.test.ts} +42 -96
- package/src/plugins/impl/anywidget/model.ts +348 -223
- package/src/plugins/impl/anywidget/schemas.ts +32 -0
- package/src/{utils/data-views.ts → plugins/impl/anywidget/serialization.ts} +13 -36
- package/src/plugins/impl/anywidget/types.ts +27 -0
- package/src/plugins/impl/chat/chat-ui.tsx +22 -20
- package/src/utils/Deferred.ts +21 -0
- package/src/utils/json/base64.ts +38 -8
- package/dist/assets/__vite-browser-external-6-UwTyQC.js +0 -1
- package/dist/assets/worker-D3e5wDxM.js +0 -73
|
@@ -7,18 +7,14 @@ import type { ReactCodeMirrorRef } from "@uiw/react-codemirror";
|
|
|
7
7
|
import { DefaultChatTransport, type FileUIPart, type TextUIPart } from "ai";
|
|
8
8
|
import { useAtom, useAtomValue, useSetAtom, useStore } from "jotai";
|
|
9
9
|
import {
|
|
10
|
-
AtSignIcon,
|
|
11
10
|
BotMessageSquareIcon,
|
|
12
11
|
HatGlasses,
|
|
13
12
|
Loader2,
|
|
14
13
|
type LucideIcon,
|
|
15
14
|
MessageCircleIcon,
|
|
16
15
|
NotebookText,
|
|
17
|
-
PaperclipIcon,
|
|
18
16
|
PlusIcon,
|
|
19
|
-
SendIcon,
|
|
20
17
|
SettingsIcon,
|
|
21
|
-
SquareIcon,
|
|
22
18
|
} from "lucide-react";
|
|
23
19
|
import { memo, useEffect, useRef, useState } from "react";
|
|
24
20
|
import useEvent from "react-use-event-hook";
|
|
@@ -33,7 +29,7 @@ import {
|
|
|
33
29
|
} from "@/components/ui/select";
|
|
34
30
|
import { replaceMessagesInChat } from "@/core/ai/chat-utils";
|
|
35
31
|
import { useModelChange } from "@/core/ai/config";
|
|
36
|
-
import { AiModelId
|
|
32
|
+
import { AiModelId } from "@/core/ai/ids/ids";
|
|
37
33
|
import { useStagedAICellsActions } from "@/core/ai/staged-cells";
|
|
38
34
|
import {
|
|
39
35
|
activeChatAtom,
|
|
@@ -64,10 +60,14 @@ import {
|
|
|
64
60
|
import { PanelEmptyState } from "../editor/chrome/panels/empty-state";
|
|
65
61
|
import { CopyClipboardIcon } from "../icons/copy-icon";
|
|
66
62
|
import { MCPStatusIndicator } from "../mcp/mcp-status-indicator";
|
|
67
|
-
import { Input } from "../ui/input";
|
|
68
63
|
import { Tooltip, TooltipProvider } from "../ui/tooltip";
|
|
69
|
-
import {
|
|
70
|
-
|
|
64
|
+
import {
|
|
65
|
+
AddContextButton,
|
|
66
|
+
AttachFileButton,
|
|
67
|
+
AttachmentRenderer,
|
|
68
|
+
FileAttachmentPill,
|
|
69
|
+
SendButton,
|
|
70
|
+
} from "./chat-components";
|
|
71
71
|
import { renderUIMessage } from "./chat-display";
|
|
72
72
|
import { ChatHistoryPopover } from "./chat-history-popover";
|
|
73
73
|
import {
|
|
@@ -77,21 +77,13 @@ import {
|
|
|
77
77
|
handleToolCall,
|
|
78
78
|
hasPendingToolCalls,
|
|
79
79
|
isLastMessageReasoning,
|
|
80
|
+
PROVIDERS_THAT_SUPPORT_ATTACHMENTS,
|
|
81
|
+
useFileState,
|
|
80
82
|
} from "./chat-utils";
|
|
81
83
|
|
|
82
84
|
// Default mode for the AI
|
|
83
85
|
const DEFAULT_MODE = "manual";
|
|
84
86
|
|
|
85
|
-
// We need to modify the backend to support attachments for other providers
|
|
86
|
-
// And other types
|
|
87
|
-
const PROVIDERS_THAT_SUPPORT_ATTACHMENTS = new Set<ProviderId>([
|
|
88
|
-
"openai",
|
|
89
|
-
"google",
|
|
90
|
-
"anthropic",
|
|
91
|
-
]);
|
|
92
|
-
const SUPPORTED_ATTACHMENT_TYPES = ["image/*", "text/*"];
|
|
93
|
-
const MAX_ATTACHMENT_SIZE = 1024 * 1024 * 50; // 50MB
|
|
94
|
-
|
|
95
87
|
interface ChatHeaderProps {
|
|
96
88
|
onNewChat: () => void;
|
|
97
89
|
activeChatId: ChatId | undefined;
|
|
@@ -316,60 +308,23 @@ const ChatInputFooter: React.FC<ChatInputFooterProps> = memo(
|
|
|
316
308
|
/>
|
|
317
309
|
</div>
|
|
318
310
|
<div className="flex flex-row">
|
|
319
|
-
<
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
onClick={onAddContext}
|
|
324
|
-
disabled={isLoading}
|
|
325
|
-
>
|
|
326
|
-
<AtSignIcon className="h-3.5 w-3.5" />
|
|
327
|
-
</Button>
|
|
328
|
-
</Tooltip>
|
|
311
|
+
<AddContextButton
|
|
312
|
+
handleAddContext={onAddContext}
|
|
313
|
+
isLoading={isLoading}
|
|
314
|
+
/>
|
|
329
315
|
{isAttachmentSupported && (
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
className="cursor-pointer"
|
|
336
|
-
onClick={() => fileInputRef.current?.click()}
|
|
337
|
-
title="Attach a file"
|
|
338
|
-
disabled={isLoading}
|
|
339
|
-
>
|
|
340
|
-
<PaperclipIcon className="h-3.5 w-3.5" />
|
|
341
|
-
</Button>
|
|
342
|
-
</Tooltip>
|
|
343
|
-
<Input
|
|
344
|
-
ref={fileInputRef}
|
|
345
|
-
type="file"
|
|
346
|
-
multiple={true}
|
|
347
|
-
hidden={true}
|
|
348
|
-
onChange={(event) => {
|
|
349
|
-
if (event.target.files) {
|
|
350
|
-
onAddFiles([...event.target.files]);
|
|
351
|
-
}
|
|
352
|
-
}}
|
|
353
|
-
accept={SUPPORTED_ATTACHMENT_TYPES.join(",")}
|
|
354
|
-
/>
|
|
355
|
-
</>
|
|
316
|
+
<AttachFileButton
|
|
317
|
+
fileInputRef={fileInputRef}
|
|
318
|
+
isLoading={isLoading}
|
|
319
|
+
onAddFiles={onAddFiles}
|
|
320
|
+
/>
|
|
356
321
|
)}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
onClick={isLoading ? onStop : onSendClick}
|
|
364
|
-
disabled={isLoading ? false : isEmpty}
|
|
365
|
-
>
|
|
366
|
-
{isLoading ? (
|
|
367
|
-
<SquareIcon className="h-3 w-3 fill-current" />
|
|
368
|
-
) : (
|
|
369
|
-
<SendIcon className="h-3 w-3" />
|
|
370
|
-
)}
|
|
371
|
-
</Button>
|
|
372
|
-
</Tooltip>
|
|
322
|
+
<SendButton
|
|
323
|
+
isLoading={isLoading}
|
|
324
|
+
onStop={onStop}
|
|
325
|
+
onSendClick={onSendClick}
|
|
326
|
+
isEmpty={isEmpty}
|
|
327
|
+
/>
|
|
373
328
|
</div>
|
|
374
329
|
</div>
|
|
375
330
|
</TooltipProvider>
|
|
@@ -473,7 +428,7 @@ const ChatPanelBody = () => {
|
|
|
473
428
|
const [activeChat, setActiveChat] = useAtom(activeChatAtom);
|
|
474
429
|
const [input, setInput] = useState("");
|
|
475
430
|
const [newThreadInput, setNewThreadInput] = useState("");
|
|
476
|
-
const
|
|
431
|
+
const { files, addFiles, clearFiles, removeFile } = useFileState();
|
|
477
432
|
const newThreadInputRef = useRef<ReactCodeMirrorRef>(null);
|
|
478
433
|
const newMessageInputRef = useRef<ReactCodeMirrorRef>(null);
|
|
479
434
|
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
|
@@ -562,33 +517,6 @@ const ChatPanelBody = () => {
|
|
|
562
517
|
},
|
|
563
518
|
});
|
|
564
519
|
|
|
565
|
-
const onAddFiles = useEvent((files: File[]) => {
|
|
566
|
-
if (files.length === 0) {
|
|
567
|
-
return;
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
let fileSize = 0;
|
|
571
|
-
for (const file of files) {
|
|
572
|
-
fileSize += file.size;
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
if (fileSize > MAX_ATTACHMENT_SIZE) {
|
|
576
|
-
toast({
|
|
577
|
-
title: "File size exceeds 50MB limit",
|
|
578
|
-
description: "Please remove some files and try again.",
|
|
579
|
-
});
|
|
580
|
-
return;
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
setFiles((prev) => [...(prev ?? []), ...files]);
|
|
584
|
-
});
|
|
585
|
-
|
|
586
|
-
const removeFile = useEvent((file: File) => {
|
|
587
|
-
if (files) {
|
|
588
|
-
setFiles(files.filter((f) => f !== file));
|
|
589
|
-
}
|
|
590
|
-
});
|
|
591
|
-
|
|
592
520
|
const isLoading = status === "submitted" || status === "streaming";
|
|
593
521
|
|
|
594
522
|
// Check if we're currently streaming reasoning in the latest message
|
|
@@ -648,7 +576,7 @@ const ChatPanelBody = () => {
|
|
|
648
576
|
...(fileParts ?? []),
|
|
649
577
|
],
|
|
650
578
|
});
|
|
651
|
-
|
|
579
|
+
clearFiles();
|
|
652
580
|
setInput("");
|
|
653
581
|
};
|
|
654
582
|
|
|
@@ -656,7 +584,7 @@ const ChatPanelBody = () => {
|
|
|
656
584
|
setActiveChat(null);
|
|
657
585
|
setInput("");
|
|
658
586
|
setNewThreadInput("");
|
|
659
|
-
|
|
587
|
+
clearFiles();
|
|
660
588
|
});
|
|
661
589
|
|
|
662
590
|
const handleMessageEdit = useEvent((index: number, newValue: string) => {
|
|
@@ -687,7 +615,7 @@ const ChatPanelBody = () => {
|
|
|
687
615
|
files: fileParts,
|
|
688
616
|
});
|
|
689
617
|
setInput("");
|
|
690
|
-
|
|
618
|
+
clearFiles();
|
|
691
619
|
},
|
|
692
620
|
);
|
|
693
621
|
|
|
@@ -720,7 +648,7 @@ const ChatPanelBody = () => {
|
|
|
720
648
|
isLoading={isLoading}
|
|
721
649
|
onStop={stop}
|
|
722
650
|
fileInputRef={fileInputRef}
|
|
723
|
-
onAddFiles={
|
|
651
|
+
onAddFiles={addFiles}
|
|
724
652
|
onClose={handleOnCloseThread}
|
|
725
653
|
/>
|
|
726
654
|
) : (
|
|
@@ -733,7 +661,7 @@ const ChatPanelBody = () => {
|
|
|
733
661
|
onStop={stop}
|
|
734
662
|
onClose={() => newMessageInputRef.current?.editor?.blur()}
|
|
735
663
|
fileInputRef={fileInputRef}
|
|
736
|
-
onAddFiles={
|
|
664
|
+
onAddFiles={addFiles}
|
|
737
665
|
/>
|
|
738
666
|
);
|
|
739
667
|
|
|
@@ -794,7 +722,7 @@ const ChatPanelBody = () => {
|
|
|
794
722
|
|
|
795
723
|
{error && (
|
|
796
724
|
<div className="flex items-center justify-center space-x-2 mb-4">
|
|
797
|
-
<ErrorBanner error={error} />
|
|
725
|
+
<ErrorBanner error={error || new Error("Unknown error")} />
|
|
798
726
|
<Button variant="outline" size="sm" onClick={handleReload}>
|
|
799
727
|
Retry
|
|
800
728
|
</Button>
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import type { components } from "@marimo-team/marimo-api";
|
|
4
4
|
import type { FileUIPart, ToolUIPart, UIMessage } from "ai";
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
import useEvent from "react-use-event-hook";
|
|
7
|
+
import type { ProviderId } from "@/core/ai/ids/ids";
|
|
5
8
|
import type { ToolNotebookContext } from "@/core/ai/tools/base";
|
|
6
9
|
import { FRONTEND_TOOL_REGISTRY } from "@/core/ai/tools/registry";
|
|
7
10
|
import type {
|
|
@@ -11,6 +14,17 @@ import type {
|
|
|
11
14
|
import { blobToString } from "@/utils/fileToBase64";
|
|
12
15
|
import { Logger } from "@/utils/Logger";
|
|
13
16
|
import { getAICompletionBodyWithAttachments } from "../editor/ai/completion-utils";
|
|
17
|
+
import { toast } from "../ui/use-toast";
|
|
18
|
+
|
|
19
|
+
// We need to modify the backend to support attachments for other providers
|
|
20
|
+
// And other types
|
|
21
|
+
export const PROVIDERS_THAT_SUPPORT_ATTACHMENTS = new Set<ProviderId>([
|
|
22
|
+
"openai",
|
|
23
|
+
"google",
|
|
24
|
+
"anthropic",
|
|
25
|
+
]);
|
|
26
|
+
export const SUPPORTED_ATTACHMENT_TYPES = ["image/*", "text/*"];
|
|
27
|
+
const MAX_ATTACHMENT_SIZE = 1024 * 1024 * 50; // 50MB
|
|
14
28
|
|
|
15
29
|
export function generateChatTitle(message: string): string {
|
|
16
30
|
return message.length > 50 ? `${message.slice(0, 50)}...` : message;
|
|
@@ -198,3 +212,31 @@ export function hasPendingToolCalls(messages: UIMessage[]): boolean {
|
|
|
198
212
|
// Only auto-send if we have completed tool calls and there is no reply yet
|
|
199
213
|
return allToolCallsCompleted && !hasTextContent;
|
|
200
214
|
}
|
|
215
|
+
|
|
216
|
+
export function useFileState() {
|
|
217
|
+
const [files, setFiles] = useState<File[]>([]);
|
|
218
|
+
|
|
219
|
+
const addFiles = useEvent((newFiles: File[]) => {
|
|
220
|
+
if (newFiles.length === 0) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const totalSize = newFiles.reduce((size, file) => size + file.size, 0);
|
|
225
|
+
if (totalSize > MAX_ATTACHMENT_SIZE) {
|
|
226
|
+
toast({
|
|
227
|
+
title: "File size exceeded",
|
|
228
|
+
description: "Attachments must be under 50 MB",
|
|
229
|
+
variant: "danger",
|
|
230
|
+
});
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
setFiles((prev) => [...prev, ...newFiles]);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
const clearFiles = () => setFiles([]);
|
|
238
|
+
const removeFile = (fileToRemove: File) =>
|
|
239
|
+
setFiles((prev) => prev.filter((f) => f !== fileToRemove));
|
|
240
|
+
|
|
241
|
+
return { files, addFiles, clearFiles, removeFile };
|
|
242
|
+
}
|
|
@@ -21,8 +21,6 @@ import { atomWithStorage } from "jotai/utils";
|
|
|
21
21
|
import {
|
|
22
22
|
ChevronsUpDown,
|
|
23
23
|
DatabaseIcon,
|
|
24
|
-
Loader2Icon,
|
|
25
|
-
SendHorizontal,
|
|
26
24
|
SparklesIcon,
|
|
27
25
|
XIcon,
|
|
28
26
|
} from "lucide-react";
|
|
@@ -30,9 +28,18 @@ import { useMemo, useRef, useState } from "react";
|
|
|
30
28
|
import useEvent from "react-use-event-hook";
|
|
31
29
|
import { z } from "zod";
|
|
32
30
|
import { AIModelDropdown } from "@/components/ai/ai-model-dropdown";
|
|
31
|
+
import {
|
|
32
|
+
AddContextButton,
|
|
33
|
+
AttachFileButton,
|
|
34
|
+
FileAttachmentPill,
|
|
35
|
+
SendButton,
|
|
36
|
+
} from "@/components/chat/chat-components";
|
|
33
37
|
import {
|
|
34
38
|
buildCompletionRequestBody,
|
|
39
|
+
convertToFileUIPart,
|
|
35
40
|
handleToolCall,
|
|
41
|
+
PROVIDERS_THAT_SUPPORT_ATTACHMENTS,
|
|
42
|
+
useFileState,
|
|
36
43
|
} from "@/components/chat/chat-utils";
|
|
37
44
|
import { Button } from "@/components/ui/button";
|
|
38
45
|
import {
|
|
@@ -43,10 +50,13 @@ import {
|
|
|
43
50
|
DropdownMenuTrigger,
|
|
44
51
|
} from "@/components/ui/dropdown-menu";
|
|
45
52
|
import { toast } from "@/components/ui/use-toast";
|
|
53
|
+
import { AiModelId } from "@/core/ai/ids/ids";
|
|
46
54
|
import { stagedAICellsAtom, useStagedCells } from "@/core/ai/staged-cells";
|
|
47
55
|
import type { ToolNotebookContext } from "@/core/ai/tools/base";
|
|
48
56
|
import { useCellActions } from "@/core/cells/cells";
|
|
49
57
|
import { resourceExtension } from "@/core/codemirror/ai/resources";
|
|
58
|
+
import { aiAtom } from "@/core/config/config";
|
|
59
|
+
import { DEFAULT_AI_MODEL } from "@/core/config/config-schema";
|
|
50
60
|
import { useRequestClient } from "@/core/network/requests";
|
|
51
61
|
import type { AiCompletionRequest } from "@/core/network/types";
|
|
52
62
|
import { useRuntimeManager } from "@/core/runtime/config";
|
|
@@ -57,7 +67,11 @@ import { jotaiJsonStorage } from "@/utils/storage/jotai";
|
|
|
57
67
|
import { ZodLocalStorage } from "@/utils/storage/typed";
|
|
58
68
|
import { PythonIcon } from "../cell/code/icons";
|
|
59
69
|
import { createAiCompletionOnKeydown } from "./completion-handlers";
|
|
60
|
-
import {
|
|
70
|
+
import {
|
|
71
|
+
addContextCompletion,
|
|
72
|
+
CONTEXT_TRIGGER,
|
|
73
|
+
mentionsCompletionSource,
|
|
74
|
+
} from "./completion-utils";
|
|
61
75
|
import { StreamingChunkTransport } from "./transport/chat-transport";
|
|
62
76
|
|
|
63
77
|
// Persist across sessions
|
|
@@ -89,6 +103,10 @@ export const AddCellWithAI: React.FC<{
|
|
|
89
103
|
const stagedAICells = useAtomValue(stagedAICellsAtom);
|
|
90
104
|
const inputRef = useRef<ReactCodeMirrorRef>(null);
|
|
91
105
|
|
|
106
|
+
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
107
|
+
const { files, addFiles, removeFile } = useFileState();
|
|
108
|
+
const aiConfig = useAtomValue(aiAtom);
|
|
109
|
+
|
|
92
110
|
const { createNewCell, prepareForRun } = useCellActions();
|
|
93
111
|
const toolContext: ToolNotebookContext = {
|
|
94
112
|
store,
|
|
@@ -149,14 +167,21 @@ export const AddCellWithAI: React.FC<{
|
|
|
149
167
|
const isLoading = status === "streaming" || status === "submitted";
|
|
150
168
|
const hasCompletion = stagedAICells.size > 0;
|
|
151
169
|
|
|
152
|
-
const
|
|
170
|
+
const currentModel = aiConfig?.models?.edit_model || DEFAULT_AI_MODEL;
|
|
171
|
+
const currentProvider = AiModelId.parse(currentModel).providerId;
|
|
172
|
+
const isAttachmentSupported =
|
|
173
|
+
PROVIDERS_THAT_SUPPORT_ATTACHMENTS.has(currentProvider);
|
|
174
|
+
|
|
175
|
+
const submit = async () => {
|
|
153
176
|
if (!isLoading) {
|
|
154
177
|
if (inputRef.current?.view) {
|
|
155
178
|
storePrompt(inputRef.current.view);
|
|
156
179
|
}
|
|
157
180
|
// TODO: When we have conversations, don't delete existing cells
|
|
158
181
|
deleteAllStagedCells();
|
|
159
|
-
|
|
182
|
+
|
|
183
|
+
const fileParts = files ? await convertToFileUIPart(files) : undefined;
|
|
184
|
+
sendMessage({ text: input, files: fileParts });
|
|
160
185
|
}
|
|
161
186
|
};
|
|
162
187
|
|
|
@@ -176,19 +201,17 @@ export const AddCellWithAI: React.FC<{
|
|
|
176
201
|
|
|
177
202
|
const languageDropdown = (
|
|
178
203
|
<DropdownMenu modal={false}>
|
|
179
|
-
<DropdownMenuTrigger
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
>
|
|
186
|
-
{language === "python" ? pythonIcon : sqlIcon}
|
|
187
|
-
<ChevronsUpDown className="ml-1 h-3.5 w-3.5 text-muted-foreground/70" />
|
|
188
|
-
</Button>
|
|
204
|
+
<DropdownMenuTrigger
|
|
205
|
+
className="flex items-center justify-between h-7 text-xs px-2 py-0.5 border rounded-md hover:text-accent-foreground"
|
|
206
|
+
data-testid="language-button"
|
|
207
|
+
>
|
|
208
|
+
{language === "python" ? pythonIcon : sqlIcon}
|
|
209
|
+
<ChevronsUpDown className="ml-1 h-3.5 w-3.5 text-muted-foreground/70" />
|
|
189
210
|
</DropdownMenuTrigger>
|
|
190
211
|
<DropdownMenuContent align="center">
|
|
191
|
-
<div className="px-2 py-1
|
|
212
|
+
<div className="px-2 py-1 text-sm text-muted-foreground">
|
|
213
|
+
Select language
|
|
214
|
+
</div>
|
|
192
215
|
<DropdownMenuSeparator />
|
|
193
216
|
<DropdownMenuItem onClick={() => setLanguage("python")}>
|
|
194
217
|
{pythonIcon}
|
|
@@ -230,54 +253,63 @@ export const AddCellWithAI: React.FC<{
|
|
|
230
253
|
hasCompletion,
|
|
231
254
|
})}
|
|
232
255
|
/>
|
|
233
|
-
{isLoading && (
|
|
234
|
-
<Button
|
|
235
|
-
data-testid="stop-completion-button"
|
|
236
|
-
variant="text"
|
|
237
|
-
size="sm"
|
|
238
|
-
className="mb-0"
|
|
239
|
-
onClick={stop}
|
|
240
|
-
>
|
|
241
|
-
<Loader2Icon className="animate-spin mr-1" size={14} />
|
|
242
|
-
Stop
|
|
243
|
-
</Button>
|
|
244
|
-
)}
|
|
245
|
-
<Button variant="text" size="sm" onClick={submit} title="Submit">
|
|
246
|
-
<SendHorizontal className="size-4" />
|
|
247
|
-
</Button>
|
|
248
256
|
<Button variant="text" size="sm" className="mb-0 px-1" onClick={onClose}>
|
|
249
257
|
<XIcon className="size-4" />
|
|
250
258
|
</Button>
|
|
251
259
|
</div>
|
|
252
260
|
);
|
|
253
261
|
|
|
254
|
-
|
|
255
|
-
<div className=
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
262
|
+
const footerComponent = (
|
|
263
|
+
<div className="px-3 pt-1 flex flex-row items-center justify-between">
|
|
264
|
+
<div className="flex items-center gap-2">
|
|
265
|
+
<AIModelDropdown
|
|
266
|
+
triggerClassName="h-7 text-xs max-w-64"
|
|
267
|
+
iconSize="small"
|
|
268
|
+
forRole="edit"
|
|
269
|
+
showAddCustomModelDocs={true}
|
|
270
|
+
/>
|
|
271
|
+
{languageDropdown}
|
|
272
|
+
</div>
|
|
273
|
+
<div className="flex flex-row items-center">
|
|
274
|
+
{files.length > 0 && (
|
|
275
|
+
<div className="flex flex-row gap-1 flex-wrap pr-1">
|
|
276
|
+
{files?.map((file, index) => (
|
|
277
|
+
<FileAttachmentPill
|
|
278
|
+
file={file}
|
|
279
|
+
key={`${file.name}-${index}`}
|
|
280
|
+
onRemove={() => removeFile(file)}
|
|
281
|
+
/>
|
|
282
|
+
))}
|
|
283
|
+
</div>
|
|
268
284
|
)}
|
|
269
|
-
<
|
|
270
|
-
{
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
285
|
+
<AddContextButton
|
|
286
|
+
handleAddContext={() => addContextCompletion(inputRef)}
|
|
287
|
+
isLoading={isLoading}
|
|
288
|
+
/>
|
|
289
|
+
{isAttachmentSupported && (
|
|
290
|
+
<AttachFileButton
|
|
291
|
+
fileInputRef={fileInputRef}
|
|
292
|
+
isLoading={isLoading}
|
|
293
|
+
onAddFiles={addFiles}
|
|
276
294
|
/>
|
|
277
|
-
|
|
295
|
+
)}
|
|
296
|
+
<SendButton
|
|
297
|
+
isLoading={isLoading}
|
|
298
|
+
onStop={stop}
|
|
299
|
+
onSendClick={submit}
|
|
300
|
+
isEmpty={!input.trim()}
|
|
301
|
+
showStopLabel={true}
|
|
302
|
+
/>
|
|
278
303
|
</div>
|
|
279
304
|
</div>
|
|
280
305
|
);
|
|
306
|
+
|
|
307
|
+
return (
|
|
308
|
+
<div className={cn("flex flex-col w-full py-2")}>
|
|
309
|
+
{inputComponent}
|
|
310
|
+
{footerComponent}
|
|
311
|
+
</div>
|
|
312
|
+
);
|
|
281
313
|
};
|
|
282
314
|
|
|
283
315
|
export interface AdditionalCompletions {
|
|
@@ -3,10 +3,8 @@
|
|
|
3
3
|
import { useCompletion } from "@ai-sdk/react";
|
|
4
4
|
import { EditorView } from "@codemirror/view";
|
|
5
5
|
import {
|
|
6
|
-
AtSignIcon,
|
|
7
6
|
CircleCheckIcon,
|
|
8
7
|
Loader2Icon,
|
|
9
|
-
SendIcon,
|
|
10
8
|
SparklesIcon,
|
|
11
9
|
XIcon,
|
|
12
10
|
} from "lucide-react";
|
|
@@ -20,6 +18,10 @@ import { storePrompt } from "@marimo-team/codemirror-ai";
|
|
|
20
18
|
import type { ReactCodeMirrorRef } from "@uiw/react-codemirror";
|
|
21
19
|
import { useAtom, useAtomValue } from "jotai";
|
|
22
20
|
import { AIModelDropdown } from "@/components/ai/ai-model-dropdown";
|
|
21
|
+
import {
|
|
22
|
+
AddContextButton,
|
|
23
|
+
SendButton,
|
|
24
|
+
} from "@/components/chat/chat-components";
|
|
23
25
|
import { Checkbox } from "@/components/ui/checkbox";
|
|
24
26
|
import { Label } from "@/components/ui/label";
|
|
25
27
|
import { Switch } from "@/components/ui/switch";
|
|
@@ -253,39 +255,6 @@ export const AiCompletionEditor: React.FC<Props> = ({
|
|
|
253
255
|
}
|
|
254
256
|
};
|
|
255
257
|
|
|
256
|
-
const loadingStopButton = (
|
|
257
|
-
<Button
|
|
258
|
-
data-testid="stop-completion-button"
|
|
259
|
-
variant="text"
|
|
260
|
-
size="xs"
|
|
261
|
-
className="mb-0"
|
|
262
|
-
onClick={stop}
|
|
263
|
-
>
|
|
264
|
-
<Loader2Icon className="animate-spin mr-1" size={14} />
|
|
265
|
-
Stop
|
|
266
|
-
</Button>
|
|
267
|
-
);
|
|
268
|
-
|
|
269
|
-
const submitButton = (
|
|
270
|
-
<Tooltip content="Submit">
|
|
271
|
-
<Button variant="text" size="icon" onClick={handleSubmit}>
|
|
272
|
-
<SendIcon className="h-3 w-3" />
|
|
273
|
-
</Button>
|
|
274
|
-
</Tooltip>
|
|
275
|
-
);
|
|
276
|
-
|
|
277
|
-
const contextButton = (
|
|
278
|
-
<Tooltip content="Add context">
|
|
279
|
-
<Button
|
|
280
|
-
variant="text"
|
|
281
|
-
size="icon"
|
|
282
|
-
onClick={() => addContextCompletion(inputRef)}
|
|
283
|
-
>
|
|
284
|
-
<AtSignIcon className="h-3 w-3" />
|
|
285
|
-
</Button>
|
|
286
|
-
</Tooltip>
|
|
287
|
-
);
|
|
288
|
-
|
|
289
258
|
const completionButtons = (
|
|
290
259
|
<>
|
|
291
260
|
<AcceptCompletionButton
|
|
@@ -355,9 +324,17 @@ export const AiCompletionEditor: React.FC<Props> = ({
|
|
|
355
324
|
|
|
356
325
|
<div className="-mr-1.5 py-1.5">
|
|
357
326
|
<div className="flex flex-row items-center justify-end gap-0.5">
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
327
|
+
<SendButton
|
|
328
|
+
isLoading={isLoading}
|
|
329
|
+
onStop={stop}
|
|
330
|
+
onSendClick={handleSubmit}
|
|
331
|
+
isEmpty={!input.trim()}
|
|
332
|
+
showStopLabel={true}
|
|
333
|
+
/>
|
|
334
|
+
<AddContextButton
|
|
335
|
+
handleAddContext={() => addContextCompletion(inputRef)}
|
|
336
|
+
isLoading={isLoading}
|
|
337
|
+
/>
|
|
361
338
|
<AIModelDropdown
|
|
362
339
|
triggerClassName="h-7 text-xs"
|
|
363
340
|
iconSize="small"
|
|
@@ -349,8 +349,9 @@ const PackagesList: React.FC<{
|
|
|
349
349
|
|
|
350
350
|
const UpgradeButton: React.FC<{
|
|
351
351
|
packageName: string;
|
|
352
|
+
tags?: { kind: string; value: string }[];
|
|
352
353
|
onSuccess: () => void;
|
|
353
|
-
}> = ({ packageName, onSuccess }) => {
|
|
354
|
+
}> = ({ packageName, tags, onSuccess }) => {
|
|
354
355
|
const [loading, setLoading] = React.useState(false);
|
|
355
356
|
const { addPackage } = useRequestClient();
|
|
356
357
|
|
|
@@ -362,9 +363,11 @@ const UpgradeButton: React.FC<{
|
|
|
362
363
|
const handleUpgradePackage = async () => {
|
|
363
364
|
try {
|
|
364
365
|
setLoading(true);
|
|
366
|
+
const group = tags?.find((tag) => tag.kind === "group")?.value;
|
|
365
367
|
const response = await addPackage({
|
|
366
368
|
package: packageName,
|
|
367
369
|
upgrade: true,
|
|
370
|
+
group,
|
|
368
371
|
});
|
|
369
372
|
if (response.success) {
|
|
370
373
|
onSuccess();
|
|
@@ -395,12 +398,10 @@ const RemoveButton: React.FC<{
|
|
|
395
398
|
const handleRemovePackage = async () => {
|
|
396
399
|
try {
|
|
397
400
|
setLoading(true);
|
|
398
|
-
const
|
|
399
|
-
(tag) => tag.kind === "group" && tag.value === "dev",
|
|
400
|
-
);
|
|
401
|
+
const group = tags?.find((tag) => tag.kind === "group")?.value;
|
|
401
402
|
const response = await removePackage({
|
|
402
403
|
package: packageName,
|
|
403
|
-
|
|
404
|
+
group,
|
|
404
405
|
});
|
|
405
406
|
if (response.success) {
|
|
406
407
|
onSuccess();
|
|
@@ -604,12 +605,14 @@ const DependencyTreeNode: React.FC<{
|
|
|
604
605
|
{/* Actions for top-level packages */}
|
|
605
606
|
{isTopLevel && (
|
|
606
607
|
<div className="flex gap-1 invisible group-hover:visible">
|
|
607
|
-
<UpgradeButton
|
|
608
|
+
<UpgradeButton
|
|
609
|
+
packageName={node.name}
|
|
610
|
+
tags={node.tags}
|
|
611
|
+
onSuccess={onSuccess}
|
|
612
|
+
/>
|
|
613
|
+
|
|
608
614
|
<RemoveButton
|
|
609
615
|
packageName={node.name}
|
|
610
|
-
// FIXME: Backend types are wrong/outdated.
|
|
611
|
-
// tags actually have the shape: Array<{ kind: string; value: string }>
|
|
612
|
-
// @ts-expect-error — backend tag types do not match frontend expectations yet
|
|
613
616
|
tags={node.tags}
|
|
614
617
|
onSuccess={onSuccess}
|
|
615
618
|
/>
|