@marimo-team/islands 0.15.2 → 0.15.3
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-C39nQwtD.js → ConnectedDataExplorerComponent-DfvW3rBn.js} +323 -328
- package/dist/{ImageComparisonComponent-BhkiyswP.js → ImageComparisonComponent-XaJshw7d.js} +13 -13
- package/dist/{_baseUniq-DdHL34FO.js → _baseUniq-dN9WKF9m.js} +67 -67
- package/dist/any-language-editor-CpFniVi-.js +27 -0
- package/dist/{arc-BXrety1g.js → arc-BOhn-m2C.js} +1 -1
- package/dist/{architectureDiagram-KFL7JDKH-BMy6ywCF.js → architectureDiagram-W76B3OCA-Bpg85ZKv.js} +144 -144
- package/dist/assets/{worker-COGufAQn.js → worker-Y-Q4G-N2.js} +30 -26
- package/dist/asterisk-DS281yxp.js +271 -0
- package/dist/{blockDiagram-ZYB65J3Q-DYT2-nlI.js → blockDiagram-QIGZ2CNN-DS1kOHlW.js} +10 -10
- package/dist/{c4Diagram-AAMF2YG6-ZiQzioe6.js → c4Diagram-FPNF74CW-CyRVKssw.js} +8 -8
- package/dist/{channel-CeuXqUAU.js → channel-BilGXox7.js} +1 -1
- package/dist/{chunk-ANTBXLJU-BvYnIrdq.js → chunk-4BX2VUAB-CZR39zCO.js} +1 -1
- package/dist/{chunk-WVR4S24B-DXj8yaUk.js → chunk-55IACEB6-BIH-MYov.js} +1 -1
- package/dist/{chunk-GLLZNHP4-CyFsosAe.js → chunk-FMBD7UC4-4PZXFZE8.js} +1 -1
- package/dist/{chunk-JBRWN2VN-DA_EEhy2.js → chunk-K7UQS3LO-CEvWKznk.js} +117 -117
- package/dist/{chunk-NRVI72HA-BYx2jMlI.js → chunk-QN33PNHL-D5LO5Jq_.js} +1 -1
- package/dist/{chunk-FHKO5MBM-DfCztBk8.js → chunk-QZHKN3VN-6gwUonWI.js} +1 -1
- package/dist/{chunk-LXBSTHXV-Se7vdY6J.js → chunk-TVAH2DTR-3gm06QdU.js} +7 -7
- package/dist/{chunk-OMD6QJNC-CqgcPMgL.js → chunk-TZMSLE5B-Cm8Iy9bO.js} +1 -1
- package/dist/{classDiagram-v2-QTMF73CY-B19A3G1l.js → classDiagram-KNZD7YFC-DC529O_z.js} +2 -2
- package/dist/{classDiagram-3BZAVTQC-B19A3G1l.js → classDiagram-v2-RKCZMP56-DC529O_z.js} +2 -2
- package/dist/{clone-78au0tn1.js → clone-CLoRX3j6.js} +1 -1
- package/dist/cose-bilkent-S5V4N54A-qf5DlS6Y.js +2609 -0
- package/dist/{cytoscape.esm-BYnVVhJX.js → cytoscape.esm-DfdJODL8.js} +34 -34
- package/dist/{dagre-2BBEFEWP-BfEn3ZUV.js → dagre-5GWH7T2D-Ceocls0m.js} +6 -6
- package/dist/{data-grid-overlay-editor-CH_qLkV2.js → data-grid-overlay-editor-AqDS_UKe.js} +11 -11
- package/dist/{diagram-4IRLE6MV-CL8xidnG.js → diagram-N5W7TBWH-CP66oSiv.js} +59 -60
- package/dist/{diagram-RP2FKANI-B1BPcUew.js → diagram-QEK2KX5R-_YD4kxxi.js} +15 -15
- package/dist/{diagram-GUPCWM2R-CZ5cfqlq.js → diagram-S2PKOQOG-Cnj8T-OP.js} +10 -10
- package/dist/dockerfile-Cm8cRYCN.js +194 -0
- package/dist/ebnf-DUPDuY4r.js +78 -0
- package/dist/{erDiagram-HZWUO2LU-BEAIww50.js → erDiagram-AWTI2OKA-CGnvoHx6.js} +8 -8
- package/dist/fcl-CPC2WYrI.js +103 -0
- package/dist/{flowDiagram-THRYKUMA-Czs2UAI2.js → flowDiagram-PVAE7QVJ-DG-pr9R9.js} +9 -9
- package/dist/{ganttDiagram-WV7ZQ7D5-ByYIAVFO.js → ganttDiagram-OWAHRB6G-JmChtxvn.js} +34 -34
- package/dist/{gitGraphDiagram-OJR772UL-BcpDsiyB.js → gitGraphDiagram-NY62KEGX-D8wLfOPd.js} +4 -4
- package/dist/{glide-data-editor-CmN6FVyi.js → glide-data-editor-9nC3iCIZ.js} +33 -33
- package/dist/{graph-77W6heli.js → graph-CoRe7vAN.js} +3 -3
- package/dist/http-D9LttvKF.js +44 -0
- package/dist/{index-Bfk9dnyS.js → index-6qYeHHjQ.js} +33090 -32892
- package/dist/{index-BOojn38D.js → index-BpzLh4Qe.js} +7711 -7711
- package/dist/{index-CmozKMxx.js → index-BthgsgYX.js} +6 -6
- package/dist/{index-pBmAzQJl.js → index-MCx5v1x0.js} +2 -2
- package/dist/index-jkm77Jrz.js +98 -0
- package/dist/{infoDiagram-6WOFNB3A-CfzLHHVP.js → infoDiagram-STP46IZ2-BlXxvOrR.js} +2 -2
- package/dist/{journeyDiagram-FFXJYRFH-ndAcpkGn.js → journeyDiagram-BIP6EPQ6-CNRYs_Fc.js} +24 -26
- package/dist/{kanban-definition-KOZQBZVT-DcQYzNvc.js → kanban-definition-6OIFK2YF-B9HeMAuP.js} +14 -14
- package/dist/{layout-XySVHJgD.js → layout-m2vOUiW1.js} +81 -81
- package/dist/{linear-PbooOqg7.js → linear-DU6Q5CX3.js} +35 -35
- package/dist/{main-B5yML0bw.js → main-BD2KGFpU.js} +74594 -68034
- package/dist/main.js +1 -1
- package/dist/{mermaid-Cg5IX6Nv.js → mermaid-HVCtvbyx.js} +6160 -7493
- package/dist/min-DcGMA4e_.js +80 -0
- package/dist/mindmap-definition-Q6HEUPPD-BW8UmIDQ.js +785 -0
- package/dist/nginx-zDPm3Z74.js +89 -0
- package/dist/{number-overlay-editor-DUhfZqtP.js → number-overlay-editor-D8Hl0Syo.js} +19 -19
- package/dist/{pieDiagram-DBDJKBY4-DTOlNsja.js → pieDiagram-ADFJNKIX-Bg-3zg5U.js} +17 -17
- package/dist/{quadrantDiagram-YPSRARAO-BX2d8VS-.js → quadrantDiagram-LMRXKWRM-BO4IG6Yz.js} +6 -6
- package/dist/{react-plotly-Dcyw-3Sa.js → react-plotly-dkvHVuRb.js} +3577 -3577
- package/dist/{requirementDiagram-EGVEC5DT-D1T5u-wG.js → requirementDiagram-4UW4RH46-5sdTguSM.js} +7 -7
- package/dist/{sankeyDiagram-HRAUVNP4-G6xDfnp-.js → sankeyDiagram-GR3RE2ED-Buhlv9OI.js} +5 -5
- package/dist/sequenceDiagram-C3RYC4MD-C3qsM2UP.js +2519 -0
- package/dist/{slides-component-BJLlPJSr.js → slides-component-D209A0-s.js} +66 -66
- package/dist/solr-BNlsLglM.js +41 -0
- package/dist/spreadsheet-C-cy4P5N.js +49 -0
- package/dist/{stateDiagram-UUKSUZ4H-CYXbjaom.js → stateDiagram-KXAO66HF-CopJ7G6P.js} +5 -5
- package/dist/{stateDiagram-v2-EYPG3UTE-Br1HYKT6.js → stateDiagram-v2-UMBNRL4Z-CejL8AKf.js} +2 -2
- package/dist/style.css +1 -1
- package/dist/tiddlywiki-5wqsXtSk.js +155 -0
- package/dist/tiki-__Kn3CeS.js +181 -0
- package/dist/{time-B9SZnSen.js → time-BwSBitlN.js} +58 -58
- package/dist/{timeline-definition-3HZDQTIS-DeK_ZRD0.js → timeline-definition-XQNQX7LJ-DzMNTX-C.js} +10 -12
- package/dist/{timer-BYwnU4DF.js → timer-B0-z63CM.js} +16 -16
- package/dist/{treemap-75Q7IDZK-CKP4vV_0.js → treemap-75Q7IDZK-zeJG07dk.js} +14 -14
- package/dist/{vega-component-CpgdqX2d.js → vega-component-CUkiTayd.js} +30 -30
- package/dist/{xychartDiagram-FDP5SA34-AMEPsx_R.js → xychartDiagram-6GGTOJPD-DiENNXMS.js} +7 -7
- package/package.json +39 -39
- package/src/__mocks__/notebook.ts +3 -0
- package/src/__mocks__/requests.ts +3 -0
- package/src/__tests__/__snapshots__/CellStatus.test.tsx.snap +12 -12
- package/src/__tests__/chat-utils.test.ts +26 -211
- package/src/components/ai/ai-model-dropdown.tsx +25 -9
- package/src/components/app-config/ai-config.tsx +7 -0
- package/src/components/chat/chat-components.tsx +71 -0
- package/src/components/chat/chat-panel.tsx +481 -291
- package/src/components/chat/chat-utils.ts +50 -0
- package/src/components/chat/markdown-renderer.tsx +3 -7
- package/src/components/chat/tool-call-accordion.tsx +5 -5
- package/src/components/datasources/__tests__/utils.test.ts +6 -0
- package/src/components/datasources/column-preview.tsx +1 -3
- package/src/components/editor/actions/useNotebookActions.tsx +1 -1
- package/src/components/editor/ai/add-cell-with-ai.tsx +20 -15
- package/src/components/editor/ai/ai-completion-editor.tsx +22 -3
- package/src/components/editor/ai/completion-handlers.tsx +2 -4
- package/src/components/editor/ai/completion-utils.ts +85 -11
- package/src/components/editor/alerts/startup-logs-alert.tsx +72 -0
- package/src/components/editor/chrome/panels/datasources-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/dependency-graph-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/documentation-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/error-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/file-explorer-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/logs-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/outline-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/packages-panel.tsx +4 -2
- package/src/components/editor/chrome/panels/scratchpad-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/secrets-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/snippets-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/tracing-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/variable-panel.tsx +3 -1
- package/src/components/editor/chrome/wrapper/app-chrome.tsx +38 -28
- package/src/components/editor/controls/command-palette-button.tsx +1 -1
- package/src/components/editor/controls/command-palette.tsx +5 -4
- package/src/components/editor/controls/state.ts +4 -0
- package/src/components/editor/package-alert.tsx +108 -58
- package/src/components/editor/renderers/CellArray.tsx +2 -0
- package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +0 -1
- package/src/components/pages/edit-page.tsx +7 -3
- package/src/core/ai/chat-utils.ts +26 -43
- package/src/core/ai/config.ts +1 -1
- package/src/core/ai/context/__tests__/registry.test.ts +277 -3
- package/src/core/ai/context/context.ts +11 -1
- package/src/core/ai/context/providers/__tests__/cell-output.test.ts +378 -0
- package/src/core/ai/context/providers/__tests__/error.test.ts +3 -2
- package/src/core/ai/context/providers/__tests__/file.test.ts +119 -0
- package/src/core/ai/context/providers/cell-output.ts +349 -0
- package/src/core/ai/context/providers/common.ts +5 -1
- package/src/core/ai/context/providers/file.ts +287 -0
- package/src/core/ai/context/registry.ts +79 -0
- package/src/core/ai/state.ts +22 -5
- package/src/core/alerts/state.ts +71 -3
- package/src/core/cells/cell.ts +2 -2
- package/src/core/cells/cells.ts +1 -1
- package/src/core/cells/logs.ts +1 -1
- package/src/core/cells/runs.ts +6 -5
- package/src/core/codemirror/ai/resources.ts +47 -5
- package/src/core/codemirror/ai/state.ts +12 -0
- package/src/core/codemirror/language/__tests__/sql.test.ts +45 -0
- package/src/core/codemirror/markdown/__tests__/commands.test.ts +1 -0
- package/src/core/codemirror/theme/dark.ts +1 -1
- package/src/core/config/capabilities.ts +1 -1
- package/src/core/datasets/__tests__/data-source.test.ts +24 -0
- package/src/core/errors/__tests__/errors.test.ts +2 -0
- package/src/core/islands/bridge.ts +1 -0
- package/src/core/islands/main.ts +1 -0
- package/src/core/kernel/messages.ts +12 -6
- package/src/core/layout/layout.ts +3 -3
- package/src/core/network/requests-network.ts +8 -0
- package/src/core/network/requests-static.ts +1 -0
- package/src/core/network/requests-toasting.ts +1 -0
- package/src/core/network/types.ts +4 -1
- package/src/core/wasm/bridge.ts +18 -2
- package/src/core/wasm/worker/bootstrap.ts +3 -1
- package/src/core/wasm/worker/getMarimoWheel.ts +3 -8
- package/src/core/wasm/worker/types.ts +3 -0
- package/src/core/websocket/useMarimoWebSocket.tsx +7 -1
- package/src/css/app/Cell.css +42 -21
- package/src/css/app/codemirror.css +5 -1
- package/src/css/globals.css +3 -0
- package/src/css/md.css +1 -1
- package/src/plugins/impl/MicrophonePlugin.tsx +2 -2
- package/src/plugins/impl/chat/ChatPlugin.tsx +2 -9
- package/src/plugins/impl/chat/chat-ui.tsx +129 -110
- package/src/plugins/impl/chat/types.ts +5 -8
- package/src/plugins/impl/code/__tests__/language.test.ts +15 -0
- package/src/plugins/impl/code/any-language-editor.tsx +11 -8
- package/src/plugins/layout/MimeRenderPlugin.tsx +3 -6
- package/src/stories/cell.stories.tsx +6 -0
- package/src/stories/layout/vertical/one-column.stories.tsx +215 -0
- package/src/theme/useTheme.ts +11 -6
- package/src/utils/__tests__/blob.test.ts +37 -0
- package/src/utils/arrays.ts +13 -0
- package/src/utils/fileToBase64.ts +21 -6
- package/src/utils/json/base64.ts +5 -2
- package/src/utils/numbers.ts +9 -7
- package/dist/any-language-editor-DC5170DQ.js +0 -45
- package/dist/asn1-jKiBa2Ya.js +0 -95
- package/dist/clojure-CCKyeQKf.js +0 -800
- package/dist/css-BkF-NPzE.js +0 -1553
- package/dist/index-5ZH_qS8j.js +0 -288
- package/dist/index-U4yn89qO.js +0 -341
- package/dist/javascript-C2yteZeJ.js +0 -691
- package/dist/min-DS5Jz-hg.js +0 -80
- package/dist/mindmap-definition-LNHGMQRG-0aOVaMR8.js +0 -3234
- package/dist/mllike-BSnXJBGA.js +0 -272
- package/dist/pug-CwAQJzGR.js +0 -248
- package/dist/python-BkR3uSy8.js +0 -313
- package/dist/rpm-IznJm2Xc.js +0 -57
- package/dist/sequenceDiagram-WFGC7UMF-DMhHzllb.js +0 -2284
- package/dist/ttcn-cfg-Bac_acMi.js +0 -88
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
3
|
import type { Completion } from "@codemirror/autocomplete";
|
|
4
|
+
import type { FileUIPart } from "ai";
|
|
4
5
|
import { createStore } from "jotai";
|
|
5
|
-
import { beforeEach, describe, expect, it } from "vitest";
|
|
6
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
7
|
import { MockNotebook } from "@/__mocks__/notebook";
|
|
7
8
|
import { notebookAtom } from "@/core/cells/cells";
|
|
8
9
|
import { CellId as CellIdClass } from "@/core/cells/ids";
|
|
@@ -19,10 +20,28 @@ import {
|
|
|
19
20
|
|
|
20
21
|
// Mock context item for testing
|
|
21
22
|
interface MockContextItem extends AIContextItem {
|
|
22
|
-
type: "mock";
|
|
23
|
-
data: {
|
|
23
|
+
type: "mock" | "attachment";
|
|
24
|
+
data: {
|
|
25
|
+
value: string;
|
|
26
|
+
needsAttachment?: boolean;
|
|
27
|
+
};
|
|
24
28
|
}
|
|
25
29
|
|
|
30
|
+
// Mock attachment data for testing
|
|
31
|
+
const mockAttachment1: FileUIPart = {
|
|
32
|
+
type: "file",
|
|
33
|
+
filename: "test-image-1.png",
|
|
34
|
+
mediaType: "image/png",
|
|
35
|
+
url: "data:image/png;base64,mockdata1",
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const mockAttachment2: FileUIPart = {
|
|
39
|
+
type: "file",
|
|
40
|
+
filename: "test-image-2.jpg",
|
|
41
|
+
mediaType: "image/jpeg",
|
|
42
|
+
url: "data:image/jpeg;base64,mockdata2",
|
|
43
|
+
};
|
|
44
|
+
|
|
26
45
|
// Concrete implementation of AIContextProvider for testing
|
|
27
46
|
class MockContextProvider extends AIContextProvider<MockContextItem> {
|
|
28
47
|
readonly title = "Mock Items";
|
|
@@ -76,6 +95,44 @@ class MockContextProvider extends AIContextProvider<MockContextItem> {
|
|
|
76
95
|
}
|
|
77
96
|
}
|
|
78
97
|
|
|
98
|
+
// Test provider that supports attachments
|
|
99
|
+
class AttachmentContextProvider extends AIContextProvider<MockContextItem> {
|
|
100
|
+
readonly title = "Attachment Items";
|
|
101
|
+
readonly mentionPrefix = "@";
|
|
102
|
+
readonly contextType = "attachment";
|
|
103
|
+
|
|
104
|
+
constructor(
|
|
105
|
+
private items: MockContextItem[] = [],
|
|
106
|
+
private attachments: FileUIPart[] = [],
|
|
107
|
+
) {
|
|
108
|
+
super();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
getItems(): MockContextItem[] {
|
|
112
|
+
return this.items;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
formatContext(item: MockContextItem): string {
|
|
116
|
+
return `Attachment: ${item.name} (${item.data.value})`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
formatCompletion(item: MockContextItem): Completion {
|
|
120
|
+
return this.createBasicCompletion(item);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
override async getAttachments(
|
|
124
|
+
items: MockContextItem[],
|
|
125
|
+
): Promise<FileUIPart[]> {
|
|
126
|
+
// Return attachments for items that need them
|
|
127
|
+
const itemsNeedingAttachments = items.filter(
|
|
128
|
+
(item) => item.data.needsAttachment,
|
|
129
|
+
);
|
|
130
|
+
return itemsNeedingAttachments
|
|
131
|
+
.map((_, index) => this.attachments[index % this.attachments.length])
|
|
132
|
+
.filter(Boolean);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
79
136
|
interface FileContextItem extends AIContextItem {
|
|
80
137
|
type: "file";
|
|
81
138
|
data: { value: string };
|
|
@@ -667,4 +724,221 @@ describe("AIContextRegistry", () => {
|
|
|
667
724
|
expect(formattedContext).toContain("Item 1");
|
|
668
725
|
});
|
|
669
726
|
});
|
|
727
|
+
|
|
728
|
+
describe("attachment functionality", () => {
|
|
729
|
+
let attachmentProvider: AttachmentContextProvider;
|
|
730
|
+
let attachmentRegistry: AIContextRegistry<MockContextItem>;
|
|
731
|
+
|
|
732
|
+
beforeEach(() => {
|
|
733
|
+
const itemWithAttachment: MockContextItem = {
|
|
734
|
+
uri: "attachment://item1" as ContextLocatorId,
|
|
735
|
+
name: "item1",
|
|
736
|
+
type: "attachment",
|
|
737
|
+
description: "Item with attachment",
|
|
738
|
+
data: { needsAttachment: true, value: "test1" },
|
|
739
|
+
};
|
|
740
|
+
|
|
741
|
+
const itemWithoutAttachment: MockContextItem = {
|
|
742
|
+
uri: "attachment://item2" as ContextLocatorId,
|
|
743
|
+
name: "item2",
|
|
744
|
+
type: "attachment",
|
|
745
|
+
description: "Item without attachment",
|
|
746
|
+
data: { needsAttachment: false, value: "test2" },
|
|
747
|
+
};
|
|
748
|
+
|
|
749
|
+
attachmentProvider = new AttachmentContextProvider(
|
|
750
|
+
[itemWithAttachment, itemWithoutAttachment],
|
|
751
|
+
[mockAttachment1, mockAttachment2],
|
|
752
|
+
);
|
|
753
|
+
|
|
754
|
+
attachmentRegistry = new AIContextRegistry<MockContextItem>().register(
|
|
755
|
+
attachmentProvider,
|
|
756
|
+
);
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
describe("getAttachmentsForContext", () => {
|
|
760
|
+
it("should return empty array for empty context IDs", async () => {
|
|
761
|
+
const attachments = await attachmentRegistry.getAttachmentsForContext(
|
|
762
|
+
[],
|
|
763
|
+
);
|
|
764
|
+
expect(attachments).toEqual([]);
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
it("should return empty array for non-existent context IDs", async () => {
|
|
768
|
+
const nonExistentIds = ["attachment://nonexistent" as ContextLocatorId];
|
|
769
|
+
const attachments =
|
|
770
|
+
await attachmentRegistry.getAttachmentsForContext(nonExistentIds);
|
|
771
|
+
expect(attachments).toEqual([]);
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
it("should get attachments from provider that supports them", async () => {
|
|
775
|
+
const contextIds = ["attachment://item1" as ContextLocatorId];
|
|
776
|
+
const attachments =
|
|
777
|
+
await attachmentRegistry.getAttachmentsForContext(contextIds);
|
|
778
|
+
|
|
779
|
+
expect(attachments).toHaveLength(1);
|
|
780
|
+
expect(attachments[0]).toEqual(mockAttachment1);
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
it("should not get attachments for items that don't need them", async () => {
|
|
784
|
+
const contextIds = ["attachment://item2" as ContextLocatorId];
|
|
785
|
+
const attachments =
|
|
786
|
+
await attachmentRegistry.getAttachmentsForContext(contextIds);
|
|
787
|
+
|
|
788
|
+
expect(attachments).toHaveLength(0);
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
it("should not get attachments from providers with default implementation", async () => {
|
|
792
|
+
const simpleRegistry =
|
|
793
|
+
new AIContextRegistry<MockContextItem>().register(mockProvider);
|
|
794
|
+
|
|
795
|
+
const contextIds = ["mock://item1" as ContextLocatorId];
|
|
796
|
+
const attachments =
|
|
797
|
+
await simpleRegistry.getAttachmentsForContext(contextIds);
|
|
798
|
+
|
|
799
|
+
expect(attachments).toHaveLength(0);
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
it("should handle multiple context IDs from different providers", async () => {
|
|
803
|
+
const mixedRegistry = new AIContextRegistry<MockContextItem>()
|
|
804
|
+
.register(attachmentProvider)
|
|
805
|
+
.register(mockProvider);
|
|
806
|
+
|
|
807
|
+
const contextIds = [
|
|
808
|
+
"attachment://item1" as ContextLocatorId, // should have attachment
|
|
809
|
+
"attachment://item2" as ContextLocatorId, // should not have attachment
|
|
810
|
+
"mock://item1" as ContextLocatorId, // provider doesn't support attachments
|
|
811
|
+
];
|
|
812
|
+
|
|
813
|
+
const attachments =
|
|
814
|
+
await mixedRegistry.getAttachmentsForContext(contextIds);
|
|
815
|
+
|
|
816
|
+
// Only attachment://item1 should contribute an attachment
|
|
817
|
+
expect(attachments).toHaveLength(1);
|
|
818
|
+
expect(attachments[0]).toEqual(mockAttachment1);
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
it("should handle multiple items needing attachments", async () => {
|
|
822
|
+
const multiItem1: MockContextItem = {
|
|
823
|
+
uri: "attachment://multi1" as ContextLocatorId,
|
|
824
|
+
name: "multi1",
|
|
825
|
+
type: "attachment",
|
|
826
|
+
description: "Multi item 1",
|
|
827
|
+
data: { needsAttachment: true, value: "multi1" },
|
|
828
|
+
};
|
|
829
|
+
|
|
830
|
+
const multiItem2: MockContextItem = {
|
|
831
|
+
uri: "attachment://multi2" as ContextLocatorId,
|
|
832
|
+
name: "multi2",
|
|
833
|
+
type: "attachment",
|
|
834
|
+
description: "Multi item 2",
|
|
835
|
+
data: { needsAttachment: true, value: "multi2" },
|
|
836
|
+
};
|
|
837
|
+
|
|
838
|
+
const multiProvider = new AttachmentContextProvider(
|
|
839
|
+
[multiItem1, multiItem2],
|
|
840
|
+
[mockAttachment1, mockAttachment2],
|
|
841
|
+
);
|
|
842
|
+
|
|
843
|
+
const multiRegistry = new AIContextRegistry<MockContextItem>().register(
|
|
844
|
+
multiProvider,
|
|
845
|
+
);
|
|
846
|
+
|
|
847
|
+
const contextIds = [
|
|
848
|
+
"attachment://multi1" as ContextLocatorId,
|
|
849
|
+
"attachment://multi2" as ContextLocatorId,
|
|
850
|
+
];
|
|
851
|
+
|
|
852
|
+
const attachments =
|
|
853
|
+
await multiRegistry.getAttachmentsForContext(contextIds);
|
|
854
|
+
|
|
855
|
+
expect(attachments).toHaveLength(2);
|
|
856
|
+
expect(attachments).toContainEqual(mockAttachment1);
|
|
857
|
+
expect(attachments).toContainEqual(mockAttachment2);
|
|
858
|
+
});
|
|
859
|
+
|
|
860
|
+
it("should handle provider errors gracefully", async () => {
|
|
861
|
+
const errorProvider = new AttachmentContextProvider([
|
|
862
|
+
{
|
|
863
|
+
uri: "attachment://error-item" as ContextLocatorId,
|
|
864
|
+
name: "error-item",
|
|
865
|
+
type: "attachment",
|
|
866
|
+
description: "Item that causes error",
|
|
867
|
+
data: { needsAttachment: true, value: "error" },
|
|
868
|
+
},
|
|
869
|
+
]);
|
|
870
|
+
|
|
871
|
+
// Override getAttachments to throw an error
|
|
872
|
+
errorProvider.getAttachments = vi
|
|
873
|
+
.fn()
|
|
874
|
+
.mockRejectedValue(new Error("Attachment error"));
|
|
875
|
+
|
|
876
|
+
const errorRegistry = new AIContextRegistry<MockContextItem>()
|
|
877
|
+
.register(errorProvider)
|
|
878
|
+
.register(mockProvider);
|
|
879
|
+
|
|
880
|
+
const contextIds = [
|
|
881
|
+
"attachment://error-item" as ContextLocatorId,
|
|
882
|
+
"mock://item1" as ContextLocatorId,
|
|
883
|
+
];
|
|
884
|
+
|
|
885
|
+
// Should not throw and should handle the error gracefully
|
|
886
|
+
const attachments =
|
|
887
|
+
await errorRegistry.getAttachmentsForContext(contextIds);
|
|
888
|
+
expect(attachments).toEqual([]);
|
|
889
|
+
});
|
|
890
|
+
});
|
|
891
|
+
|
|
892
|
+
describe("provider batching for attachments", () => {
|
|
893
|
+
it("should batch multiple items to the same provider", async () => {
|
|
894
|
+
const getAttachmentsSpy = vi.spyOn(
|
|
895
|
+
attachmentProvider,
|
|
896
|
+
"getAttachments",
|
|
897
|
+
);
|
|
898
|
+
|
|
899
|
+
const contextIds = [
|
|
900
|
+
"attachment://item1" as ContextLocatorId,
|
|
901
|
+
"attachment://item2" as ContextLocatorId,
|
|
902
|
+
];
|
|
903
|
+
|
|
904
|
+
await attachmentRegistry.getAttachmentsForContext(contextIds);
|
|
905
|
+
|
|
906
|
+
// Should call getAttachments once with both items
|
|
907
|
+
expect(getAttachmentsSpy).toHaveBeenCalledTimes(1);
|
|
908
|
+
expect(getAttachmentsSpy).toHaveBeenCalledWith(
|
|
909
|
+
expect.arrayContaining([
|
|
910
|
+
expect.objectContaining({ uri: "attachment://item1" }),
|
|
911
|
+
expect.objectContaining({ uri: "attachment://item2" }),
|
|
912
|
+
]),
|
|
913
|
+
);
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
it("should call different providers separately", async () => {
|
|
917
|
+
const mixedRegistry = new AIContextRegistry<MockContextItem>()
|
|
918
|
+
.register(attachmentProvider)
|
|
919
|
+
.register(mockProvider);
|
|
920
|
+
|
|
921
|
+
const attachmentSpy = vi.spyOn(attachmentProvider, "getAttachments");
|
|
922
|
+
const mockSpy = vi.spyOn(mockProvider, "getAttachments");
|
|
923
|
+
|
|
924
|
+
const contextIds = [
|
|
925
|
+
"attachment://item1" as ContextLocatorId,
|
|
926
|
+
"mock://item1" as ContextLocatorId,
|
|
927
|
+
];
|
|
928
|
+
|
|
929
|
+
await mixedRegistry.getAttachmentsForContext(contextIds);
|
|
930
|
+
|
|
931
|
+
// Should call each provider once
|
|
932
|
+
expect(attachmentSpy).toHaveBeenCalledTimes(1);
|
|
933
|
+
expect(mockSpy).toHaveBeenCalledTimes(1);
|
|
934
|
+
|
|
935
|
+
expect(attachmentSpy).toHaveBeenCalledWith([
|
|
936
|
+
expect.objectContaining({ uri: "attachment://item1" }),
|
|
937
|
+
]);
|
|
938
|
+
expect(mockSpy).toHaveBeenCalledWith([
|
|
939
|
+
expect.objectContaining({ uri: "mock://item1" }),
|
|
940
|
+
]);
|
|
941
|
+
});
|
|
942
|
+
});
|
|
943
|
+
});
|
|
670
944
|
});
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
3
|
import { allTablesAtom } from "@/core/datasets/data-source-connections";
|
|
4
|
+
import { getRequestClient } from "@/core/network/requests";
|
|
4
5
|
import type { JotaiStore } from "@/core/state/jotai";
|
|
5
6
|
import { variablesAtom } from "@/core/variables/state";
|
|
7
|
+
import { CellOutputContextProvider } from "./providers/cell-output";
|
|
6
8
|
import { ErrorContextProvider } from "./providers/error";
|
|
9
|
+
import { FileContextProvider } from "./providers/file";
|
|
7
10
|
import { TableContextProvider } from "./providers/tables";
|
|
8
11
|
import { VariableContextProvider } from "./providers/variable";
|
|
9
12
|
import { AIContextRegistry } from "./registry";
|
|
@@ -11,8 +14,15 @@ import { AIContextRegistry } from "./registry";
|
|
|
11
14
|
export function getAIContextRegistry(store: JotaiStore) {
|
|
12
15
|
const tablesMap = store.get(allTablesAtom);
|
|
13
16
|
const variables = store.get(variablesAtom);
|
|
17
|
+
|
|
14
18
|
return new AIContextRegistry()
|
|
15
19
|
.register(new TableContextProvider(tablesMap))
|
|
16
20
|
.register(new VariableContextProvider(variables, tablesMap))
|
|
17
|
-
.register(new ErrorContextProvider(store))
|
|
21
|
+
.register(new ErrorContextProvider(store))
|
|
22
|
+
.register(new CellOutputContextProvider(store));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function getFileContextProvider(): FileContextProvider {
|
|
26
|
+
const apiRequests = getRequestClient();
|
|
27
|
+
return new FileContextProvider(apiRequests);
|
|
18
28
|
}
|