@marimo-team/islands 0.23.12-dev2 → 0.23.12-dev4
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/{code-visibility-DZ_6U5hT.js → code-visibility-C5i5yDQ-.js} +1 -1
- package/dist/main.js +2 -2
- package/dist/{reveal-component-BxDb5eK0.js → reveal-component-g70KQOGv.js} +1 -1
- package/package.json +1 -1
- package/src/components/chat/chat-panel.tsx +47 -23
- package/src/components/editor/ai/__tests__/completion-utils.test.ts +91 -1
- package/src/components/editor/ai/completion-utils.ts +86 -1
|
@@ -35996,7 +35996,7 @@ ${d}`,
|
|
|
35996
35996
|
return Logger.warn("Failed to get version from mount config"), null;
|
|
35997
35997
|
}
|
|
35998
35998
|
}
|
|
35999
|
-
marimoVersionAtom = atom(getVersionFromMountConfig() || "0.23.12-
|
|
35999
|
+
marimoVersionAtom = atom(getVersionFromMountConfig() || "0.23.12-dev4");
|
|
36000
36000
|
showCodeInRunModeAtom = atom(true);
|
|
36001
36001
|
atom(null);
|
|
36002
36002
|
var import_compiler_runtime = require_compiler_runtime();
|
package/dist/main.js
CHANGED
|
@@ -26,7 +26,7 @@ import { $ as reducer, B as safeExtractSetUIElementMessageBuffers, Bn as CircleA
|
|
|
26
26
|
import { __tla as __tla_1 } from "./chunk-5FQGJX7Z-BbqSm5gU.js";
|
|
27
27
|
import { o as useSize, s as Root$1, u as createLucideIcon } from "./dist-DYGLrbYQ.js";
|
|
28
28
|
import { A as SquareFunction, C as DEFAULT_COLOR_SCHEME, D as SCALE_TYPE_DESCRIPTIONS, E as EMPTY_VALUE$1, O as TIME_UNIT_DESCRIPTIONS, S as DEFAULT_AGGREGATION, T as DEFAULT_TIME_UNIT, _ as AGGREGATION_TYPE_DESCRIPTIONS, a as AGGREGATION_FNS$1, b as COLOR_SCHEMES, c as COLOR_BY_FIELDS, d as NONE_VALUE, f as SELECTABLE_DATA_TYPES, g as TIME_UNITS, h as STRING_AGGREGATION_FNS, i as convertDataTypeToSelectable, j as ChartColumn, k as escapeFieldName, l as COMBINED_TIME_UNITS, m as SORT_TYPES, n as createSpecWithoutData, o as BIN_AGGREGATION, p as SINGLE_TIME_UNITS, r as isFieldSet, s as CHART_TYPES, t as augmentSpecWithData, u as ChartType, v as AGGREGATION_TYPE_ICON, w as DEFAULT_MAX_BINS_FACET, x as COUNT_FIELD, y as CHART_TYPE_ICON } from "./spec-B45_YCNI.js";
|
|
29
|
-
import { $ as contextAwarePanelOpen, $t as $fae977aafc393c5c$export$6b862160d295c8e, A as prettifyRowCount, At as SELECT_COLUMN_ID, B as DatePicker, Bt as TabsContent, C as downloadSizeLimitAtom, Ct as DelayMount, D as ErrorState, Dt as loadTableAndRawData, E as EmptyState, Et as getPageIndexForRow, F as ContextMenuSeparator, Ft as Maps, G as CommandEmpty, Gt as ChartLoadingState, H as Combobox, Ht as TabsTrigger, I as ContextMenuTrigger, It as dateToLocalISODate, J as CommandList, Jt as RenderTextWithLinks, K as CommandInput, Kt as LazyVegaEmbed, L as useInternalStateWithSync, Lt as dateToLocalISODateTime, M as ContextMenu, Mt as toFieldTypes, N as ContextMenuContent, Nt as getMimeValues, O as LoadingState, Ot as loadTableData, P as ContextMenuItem, Pt as isNullishFilter, Q as PANEL_TYPES, Qt as $fae977aafc393c5c$export$588937bcd60ade55, R as useSelectList, Rt as dateToLocalISOTime, S as Filenames, St as ColumnChartSpecModel, T as ColumnPreviewContainer, Tt as usePrevious$1, U as ComboboxItem, Ut as ChartErrorState, V as DateRangePicker, Vt as TabsList, W as Command, Wt as ChartInfoState, X as smartMatch, Xt as HtmlOutput, Y as CommandSeparator, Yt as Kbd, Z as ContextAwarePanelItem, Zt as EmotionCacheProvider, _ as ADD_PRINTING_CLASS, _t as NAMELESS_COLUMN_PREFIX, an as Ellipsis, at as Toggle, b as downloadHTMLAsImage, bt as renderCellValue, c as Slide, cn as ChevronsUpDown, d as RadioGroupItem, dn as ChevronsDownUp, dt as Table, en as TextWrap, et as contextAwarePanelOwner, f as JsonOutput, fn as ChevronLeft, ft as TableBody, g as InstallPackageButton, gt as TableRow, h as DataTable, ht as TableHeader, it as slotsController, j as getColumnCountForDisplay, jt as TOO_MANY_ROWS, k as prettifyRowColumnCount, kt as INDEX_COLUMN_NAME, l as Switch, ln as ChevronsRight, lt as Fill, m as OutputRenderer, mt as TableHead, n as marimoVersionAtom, nn as Funnel, nt as isCellAwareAtom, o as SLIDE_TYPE_OPTIONS_BY_VALUE, on as Download, p as OutputArea, pn as ArrowDownWideNarrow, pt as TableCell, q as CommandItem, qt as useOverflowDetection, r as showCodeInRunModeAtom, rn as EyeOff, rt as SlotNames, sn as Code, t as useNotebookCodeAvailable, tn as GripHorizontal, tt as contextAwarePanelType, u as RadioGroup, un as ChevronsLeft, ut as Provider$1, v as downloadBlob, vt as generateColumns, w as ColumnName, wt as useIntersectionObserver, x as Progress, xt as ColumnChartContext, y as downloadByURL, yt as inferFieldTypes, z as CompactChipRow, zt as Tabs, __tla as __tla_2 } from "./code-visibility-
|
|
29
|
+
import { $ as contextAwarePanelOpen, $t as $fae977aafc393c5c$export$6b862160d295c8e, A as prettifyRowCount, At as SELECT_COLUMN_ID, B as DatePicker, Bt as TabsContent, C as downloadSizeLimitAtom, Ct as DelayMount, D as ErrorState, Dt as loadTableAndRawData, E as EmptyState, Et as getPageIndexForRow, F as ContextMenuSeparator, Ft as Maps, G as CommandEmpty, Gt as ChartLoadingState, H as Combobox, Ht as TabsTrigger, I as ContextMenuTrigger, It as dateToLocalISODate, J as CommandList, Jt as RenderTextWithLinks, K as CommandInput, Kt as LazyVegaEmbed, L as useInternalStateWithSync, Lt as dateToLocalISODateTime, M as ContextMenu, Mt as toFieldTypes, N as ContextMenuContent, Nt as getMimeValues, O as LoadingState, Ot as loadTableData, P as ContextMenuItem, Pt as isNullishFilter, Q as PANEL_TYPES, Qt as $fae977aafc393c5c$export$588937bcd60ade55, R as useSelectList, Rt as dateToLocalISOTime, S as Filenames, St as ColumnChartSpecModel, T as ColumnPreviewContainer, Tt as usePrevious$1, U as ComboboxItem, Ut as ChartErrorState, V as DateRangePicker, Vt as TabsList, W as Command, Wt as ChartInfoState, X as smartMatch, Xt as HtmlOutput, Y as CommandSeparator, Yt as Kbd, Z as ContextAwarePanelItem, Zt as EmotionCacheProvider, _ as ADD_PRINTING_CLASS, _t as NAMELESS_COLUMN_PREFIX, an as Ellipsis, at as Toggle, b as downloadHTMLAsImage, bt as renderCellValue, c as Slide, cn as ChevronsUpDown, d as RadioGroupItem, dn as ChevronsDownUp, dt as Table, en as TextWrap, et as contextAwarePanelOwner, f as JsonOutput, fn as ChevronLeft, ft as TableBody, g as InstallPackageButton, gt as TableRow, h as DataTable, ht as TableHeader, it as slotsController, j as getColumnCountForDisplay, jt as TOO_MANY_ROWS, k as prettifyRowColumnCount, kt as INDEX_COLUMN_NAME, l as Switch, ln as ChevronsRight, lt as Fill, m as OutputRenderer, mt as TableHead, n as marimoVersionAtom, nn as Funnel, nt as isCellAwareAtom, o as SLIDE_TYPE_OPTIONS_BY_VALUE, on as Download, p as OutputArea, pn as ArrowDownWideNarrow, pt as TableCell, q as CommandItem, qt as useOverflowDetection, r as showCodeInRunModeAtom, rn as EyeOff, rt as SlotNames, sn as Code, t as useNotebookCodeAvailable, tn as GripHorizontal, tt as contextAwarePanelType, u as RadioGroup, un as ChevronsLeft, ut as Provider$1, v as downloadBlob, vt as generateColumns, w as ColumnName, wt as useIntersectionObserver, x as Progress, xt as ColumnChartContext, y as downloadByURL, yt as inferFieldTypes, z as CompactChipRow, zt as Tabs, __tla as __tla_2 } from "./code-visibility-C5i5yDQ-.js";
|
|
30
30
|
import { c as Calendar, i as createReducerAndAtoms, n as useOnUnmount, o as ToggleLeft, t as useOnMount } from "./useLifecycle-C3Ec71q0.js";
|
|
31
31
|
import { t as Check } from "./check-nrzHDi45.js";
|
|
32
32
|
import { A as Icon, C as logNever, D as $18f2051aff69b9bf$export$a54013f0d02a8f82, E as $18f2051aff69b9bf$export$43bb16f9c6d9e3f7, F as createCollection, I as X, M as clamp$2, N as usePrevious$2, P as useDirection, R as ChevronDown, S as assertNever, a as SelectGroup, c as SelectSeparator, d as NativeSelect, i as SelectContent, j as Trigger$1, l as SelectTrigger, n as capitalize, o as SelectItem, r as Select, s as SelectLabel, t as Strings, u as SelectValue, w as $a916eb452884faea$export$b7a616150fdb9f44 } from "./strings-Cq2s9_EQ.js";
|
|
@@ -36190,7 +36190,7 @@ ${c}
|
|
|
36190
36190
|
function _temp2$2(e) {
|
|
36191
36191
|
e.target === e.currentTarget && e.key === "Enter" && (e.preventDefault(), e.stopPropagation(), e.currentTarget.click());
|
|
36192
36192
|
}
|
|
36193
|
-
var LazySlidesComponent = import_react.lazy(() => import("./reveal-component-
|
|
36193
|
+
var LazySlidesComponent = import_react.lazy(() => import("./reveal-component-g70KQOGv.js"));
|
|
36194
36194
|
const SlidesLayoutRenderer = ({ layout: e, setLayout: r, cells: c, mode: l }) => {
|
|
36195
36195
|
var _a3;
|
|
36196
36196
|
let u = useAtomValue(kioskModeAtom), d = l === "read" || u, f = useAtomValue(numColumnsAtom) > 1, [p, m] = (0, import_react.useState)(null), { slideCells: h, skippedIds: g, noOutputIds: _, slideTypes: v, startCellIndex: y } = (0, import_react.useMemo)(() => computeSlideCellsInfo(c, e), [
|
|
@@ -9,7 +9,7 @@ import { t as require_compiler_runtime } from "./compiler-runtime-CEbnTgxf.js";
|
|
|
9
9
|
import { lt as kioskModeAtom } from "./html-to-image-C86pQALH.js";
|
|
10
10
|
import "./chunk-5FQGJX7Z-BbqSm5gU.js";
|
|
11
11
|
import { u as createLucideIcon } from "./dist-DYGLrbYQ.js";
|
|
12
|
-
import { a as DEFAULT_SLIDE_TYPE, c as Slide, ct as PanelResizeHandle, i as DEFAULT_DECK_TRANSITION, in as Expand, ot as Panel, rn as EyeOff, s as SlideSidebar, sn as Code, st as PanelGroup, t as useNotebookCodeAvailable } from "./code-visibility-
|
|
12
|
+
import { a as DEFAULT_SLIDE_TYPE, c as Slide, ct as PanelResizeHandle, i as DEFAULT_DECK_TRANSITION, in as Expand, ot as Panel, rn as EyeOff, s as SlideSidebar, sn as Code, st as PanelGroup, t as useNotebookCodeAvailable } from "./code-visibility-C5i5yDQ-.js";
|
|
13
13
|
import { X as useDebouncedCallback } from "./input-AKkGXdyV.js";
|
|
14
14
|
import "./toDate-Do1xRzAo.js";
|
|
15
15
|
import "./react-dom-BTJzcVJ9.js";
|
package/package.json
CHANGED
|
@@ -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();
|
|
@@ -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
|
*/
|