@marimo-team/islands 0.23.12-dev2 → 0.23.12-dev20
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-02AuLxDs.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-CX0nM3qj.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 +91 -1
- package/src/components/editor/ai/ai-completion-editor.tsx +1 -1
- package/src/components/editor/ai/completion-utils.ts +86 -1
- 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");
|
|
@@ -8,7 +8,14 @@ import { datasetsAtom } from "@/core/datasets/state";
|
|
|
8
8
|
import type { DatasetsState } from "@/core/datasets/types";
|
|
9
9
|
import { store } from "@/core/state/jotai";
|
|
10
10
|
import { variablesAtom } from "@/core/variables/state";
|
|
11
|
-
import {
|
|
11
|
+
import type { UIMessage } from "ai";
|
|
12
|
+
import {
|
|
13
|
+
codeToCells,
|
|
14
|
+
getAICompletionBody,
|
|
15
|
+
isContextAttachment,
|
|
16
|
+
MARIMO_CONTEXT_PART_TYPE,
|
|
17
|
+
resolveChatContext,
|
|
18
|
+
} from "../completion-utils";
|
|
12
19
|
|
|
13
20
|
// Mock getCodes function
|
|
14
21
|
vi.mock("@/core/codemirror/copilot/getCodes", () => ({
|
|
@@ -350,6 +357,89 @@ describe("getAICompletionBody", () => {
|
|
|
350
357
|
});
|
|
351
358
|
});
|
|
352
359
|
|
|
360
|
+
describe("resolveChatContext", () => {
|
|
361
|
+
beforeEach(() => {
|
|
362
|
+
store.set(datasetsAtom, {
|
|
363
|
+
tables: [],
|
|
364
|
+
} as unknown as DatasetsState);
|
|
365
|
+
store.set(dataSourceConnectionsAtom, {
|
|
366
|
+
latestEngineSelected: DUCKDB_ENGINE,
|
|
367
|
+
connectionsMap: new Map(),
|
|
368
|
+
});
|
|
369
|
+
store.set(variablesAtom, {});
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it("returns no context when the input has no @-mentions", async () => {
|
|
373
|
+
const result = await resolveChatContext("just a plain question");
|
|
374
|
+
expect(result).toEqual({ contextPart: null, attachments: [] });
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
it("returns no context part when @-mentions resolve to nothing", async () => {
|
|
378
|
+
const result = await resolveChatContext("look at @variable://ghost");
|
|
379
|
+
expect(result.contextPart).toBeNull();
|
|
380
|
+
expect(result.attachments).toEqual([]);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it("captures resolved @-context into a data part", async () => {
|
|
384
|
+
store.set(variablesAtom, {
|
|
385
|
+
[variableName("var1")]: {
|
|
386
|
+
name: variableName("var1"),
|
|
387
|
+
value: "string value",
|
|
388
|
+
dataType: "string",
|
|
389
|
+
declaredBy: [],
|
|
390
|
+
usedBy: [],
|
|
391
|
+
},
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
const result = await resolveChatContext("inspect @variable://var1");
|
|
395
|
+
|
|
396
|
+
expect(result.contextPart?.type).toBe(MARIMO_CONTEXT_PART_TYPE);
|
|
397
|
+
expect(result.contextPart?.data.contextIds).toEqual(["variable://var1"]);
|
|
398
|
+
expect(result.contextPart?.data.plainText).toMatchInlineSnapshot(
|
|
399
|
+
`"<variable name="var1" dataType="string">"string value"</variable>"`,
|
|
400
|
+
);
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
describe("isContextAttachment", () => {
|
|
405
|
+
type Part = UIMessage["parts"][number];
|
|
406
|
+
|
|
407
|
+
it("is true for a file part tagged as context", () => {
|
|
408
|
+
const part = {
|
|
409
|
+
type: "file",
|
|
410
|
+
mediaType: "image/png",
|
|
411
|
+
url: "data:image/png;base64,abc",
|
|
412
|
+
providerMetadata: { marimo: { source: "context" } },
|
|
413
|
+
} as Part;
|
|
414
|
+
expect(isContextAttachment(part)).toBe(true);
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
it("is false for a user-uploaded file part (no marker)", () => {
|
|
418
|
+
const part = {
|
|
419
|
+
type: "file",
|
|
420
|
+
mediaType: "image/png",
|
|
421
|
+
url: "data:image/png;base64,abc",
|
|
422
|
+
} as Part;
|
|
423
|
+
expect(isContextAttachment(part)).toBe(false);
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
it("is false for a file part with unrelated provider metadata", () => {
|
|
427
|
+
const part = {
|
|
428
|
+
type: "file",
|
|
429
|
+
mediaType: "image/png",
|
|
430
|
+
url: "data:image/png;base64,abc",
|
|
431
|
+
providerMetadata: { openai: { foo: "bar" } },
|
|
432
|
+
} as Part;
|
|
433
|
+
expect(isContextAttachment(part)).toBe(false);
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
it("is false for non-file parts", () => {
|
|
437
|
+
expect(isContextAttachment({ type: "text", text: "hi" } as Part)).toBe(
|
|
438
|
+
false,
|
|
439
|
+
);
|
|
440
|
+
});
|
|
441
|
+
});
|
|
442
|
+
|
|
353
443
|
describe("codeToCells", () => {
|
|
354
444
|
it("should return empty array for empty string", () => {
|
|
355
445
|
const code = "";
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
startCompletion,
|
|
8
8
|
} from "@codemirror/autocomplete";
|
|
9
9
|
import type { ReactCodeMirrorRef } from "@uiw/react-codemirror";
|
|
10
|
-
import type { FileUIPart } from "ai";
|
|
10
|
+
import type { DataUIPart, FileUIPart, UIMessage } from "ai";
|
|
11
11
|
import { getAIContextRegistry } from "@/core/ai/context/context";
|
|
12
12
|
import { getCodes } from "@/core/codemirror/copilot/getCodes";
|
|
13
13
|
import type { LanguageAdapterType } from "@/core/codemirror/language/types";
|
|
@@ -50,6 +50,91 @@ export function getAICompletionBody({
|
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
export interface MarimoContextData {
|
|
54
|
+
plainText: string;
|
|
55
|
+
contextIds: string[];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export type MarimoContextUIPart = DataUIPart<{
|
|
59
|
+
"marimo-context": MarimoContextData;
|
|
60
|
+
}>;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Wire `type` of the @-context data part. Must match
|
|
64
|
+
* `MARIMO_CONTEXT_PART_TYPE` on the backend.
|
|
65
|
+
*/
|
|
66
|
+
export const MARIMO_CONTEXT_PART_TYPE =
|
|
67
|
+
"data-marimo-context" as const satisfies MarimoContextUIPart["type"];
|
|
68
|
+
|
|
69
|
+
export interface ResolvedChatContext {
|
|
70
|
+
contextPart: MarimoContextUIPart | null;
|
|
71
|
+
attachments: FileUIPart[];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Marker stamped onto attachments derived from @-context (as opposed to files
|
|
76
|
+
* the user uploaded directly).
|
|
77
|
+
*/
|
|
78
|
+
const CONTEXT_ATTACHMENT_METADATA = {
|
|
79
|
+
marimo: { source: "context" },
|
|
80
|
+
} as const;
|
|
81
|
+
|
|
82
|
+
/** Whether a part is an attachment that was derived from @-context. */
|
|
83
|
+
export function isContextAttachment(part: UIMessage["parts"][number]): boolean {
|
|
84
|
+
return (
|
|
85
|
+
part.type === "file" &&
|
|
86
|
+
part.providerMetadata?.marimo?.source ===
|
|
87
|
+
CONTEXT_ATTACHMENT_METADATA.marimo.source
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Resolve @-context for messages. They represent referenced
|
|
93
|
+
* datasets, variables, or other context from the user's prompt.
|
|
94
|
+
*/
|
|
95
|
+
export async function resolveChatContext(
|
|
96
|
+
input: string,
|
|
97
|
+
): Promise<ResolvedChatContext> {
|
|
98
|
+
if (!input.includes(CONTEXT_TRIGGER)) {
|
|
99
|
+
return { contextPart: null, attachments: [] };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const registry = getAIContextRegistry(store);
|
|
103
|
+
const contextIds = registry.parseAllContextIds(input);
|
|
104
|
+
if (contextIds.length === 0) {
|
|
105
|
+
return { contextPart: null, attachments: [] };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const plainText = registry.formatContextForAI(contextIds);
|
|
109
|
+
|
|
110
|
+
let attachments: FileUIPart[] = [];
|
|
111
|
+
try {
|
|
112
|
+
const resolved = await registry.getAttachmentsForContext(contextIds);
|
|
113
|
+
attachments = resolved.map((attachment) => ({
|
|
114
|
+
...attachment,
|
|
115
|
+
providerMetadata: {
|
|
116
|
+
...attachment.providerMetadata,
|
|
117
|
+
marimo: {
|
|
118
|
+
...attachment.providerMetadata?.marimo,
|
|
119
|
+
...CONTEXT_ATTACHMENT_METADATA.marimo,
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
}));
|
|
123
|
+
} catch (error) {
|
|
124
|
+
Logger.error("Error getting attachments:", error);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let contextPart: MarimoContextUIPart | null = null;
|
|
128
|
+
if (plainText.trim()) {
|
|
129
|
+
contextPart = {
|
|
130
|
+
type: MARIMO_CONTEXT_PART_TYPE,
|
|
131
|
+
data: { plainText, contextIds: contextIds.map(String) },
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return { contextPart, attachments };
|
|
136
|
+
}
|
|
137
|
+
|
|
53
138
|
/**
|
|
54
139
|
* Gets the request body and attachments for the AI completion API.
|
|
55
140
|
*/
|
|
@@ -142,7 +142,7 @@ export const CreateCellButton = ({
|
|
|
142
142
|
<DropdownMenuTrigger asChild={true}>
|
|
143
143
|
<Button
|
|
144
144
|
className={cn(
|
|
145
|
-
"border-none hover-action shadow-none! bg-transparent! focus-visible:outline-
|
|
145
|
+
"border-none hover-action shadow-none! bg-transparent! focus-visible:outline-hidden",
|
|
146
146
|
isAppInteractionDisabled(connectionState) && " inactive-button",
|
|
147
147
|
)}
|
|
148
148
|
onPointerDownCapture={handlePointerDownCapture}
|
|
@@ -20,7 +20,7 @@ export const PanelEmptyState = ({
|
|
|
20
20
|
{icon &&
|
|
21
21
|
// oxlint-disable-next-line react/no-clone-element
|
|
22
22
|
React.cloneElement(icon, {
|
|
23
|
-
className: "text-accent-foreground
|
|
23
|
+
className: "text-accent-foreground shrink-0",
|
|
24
24
|
})}
|
|
25
25
|
<span className="mt-1 text-accent-foreground">{title}</span>
|
|
26
26
|
</div>
|
|
@@ -38,7 +38,7 @@ export const FloatingOutline: React.FC = () => {
|
|
|
38
38
|
<OutlineList
|
|
39
39
|
className={cn(
|
|
40
40
|
"-top-4 max-h-[70vh] bg-background rounded-lg shadow-lg absolute overflow-auto transition-all duration-300 w-[300px] border",
|
|
41
|
-
isHovered ? "
|
|
41
|
+
isHovered ? "left-[-280px] opacity-100" : "left-[300px] opacity-0",
|
|
42
42
|
)}
|
|
43
43
|
items={items}
|
|
44
44
|
activeHeaderId={activeHeaderId}
|
|
@@ -74,7 +74,7 @@ export const PendingAICells: React.FC = () => {
|
|
|
74
74
|
<Button variant="ghost" size="icon" onClick={() => clickNext("up")}>
|
|
75
75
|
<ChevronUp className="h-3.5 w-3.5" />
|
|
76
76
|
</Button>
|
|
77
|
-
<span className="text-xs font-mono min-w-
|
|
77
|
+
<span className="text-xs font-mono min-w-14 text-center">
|
|
78
78
|
{currentIndex === null
|
|
79
79
|
? `${listStagedCells.length} pending`
|
|
80
80
|
: `${currentIndex + 1} / ${listStagedCells.length}`}
|
|
@@ -81,7 +81,7 @@ const ResizableComponent = ({
|
|
|
81
81
|
<div
|
|
82
82
|
ref={ref}
|
|
83
83
|
className={`w-[3px] cursor-col-resize transition-colors duration-200 z-100
|
|
84
|
-
relative before:content-[''] before:absolute before:inset-y-0 before
|
|
84
|
+
relative before:content-[''] before:absolute before:inset-y-0 before:left-[-3px]
|
|
85
85
|
before:right-[-3px] before:w-[9px] before:z-[-1]
|
|
86
86
|
hover/column:bg-[var(--slate-3)] dark:hover/column:bg-[var(--slate-5)]
|
|
87
87
|
hover/column:hover:bg-primary/60 dark:hover/column:hover:bg-primary/60`}
|
|
@@ -78,7 +78,7 @@ const SortableColumnInternal = React.forwardRef(
|
|
|
78
78
|
};
|
|
79
79
|
|
|
80
80
|
const dragHandle = (
|
|
81
|
-
<div className="px-2 pb-0 group flex items-center overflow-hidden border-b border-
|
|
81
|
+
<div className="px-2 pb-0 group flex items-center overflow-hidden border-b border-(--slate-7)">
|
|
82
82
|
<Tooltip content="Move column left" side="top" delayDuration={300}>
|
|
83
83
|
<Button
|
|
84
84
|
variant="text"
|
|
@@ -161,7 +161,7 @@ const SortableColumnInternal = React.forwardRef(
|
|
|
161
161
|
isOver && "bg-accent/20", // Add a background color when dragging over
|
|
162
162
|
)}
|
|
163
163
|
>
|
|
164
|
-
<div className="border border-
|
|
164
|
+
<div className="border border-(--slate-7)">
|
|
165
165
|
{dragHandle}
|
|
166
166
|
{props.children}
|
|
167
167
|
</div>
|
|
@@ -722,7 +722,7 @@ export const MarimoErrorOutput = ({
|
|
|
722
722
|
<Alert
|
|
723
723
|
variant={alertVariant}
|
|
724
724
|
className={cn(
|
|
725
|
-
"border-none font-code text-sm text-[0.84375rem] p-0 text-muted-foreground normal [
|
|
725
|
+
"border-none font-code text-sm text-[0.84375rem] p-0 text-muted-foreground normal has-[svg]:pl-0 space-y-2",
|
|
726
726
|
className,
|
|
727
727
|
)}
|
|
728
728
|
>
|
|
@@ -18,7 +18,7 @@ export const TextOutput = ({ text, channel, wrapText }: Props): JSX.Element => {
|
|
|
18
18
|
return (
|
|
19
19
|
<span
|
|
20
20
|
className={
|
|
21
|
-
wrapText ? "whitespace-pre-wrap break-
|
|
21
|
+
wrapText ? "whitespace-pre-wrap wrap-break-word" : "whitespace-pre"
|
|
22
22
|
}
|
|
23
23
|
>
|
|
24
24
|
<RenderTextWithLinks text={text} />
|
|
@@ -30,7 +30,7 @@ export const TextOutput = ({ text, channel, wrapText }: Props): JSX.Element => {
|
|
|
30
30
|
<span
|
|
31
31
|
className={cn(
|
|
32
32
|
!shouldRenderAnsi &&
|
|
33
|
-
(wrapText ? "whitespace-pre-wrap break-
|
|
33
|
+
(wrapText ? "whitespace-pre-wrap wrap-break-word" : "whitespace-pre"),
|
|
34
34
|
channel === "output" && "font-prose",
|
|
35
35
|
channel,
|
|
36
36
|
)}
|