@marimo-team/frontend 0.15.1-dev34 → 0.15.1-dev36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{ConnectedDataExplorerComponent-BcJwEN2q.js → ConnectedDataExplorerComponent-D8km1NYJ.js} +1 -1
- package/dist/assets/{ImageComparisonComponent-CnwGwcTi.js → ImageComparisonComponent-CERR01UB.js} +1 -1
- package/dist/assets/{VegaLite-Cnk7Ozer.js → VegaLite-DzZIYwlo.js} +1 -1
- package/dist/assets/{_baseEach-DJPM6ZzY.js → _baseEach-DO-XcSIV.js} +1 -1
- package/dist/assets/_baseMap-Dg1mkEJY.js +1 -0
- package/dist/assets/{_baseUniq-CrXjB6JP.js → _baseUniq-urG2vBXB.js} +1 -1
- package/dist/assets/{_createAggregator-Cd0noqMc.js → _createAggregator-DBxX1xI5.js} +1 -1
- package/dist/assets/{any-language-editor-ZfVYrHPH.js → any-language-editor-BhBzti0Q.js} +1 -1
- package/dist/assets/{architectureDiagram-KFL7JDKH-DuJCaD_M.js → architectureDiagram-KFL7JDKH-mgZE9P9E.js} +1 -1
- package/dist/assets/{blockDiagram-ZYB65J3Q-B0f3bVJ0.js → blockDiagram-ZYB65J3Q-DhlvqBBN.js} +1 -1
- package/dist/assets/{c4Diagram-AAMF2YG6-3srX8_ui.js → c4Diagram-AAMF2YG6-WgF7TmcX.js} +1 -1
- package/dist/assets/channel-D1aHTD78.js +1 -0
- package/dist/assets/{chunk-ANTBXLJU-CD8Ya77t.js → chunk-ANTBXLJU-Cc0FLBv4.js} +1 -1
- package/dist/assets/{chunk-FHKO5MBM-34L0hhh_.js → chunk-FHKO5MBM-TDVJkINs.js} +1 -1
- package/dist/assets/{chunk-GLLZNHP4-CRWkmhQj.js → chunk-GLLZNHP4-BFZQsgmM.js} +1 -1
- package/dist/assets/{chunk-JBRWN2VN-CB8a_SQv.js → chunk-JBRWN2VN-jewL7EcA.js} +1 -1
- package/dist/assets/{chunk-LXBSTHXV-YNRk01ob.js → chunk-LXBSTHXV-DtMvensd.js} +1 -1
- package/dist/assets/{chunk-NRVI72HA-nFtyi9pH.js → chunk-NRVI72HA-B7LxIi5N.js} +1 -1
- package/dist/assets/{chunk-OMD6QJNC-BXZ4Kapq.js → chunk-OMD6QJNC-D77lwHsv.js} +1 -1
- package/dist/assets/{chunk-WVR4S24B-ZnYlHppS.js → chunk-WVR4S24B-BdzaJi_f.js} +1 -1
- package/dist/assets/{circle-play-k88jloBa.js → circle-play-DbLJhgWd.js} +1 -1
- package/dist/assets/classDiagram-3BZAVTQC-C8Rjm-tQ.js +1 -0
- package/dist/assets/classDiagram-v2-QTMF73CY-C8Rjm-tQ.js +1 -0
- package/dist/assets/clone-BWlvmMhq.js +1 -0
- package/dist/assets/{compile-CDMtSySK.js → compile-ittiAa-p.js} +1 -1
- package/dist/assets/{dagre-2BBEFEWP-Bfm20vyd.js → dagre-2BBEFEWP-woDg8Awj.js} +1 -1
- package/dist/assets/{data-grid-overlay-editor-CwEfDFcK.js → data-grid-overlay-editor-ncLMoVIb.js} +1 -1
- package/dist/assets/{diagram-4IRLE6MV-A9psNOWP.js → diagram-4IRLE6MV-PAN7AATi.js} +1 -1
- package/dist/assets/{diagram-GUPCWM2R-B23WLgai.js → diagram-GUPCWM2R-Ce2AHqMP.js} +1 -1
- package/dist/assets/{diagram-RP2FKANI-CU5qL55k.js → diagram-RP2FKANI-2WiKea8D.js} +1 -1
- package/dist/assets/{edit-page-DYm-s1ff.js → edit-page-CZn2bhY3.js} +42 -42
- package/dist/assets/{erDiagram-HZWUO2LU-D31RvIVf.js → erDiagram-HZWUO2LU-D5yTZwmt.js} +1 -1
- package/dist/assets/{flowDiagram-THRYKUMA-ClYGAL8Y.js → flowDiagram-THRYKUMA-CgN86sZ8.js} +1 -1
- package/dist/assets/{ganttDiagram-WV7ZQ7D5-Cmed9KL9.js → ganttDiagram-WV7ZQ7D5-5skQ2H3l.js} +1 -1
- package/dist/assets/{gitGraphDiagram-OJR772UL-CWvdnGVc.js → gitGraphDiagram-OJR772UL-C75De8fN.js} +1 -1
- package/dist/assets/{glide-data-editor-CFVz2aZ5.js → glide-data-editor-x0d0Up5U.js} +4 -4
- package/dist/assets/{graph-OxTx0_rA.js → graph-Die-6Bfl.js} +1 -1
- package/dist/assets/{home-page-Dt3nnRSz.js → home-page-XrEKfDMd.js} +1 -1
- package/dist/assets/{index-C0Q8e5FM.js → index-B1-fG89N.js} +1 -1
- package/dist/assets/{index-BLXqwIOX.js → index-B3-zxsj0.js} +1 -1
- package/dist/assets/{index-DjT-1R5N.js → index-B7JwzNrx.js} +1 -1
- package/dist/assets/{index-quG0To4_.js → index-BSJet7Zq.js} +1 -1
- package/dist/assets/{index-Bu3XvTHk.js → index-BWBg8Lhk.js} +1 -1
- package/dist/assets/{index-DLO62gfd.js → index-BfqMljbw.js} +1 -1
- package/dist/assets/{index-DHlxJ27b.js → index-CCZkMG5j.js} +1 -1
- package/dist/assets/{index-CQFSXxPp.js → index-CXwX4pUI.js} +133 -133
- package/dist/assets/{index-DETsDEp1.js → index-CoAopzxc.js} +1 -1
- package/dist/assets/{index-BOFl7de8.js → index-D9ot8WPl.js} +1 -1
- package/dist/assets/{index-WltWzc1Z.js → index-D_ebOE-e.js} +1 -1
- package/dist/assets/{index-Bd2C9zUZ.js → index-De7juNGF.js} +1 -1
- package/dist/assets/{index-KCIhgRrL.js → index-Dh8C39yO.js} +1 -1
- package/dist/assets/{index-BUwTI3go.js → index-Dlrab0dK.js} +1 -1
- package/dist/assets/{index-4GKIL9qL.js → index-DmHX1Rlv.js} +1 -1
- package/dist/assets/{index-Baw8vWOg.js → index-F531CohR.js} +1 -1
- package/dist/assets/{index-BM1NAxco.js → index-UAPuSZUk.js} +1 -1
- package/dist/assets/{index-DsWckHTg.js → index-jwhWh6uS.js} +1 -1
- package/dist/assets/{index-B-P7QUhF.js → index-kraNHKP1.js} +1 -1
- package/dist/assets/{index-D4HfKv3U.js → index-v546YXgW.js} +1 -1
- package/dist/assets/{infoDiagram-6WOFNB3A-BQOY1VMN.js → infoDiagram-6WOFNB3A-mJ455ogl.js} +1 -1
- package/dist/assets/{journeyDiagram-FFXJYRFH-BLEcUGJo.js → journeyDiagram-FFXJYRFH-CrWxkiXG.js} +1 -1
- package/dist/assets/{kanban-definition-KOZQBZVT-RanT6u8e.js → kanban-definition-KOZQBZVT-D5haTqpz.js} +1 -1
- package/dist/assets/{layout-D4n_CEl8.js → layout-BEc7rmra.js} +1 -1
- package/dist/assets/{linear-BbPKk0w3.js → linear-BLmy5c_P.js} +1 -1
- package/dist/assets/{links-B7_BzQLv.js → links-BGVSWGyv.js} +1 -1
- package/dist/assets/{mermaid-CUkYs7pX.js → mermaid-Bu2fp8N3.js} +4 -4
- package/dist/assets/{min-Yuyj_o4k.js → min-BF5a70Ha.js} +1 -1
- package/dist/assets/{mindmap-definition-LNHGMQRG-x4MjCJ_j.js → mindmap-definition-LNHGMQRG-jD2ylGBG.js} +1 -1
- package/dist/assets/{number-overlay-editor-DeKR_mfB.js → number-overlay-editor-BadGHMrr.js} +1 -1
- package/dist/assets/{pieDiagram-DBDJKBY4-ZOMhJp-a.js → pieDiagram-DBDJKBY4-B11UeqxS.js} +1 -1
- package/dist/assets/{quadrantDiagram-YPSRARAO-vZeyiYxN.js → quadrantDiagram-YPSRARAO-DznrQXs-.js} +1 -1
- package/dist/assets/{react-plotly-BV6-1ruP.js → react-plotly-B6W1vV_l.js} +1 -1
- package/dist/assets/{requirementDiagram-EGVEC5DT-DkgbH0oi.js → requirementDiagram-EGVEC5DT-BuWp2ov9.js} +1 -1
- package/dist/assets/{run-page-0slxHZwl.js → run-page-BD4YehOu.js} +1 -1
- package/dist/assets/{sankeyDiagram-HRAUVNP4-BuFPw2i8.js → sankeyDiagram-HRAUVNP4-BmDCzHkG.js} +1 -1
- package/dist/assets/{sequenceDiagram-WFGC7UMF-BKGIaix6.js → sequenceDiagram-WFGC7UMF-B6aCHwAd.js} +1 -1
- package/dist/assets/{slides-component-DYhLRND3.js → slides-component-CkHAJhvv.js} +1 -1
- package/dist/assets/{sortBy-C_bWmRoh.js → sortBy-BgSF6Z3p.js} +1 -1
- package/dist/assets/{stateDiagram-UUKSUZ4H-Cm2zkw0M.js → stateDiagram-UUKSUZ4H-DcGKoXUB.js} +1 -1
- package/dist/assets/stateDiagram-v2-EYPG3UTE-BW5Lhcrb.js +1 -0
- package/dist/assets/{storage-Dk-ciwQU.js → storage-BX5GfYeC.js} +3 -3
- package/dist/assets/{terminal-DrDWxiEU.js → terminal-CpmTRzDD.js} +1 -1
- package/dist/assets/{time-DW3BCgky.js → time-igUPFhda.js} +1 -1
- package/dist/assets/{timeline-definition-3HZDQTIS-B_ZdpuLp.js → timeline-definition-3HZDQTIS-7b4K28uW.js} +1 -1
- package/dist/assets/{tracing-BuGDnWcg.js → tracing-BWeRyJmf.js} +2 -2
- package/dist/assets/{trash-BKmRbRfw.js → trash-Bv9cD3VE.js} +1 -1
- package/dist/assets/{treemap-75Q7IDZK-BBbbCdps.js → treemap-75Q7IDZK-DPkOPTcz.js} +1 -1
- package/dist/assets/{vega-component-DogOWa6S.js → vega-component-CGVDFtwG.js} +1 -1
- package/dist/assets/{xychartDiagram-FDP5SA34-3B1j_9pq.js → xychartDiagram-FDP5SA34-BbvKlNzf.js} +1 -1
- package/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/__mocks__/notebook.ts +108 -0
- package/src/components/editor/Cell.tsx +16 -17
- package/src/components/editor/ai/__tests__/completion-utils.test.ts +119 -139
- package/src/components/editor/ai/completion-utils.ts +7 -33
- package/src/components/editor/navigation/navigation.ts +7 -7
- package/src/components/editor/navigation/state.ts +34 -2
- package/src/core/ai/context/__tests__/registry.test.ts +154 -0
- package/src/core/ai/context/__tests__/utils.test.ts +174 -0
- package/src/core/ai/context/context.ts +3 -1
- package/src/core/ai/context/providers/__tests__/__snapshots__/error.test.ts.snap +3 -0
- package/src/core/ai/context/providers/__tests__/__snapshots__/tables.test.ts.snap +8 -12
- package/src/core/ai/context/providers/__tests__/__snapshots__/variable.test.ts.snap +4 -20
- package/src/core/ai/context/providers/__tests__/error.test.ts +328 -0
- package/src/core/ai/context/providers/common.ts +1 -0
- package/src/core/ai/context/providers/error.ts +165 -0
- package/src/core/ai/context/providers/tables.ts +14 -6
- package/src/core/ai/context/providers/variable.ts +11 -2
- package/src/core/ai/context/utils.ts +49 -0
- package/src/core/cells/cells.ts +1 -1
- package/dist/assets/_baseMap-u5TY8JMW.js +0 -1
- package/dist/assets/channel-BIZI32eZ.js +0 -1
- package/dist/assets/classDiagram-3BZAVTQC-DfPpF-gd.js +0 -1
- package/dist/assets/classDiagram-v2-QTMF73CY-DfPpF-gd.js +0 -1
- package/dist/assets/clone-yHYNh3QG.js +0 -1
- package/dist/assets/stateDiagram-v2-EYPG3UTE-DX5NRIHK.js +0 -1
@@ -0,0 +1,328 @@
|
|
1
|
+
/* Copyright 2024 Marimo. All rights reserved. */
|
2
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
3
|
+
|
4
|
+
import { createStore } from "jotai";
|
5
|
+
import { beforeEach, describe, expect, it } from "vitest";
|
6
|
+
import { MockNotebook } from "@/__mocks__/notebook";
|
7
|
+
import { notebookAtom } from "@/core/cells/cells";
|
8
|
+
import { type CellId, CellId as CellIdClass } from "@/core/cells/ids";
|
9
|
+
import { ErrorContextProvider } from "../error";
|
10
|
+
|
11
|
+
describe("ErrorContextProvider", () => {
|
12
|
+
let provider: ErrorContextProvider;
|
13
|
+
let store: ReturnType<typeof createStore>;
|
14
|
+
|
15
|
+
beforeEach(() => {
|
16
|
+
store = createStore();
|
17
|
+
provider = new ErrorContextProvider(store);
|
18
|
+
});
|
19
|
+
|
20
|
+
const createMockNotebookWithErrors = (
|
21
|
+
errors: Parameters<typeof MockNotebook.notebookStateWithErrors>[0],
|
22
|
+
) => {
|
23
|
+
const notebookState = MockNotebook.notebookStateWithErrors(errors);
|
24
|
+
store.set(notebookAtom, notebookState);
|
25
|
+
};
|
26
|
+
|
27
|
+
describe("provider properties", () => {
|
28
|
+
it("should have correct provider properties", () => {
|
29
|
+
expect(provider.title).toBe("Errors");
|
30
|
+
expect(provider.mentionPrefix).toBe("@");
|
31
|
+
expect(provider.contextType).toBe("error");
|
32
|
+
});
|
33
|
+
});
|
34
|
+
|
35
|
+
describe("getItems", () => {
|
36
|
+
it("should return empty array when no errors", () => {
|
37
|
+
const items = provider.getItems();
|
38
|
+
expect(items).toEqual([]);
|
39
|
+
});
|
40
|
+
|
41
|
+
it("should return error items when errors exist", () => {
|
42
|
+
const cellId1 = CellIdClass.create();
|
43
|
+
const cellId2 = CellIdClass.create();
|
44
|
+
|
45
|
+
createMockNotebookWithErrors([
|
46
|
+
{
|
47
|
+
cellId: cellId1,
|
48
|
+
cellName: "Cell 1",
|
49
|
+
errorData: [MockNotebook.errors.syntax("Invalid syntax")],
|
50
|
+
},
|
51
|
+
{
|
52
|
+
cellId: cellId2,
|
53
|
+
cellName: "Cell 2",
|
54
|
+
errorData: [MockNotebook.errors.exception("Runtime error")],
|
55
|
+
},
|
56
|
+
]);
|
57
|
+
|
58
|
+
const items = provider.getItems();
|
59
|
+
|
60
|
+
expect(items).toHaveLength(1);
|
61
|
+
expect(items[0]).toMatchObject({
|
62
|
+
name: "Errors",
|
63
|
+
type: "error",
|
64
|
+
description: "All errors in the notebook",
|
65
|
+
data: {
|
66
|
+
type: "all-errors",
|
67
|
+
errors: [
|
68
|
+
{
|
69
|
+
cellId: cellId1,
|
70
|
+
cellName: "Cell 1",
|
71
|
+
errorData: [{ type: "syntax", msg: "Invalid syntax" }],
|
72
|
+
},
|
73
|
+
{
|
74
|
+
cellId: cellId2,
|
75
|
+
cellName: "Cell 2",
|
76
|
+
errorData: [{ type: "exception", msg: "Runtime error" }],
|
77
|
+
},
|
78
|
+
],
|
79
|
+
},
|
80
|
+
});
|
81
|
+
});
|
82
|
+
|
83
|
+
it("should handle cells without names", () => {
|
84
|
+
const cellId = CellIdClass.create();
|
85
|
+
|
86
|
+
createMockNotebookWithErrors([
|
87
|
+
{
|
88
|
+
cellId,
|
89
|
+
cellName: "",
|
90
|
+
errorData: [MockNotebook.errors.syntax("Invalid syntax")],
|
91
|
+
},
|
92
|
+
]);
|
93
|
+
|
94
|
+
const items = provider.getItems();
|
95
|
+
expect(items[0].data.errors[0].cellName).toBe("");
|
96
|
+
});
|
97
|
+
});
|
98
|
+
|
99
|
+
describe("formatCompletion", () => {
|
100
|
+
it("should format completion for all-errors type", () => {
|
101
|
+
const cellId1 = CellIdClass.create();
|
102
|
+
const cellId2 = CellIdClass.create();
|
103
|
+
|
104
|
+
createMockNotebookWithErrors([
|
105
|
+
{
|
106
|
+
cellId: cellId1,
|
107
|
+
cellName: "Cell 1",
|
108
|
+
errorData: [MockNotebook.errors.syntax("Invalid syntax")],
|
109
|
+
},
|
110
|
+
{
|
111
|
+
cellId: cellId2,
|
112
|
+
cellName: "Cell 2",
|
113
|
+
errorData: [MockNotebook.errors.exception("Runtime error")],
|
114
|
+
},
|
115
|
+
]);
|
116
|
+
|
117
|
+
const items = provider.getItems();
|
118
|
+
const completion = provider.formatCompletion(items[0]);
|
119
|
+
|
120
|
+
expect(completion).toMatchObject({
|
121
|
+
label: "@Errors",
|
122
|
+
displayLabel: "Errors",
|
123
|
+
detail: "2 errors",
|
124
|
+
boost: 2, // Boosts.ERROR
|
125
|
+
type: "error",
|
126
|
+
apply: "@Errors",
|
127
|
+
section: "Errors",
|
128
|
+
});
|
129
|
+
|
130
|
+
// Test the info function
|
131
|
+
expect(completion.info).toBeDefined();
|
132
|
+
if (typeof completion.info === "function") {
|
133
|
+
const infoResult = completion.info(completion);
|
134
|
+
if (
|
135
|
+
infoResult &&
|
136
|
+
typeof infoResult === "object" &&
|
137
|
+
"dom" in infoResult
|
138
|
+
) {
|
139
|
+
const infoElement = infoResult.dom as HTMLElement;
|
140
|
+
expect(infoElement.tagName).toBe("DIV");
|
141
|
+
expect(infoElement.textContent).toContain("Errors");
|
142
|
+
expect(infoElement.textContent).toContain("2 errors");
|
143
|
+
} else if (infoResult && "tagName" in (infoResult as any)) {
|
144
|
+
const infoElement = infoResult as HTMLElement;
|
145
|
+
expect(infoElement.tagName).toBe("DIV");
|
146
|
+
expect(infoElement.textContent).toContain("Errors");
|
147
|
+
expect(infoElement.textContent).toContain("2 errors");
|
148
|
+
}
|
149
|
+
}
|
150
|
+
});
|
151
|
+
|
152
|
+
it("should handle single error correctly", () => {
|
153
|
+
const cellId = CellIdClass.create();
|
154
|
+
|
155
|
+
createMockNotebookWithErrors([
|
156
|
+
{
|
157
|
+
cellId,
|
158
|
+
cellName: "Cell 1",
|
159
|
+
errorData: [MockNotebook.errors.syntax("Invalid syntax")],
|
160
|
+
},
|
161
|
+
]);
|
162
|
+
|
163
|
+
const items = provider.getItems();
|
164
|
+
const completion = provider.formatCompletion(items[0]);
|
165
|
+
expect(completion.detail).toBe("1 error");
|
166
|
+
});
|
167
|
+
|
168
|
+
it("should handle fallback for unknown error types", () => {
|
169
|
+
const item = {
|
170
|
+
uri: "error://unknown",
|
171
|
+
name: "Unknown Error",
|
172
|
+
type: "error" as const,
|
173
|
+
data: {
|
174
|
+
type: "unknown" as any,
|
175
|
+
errors: [],
|
176
|
+
},
|
177
|
+
description: "Unknown error type",
|
178
|
+
};
|
179
|
+
|
180
|
+
const completion = provider.formatCompletion(item);
|
181
|
+
expect(completion).toMatchObject({
|
182
|
+
label: "Error",
|
183
|
+
displayLabel: "Error",
|
184
|
+
boost: 2, // Boosts.ERROR
|
185
|
+
});
|
186
|
+
});
|
187
|
+
});
|
188
|
+
|
189
|
+
describe("formatContext", () => {
|
190
|
+
it("should format context for basic errors", () => {
|
191
|
+
const cellId = CellIdClass.create();
|
192
|
+
|
193
|
+
createMockNotebookWithErrors([
|
194
|
+
{
|
195
|
+
cellId,
|
196
|
+
cellName: "Cell 1",
|
197
|
+
errorData: [MockNotebook.errors.syntax("Invalid syntax")],
|
198
|
+
},
|
199
|
+
]);
|
200
|
+
|
201
|
+
const items = provider.getItems();
|
202
|
+
const context = provider.formatContext(items[0]);
|
203
|
+
expect(context).toMatchSnapshot("basic-error-context");
|
204
|
+
});
|
205
|
+
|
206
|
+
it("should format context for multiple error types", () => {
|
207
|
+
const cellId1 = "cell-1" as CellId;
|
208
|
+
const cellId2 = "cell-2" as CellId;
|
209
|
+
|
210
|
+
createMockNotebookWithErrors([
|
211
|
+
{
|
212
|
+
cellId: cellId1,
|
213
|
+
cellName: "Cell 1",
|
214
|
+
errorData: [
|
215
|
+
MockNotebook.errors.syntax("Invalid syntax"),
|
216
|
+
MockNotebook.errors.exception("Runtime error"),
|
217
|
+
],
|
218
|
+
},
|
219
|
+
{
|
220
|
+
cellId: cellId2,
|
221
|
+
cellName: "",
|
222
|
+
errorData: [
|
223
|
+
MockNotebook.errors.cycle(),
|
224
|
+
MockNotebook.errors.multipleDefs("variable_x"),
|
225
|
+
],
|
226
|
+
},
|
227
|
+
]);
|
228
|
+
|
229
|
+
const items = provider.getItems();
|
230
|
+
const context = provider.formatContext(items[0]);
|
231
|
+
|
232
|
+
// Check for expected content instead of exact snapshot match due to random cell IDs
|
233
|
+
expect(context).toMatchInlineSnapshot(`
|
234
|
+
"<error name="Cell 1" description="Invalid syntax
|
235
|
+
Runtime error"></error>
|
236
|
+
|
237
|
+
<error name="Cell cell-2" description="This cell is in a cycle
|
238
|
+
The variable 'variable_x' was defined by another cell"></error>"
|
239
|
+
`);
|
240
|
+
});
|
241
|
+
|
242
|
+
it("should handle cells without names", () => {
|
243
|
+
const cellId = CellIdClass.create();
|
244
|
+
|
245
|
+
createMockNotebookWithErrors([
|
246
|
+
{
|
247
|
+
cellId,
|
248
|
+
cellName: "",
|
249
|
+
errorData: [MockNotebook.errors.syntax("Invalid syntax")],
|
250
|
+
},
|
251
|
+
]);
|
252
|
+
|
253
|
+
const items = provider.getItems();
|
254
|
+
const context = provider.formatContext(items[0]);
|
255
|
+
expect(context).toContain(`Cell ${cellId}`);
|
256
|
+
});
|
257
|
+
});
|
258
|
+
|
259
|
+
describe("error description handling", () => {
|
260
|
+
it("should handle all error types correctly", () => {
|
261
|
+
// Note: ancestor-* error types are intentionally filtered out by cellErrorsAtom
|
262
|
+
const errorTypes = [
|
263
|
+
{
|
264
|
+
error: MockNotebook.errors.setupRefs(),
|
265
|
+
expected: "The setup cell cannot have references",
|
266
|
+
},
|
267
|
+
{
|
268
|
+
error: MockNotebook.errors.cycle(),
|
269
|
+
expected: "This cell is in a cycle",
|
270
|
+
},
|
271
|
+
{
|
272
|
+
error: MockNotebook.errors.multipleDefs("var_x"),
|
273
|
+
expected: "The variable 'var_x' was defined by another cell",
|
274
|
+
},
|
275
|
+
{
|
276
|
+
error: MockNotebook.errors.importStar("Import star error"),
|
277
|
+
expected: "Import star error",
|
278
|
+
},
|
279
|
+
{
|
280
|
+
error: MockNotebook.errors.exception("Runtime exception"),
|
281
|
+
expected: "Runtime exception",
|
282
|
+
},
|
283
|
+
{
|
284
|
+
error: MockNotebook.errors.strictException("Strict exception", "ref"),
|
285
|
+
expected: "Strict exception",
|
286
|
+
},
|
287
|
+
{
|
288
|
+
error: MockNotebook.errors.interruption(),
|
289
|
+
expected: "This cell was interrupted and needs to be re-run",
|
290
|
+
},
|
291
|
+
{
|
292
|
+
error: MockNotebook.errors.syntax("Syntax error"),
|
293
|
+
expected: "Syntax error",
|
294
|
+
},
|
295
|
+
{
|
296
|
+
error: MockNotebook.errors.unknown("Unknown error"),
|
297
|
+
expected: "Unknown error",
|
298
|
+
},
|
299
|
+
];
|
300
|
+
|
301
|
+
for (const { error, expected } of errorTypes) {
|
302
|
+
// Create a fresh store and provider for each error type
|
303
|
+
const testStore = createStore();
|
304
|
+
const testProvider = new ErrorContextProvider(testStore);
|
305
|
+
|
306
|
+
const { notebookState } = MockNotebook.cellWithErrors("Cell 1", [
|
307
|
+
error,
|
308
|
+
]);
|
309
|
+
testStore.set(notebookAtom, notebookState);
|
310
|
+
|
311
|
+
const items = testProvider.getItems();
|
312
|
+
expect(items).toHaveLength(1);
|
313
|
+
const context = testProvider.formatContext(items[0]);
|
314
|
+
expect(context).toContain(expected);
|
315
|
+
}
|
316
|
+
});
|
317
|
+
|
318
|
+
it("should handle unknown error type gracefully", () => {
|
319
|
+
const error = { type: "unknown-type" as any };
|
320
|
+
const { notebookState } = MockNotebook.cellWithErrors("Cell 1", [error]);
|
321
|
+
store.set(notebookAtom, notebookState);
|
322
|
+
|
323
|
+
const items = provider.getItems();
|
324
|
+
const context = provider.formatContext(items[0]);
|
325
|
+
expect(context).toContain("Unknown error");
|
326
|
+
});
|
327
|
+
});
|
328
|
+
});
|
@@ -0,0 +1,165 @@
|
|
1
|
+
/* Copyright 2024 Marimo. All rights reserved. */
|
2
|
+
|
3
|
+
import type { Completion } from "@codemirror/autocomplete";
|
4
|
+
import { cellErrorsAtom } from "@/core/cells/cells";
|
5
|
+
import type { CellId } from "@/core/cells/ids";
|
6
|
+
import type { MarimoError } from "@/core/kernel/messages";
|
7
|
+
import type { JotaiStore } from "@/core/state/jotai";
|
8
|
+
import { PluralWord } from "@/utils/pluralize";
|
9
|
+
import { type AIContextItem, AIContextProvider } from "../registry";
|
10
|
+
import { contextToXml } from "../utils";
|
11
|
+
import { Boosts } from "./common";
|
12
|
+
|
13
|
+
export interface ErrorContextItem extends AIContextItem {
|
14
|
+
type: "error";
|
15
|
+
data: {
|
16
|
+
type: "all-errors";
|
17
|
+
errors: Array<{
|
18
|
+
cellId: CellId;
|
19
|
+
cellName: string;
|
20
|
+
errorData: MarimoError[];
|
21
|
+
}>;
|
22
|
+
};
|
23
|
+
}
|
24
|
+
|
25
|
+
function describeError(error: MarimoError): string {
|
26
|
+
if (error.type === "setup-refs") {
|
27
|
+
return "The setup cell cannot have references";
|
28
|
+
}
|
29
|
+
if (error.type === "cycle") {
|
30
|
+
return "This cell is in a cycle";
|
31
|
+
}
|
32
|
+
if (error.type === "multiple-defs") {
|
33
|
+
return `The variable '${error.name}' was defined by another cell`;
|
34
|
+
}
|
35
|
+
if (error.type === "import-star") {
|
36
|
+
return error.msg;
|
37
|
+
}
|
38
|
+
if (error.type === "ancestor-stopped") {
|
39
|
+
return error.msg;
|
40
|
+
}
|
41
|
+
if (error.type === "ancestor-prevented") {
|
42
|
+
return error.msg;
|
43
|
+
}
|
44
|
+
if (error.type === "exception") {
|
45
|
+
return error.msg;
|
46
|
+
}
|
47
|
+
if (error.type === "strict-exception") {
|
48
|
+
return error.msg;
|
49
|
+
}
|
50
|
+
if (error.type === "interruption") {
|
51
|
+
return "This cell was interrupted and needs to be re-run";
|
52
|
+
}
|
53
|
+
if (error.type === "syntax") {
|
54
|
+
return error.msg;
|
55
|
+
}
|
56
|
+
if (error.type === "unknown") {
|
57
|
+
return error.msg;
|
58
|
+
}
|
59
|
+
return "Unknown error";
|
60
|
+
}
|
61
|
+
|
62
|
+
const errorsTxt = new PluralWord("error", "errors");
|
63
|
+
export class ErrorContextProvider extends AIContextProvider<ErrorContextItem> {
|
64
|
+
readonly title = "Errors";
|
65
|
+
readonly mentionPrefix = "@";
|
66
|
+
readonly contextType = "error";
|
67
|
+
|
68
|
+
constructor(private store: JotaiStore) {
|
69
|
+
super();
|
70
|
+
}
|
71
|
+
|
72
|
+
getItems(): ErrorContextItem[] {
|
73
|
+
const errors = this.store.get(cellErrorsAtom);
|
74
|
+
if (errors.length === 0) {
|
75
|
+
return [];
|
76
|
+
}
|
77
|
+
|
78
|
+
return [
|
79
|
+
{
|
80
|
+
uri: this.asURI("all"),
|
81
|
+
name: "Errors",
|
82
|
+
type: this.contextType,
|
83
|
+
data: {
|
84
|
+
type: "all-errors",
|
85
|
+
errors: errors.map((error) => ({
|
86
|
+
cellId: error.cellId,
|
87
|
+
cellName: error.cellName,
|
88
|
+
errorData: error.output.data,
|
89
|
+
})),
|
90
|
+
},
|
91
|
+
description: "All errors in the notebook",
|
92
|
+
},
|
93
|
+
];
|
94
|
+
|
95
|
+
// TODO: maybe handle single errors or grouped by types
|
96
|
+
}
|
97
|
+
|
98
|
+
formatCompletion(item: ErrorContextItem): Completion {
|
99
|
+
if (item.data.type === "all-errors") {
|
100
|
+
return {
|
101
|
+
label: "@Errors",
|
102
|
+
displayLabel: "Errors",
|
103
|
+
detail: `${item.data.errors.length} ${errorsTxt.pluralize(item.data.errors.length)}`,
|
104
|
+
boost: Boosts.ERROR,
|
105
|
+
type: "error",
|
106
|
+
apply: "@Errors",
|
107
|
+
section: "Errors",
|
108
|
+
info: () => {
|
109
|
+
const infoContainer = document.createElement("div");
|
110
|
+
infoContainer.classList.add(
|
111
|
+
"mo-cm-tooltip",
|
112
|
+
"docs-documentation",
|
113
|
+
"min-w-[200px]",
|
114
|
+
"flex",
|
115
|
+
"flex-col",
|
116
|
+
"gap-1",
|
117
|
+
"p-2",
|
118
|
+
);
|
119
|
+
|
120
|
+
const headerDiv = document.createElement("div");
|
121
|
+
headerDiv.classList.add("flex", "flex-col", "gap-1");
|
122
|
+
|
123
|
+
const nameDiv = document.createElement("div");
|
124
|
+
nameDiv.classList.add("font-bold", "text-base");
|
125
|
+
nameDiv.textContent = "Errors";
|
126
|
+
headerDiv.append(nameDiv);
|
127
|
+
|
128
|
+
const descriptionDiv = document.createElement("div");
|
129
|
+
descriptionDiv.classList.add("text-sm", "text-muted-foreground");
|
130
|
+
descriptionDiv.textContent = `${item.data.errors.length} ${errorsTxt.pluralize(item.data.errors.length)}`;
|
131
|
+
headerDiv.append(descriptionDiv);
|
132
|
+
|
133
|
+
infoContainer.append(headerDiv);
|
134
|
+
|
135
|
+
return infoContainer;
|
136
|
+
},
|
137
|
+
};
|
138
|
+
}
|
139
|
+
|
140
|
+
return {
|
141
|
+
label: "Error",
|
142
|
+
displayLabel: "Error",
|
143
|
+
boost: Boosts.ERROR,
|
144
|
+
};
|
145
|
+
}
|
146
|
+
|
147
|
+
formatContext(item: ErrorContextItem): string {
|
148
|
+
const { data } = item;
|
149
|
+
|
150
|
+
const xmls = data.errors.map((err) => {
|
151
|
+
return contextToXml({
|
152
|
+
type: this.contextType,
|
153
|
+
data: {
|
154
|
+
name: err.cellName || `Cell ${err.cellId}`,
|
155
|
+
description: err.errorData
|
156
|
+
.map((err) => describeError(err))
|
157
|
+
.join("\n"),
|
158
|
+
},
|
159
|
+
});
|
160
|
+
});
|
161
|
+
|
162
|
+
const xml = xmls.join("\n\n");
|
163
|
+
return xml;
|
164
|
+
}
|
165
|
+
}
|
@@ -5,6 +5,7 @@ import type { DatasetTablesMap } from "@/core/datasets/data-source-connections";
|
|
5
5
|
import type { DataTable } from "@/core/kernel/messages";
|
6
6
|
import type { AIContextItem } from "../registry";
|
7
7
|
import { AIContextProvider } from "../registry";
|
8
|
+
import { contextToXml } from "../utils";
|
8
9
|
import { Boosts } from "./common";
|
9
10
|
|
10
11
|
export interface TableContextItem extends AIContextItem {
|
@@ -32,8 +33,8 @@ export class TableContextProvider extends AIContextProvider<TableContextItem> {
|
|
32
33
|
}
|
33
34
|
|
34
35
|
formatContext(item: TableContextItem): string {
|
35
|
-
const { data
|
36
|
-
const { columns, source, num_rows, num_columns } = data;
|
36
|
+
const { data } = item;
|
37
|
+
const { columns, source, num_rows, num_columns, name } = data;
|
37
38
|
const shape = [
|
38
39
|
num_rows == null ? undefined : `${num_rows} rows`,
|
39
40
|
num_columns == null ? undefined : `${num_columns} columns`,
|
@@ -41,16 +42,23 @@ export class TableContextProvider extends AIContextProvider<TableContextItem> {
|
|
41
42
|
.filter(Boolean)
|
42
43
|
.join(", ");
|
43
44
|
|
44
|
-
let
|
45
|
+
let details = "";
|
45
46
|
if (shape) {
|
46
|
-
|
47
|
+
details += `\nShape: ${shape}`;
|
47
48
|
}
|
48
49
|
|
49
50
|
if (columns && columns.length > 0) {
|
50
|
-
|
51
|
+
details += `\nColumns:\n${columns.map((col) => ` - ${col.name}: ${col.type}`).join("\n")}`;
|
51
52
|
}
|
52
53
|
|
53
|
-
return
|
54
|
+
return contextToXml({
|
55
|
+
type: this.contextType,
|
56
|
+
data: {
|
57
|
+
name: name,
|
58
|
+
source: source ?? "unknown",
|
59
|
+
},
|
60
|
+
details: details,
|
61
|
+
});
|
54
62
|
}
|
55
63
|
|
56
64
|
formatCompletion(item: TableContextItem): Completion {
|
@@ -5,6 +5,7 @@ import { createVariableInfoElement } from "@/core/codemirror/completion/variable
|
|
5
5
|
import type { DatasetTablesMap } from "@/core/datasets/data-source-connections";
|
6
6
|
import type { Variable, Variables } from "@/core/variables/types";
|
7
7
|
import { type AIContextItem, AIContextProvider } from "../registry";
|
8
|
+
import { contextToXml } from "../utils";
|
8
9
|
import { Boosts } from "./common";
|
9
10
|
|
10
11
|
export interface VariableContextItem extends AIContextItem {
|
@@ -65,8 +66,16 @@ export class VariableContextProvider extends AIContextProvider<VariableContextIt
|
|
65
66
|
}
|
66
67
|
|
67
68
|
formatContext(item: VariableContextItem): string {
|
68
|
-
const {
|
69
|
+
const { data } = item;
|
69
70
|
const { variable } = data;
|
70
|
-
return
|
71
|
+
return contextToXml({
|
72
|
+
type: this.contextType,
|
73
|
+
data: {
|
74
|
+
name: variable.name,
|
75
|
+
dataType: variable.dataType,
|
76
|
+
},
|
77
|
+
details:
|
78
|
+
variable.value == null ? undefined : JSON.stringify(variable.value),
|
79
|
+
});
|
71
80
|
}
|
72
81
|
}
|
@@ -0,0 +1,49 @@
|
|
1
|
+
/* Copyright 2024 Marimo. All rights reserved. */
|
2
|
+
|
3
|
+
export interface AiContextPayload {
|
4
|
+
type: string;
|
5
|
+
data: Record<string, unknown>;
|
6
|
+
details?: string;
|
7
|
+
}
|
8
|
+
|
9
|
+
// XML escaping utility
|
10
|
+
function escapeXml(unsafe: string): string {
|
11
|
+
return (
|
12
|
+
unsafe
|
13
|
+
// We don't escape these characters because this is for an LLM and they can interpret this just fine.
|
14
|
+
// .replaceAll("&", "&")
|
15
|
+
// .replaceAll('"', """)
|
16
|
+
// .replaceAll("'", "'")
|
17
|
+
.replaceAll("<", "<")
|
18
|
+
.replaceAll(">", ">")
|
19
|
+
);
|
20
|
+
}
|
21
|
+
|
22
|
+
// Convert a single Context object to XML
|
23
|
+
export function contextToXml(context: AiContextPayload): string {
|
24
|
+
const { type, data, details } = context;
|
25
|
+
|
26
|
+
// Start with opening tag
|
27
|
+
let xml = `<${type}`;
|
28
|
+
|
29
|
+
// Add data as attributes
|
30
|
+
for (const [key, value] of Object.entries(data)) {
|
31
|
+
const escapedValue = escapeXml(String(value));
|
32
|
+
if (value !== undefined) {
|
33
|
+
xml += ` ${key}="${escapedValue}"`;
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
// Close the opening tag
|
38
|
+
xml += ">";
|
39
|
+
|
40
|
+
// Add details as content if present
|
41
|
+
if (details) {
|
42
|
+
xml += escapeXml(details);
|
43
|
+
}
|
44
|
+
|
45
|
+
// Add closing tag
|
46
|
+
xml += `</${type}>`;
|
47
|
+
|
48
|
+
return xml;
|
49
|
+
}
|
package/src/core/cells/cells.ts
CHANGED
@@ -1489,7 +1489,7 @@ export const canUndoDeletesAtom = atom((get) =>
|
|
1489
1489
|
|
1490
1490
|
export const needsRunAtom = atom((get) => notebookNeedsRun(get(notebookAtom)));
|
1491
1491
|
|
1492
|
-
const cellErrorsAtom = atom((get) => {
|
1492
|
+
export const cellErrorsAtom = atom((get) => {
|
1493
1493
|
const { cellIds, cellRuntime, cellData } = get(notebookAtom);
|
1494
1494
|
const errors = cellIds.inOrderIds
|
1495
1495
|
.map((cellId) => {
|
@@ -1 +0,0 @@
|
|
1
|
-
import{b as m}from"./_baseEach-DJPM6ZzY.js";import{x as s}from"./index-CQFSXxPp.js";function e(r,o){var a=-1,t=s(r)?Array(r.length):[];return m(r,function(n,f,i){t[++a]=o(n,f,i)}),t}export{e as b};
|
@@ -1 +0,0 @@
|
|
1
|
-
import{U as s,C as o}from"./mermaid-CUkYs7pX.js";const n=(a,r)=>s.lang.round(o.parse(a)[r]);export{n as c};
|
@@ -1 +0,0 @@
|
|
1
|
-
import{s as a,c as s,a as t,C as o}from"./chunk-JBRWN2VN-CB8a_SQv.js";import{_ as e}from"./mermaid-CUkYs7pX.js";import"./transform-B8bpuzxV.js";import"./chunk-GLLZNHP4-CRWkmhQj.js";import"./chunk-WVR4S24B-ZnYlHppS.js";import"./chunk-NRVI72HA-nFtyi9pH.js";import"./index-CQFSXxPp.js";import"./step-BwsUM5iJ.js";import"./timer-BwIYMJWC.js";var i={parser:t,get db(){return new o},renderer:s,styles:a,init:e(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{i as diagram};
|
@@ -1 +0,0 @@
|
|
1
|
-
import{s as a,c as s,a as t,C as o}from"./chunk-JBRWN2VN-CB8a_SQv.js";import{_ as e}from"./mermaid-CUkYs7pX.js";import"./transform-B8bpuzxV.js";import"./chunk-GLLZNHP4-CRWkmhQj.js";import"./chunk-WVR4S24B-ZnYlHppS.js";import"./chunk-NRVI72HA-nFtyi9pH.js";import"./index-CQFSXxPp.js";import"./step-BwsUM5iJ.js";import"./timer-BwIYMJWC.js";var i={parser:t,get db(){return new o},renderer:s,styles:a,init:e(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{i as diagram};
|
@@ -1 +0,0 @@
|
|
1
|
-
import{b as n}from"./_baseUniq-CrXjB6JP.js";function o(r){return n(r,4)}export{o as c};
|
@@ -1 +0,0 @@
|
|
1
|
-
import{s as t,b as a,a as e,S as o}from"./chunk-LXBSTHXV-YNRk01ob.js";import{_ as s}from"./mermaid-CUkYs7pX.js";import"./transform-B8bpuzxV.js";import"./chunk-WVR4S24B-ZnYlHppS.js";import"./chunk-NRVI72HA-nFtyi9pH.js";import"./index-CQFSXxPp.js";import"./step-BwsUM5iJ.js";import"./timer-BwIYMJWC.js";var i={parser:e,get db(){return new o(2)},renderer:a,styles:t,init:s(r=>{r.state||(r.state={}),r.state.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{i as diagram};
|