@marimo-team/islands 0.23.12-dev2 → 0.23.12-dev21
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-WqG-xX4l.js → ConnectedDataExplorerComponent-Du3_nUzI.js} +13 -13
- package/dist/{ErrorBoundary-BNx_OSVo.js → ErrorBoundary-DE6tzZf-.js} +2 -2
- package/dist/{any-language-editor-rPSlOll9.js → any-language-editor-DN1R-1KZ.js} +5 -5
- package/dist/{button-vQhauTmO.js → button-BacYv-bE.js} +7 -1
- package/dist/{capabilities-BEHzIS99.js → capabilities-D_4LYhSU.js} +1 -1
- package/dist/{chat-ui-k2kqhCv5.js → chat-ui-CsPewo4h.js} +16 -16
- package/dist/{check-nrzHDi45.js → check-C9OoNtR4.js} +1 -1
- package/dist/{code-visibility-DZ_6U5hT.js → code-visibility-DBnAQPtB.js} +664 -663
- package/dist/{copy-UhDed7D4.js → copy-COam1EG7.js} +2 -2
- package/dist/{dist-DYGLrbYQ.js → dist--2Bqjvs0.js} +2 -2
- package/dist/{error-banner-BHAkVFc2.js → error-banner-DFPfz_Qf.js} +2 -2
- package/dist/{esm-Bqu9AE2K.js → esm-M837UxV5.js} +1 -1
- package/dist/{extends-9Yl5BEcg.js → extends-9MVIxxRo.js} +4 -4
- package/dist/{formats-BV4bOfMI.js → formats-d6MhLuQ9.js} +4 -4
- package/dist/{glide-data-editor-BDTq6YUb.js → glide-data-editor-DkzAInWG.js} +9 -9
- package/dist/{html-to-image-C86pQALH.js → html-to-image-DXwLcQ6l.js} +95 -88
- package/dist/{input-AKkGXdyV.js → input-CbEz_aj_.js} +6 -6
- package/dist/{label-E3ZJXHu8.js → label-WfTSU8L4.js} +2 -2
- package/dist/{loader-YPuQvn1Y.js → loader-Boph2xIS.js} +1 -1
- package/dist/main.js +1753 -1626
- package/dist/{mermaid-QFAR9YgY.js → mermaid-CJW9vIyO.js} +5 -5
- package/dist/{process-output-nNw4OpSj.js → process-output-C6_e1pT_.js} +3 -3
- package/dist/{reveal-component-BxDb5eK0.js → reveal-component-DIzKQ1NS.js} +11 -11
- package/dist/{spec-B45_YCNI.js → spec-Bv-XlYiv.js} +4 -4
- package/dist/{strings-Cq2s9_EQ.js → strings-Dq_j3Rxw.js} +4 -4
- package/dist/style.css +2 -2
- package/dist/{swiper-component-BNa_4kh2.js → swiper-component-5HoSsPi1.js} +2 -2
- package/dist/{toDate-Do1xRzAo.js → toDate-D-l5s8nn.js} +3 -3
- package/dist/{tooltip-Bz3OAwrU.js → tooltip-Czds6Qr8.js} +3 -3
- package/dist/{types-D8gEGs4R.js → types-C2Ir191_.js} +1 -1
- package/dist/{useAsyncData-CL3o2p4i.js → useAsyncData-1Dhzjfwf.js} +1 -1
- package/dist/{useDateFormatter-BC6iSz9g.js → useDateFormatter-CMnRuVmN.js} +2 -2
- package/dist/{useDeepCompareMemoize-BPx2MuOK.js → useDeepCompareMemoize-CDWT3BDz.js} +1 -1
- package/dist/{useIframeCapabilities-C6Ta3EyP.js → useIframeCapabilities-DWIYvDh7.js} +1 -1
- package/dist/{useLifecycle-C3Ec71q0.js → useLifecycle-AHlswLw-.js} +3 -3
- package/dist/{useTheme-ZhT6uIu3.js → useTheme-BrYvK-_A.js} +2 -2
- package/dist/{vega-component-C3AWYGAL.js → vega-component-Pk6lyc_a.js} +10 -10
- package/dist/{zod-DXqkaI_w.js → zod-CijjQh4u.js} +1 -1
- package/package.json +3 -3
- package/src/components/ai/display-helpers.tsx +5 -5
- package/src/components/app-config/ai-config.tsx +5 -5
- package/src/components/app-config/mcp-config.tsx +3 -3
- package/src/components/chat/acp/agent-panel.tsx +3 -3
- package/src/components/chat/acp/blocks.tsx +36 -38
- package/src/components/chat/acp/common.tsx +12 -16
- package/src/components/chat/acp/scroll-to-bottom-button.tsx +1 -1
- package/src/components/chat/acp/session-tabs.tsx +2 -2
- package/src/components/chat/chat-history-popover.tsx +1 -1
- package/src/components/chat/chat-panel.tsx +47 -23
- package/src/components/data-table/TableBottomBar.tsx +4 -1
- package/src/components/data-table/columns.tsx +2 -2
- package/src/components/data-table/data-table.tsx +26 -17
- package/src/components/data-table/filter-pill-editor.tsx +1 -1
- package/src/components/dependency-graph/minimap-content.tsx +1 -1
- package/src/components/editor/RecoveryButton.tsx +1 -1
- package/src/components/editor/actions/pair-with-agent-modal.tsx +2 -2
- package/src/components/editor/actions/useNotebookActions.tsx +4 -4
- package/src/components/editor/ai/__tests__/completion-utils.test.ts +138 -2
- package/src/components/editor/ai/ai-completion-editor.tsx +1 -1
- package/src/components/editor/ai/completion-utils.ts +124 -21
- package/src/components/editor/cell/CreateCellButton.tsx +1 -1
- package/src/components/editor/chrome/panels/empty-state.tsx +1 -1
- package/src/components/editor/chrome/panels/outline/floating-outline.tsx +1 -1
- package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +1 -1
- package/src/components/editor/columns/cell-column.tsx +1 -1
- package/src/components/editor/columns/sortable-column.tsx +2 -2
- package/src/components/editor/output/MarimoErrorOutput.tsx +1 -1
- package/src/components/editor/output/TextOutput.tsx +2 -2
- package/src/components/home/components.tsx +4 -4
- package/src/components/icons/github.tsx +21 -0
- package/src/components/icons/youtube.tsx +21 -0
- package/src/components/slides/minimap.tsx +2 -2
- package/src/components/slides/reveal-component.tsx +1 -1
- package/src/components/storage/components.tsx +3 -7
- package/src/components/ui/alert.tsx +1 -1
- package/src/components/ui/command.tsx +2 -2
- package/src/components/ui/reorderable-list.tsx +1 -1
- package/src/components/ui/table.tsx +2 -5
- package/src/core/codemirror/go-to-definition/__tests__/commands.test.ts +67 -0
- package/src/core/codemirror/go-to-definition/__tests__/utils.test.ts +47 -0
- package/src/core/codemirror/go-to-definition/commands.ts +47 -30
- package/src/core/codemirror/go-to-definition/utils.ts +0 -1
- package/src/core/codemirror/language/languages/sql/renderers.tsx +60 -68
- package/src/core/codemirror/reactive-references/__tests__/analyzer.test.ts +54 -0
- package/src/core/codemirror/reactive-references/analyzer.ts +44 -35
- package/src/core/hotkeys/hotkeys.ts +1 -0
- package/src/core/islands/__tests__/bridge.test.ts +25 -0
- package/src/core/islands/__tests__/parse.test.ts +585 -1
- package/src/core/islands/__tests__/test-utils.tsx +10 -1
- package/src/core/islands/bridge.ts +6 -1
- package/src/core/islands/constants.ts +2 -0
- package/src/core/islands/parse.ts +293 -13
- package/src/plugins/impl/DataTablePlugin.tsx +20 -1
- package/src/plugins/impl/FileBrowserPlugin.tsx +165 -74
- package/src/plugins/impl/MatrixPlugin.tsx +2 -2
- package/src/plugins/impl/TabsPlugin.tsx +1 -1
- package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +141 -1
- package/src/plugins/impl/__tests__/FileBrowserPlugin.test.tsx +314 -0
- package/src/plugins/impl/anywidget/AnyWidgetPlugin.tsx +4 -1
- package/src/plugins/impl/anywidget/__tests__/AnyWidgetPlugin.test.tsx +34 -0
- package/src/plugins/impl/anywidget/__tests__/model.test.ts +19 -0
- package/src/plugins/impl/anywidget/model.ts +15 -0
- package/src/plugins/impl/matplotlib/matplotlib-renderer.ts +1 -1
- package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +155 -98
- package/src/plugins/impl/mpl-interactive/__tests__/MplInteractivePlugin.test.tsx +154 -1
- package/src/plugins/impl/mpl-interactive/mpl-websocket-shim.ts +10 -0
|
@@ -67,6 +67,8 @@ import { PromptInput } from "../editor/ai/add-cell-with-ai";
|
|
|
67
67
|
import {
|
|
68
68
|
addContextCompletion,
|
|
69
69
|
CONTEXT_TRIGGER,
|
|
70
|
+
isContextAttachment,
|
|
71
|
+
resolveChatContext,
|
|
70
72
|
} from "../editor/ai/completion-utils";
|
|
71
73
|
import { PanelEmptyState } from "../editor/chrome/panels/empty-state";
|
|
72
74
|
import { CopyClipboardIcon } from "../icons/copy-icon";
|
|
@@ -83,7 +85,6 @@ import {
|
|
|
83
85
|
import { renderUIMessage } from "./chat-display";
|
|
84
86
|
import { ChatHistoryPopover } from "./chat-history-popover";
|
|
85
87
|
import {
|
|
86
|
-
buildCompletionRequestBody,
|
|
87
88
|
convertToFileUIPart,
|
|
88
89
|
generateChatTitle,
|
|
89
90
|
handleToolCall,
|
|
@@ -92,6 +93,7 @@ import {
|
|
|
92
93
|
PROVIDERS_THAT_SUPPORT_ATTACHMENTS,
|
|
93
94
|
useFileState,
|
|
94
95
|
} from "./chat-utils";
|
|
96
|
+
import { getCodes } from "@/core/codemirror/copilot/getCodes";
|
|
95
97
|
|
|
96
98
|
// Default mode for the AI
|
|
97
99
|
const DEFAULT_MODE = "manual";
|
|
@@ -281,16 +283,13 @@ const ChatInputFooter: React.FC<ChatInputFooterProps> = memo(
|
|
|
281
283
|
subtitle: "AI with access to read and write tools",
|
|
282
284
|
Icon: HatGlasses,
|
|
283
285
|
},
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
if (import.meta.env.DEV) {
|
|
287
|
-
modeOptions.push({
|
|
286
|
+
{
|
|
288
287
|
value: "code_mode",
|
|
289
288
|
label: "Code Mode (experimental)",
|
|
290
289
|
subtitle: "AI with access to the notebook's kernel. Use with caution.",
|
|
291
290
|
Icon: CodeIcon,
|
|
292
|
-
}
|
|
293
|
-
|
|
291
|
+
},
|
|
292
|
+
];
|
|
294
293
|
|
|
295
294
|
const isAttachmentSupported =
|
|
296
295
|
PROVIDERS_THAT_SUPPORT_ATTACHMENTS.has(currentProvider);
|
|
@@ -538,9 +537,10 @@ const ChatPanelBody = () => {
|
|
|
538
537
|
);
|
|
539
538
|
}
|
|
540
539
|
|
|
541
|
-
const completionBody =
|
|
542
|
-
options.messages,
|
|
543
|
-
|
|
540
|
+
const completionBody = {
|
|
541
|
+
uiMessages: options.messages,
|
|
542
|
+
includeOtherCode: getCodes(""),
|
|
543
|
+
};
|
|
544
544
|
|
|
545
545
|
// Call this here to ensure the value is not stale
|
|
546
546
|
const chatMode = store.get(aiAtom)?.mode || DEFAULT_MODE;
|
|
@@ -629,6 +629,8 @@ const ChatPanelBody = () => {
|
|
|
629
629
|
initialAttachments && initialAttachments.length > 0
|
|
630
630
|
? await convertToFileUIPart(initialAttachments)
|
|
631
631
|
: undefined;
|
|
632
|
+
const { contextPart, attachments } =
|
|
633
|
+
await resolveChatContext(initialMessage);
|
|
632
634
|
|
|
633
635
|
// Trigger AI conversation with append
|
|
634
636
|
sendMessage({
|
|
@@ -638,7 +640,9 @@ const ChatPanelBody = () => {
|
|
|
638
640
|
type: "text" as const,
|
|
639
641
|
text: initialMessage,
|
|
640
642
|
},
|
|
643
|
+
...(contextPart ? [contextPart] : []),
|
|
641
644
|
...(fileParts ?? []),
|
|
645
|
+
...attachments,
|
|
642
646
|
],
|
|
643
647
|
});
|
|
644
648
|
clearFiles();
|
|
@@ -656,17 +660,31 @@ const ChatPanelBody = () => {
|
|
|
656
660
|
openModal(<PairWithAgentModal onClose={closeModal} />);
|
|
657
661
|
});
|
|
658
662
|
|
|
659
|
-
const handleMessageEdit = useEvent(
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
663
|
+
const handleMessageEdit = useEvent(
|
|
664
|
+
async (index: number, newValue: string) => {
|
|
665
|
+
const editedMessage = messages[index];
|
|
666
|
+
// Keep the user's own uploaded files, but drop the previous @-context
|
|
667
|
+
// snapshot (data part + its attachments) so we can re-resolve a fresh,
|
|
668
|
+
// point-in-time snapshot from the edited text below.
|
|
669
|
+
const userFileParts =
|
|
670
|
+
editedMessage.parts?.filter(
|
|
671
|
+
(p) => p.type === "file" && !isContextAttachment(p),
|
|
672
|
+
) ?? [];
|
|
673
|
+
const { contextPart, attachments } = await resolveChatContext(newValue);
|
|
674
|
+
|
|
675
|
+
const messageId = editedMessage.id;
|
|
676
|
+
sendMessage({
|
|
677
|
+
messageId: messageId, // replace the message
|
|
678
|
+
role: "user",
|
|
679
|
+
parts: [
|
|
680
|
+
{ type: "text", text: newValue },
|
|
681
|
+
...(contextPart ? [contextPart] : []),
|
|
682
|
+
...userFileParts,
|
|
683
|
+
...attachments,
|
|
684
|
+
],
|
|
685
|
+
});
|
|
686
|
+
},
|
|
687
|
+
);
|
|
670
688
|
|
|
671
689
|
const handleChatInputSubmit = useEvent(
|
|
672
690
|
async (e: KeyboardEvent | undefined, newValue: string): Promise<void> => {
|
|
@@ -677,11 +695,17 @@ const ChatPanelBody = () => {
|
|
|
677
695
|
storePrompt(newMessageInputRef.current.view);
|
|
678
696
|
}
|
|
679
697
|
const fileParts = files ? await convertToFileUIPart(files) : undefined;
|
|
698
|
+
const { contextPart, attachments } = await resolveChatContext(newValue);
|
|
680
699
|
|
|
681
700
|
e?.preventDefault();
|
|
682
701
|
sendMessage({
|
|
683
|
-
|
|
684
|
-
|
|
702
|
+
role: "user",
|
|
703
|
+
parts: [
|
|
704
|
+
{ type: "text", text: newValue },
|
|
705
|
+
...(contextPart ? [contextPart] : []),
|
|
706
|
+
...(fileParts ?? []),
|
|
707
|
+
...attachments,
|
|
708
|
+
],
|
|
685
709
|
});
|
|
686
710
|
setInput("");
|
|
687
711
|
clearFiles();
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import type { RowSelectionState, Table } from "@tanstack/react-table";
|
|
5
5
|
import { useLocale } from "react-aria";
|
|
6
|
+
import { isStaticNotebook } from "@/core/static/static-state";
|
|
6
7
|
import type { GetRowIds } from "@/plugins/impl/DataTablePlugin";
|
|
7
8
|
import { cn } from "@/utils/cn";
|
|
8
9
|
import { Events } from "@/utils/events";
|
|
@@ -40,6 +41,8 @@ export const TableBottomBar = <TData,>({
|
|
|
40
41
|
className,
|
|
41
42
|
}: TableBottomBarProps<TData>) => {
|
|
42
43
|
const { locale } = useLocale();
|
|
44
|
+
// Pagination fetches each page via a kernel RPC, absent in static exports.
|
|
45
|
+
const isStatic = isStaticNotebook();
|
|
43
46
|
const handleSelectAllRows = (value: boolean) => {
|
|
44
47
|
if (!onRowSelectionChange) {
|
|
45
48
|
return;
|
|
@@ -171,7 +174,7 @@ export const TableBottomBar = <TData,>({
|
|
|
171
174
|
<CellSelectionStats table={table} className="lg:hidden" />
|
|
172
175
|
</div>
|
|
173
176
|
<div className="ml-auto lg:ml-0 lg:justify-self-center flex items-center shrink-0">
|
|
174
|
-
{pagination && (
|
|
177
|
+
{pagination && !isStatic && (
|
|
175
178
|
<DataTablePagination
|
|
176
179
|
table={table}
|
|
177
180
|
tableLoading={tableLoading}
|
|
@@ -503,7 +503,7 @@ function getCellStyleClass({
|
|
|
503
503
|
isNumeric && "tabular-nums",
|
|
504
504
|
justify === "center" && "text-center",
|
|
505
505
|
justify === "right" && "text-right",
|
|
506
|
-
wrapped && `${COLUMN_WRAPPING_STYLES} break-
|
|
506
|
+
wrapped && `${COLUMN_WRAPPING_STYLES} wrap-break-word`,
|
|
507
507
|
);
|
|
508
508
|
}
|
|
509
509
|
|
|
@@ -651,7 +651,7 @@ export function renderCellValue<TData, TValue>({
|
|
|
651
651
|
selectCell={selectCell}
|
|
652
652
|
rawStringValue={stringValue}
|
|
653
653
|
edges={{ leading, trailing }}
|
|
654
|
-
contentClassName="max-h-64 overflow-auto whitespace-pre-wrap break-
|
|
654
|
+
contentClassName="max-h-64 overflow-auto whitespace-pre-wrap wrap-break-word text-sm w-96"
|
|
655
655
|
buttonText="X"
|
|
656
656
|
wrapped={isWrapped}
|
|
657
657
|
>
|
|
@@ -25,6 +25,7 @@ import { useLocale } from "react-aria";
|
|
|
25
25
|
|
|
26
26
|
import { Button } from "@/components/ui/button";
|
|
27
27
|
import { Table } from "@/components/ui/table";
|
|
28
|
+
import { isStaticNotebook } from "@/core/static/static-state";
|
|
28
29
|
import { Banner } from "@/plugins/impl/common/error-banner";
|
|
29
30
|
import type {
|
|
30
31
|
CalculateTopKRows,
|
|
@@ -182,6 +183,11 @@ const DataTableInternal = <TData,>({
|
|
|
182
183
|
onViewedRowChange,
|
|
183
184
|
renderTableExplorerPanel,
|
|
184
185
|
}: DataTableProps<TData>) => {
|
|
186
|
+
// The top bar's controls (search, filters, column explorer, chart builder)
|
|
187
|
+
// all require a live kernel, which static exports don't have.
|
|
188
|
+
const isStatic = isStaticNotebook();
|
|
189
|
+
const showTableTopBar = !isStatic;
|
|
190
|
+
|
|
185
191
|
const [showLoadingBar, setShowLoadingBar] = React.useState<boolean>(false);
|
|
186
192
|
const { locale } = useLocale();
|
|
187
193
|
|
|
@@ -267,11 +273,12 @@ const DataTableInternal = <TData,>({
|
|
|
267
273
|
}
|
|
268
274
|
: {}),
|
|
269
275
|
manualSorting: manualSorting,
|
|
276
|
+
enableSorting: !isStatic,
|
|
270
277
|
enableMultiSort: true,
|
|
271
278
|
getSortedRowModel: getSortedRowModel(),
|
|
272
279
|
// filtering
|
|
273
280
|
manualFiltering: true,
|
|
274
|
-
enableColumnFilters: showFilters,
|
|
281
|
+
enableColumnFilters: showFilters && !isStatic,
|
|
275
282
|
getFilteredRowModel: getFilteredRowModel(),
|
|
276
283
|
onColumnFiltersChange: onFiltersChange,
|
|
277
284
|
// selection
|
|
@@ -355,22 +362,24 @@ const DataTableInternal = <TData,>({
|
|
|
355
362
|
part="table-wrapper"
|
|
356
363
|
className={cn(className || "rounded-md border overflow-hidden")}
|
|
357
364
|
>
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
365
|
+
{showTableTopBar && (
|
|
366
|
+
<TableTopBar
|
|
367
|
+
table={table}
|
|
368
|
+
showSearch={showSearch}
|
|
369
|
+
searchQuery={searchQuery}
|
|
370
|
+
onSearchQueryChange={onSearchQueryChange}
|
|
371
|
+
reloading={reloading}
|
|
372
|
+
showChartBuilder={showChartBuilder}
|
|
373
|
+
isChartBuilderOpen={isChartBuilderOpen}
|
|
374
|
+
toggleDisplayHeader={toggleDisplayHeader}
|
|
375
|
+
showTableExplorer={showTableExplorer}
|
|
376
|
+
togglePanel={togglePanel}
|
|
377
|
+
isAnyPanelOpen={isAnyPanelOpen}
|
|
378
|
+
downloadAs={downloadAs}
|
|
379
|
+
sizeBytes={sizeBytes}
|
|
380
|
+
sizeBytesIsLoading={sizeBytesIsLoading}
|
|
381
|
+
/>
|
|
382
|
+
)}
|
|
374
383
|
{allUserColumnsHidden && (
|
|
375
384
|
<Banner className="mb-1 mx-2 rounded flex items-center justify-between">
|
|
376
385
|
<span>All columns are hidden.</span>
|
|
@@ -435,7 +435,7 @@ const ValueSlot = <TData, TValue>({
|
|
|
435
435
|
const v =
|
|
436
436
|
value.kind === "multi-values" ? value : { kind: "multi-values" as const };
|
|
437
437
|
return (
|
|
438
|
-
<div className="min-w-
|
|
438
|
+
<div className="min-w-56 w-fit max-w-[24rem]">
|
|
439
439
|
<FilterByValuesPicker
|
|
440
440
|
column={column}
|
|
441
441
|
calculateTopKRows={calculateTopKRows}
|
|
@@ -107,7 +107,7 @@ const MinimapCell: React.FC<MinimapCellProps> = (props) => {
|
|
|
107
107
|
<svg
|
|
108
108
|
className={cn(
|
|
109
109
|
"absolute overflow-visible top-[10.5px] left-[calc(var(--spacing-extra-small,8px)+31px)] pointer-events-none",
|
|
110
|
-
isSelected ? "z-
|
|
110
|
+
isSelected ? "z-1" : "z-0",
|
|
111
111
|
getTextColor({ cell, selectedCell }),
|
|
112
112
|
)}
|
|
113
113
|
width="1"
|
|
@@ -76,7 +76,7 @@ const RecoveryModal = (props: {
|
|
|
76
76
|
Download unsaved changes?
|
|
77
77
|
</DialogTitle>
|
|
78
78
|
<DialogDescription
|
|
79
|
-
className="markdown break-
|
|
79
|
+
className="markdown wrap-break-word"
|
|
80
80
|
style={{ wordBreak: "break-word" }}
|
|
81
81
|
>
|
|
82
82
|
<div className="prose dark:prose-invert">
|
|
@@ -234,7 +234,7 @@ const CommandBlock: React.FC<{
|
|
|
234
234
|
if (multiline) {
|
|
235
235
|
return (
|
|
236
236
|
<div className="relative rounded-md bg-muted">
|
|
237
|
-
<pre className="max-h-64 overflow-auto whitespace-pre-wrap break-
|
|
237
|
+
<pre className="max-h-64 overflow-auto whitespace-pre-wrap wrap-break-word px-3 py-2 pr-10 font-mono text-xs select-all">
|
|
238
238
|
{display ?? command}
|
|
239
239
|
</pre>
|
|
240
240
|
<Tooltip content="Copied!" open={copied}>
|
|
@@ -257,7 +257,7 @@ const CommandBlock: React.FC<{
|
|
|
257
257
|
|
|
258
258
|
return (
|
|
259
259
|
<div className="flex items-center gap-2 rounded-md bg-muted px-3 py-2 font-mono text-xs">
|
|
260
|
-
<code className="flex-1 select-all break-
|
|
260
|
+
<code className="flex-1 select-all wrap-break-word">
|
|
261
261
|
{display ?? command}
|
|
262
262
|
</code>
|
|
263
263
|
<Tooltip content="Copied!" open={copied}>
|
|
@@ -21,7 +21,6 @@ import {
|
|
|
21
21
|
Files,
|
|
22
22
|
FileTextIcon,
|
|
23
23
|
FolderDownIcon,
|
|
24
|
-
GithubIcon,
|
|
25
24
|
GlobeIcon,
|
|
26
25
|
HardDrive,
|
|
27
26
|
Home,
|
|
@@ -39,7 +38,6 @@ import {
|
|
|
39
38
|
SparklesIcon,
|
|
40
39
|
Undo2Icon,
|
|
41
40
|
XCircleIcon,
|
|
42
|
-
YoutubeIcon,
|
|
43
41
|
ZapIcon,
|
|
44
42
|
} from "lucide-react";
|
|
45
43
|
import {
|
|
@@ -47,7 +45,9 @@ import {
|
|
|
47
45
|
useOpenSettingsToTab,
|
|
48
46
|
} from "@/components/app-config/state";
|
|
49
47
|
import { MarkdownIcon } from "@/components/editor/cell/code/icons";
|
|
48
|
+
import { GitHubIcon } from "@/components/icons/github";
|
|
50
49
|
import { MarimoPlusIcon } from "@/components/icons/marimo-icons";
|
|
50
|
+
import { YouTubeIcon } from "@/components/icons/youtube";
|
|
51
51
|
import { useImperativeModal } from "@/components/modal/ImperativeModal";
|
|
52
52
|
import { renderShortcut } from "@/components/shortcuts/renderShortcut";
|
|
53
53
|
import { PairWithAgentModal } from "@/components/editor/actions/pair-with-agent-modal";
|
|
@@ -655,7 +655,7 @@ export function useNotebookActions() {
|
|
|
655
655
|
},
|
|
656
656
|
},
|
|
657
657
|
{
|
|
658
|
-
icon: <
|
|
658
|
+
icon: <GitHubIcon className="h-3.5 w-3.5" />,
|
|
659
659
|
label: "GitHub",
|
|
660
660
|
handle: () => {
|
|
661
661
|
window.open(Constants.githubPage, "_blank");
|
|
@@ -669,7 +669,7 @@ export function useNotebookActions() {
|
|
|
669
669
|
},
|
|
670
670
|
},
|
|
671
671
|
{
|
|
672
|
-
icon: <
|
|
672
|
+
icon: <YouTubeIcon className="h-3.5 w-3.5" />,
|
|
673
673
|
label: "YouTube",
|
|
674
674
|
handle: () => {
|
|
675
675
|
window.open(Constants.youtube, "_blank");
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
afterEach,
|
|
4
|
+
beforeEach,
|
|
5
|
+
describe,
|
|
6
|
+
expect,
|
|
7
|
+
it,
|
|
8
|
+
type Mock,
|
|
9
|
+
vi,
|
|
10
|
+
} from "vitest";
|
|
3
11
|
import { variableName } from "@/__tests__/branded";
|
|
12
|
+
import * as aiContext from "@/core/ai/context/context";
|
|
4
13
|
import { getCodes } from "@/core/codemirror/copilot/getCodes";
|
|
5
14
|
import { dataSourceConnectionsAtom } from "@/core/datasets/data-source-connections";
|
|
6
15
|
import { DUCKDB_ENGINE } from "@/core/datasets/engines";
|
|
@@ -8,7 +17,15 @@ import { datasetsAtom } from "@/core/datasets/state";
|
|
|
8
17
|
import type { DatasetsState } from "@/core/datasets/types";
|
|
9
18
|
import { store } from "@/core/state/jotai";
|
|
10
19
|
import { variablesAtom } from "@/core/variables/state";
|
|
11
|
-
import {
|
|
20
|
+
import type { FileUIPart, UIMessage } from "ai";
|
|
21
|
+
import {
|
|
22
|
+
codeToCells,
|
|
23
|
+
getAICompletionBody,
|
|
24
|
+
getAICompletionBodyWithAttachments,
|
|
25
|
+
isContextAttachment,
|
|
26
|
+
MARIMO_CONTEXT_PART_TYPE,
|
|
27
|
+
resolveChatContext,
|
|
28
|
+
} from "../completion-utils";
|
|
12
29
|
|
|
13
30
|
// Mock getCodes function
|
|
14
31
|
vi.mock("@/core/codemirror/copilot/getCodes", () => ({
|
|
@@ -350,6 +367,125 @@ describe("getAICompletionBody", () => {
|
|
|
350
367
|
});
|
|
351
368
|
});
|
|
352
369
|
|
|
370
|
+
describe("resolveChatContext", () => {
|
|
371
|
+
beforeEach(() => {
|
|
372
|
+
store.set(datasetsAtom, {
|
|
373
|
+
tables: [],
|
|
374
|
+
} as unknown as DatasetsState);
|
|
375
|
+
store.set(dataSourceConnectionsAtom, {
|
|
376
|
+
latestEngineSelected: DUCKDB_ENGINE,
|
|
377
|
+
connectionsMap: new Map(),
|
|
378
|
+
});
|
|
379
|
+
store.set(variablesAtom, {});
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it("returns no context when the input has no @-mentions", async () => {
|
|
383
|
+
const result = await resolveChatContext("just a plain question");
|
|
384
|
+
expect(result).toEqual({ contextPart: null, attachments: [] });
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it("returns no context part when @-mentions resolve to nothing", async () => {
|
|
388
|
+
const result = await resolveChatContext("look at @variable://ghost");
|
|
389
|
+
expect(result.contextPart).toBeNull();
|
|
390
|
+
expect(result.attachments).toEqual([]);
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it("captures resolved @-context into a data part", async () => {
|
|
394
|
+
store.set(variablesAtom, {
|
|
395
|
+
[variableName("var1")]: {
|
|
396
|
+
name: variableName("var1"),
|
|
397
|
+
value: "string value",
|
|
398
|
+
dataType: "string",
|
|
399
|
+
declaredBy: [],
|
|
400
|
+
usedBy: [],
|
|
401
|
+
},
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
const result = await resolveChatContext("inspect @variable://var1");
|
|
405
|
+
|
|
406
|
+
expect(result.contextPart?.type).toBe(MARIMO_CONTEXT_PART_TYPE);
|
|
407
|
+
expect(result.contextPart?.data.contextIds).toEqual(["variable://var1"]);
|
|
408
|
+
expect(result.contextPart?.data.plainText).toMatchInlineSnapshot(
|
|
409
|
+
`"<variable name="var1" dataType="string">"string value"</variable>"`,
|
|
410
|
+
);
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
describe("isContextAttachment", () => {
|
|
415
|
+
type Part = UIMessage["parts"][number];
|
|
416
|
+
|
|
417
|
+
it("is true for a file part tagged as context", () => {
|
|
418
|
+
const part = {
|
|
419
|
+
type: "file",
|
|
420
|
+
mediaType: "image/png",
|
|
421
|
+
url: "data:image/png;base64,abc",
|
|
422
|
+
providerMetadata: { marimo: { source: "context" } },
|
|
423
|
+
} as Part;
|
|
424
|
+
expect(isContextAttachment(part)).toBe(true);
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it("is false for a user-uploaded file part (no marker)", () => {
|
|
428
|
+
const part = {
|
|
429
|
+
type: "file",
|
|
430
|
+
mediaType: "image/png",
|
|
431
|
+
url: "data:image/png;base64,abc",
|
|
432
|
+
} as Part;
|
|
433
|
+
expect(isContextAttachment(part)).toBe(false);
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
it("is false for a file part with unrelated provider metadata", () => {
|
|
437
|
+
const part = {
|
|
438
|
+
type: "file",
|
|
439
|
+
mediaType: "image/png",
|
|
440
|
+
url: "data:image/png;base64,abc",
|
|
441
|
+
providerMetadata: { openai: { foo: "bar" } },
|
|
442
|
+
} as Part;
|
|
443
|
+
expect(isContextAttachment(part)).toBe(false);
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
it("is false for non-file parts", () => {
|
|
447
|
+
expect(isContextAttachment({ type: "text", text: "hi" } as Part)).toBe(
|
|
448
|
+
false,
|
|
449
|
+
);
|
|
450
|
+
});
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
describe("context attachment stamping", () => {
|
|
454
|
+
const rawAttachment: FileUIPart = {
|
|
455
|
+
type: "file",
|
|
456
|
+
mediaType: "image/png",
|
|
457
|
+
url: "data:image/png;base64,abc",
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
beforeEach(() => {
|
|
461
|
+
vi.spyOn(aiContext, "getAIContextRegistry").mockReturnValue({
|
|
462
|
+
parseAllContextIds: () => ["data://t1"],
|
|
463
|
+
formatContextForAI: () => '<data name="t1" />',
|
|
464
|
+
getAttachmentsForContext: async () => [rawAttachment],
|
|
465
|
+
} as unknown as ReturnType<typeof aiContext.getAIContextRegistry>);
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
afterEach(() => {
|
|
469
|
+
vi.restoreAllMocks();
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
it("stamps chat attachments as context-derived", async () => {
|
|
473
|
+
const { attachments } = await resolveChatContext("see @data://t1");
|
|
474
|
+
expect(attachments).toHaveLength(1);
|
|
475
|
+
expect(isContextAttachment(attachments[0])).toBe(true);
|
|
476
|
+
// The original attachment is left untouched (we return a stamped copy).
|
|
477
|
+
expect(rawAttachment.providerMetadata).toBeUndefined();
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
it("stamps completion attachments the same way as chat", async () => {
|
|
481
|
+
const { attachments } = await getAICompletionBodyWithAttachments({
|
|
482
|
+
input: "see @data://t1",
|
|
483
|
+
});
|
|
484
|
+
expect(attachments).toHaveLength(1);
|
|
485
|
+
expect(isContextAttachment(attachments[0])).toBe(true);
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
|
|
353
489
|
describe("codeToCells", () => {
|
|
354
490
|
it("should return empty array for empty string", () => {
|
|
355
491
|
const code = "";
|