@marimo-team/islands 0.23.15-dev49 → 0.23.15-dev50
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-7p7rt9iw.js → ConnectedDataExplorerComponent-Cmq_9LVS.js} +4 -4
- package/dist/{ErrorBoundary-B_CAG7_e.js → ErrorBoundary-CnMJ8jR9.js} +1 -1
- package/dist/{chat-ui-C0z13gJB.js → chat-ui-Dy0arY8E.js} +6 -6
- package/dist/{code-visibility-CoTgpfnd.js → code-visibility-B5xe1Xp2.js} +9 -9
- package/dist/{formats-CitsMtUm.js → formats-CdvkxTS6.js} +1 -1
- package/dist/{glide-data-editor-CwZz71BD.js → glide-data-editor-D5w23DvC.js} +3 -3
- package/dist/{html-to-image-CEo5pRYw.js → html-to-image-f-kPEnn0.js} +5 -5
- package/dist/{input-BGPrFH3g.js → input-CF5Sgjba.js} +1 -1
- package/dist/main.js +19 -18
- package/dist/{mermaid-UdmxG2PZ.js → mermaid-BaZJ2mds.js} +2 -2
- package/dist/{process-output-BOvvilRG.js → process-output-O9FeFAVM.js} +1 -1
- package/dist/{reveal-component-Bvigsc8L.js → reveal-component-BkZp_qsV.js} +5 -5
- package/dist/{spec-DSs9v0xx.js → spec-DxIove3q.js} +1 -1
- package/dist/style.css +1 -1
- package/dist/{toDate-BRJgtOGm.js → toDate-CEOdz9nC.js} +1 -1
- package/dist/{useAsyncData-BSOyAbac.js → useAsyncData-Cs0mpJTy.js} +1 -1
- package/dist/{useDeepCompareMemoize-BTLeWzuR.js → useDeepCompareMemoize-DoOZicGb.js} +1 -1
- package/dist/{useLifecycle-C6wHjkhW.js → useLifecycle-DR_F1d7F.js} +1 -1
- package/dist/{useTheme-Df_vGflw.js → useTheme-BA-8MM7N.js} +1 -0
- package/dist/{vega-component-BAvmvTjO.js → vega-component-BN0fz-QA.js} +5 -5
- package/package.json +1 -1
- package/src/__mocks__/requests.ts +3 -0
- package/src/components/editor/actions/useNotebookActions.tsx +8 -0
- package/src/components/editor/chrome/components/__tests__/feedback-button.test.tsx +221 -0
- package/src/components/editor/chrome/components/feedback-button.tsx +308 -77
- package/src/core/ai/context/providers/error.ts +6 -158
- package/src/core/constants.ts +2 -0
- package/src/core/diagnostics/__tests__/issue-details.test.ts +173 -0
- package/src/core/diagnostics/issue-details.ts +139 -0
- package/src/core/errors/__tests__/error-entries.test.ts +25 -0
- package/src/core/errors/error-entries.ts +176 -0
- package/src/core/islands/bridge.ts +1 -0
- package/src/core/network/__tests__/requests-lazy.test.ts +18 -0
- package/src/core/network/__tests__/requests-network.test.ts +7 -0
- package/src/core/network/requests-lazy.ts +13 -1
- package/src/core/network/requests-network.ts +3 -0
- package/src/core/network/requests-static.ts +1 -0
- package/src/core/network/requests-toasting.tsx +1 -0
- package/src/core/network/types.ts +3 -0
- package/src/core/wasm/bridge.ts +8 -0
- package/src/core/wasm/worker/types.ts +2 -0
|
@@ -4,16 +4,75 @@ import { Slot as SlotPrimitive } from "radix-ui";
|
|
|
4
4
|
|
|
5
5
|
const Slot = SlotPrimitive.Slot;
|
|
6
6
|
|
|
7
|
-
import
|
|
7
|
+
import { useAtomValue } from "jotai";
|
|
8
|
+
import { CheckIcon, ExternalLinkIcon, TriangleAlertIcon } from "lucide-react";
|
|
9
|
+
import React, { type PropsWithChildren, useMemo, useState } from "react";
|
|
10
|
+
import { CopyClipboardIcon } from "@/components/icons/copy-icon";
|
|
8
11
|
import { useImperativeModal } from "@/components/modal/ImperativeModal";
|
|
12
|
+
import { Button } from "@/components/ui/button";
|
|
13
|
+
import { Checkbox } from "@/components/ui/checkbox";
|
|
9
14
|
import {
|
|
10
15
|
DialogContent,
|
|
11
16
|
DialogDescription,
|
|
12
17
|
DialogHeader,
|
|
13
18
|
DialogTitle,
|
|
14
19
|
} from "@/components/ui/dialog";
|
|
20
|
+
import { Skeleton } from "@/components/ui/skeleton";
|
|
15
21
|
import { toast } from "@/components/ui/use-toast";
|
|
22
|
+
import { notebookAtom } from "@/core/cells/cells";
|
|
16
23
|
import { Constants } from "@/core/constants";
|
|
24
|
+
import {
|
|
25
|
+
buildBugReportUrl,
|
|
26
|
+
buildIssueDetails,
|
|
27
|
+
createPartialEnvironment,
|
|
28
|
+
type EnvironmentDiagnostics,
|
|
29
|
+
enrichEnvironment,
|
|
30
|
+
type NotebookSource,
|
|
31
|
+
} from "@/core/diagnostics/issue-details";
|
|
32
|
+
import {
|
|
33
|
+
formatCellError,
|
|
34
|
+
getCellErrorEntries,
|
|
35
|
+
} from "@/core/errors/error-entries";
|
|
36
|
+
import { useNotebookCodeAvailable } from "@/core/meta/code-visibility";
|
|
37
|
+
import { getMarimoVersion } from "@/core/meta/globals";
|
|
38
|
+
import { connectionAtom } from "@/core/network/connection";
|
|
39
|
+
import { useRequestClient } from "@/core/network/requests";
|
|
40
|
+
import { filenameAtom } from "@/core/saving/file-state";
|
|
41
|
+
import { store } from "@/core/state/jotai";
|
|
42
|
+
import { WebSocketState } from "@/core/websocket/types";
|
|
43
|
+
import { useAsyncData } from "@/hooks/useAsyncData";
|
|
44
|
+
import { cn } from "@/utils/cn";
|
|
45
|
+
import { copyToClipboard } from "@/utils/copy";
|
|
46
|
+
|
|
47
|
+
const CollapsiblePreview: React.FC<{ content: string }> = ({ content }) => {
|
|
48
|
+
const [expanded, setExpanded] = useState(false);
|
|
49
|
+
return (
|
|
50
|
+
<div className="flex flex-col gap-1">
|
|
51
|
+
<div className="relative">
|
|
52
|
+
<pre
|
|
53
|
+
className={cn(
|
|
54
|
+
"text-xs bg-muted rounded p-2 overflow-x-auto whitespace-pre-wrap",
|
|
55
|
+
!expanded && "max-h-24 overflow-hidden",
|
|
56
|
+
)}
|
|
57
|
+
>
|
|
58
|
+
{content}
|
|
59
|
+
</pre>
|
|
60
|
+
{!expanded && (
|
|
61
|
+
<div className="pointer-events-none absolute inset-x-0 bottom-0 h-10 rounded-b bg-gradient-to-b from-transparent to-muted" />
|
|
62
|
+
)}
|
|
63
|
+
</div>
|
|
64
|
+
<Button
|
|
65
|
+
type="button"
|
|
66
|
+
variant="link"
|
|
67
|
+
size="xs"
|
|
68
|
+
className="self-start"
|
|
69
|
+
onClick={() => setExpanded((value) => !value)}
|
|
70
|
+
>
|
|
71
|
+
{expanded ? "Show less" : "Show more"}
|
|
72
|
+
</Button>
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
17
76
|
|
|
18
77
|
export const FeedbackButton: React.FC<PropsWithChildren> = ({ children }) => {
|
|
19
78
|
const { openModal, closeModal } = useImperativeModal();
|
|
@@ -25,84 +84,256 @@ export const FeedbackButton: React.FC<PropsWithChildren> = ({ children }) => {
|
|
|
25
84
|
);
|
|
26
85
|
};
|
|
27
86
|
|
|
28
|
-
const FeedbackModal: React.FC<{
|
|
87
|
+
export const FeedbackModal: React.FC<{
|
|
29
88
|
onClose: () => void;
|
|
30
|
-
}> = (
|
|
89
|
+
}> = () => {
|
|
90
|
+
const { getEnvironmentInfo, readCode } = useRequestClient();
|
|
91
|
+
const environmentRequest = useAsyncData(
|
|
92
|
+
async () => getEnvironmentInfo(),
|
|
93
|
+
[getEnvironmentInfo],
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
const notebook = useAtomValue(notebookAtom);
|
|
97
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: recompute when the notebook changes
|
|
98
|
+
const errors = useMemo(() => getCellErrorEntries(store), [notebook]);
|
|
99
|
+
|
|
100
|
+
const cells = notebook.cellIds.inOrderIds.map(
|
|
101
|
+
(cellId) => notebook.cellData[cellId],
|
|
102
|
+
);
|
|
103
|
+
const codeAvailable = useNotebookCodeAvailable(cells);
|
|
104
|
+
const filename = useAtomValue(filenameAtom);
|
|
105
|
+
const connection = useAtomValue(connectionAtom);
|
|
106
|
+
const notebookSourceAvailable =
|
|
107
|
+
filename !== null &&
|
|
108
|
+
codeAvailable &&
|
|
109
|
+
connection.state === WebSocketState.OPEN;
|
|
110
|
+
|
|
111
|
+
const [includeNotebook, setIncludeNotebook] = useState(false);
|
|
112
|
+
|
|
113
|
+
const notebookSourceReason = notebookSourceAvailable
|
|
114
|
+
? undefined
|
|
115
|
+
: filename === null
|
|
116
|
+
? "Save the notebook first."
|
|
117
|
+
: !codeAvailable
|
|
118
|
+
? "Notebook source is hidden in this view."
|
|
119
|
+
: "Connect the notebook to include its source.";
|
|
120
|
+
|
|
121
|
+
const notebookSourceRequest = useAsyncData(async () => {
|
|
122
|
+
if (!includeNotebook || !notebookSourceAvailable || filename === null) {
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
const { contents } = await readCode();
|
|
126
|
+
return { filename, contents } satisfies NotebookSource;
|
|
127
|
+
}, [includeNotebook, notebookSourceAvailable, filename, readCode]);
|
|
128
|
+
|
|
129
|
+
const environment: EnvironmentDiagnostics | undefined =
|
|
130
|
+
environmentRequest.data
|
|
131
|
+
? enrichEnvironment(environmentRequest.data, navigator.userAgent)
|
|
132
|
+
: environmentRequest.status === "error"
|
|
133
|
+
? createPartialEnvironment(
|
|
134
|
+
getMarimoVersion(),
|
|
135
|
+
navigator.userAgent,
|
|
136
|
+
navigator.language,
|
|
137
|
+
"Server environment information unavailable",
|
|
138
|
+
)
|
|
139
|
+
: undefined;
|
|
140
|
+
|
|
141
|
+
const [issueDetailsCopied, setIssueDetailsCopied] = useState(false);
|
|
142
|
+
|
|
143
|
+
const githubIssueUrl = useMemo(() => {
|
|
144
|
+
if (!environment) {
|
|
145
|
+
return Constants.bugReportUrl;
|
|
146
|
+
}
|
|
147
|
+
return buildBugReportUrl(
|
|
148
|
+
Constants.bugReportUrl,
|
|
149
|
+
buildIssueDetails({ environment, errors }),
|
|
150
|
+
);
|
|
151
|
+
}, [environment, errors]);
|
|
152
|
+
|
|
153
|
+
const copyIssueDetails = async () => {
|
|
154
|
+
if (!environment) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const notebookForReport = includeNotebook
|
|
158
|
+
? notebookSourceRequest.data
|
|
159
|
+
: undefined;
|
|
160
|
+
await copyToClipboard(
|
|
161
|
+
buildIssueDetails({ environment, errors, notebook: notebookForReport }),
|
|
162
|
+
);
|
|
163
|
+
setIssueDetailsCopied(true);
|
|
164
|
+
setTimeout(() => setIssueDetailsCopied(false), 2000);
|
|
165
|
+
toast({
|
|
166
|
+
title:
|
|
167
|
+
environmentRequest.status === "error"
|
|
168
|
+
? "Partial issue details copied"
|
|
169
|
+
: "Issue details copied",
|
|
170
|
+
});
|
|
171
|
+
};
|
|
172
|
+
|
|
31
173
|
return (
|
|
32
|
-
<DialogContent className="w-
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
</
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
174
|
+
<DialogContent className="w-[540px] max-w-[90vw]">
|
|
175
|
+
<DialogHeader>
|
|
176
|
+
<DialogTitle>Report an issue</DialogTitle>
|
|
177
|
+
<DialogDescription>
|
|
178
|
+
Copy your environment and any current errors to include in a GitHub
|
|
179
|
+
bug report. Nothing is uploaded automatically; review the details
|
|
180
|
+
before posting.
|
|
181
|
+
</DialogDescription>
|
|
182
|
+
</DialogHeader>
|
|
183
|
+
|
|
184
|
+
<div className="flex flex-col gap-4 max-h-[60vh] overflow-y-auto">
|
|
185
|
+
<div className="flex flex-wrap gap-2">
|
|
186
|
+
<Button
|
|
187
|
+
type="button"
|
|
188
|
+
variant="default"
|
|
189
|
+
size="xs"
|
|
190
|
+
disabled={
|
|
191
|
+
!environment ||
|
|
192
|
+
(includeNotebook && notebookSourceRequest.isFetching)
|
|
193
|
+
}
|
|
194
|
+
onClick={copyIssueDetails}
|
|
195
|
+
>
|
|
196
|
+
{issueDetailsCopied && (
|
|
197
|
+
<CheckIcon
|
|
198
|
+
className="w-4 h-4 mr-2 text-(--grass-11)"
|
|
199
|
+
color="white"
|
|
200
|
+
/>
|
|
201
|
+
)}
|
|
202
|
+
{issueDetailsCopied ? "Copied!" : "Copy issue details"}
|
|
203
|
+
</Button>
|
|
204
|
+
<Button type="button" variant="outline" size="xs" asChild={true}>
|
|
205
|
+
<a href={githubIssueUrl} target="_blank" rel="noreferrer">
|
|
206
|
+
<ExternalLinkIcon className="w-4 h-4 mr-2" />
|
|
207
|
+
Open GitHub issue
|
|
208
|
+
</a>
|
|
209
|
+
</Button>
|
|
210
|
+
</div>
|
|
211
|
+
|
|
212
|
+
{includeNotebook && (
|
|
213
|
+
<p className="text-xs text-muted-foreground">
|
|
214
|
+
The GitHub link prefills your environment only. Use Copy issue
|
|
215
|
+
details to include the full notebook source.
|
|
216
|
+
</p>
|
|
217
|
+
)}
|
|
218
|
+
|
|
219
|
+
{environmentRequest.status === "pending" && (
|
|
220
|
+
<div className="flex flex-col gap-2">
|
|
221
|
+
<span className="text-sm text-muted-foreground">
|
|
222
|
+
Loading environment details…
|
|
223
|
+
</span>
|
|
224
|
+
<Skeleton className="h-4 w-full" />
|
|
225
|
+
<Skeleton className="h-4 w-3/4" />
|
|
226
|
+
<Skeleton className="h-4 w-1/2" />
|
|
227
|
+
</div>
|
|
228
|
+
)}
|
|
229
|
+
|
|
230
|
+
{environmentRequest.status === "error" && (
|
|
231
|
+
<div className="flex items-center gap-2 text-sm">
|
|
232
|
+
<TriangleAlertIcon className="w-4 h-4 text-(--yellow-11) shrink-0" />
|
|
233
|
+
<span>Server environment information unavailable</span>
|
|
234
|
+
<Button
|
|
235
|
+
type="button"
|
|
236
|
+
variant="link"
|
|
237
|
+
size="xs"
|
|
238
|
+
onClick={() => environmentRequest.refetch()}
|
|
239
|
+
>
|
|
240
|
+
Retry
|
|
241
|
+
</Button>
|
|
242
|
+
</div>
|
|
243
|
+
)}
|
|
244
|
+
|
|
245
|
+
{environment && (
|
|
246
|
+
<div className="flex flex-col gap-1">
|
|
247
|
+
<div className="flex items-center justify-between">
|
|
248
|
+
<span className="text-sm font-medium">Environment details</span>
|
|
249
|
+
<CopyClipboardIcon
|
|
250
|
+
className="w-3.5 h-3.5"
|
|
251
|
+
value={JSON.stringify(environment, null, 2)}
|
|
252
|
+
ariaLabel="Copy environment JSON"
|
|
253
|
+
toastTitle="Environment details copied"
|
|
254
|
+
/>
|
|
255
|
+
</div>
|
|
256
|
+
<CollapsiblePreview
|
|
257
|
+
content={JSON.stringify(environment, null, 2)}
|
|
258
|
+
/>
|
|
259
|
+
</div>
|
|
260
|
+
)}
|
|
261
|
+
|
|
262
|
+
{environment && errors.length > 0 && (
|
|
263
|
+
<div className="flex flex-col gap-1">
|
|
264
|
+
<span className="text-sm font-medium">Current errors</span>
|
|
265
|
+
<CollapsiblePreview
|
|
266
|
+
content={errors.map(formatCellError).join("\n\n---\n\n")}
|
|
267
|
+
/>
|
|
268
|
+
</div>
|
|
269
|
+
)}
|
|
270
|
+
|
|
271
|
+
{environment && errors.length === 0 && (
|
|
272
|
+
<span className="text-sm text-muted-foreground">
|
|
273
|
+
No current errors detected.
|
|
274
|
+
</span>
|
|
275
|
+
)}
|
|
276
|
+
|
|
277
|
+
<div className="flex flex-col gap-1">
|
|
278
|
+
<div className="flex items-start gap-2 text-sm">
|
|
279
|
+
<Checkbox
|
|
280
|
+
id="issue-include-notebook"
|
|
281
|
+
className="mt-0.5"
|
|
282
|
+
checked={includeNotebook}
|
|
283
|
+
disabled={!notebookSourceAvailable}
|
|
284
|
+
onCheckedChange={(checked) =>
|
|
285
|
+
setIncludeNotebook(checked === true)
|
|
286
|
+
}
|
|
287
|
+
aria-label="Include full notebook source"
|
|
288
|
+
/>
|
|
289
|
+
<label htmlFor="issue-include-notebook">
|
|
290
|
+
Include full notebook source
|
|
291
|
+
</label>
|
|
292
|
+
</div>
|
|
293
|
+
<p className="text-xs text-muted-foreground ml-6">
|
|
294
|
+
Copies the entire saved Python source, including comments, literal
|
|
295
|
+
data, embedded credentials, and package metadata. Outputs and
|
|
296
|
+
external files are not included. Review it before posting.{" "}
|
|
297
|
+
<span className="font-bold">
|
|
298
|
+
For private notebooks, paste a minimal reproduction instead.
|
|
299
|
+
</span>
|
|
300
|
+
</p>
|
|
301
|
+
{notebookSourceReason && (
|
|
302
|
+
<span className="text-xs text-muted-foreground ml-6">
|
|
303
|
+
{notebookSourceReason}
|
|
304
|
+
</span>
|
|
305
|
+
)}
|
|
306
|
+
{includeNotebook && notebookSourceRequest.status === "error" && (
|
|
307
|
+
<span className="text-xs text-(--red-11) ml-6">
|
|
308
|
+
Notebook source could not be loaded.
|
|
309
|
+
</span>
|
|
310
|
+
)}
|
|
311
|
+
</div>
|
|
312
|
+
|
|
313
|
+
<div className="border-t pt-3">
|
|
314
|
+
<p className="text-sm text-muted-foreground">
|
|
315
|
+
Other feedback? Take our{" "}
|
|
316
|
+
<a
|
|
317
|
+
href={Constants.feedbackForm}
|
|
318
|
+
target="_blank"
|
|
319
|
+
rel="noreferrer"
|
|
320
|
+
className="underline"
|
|
321
|
+
>
|
|
322
|
+
two-minute survey
|
|
323
|
+
</a>{" "}
|
|
324
|
+
or chat with us on{" "}
|
|
325
|
+
<a
|
|
326
|
+
href={Constants.discordLink}
|
|
327
|
+
target="_blank"
|
|
328
|
+
rel="noreferrer"
|
|
329
|
+
className="underline"
|
|
330
|
+
>
|
|
331
|
+
Discord
|
|
332
|
+
</a>
|
|
333
|
+
.
|
|
334
|
+
</p>
|
|
335
|
+
</div>
|
|
336
|
+
</div>
|
|
106
337
|
</DialogContent>
|
|
107
338
|
);
|
|
108
339
|
};
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
3
|
import type { Completion } from "@codemirror/autocomplete";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
import {
|
|
5
|
+
type CellErrorEntry,
|
|
6
|
+
describeError,
|
|
7
|
+
formatSingleError,
|
|
8
|
+
getCellErrorEntries,
|
|
9
|
+
} from "@/core/errors/error-entries";
|
|
9
10
|
import type { JotaiStore } from "@/core/state/jotai";
|
|
10
|
-
import { logNever } from "@/utils/assertNever";
|
|
11
11
|
import { parseHtmlContent } from "@/utils/dom";
|
|
12
12
|
import { PluralWord } from "@/utils/pluralize";
|
|
13
13
|
import { type AIContextItem, AIContextProvider } from "../registry";
|
|
@@ -15,14 +15,6 @@ import { contextToXml } from "../utils";
|
|
|
15
15
|
import { Sections } from "./common";
|
|
16
16
|
import { formatDatasourceContextForCell } from "./datasource";
|
|
17
17
|
|
|
18
|
-
export interface CellErrorEntry {
|
|
19
|
-
cellId: CellId;
|
|
20
|
-
cellName: string;
|
|
21
|
-
cellCode: string;
|
|
22
|
-
errorData: MarimoError[];
|
|
23
|
-
tracebackHtml?: string;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
18
|
export type ErrorContextItemData =
|
|
27
19
|
| {
|
|
28
20
|
type: "all-errors";
|
|
@@ -38,150 +30,6 @@ export interface ErrorContextItem extends AIContextItem {
|
|
|
38
30
|
data: ErrorContextItemData;
|
|
39
31
|
}
|
|
40
32
|
|
|
41
|
-
function parseCellErrorOutput(
|
|
42
|
-
output: OutputMessage,
|
|
43
|
-
): Pick<CellErrorEntry, "errorData" | "tracebackHtml"> | null {
|
|
44
|
-
if (isMarimoErrorsMime(output.mimetype)) {
|
|
45
|
-
if (!Array.isArray(output.data)) {
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
const errorData = output.data.filter(
|
|
49
|
-
(error) => !error.type.includes("ancestor"),
|
|
50
|
-
);
|
|
51
|
-
if (errorData.length === 0) {
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
return { errorData };
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (isTracebackMime(output.mimetype)) {
|
|
58
|
-
if (typeof output.data !== "string" || output.data.length === 0) {
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
return { errorData: [], tracebackHtml: output.data };
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function errorDataHasTraceback(errorData: MarimoError[]): boolean {
|
|
68
|
-
return errorData.some((error) => "traceback" in error && error.traceback);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function getTracebackFromConsole(
|
|
72
|
-
consoleOutputs: OutputMessage[] | undefined,
|
|
73
|
-
): string | undefined {
|
|
74
|
-
const tracebackOutput = consoleOutputs?.find((output) =>
|
|
75
|
-
isTracebackMime(output.mimetype),
|
|
76
|
-
);
|
|
77
|
-
if (
|
|
78
|
-
!tracebackOutput ||
|
|
79
|
-
typeof tracebackOutput.data !== "string" ||
|
|
80
|
-
tracebackOutput.data.length === 0
|
|
81
|
-
) {
|
|
82
|
-
return undefined;
|
|
83
|
-
}
|
|
84
|
-
return tracebackOutput.data;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function resolveTracebackHtml(
|
|
88
|
-
parsed: Pick<CellErrorEntry, "errorData" | "tracebackHtml">,
|
|
89
|
-
consoleOutputs: OutputMessage[] | undefined,
|
|
90
|
-
): string | undefined {
|
|
91
|
-
if (parsed.tracebackHtml) {
|
|
92
|
-
return parsed.tracebackHtml;
|
|
93
|
-
}
|
|
94
|
-
if (errorDataHasTraceback(parsed.errorData)) {
|
|
95
|
-
return undefined;
|
|
96
|
-
}
|
|
97
|
-
return getTracebackFromConsole(consoleOutputs);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export function getCellErrorEntries(store: JotaiStore): CellErrorEntry[] {
|
|
101
|
-
const { cellIds, cellRuntime, cellData } = store.get(notebookAtom);
|
|
102
|
-
const entries: CellErrorEntry[] = [];
|
|
103
|
-
|
|
104
|
-
for (const [cellIndex, cellId] of cellIds.inOrderIds.entries()) {
|
|
105
|
-
const cell = cellRuntime[cellId];
|
|
106
|
-
const output = cell.output;
|
|
107
|
-
if (!output || !isErrorMime(output.mimetype)) {
|
|
108
|
-
continue;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const parsed = parseCellErrorOutput(output);
|
|
112
|
-
if (!parsed) {
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const cellName = displayCellName(cellData[cellId].name, cellIndex);
|
|
117
|
-
// Prefer the code from the last execution: runtime errors correspond to
|
|
118
|
-
// the previous run, while `code` may already contain unsaved edits.
|
|
119
|
-
const cellCode = cellData[cellId].lastCodeRun ?? cellData[cellId].code;
|
|
120
|
-
|
|
121
|
-
entries.push({
|
|
122
|
-
cellId,
|
|
123
|
-
cellName,
|
|
124
|
-
cellCode,
|
|
125
|
-
errorData: parsed.errorData,
|
|
126
|
-
tracebackHtml: resolveTracebackHtml(parsed, cell.consoleOutputs),
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return entries;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function describeError(error: MarimoError): string {
|
|
134
|
-
if (error.type === "setup-refs") {
|
|
135
|
-
return "The setup cell cannot have references";
|
|
136
|
-
}
|
|
137
|
-
if (error.type === "cycle") {
|
|
138
|
-
return "This cell is in a cycle";
|
|
139
|
-
}
|
|
140
|
-
if (error.type === "multiple-defs") {
|
|
141
|
-
return `The variable '${error.name}' was defined by another cell`;
|
|
142
|
-
}
|
|
143
|
-
if (error.type === "import-star") {
|
|
144
|
-
return error.msg;
|
|
145
|
-
}
|
|
146
|
-
if (error.type === "ancestor-stopped") {
|
|
147
|
-
return error.msg;
|
|
148
|
-
}
|
|
149
|
-
if (error.type === "ancestor-prevented") {
|
|
150
|
-
return error.msg;
|
|
151
|
-
}
|
|
152
|
-
if (error.type === "exception") {
|
|
153
|
-
return error.msg;
|
|
154
|
-
}
|
|
155
|
-
if (error.type === "strict-exception") {
|
|
156
|
-
return error.msg;
|
|
157
|
-
}
|
|
158
|
-
if (error.type === "interruption") {
|
|
159
|
-
return "This cell was interrupted and needs to be re-run";
|
|
160
|
-
}
|
|
161
|
-
if (error.type === "syntax") {
|
|
162
|
-
return error.msg;
|
|
163
|
-
}
|
|
164
|
-
if (error.type === "unknown") {
|
|
165
|
-
return error.msg;
|
|
166
|
-
}
|
|
167
|
-
if (error.type === "sql-error") {
|
|
168
|
-
return error.msg;
|
|
169
|
-
}
|
|
170
|
-
if (error.type === "internal") {
|
|
171
|
-
return error.msg || "An internal error occurred";
|
|
172
|
-
}
|
|
173
|
-
logNever(error);
|
|
174
|
-
return "Unknown error";
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function formatSingleError(error: MarimoError): string {
|
|
178
|
-
let text = describeError(error);
|
|
179
|
-
if ("traceback" in error && error.traceback) {
|
|
180
|
-
text += `\n\nTraceback:\n${parseHtmlContent(error.traceback)}`;
|
|
181
|
-
}
|
|
182
|
-
return text;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
33
|
function formatCellErrorDetails(
|
|
186
34
|
entry: CellErrorEntry,
|
|
187
35
|
store: JotaiStore,
|
package/src/core/constants.ts
CHANGED
|
@@ -3,6 +3,8 @@ export const Constants = {
|
|
|
3
3
|
githubPage: "https://github.com/marimo-team/marimo",
|
|
4
4
|
releasesPage: "https://github.com/marimo-team/marimo/releases",
|
|
5
5
|
issuesPage: "https://github.com/marimo-team/marimo/issues",
|
|
6
|
+
bugReportUrl:
|
|
7
|
+
"https://github.com/marimo-team/marimo/issues/new?template=bug_report.yaml",
|
|
6
8
|
feedbackForm: "https://marimo.io/feedback",
|
|
7
9
|
discordLink: "https://marimo.io/discord?ref=notebook",
|
|
8
10
|
docsPage: "https://docs.marimo.io",
|