@marimo-team/islands 0.23.17-dev4 → 0.23.17-dev40
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-f9vLjQiF.js → ConnectedDataExplorerComponent-BBZRR1-C.js} +16 -16
- package/dist/{ErrorBoundary-DhLqwSWA.js → ErrorBoundary-CptUP1_t.js} +3 -3
- package/dist/{ImageComparisonComponent-C3y7L3eJ.js → ImageComparisonComponent-fdY0LUuf.js} +38 -27
- package/dist/{any-language-editor-DifE-fl7.js → any-language-editor-BnIS9Mvg.js} +21 -21
- package/dist/assets/__vite-browser-external-DUz7vvG0.js +1 -0
- package/dist/assets/{worker-CDEKsBgX.js → worker-Cl1KJG2_.js} +2 -2
- package/dist/{button-BSm8IXRU.js → button-8aS1QcHr.js} +4 -2
- package/dist/{capabilities-C6OQ5YBO.js → capabilities-D23lgbH_.js} +1 -1
- package/dist/chat-ui-DH9M4L-6.js +8620 -0
- package/dist/{check-DP7U0H0G.js → check-D-BaEjEh.js} +1 -1
- package/dist/{common-BcQ4kHDP.js → common-DSVkRiDF.js} +652 -652
- package/dist/{copy-DNq8q3zw.js → copy-BkA3t7rJ.js} +2 -2
- package/dist/{defaultLocale-BpsHxBd7.js → defaultLocale-BsfI21d_.js} +26 -26
- package/dist/{diagram-PSM6KHXK-D4ftnaua.js → diagram-PSM6KHXK-D3GsWNcQ.js} +1 -1
- package/dist/{dist-BpeiYpG2.js → dist-3F1XDSYc.js} +2 -2
- package/dist/{dist-BP39XM8u.js → dist-B-upo54Y.js} +1 -1
- package/dist/{dist-BdqCKQ1O.js → dist-B1W12VzQ.js} +2 -2
- package/dist/{dist-3NCqaWjt.js → dist-B6au1PTB.js} +2 -2
- package/dist/{dist-BCX980mU.js → dist-BDwn6WIk.js} +2 -2
- package/dist/dist-BDzVdDo2.js +8 -0
- package/dist/dist-BH2Tq1S1.js +5 -0
- package/dist/dist-BIhBrzqg.js +5 -0
- package/dist/dist-BLCsvXTG.js +6 -0
- package/dist/dist-BNVtuFdd.js +5 -0
- package/dist/{dist-BOMbzyb3.js → dist-BNeJ8P-4.js} +3 -3
- package/dist/{dist-LpLXa9Lb.js → dist-BT96P4oR.js} +2 -2
- package/dist/{dist-CFSb_Frm.js → dist-BUzxp0Xf.js} +1 -1
- package/dist/{dist-JGehap7K.js → dist-Ba1WFESi.js} +5 -5
- package/dist/{dist-CRmSjkLP.js → dist-BrKNRKC8.js} +2 -2
- package/dist/{dist-BIEt-mlF.js → dist-BvvQUFUK.js} +1 -1
- package/dist/{dist-pqIwD8VD.js → dist-BxR-g1ig.js} +2 -2
- package/dist/{dist-DviUBLUp.js → dist-CBraAJlT.js} +4 -4
- package/dist/{dist-BwhXBwMd.js → dist-CDBV8n0I.js} +1 -1
- package/dist/dist-CEJ6Lowi.js +5 -0
- package/dist/{dist-CSlJrrAw.js → dist-CWYsUX_O.js} +1 -1
- package/dist/dist-CYoa69kj.js +5 -0
- package/dist/{dist-DwqBJ014.js → dist-CdKRSqjo.js} +2 -2
- package/dist/{dist-De-GlSAs.js → dist-Cds3J-EE.js} +15 -9
- package/dist/dist-CnZyXFq3.js +8 -0
- package/dist/dist-D-4HaJKc.js +8 -0
- package/dist/{dist-C18MUWTj.js → dist-D8SX5Qdg.js} +2 -2
- package/dist/{dist-D3bdsa1d.js → dist-DFQ91Mx1.js} +4 -4
- package/dist/{dist-BPF0gTRZ.js → dist-DGQmYLkh.js} +1 -1
- package/dist/dist-DIGyvbg3.js +5 -0
- package/dist/dist-DIhaoCL4.js +5 -0
- package/dist/{dist-C2_4pjuS.js → dist-DK0pFEmc.js} +1 -1
- package/dist/{dist-BTdrwBWP.js → dist-DQj6A7SE.js} +3 -3
- package/dist/{dist-DIlxLFXG.js → dist-DarwE0ZO.js} +11 -11
- package/dist/{dist-DaXJPX8F.js → dist-Ddb0VFS3.js} +2 -2
- package/dist/dist-DgQleHmb.js +8 -0
- package/dist/{dist-DZwp8lro.js → dist-DgkJegw4.js} +3 -3
- package/dist/{dist-BO5HRiTf.js → dist-DjQRRttD.js} +2 -2
- package/dist/dist-JaoLzZmt.js +5 -0
- package/dist/{dist-B2jmQKKx.js → dist-PQDZaPEo.js} +2 -2
- package/dist/{dist-4lVIWUT3.js → dist-Slyx4vXr.js} +1 -1
- package/dist/{dist-DgjbnqTg.js → dist-dJeh9ef4.js} +2 -2
- package/dist/dist-pEX0t8ZV.js +6 -0
- package/dist/{dist-w_gWE5Wf.js → dist-v-kAJfMq.js} +1 -1
- package/dist/{error-banner-B_GQfxxD.js → error-banner-dmkbm21G.js} +2 -2
- package/dist/{esm-DzZVaNH4.js → esm-D8vjxCyn.js} +3 -3
- package/dist/{esm-DKOv6P2M.js → esm-DEQU-LUO.js} +22 -22
- package/dist/{extends-w2_S44bV.js → extends-DDOVg_b8.js} +5 -5
- package/dist/{formats-Dwq1THhr.js → formats-xc-Msa0v.js} +3 -3
- package/dist/{ganttDiagram-JELNMOA3-CXXCN7LN.js → ganttDiagram-JELNMOA3-D9ywbC5g.js} +3 -3
- package/dist/{glide-data-editor-B9K6-MJe.js → glide-data-editor-CyTNN635.js} +9 -9
- package/dist/{html-to-image-CZ1kLKkq.js → html-to-image-DrEMf75G.js} +2219 -2209
- package/dist/{input-C5K4VBvB.js → input-B4ddy8Jy.js} +6 -6
- package/dist/{label-DjoYwqmj.js → label-CzHncDvS.js} +2 -2
- package/dist/{linear-H_vsH9ao.js → linear-BOGUO8Cd.js} +2 -2
- package/dist/{loader-DX-IDS7n.js → loader-C7SfUdOD.js} +10 -10
- package/dist/main.js +729 -733
- package/dist/{mermaid-hv6IckVw.js → mermaid-CpL8y5pw.js} +9 -9
- package/dist/{precisionRound-CnHPY_5v.js → precisionRound-Bq50n-bG.js} +1 -1
- package/dist/{process-output-bVfbcx5_.js → process-output-B9IHQuus.js} +3 -3
- package/dist/{quadrantDiagram-AYHSOK5B-BqDB9tUC.js → quadrantDiagram-AYHSOK5B-DD4QBdSv.js} +2 -2
- package/dist/{react-vega-BazDALhI.js → react-vega-Cg17A6rO.js} +8017 -4777
- package/dist/{reveal-component-9QGZq7uF.js → reveal-component-CUzZw4ZH.js} +19 -19
- package/dist/{spec-CQKL_D3i.js → spec-uMKNvMRZ.js} +4 -4
- package/dist/{strings-DIy9NAmg.js → strings-BUDhKc0s.js} +299 -299
- package/dist/style.css +1 -1
- package/dist/{swiper-component-CEdAYjb7.js → swiper-component-dl1H9WIW.js} +2 -2
- package/dist/{time-Bb4A7I6a.js → time-CQnohwNM.js} +2 -2
- package/dist/{toDate-CqhBWjvO.js → toDate-BiyuFdnT.js} +3 -3
- package/dist/{tooltip-f-qR0iUx.js → tooltip-CkCWlUOf.js} +3 -3
- package/dist/{types-DicH9Oi0.js → types-uENjXsiC.js} +1 -1
- package/dist/{useAsyncData-CYOQb1EW.js → useAsyncData-DqJLysjC.js} +1 -1
- package/dist/{useDateFormatter-Bt2qr_XS.js → useDateFormatter-BeN3fHMH.js} +2 -2
- package/dist/{useDeepCompareMemoize-CSHQeQEI.js → useDeepCompareMemoize-CvlmHAmy.js} +1 -1
- package/dist/{useIframeCapabilities-kA09ykNq.js → useIframeCapabilities-pLC-ceDP.js} +1 -1
- package/dist/{useLifecycle-CMJGqBaY.js → useLifecycle-D4N02q82.js} +3 -3
- package/dist/{useTheme-DYfby65G.js → useTheme-Dur2p3iC.js} +4 -3
- package/dist/{vega-component-C7nO5zcf.js → vega-component-Cft458Sr.js} +13 -13
- package/dist/{vega-embed-container-DAQAMjiO.js → vega-embed-container-S9gRu4TZ.js} +3 -3
- package/dist/{vega-loader.browser-DmpxCJqx.js → vega-loader.browser-CmLRI3wk.js} +104 -111
- package/dist/{xychartDiagram-PRI3JC2R-BSkNxYNw.js → xychartDiagram-PRI3JC2R-D9DgEtKn.js} +2 -2
- package/dist/{zod-BUcyXSdR.js → zod-yTHHzJiv.js} +37 -38
- package/package.json +6 -6
- package/src/__mocks__/requests.ts +5 -0
- package/src/components/app-config/user-config-form.tsx +26 -0
- package/src/components/chat/__tests__/chat-abort.test.ts +51 -0
- package/src/components/chat/chat-abort.ts +44 -0
- package/src/components/chat/chat-display.tsx +17 -0
- package/src/components/chat/chat-panel.tsx +85 -37
- package/src/components/chat/chat-utils.ts +1 -1
- package/src/components/data-table/charts/__tests__/schemas.test.ts +35 -0
- package/src/components/data-table/charts/schemas.ts +5 -2
- package/src/components/databases/icons/huggingface.svg +8 -0
- package/src/components/datasets/__tests__/icons.test.ts +21 -0
- package/src/components/datasets/icons.tsx +10 -0
- package/src/components/datasources/__tests__/components.test.tsx +29 -0
- package/src/components/datasources/components.tsx +8 -3
- package/src/components/editor/actions/export-dialog/__tests__/export-dialog.test.tsx +35 -0
- package/src/components/editor/actions/export-dialog/__tests__/state.test.ts +61 -0
- package/src/components/editor/actions/export-dialog/export-command.ts +4 -9
- package/src/components/editor/actions/export-dialog/export-dialog.tsx +27 -23
- package/src/components/editor/actions/export-dialog/format-notice.tsx +28 -0
- package/src/components/editor/actions/export-dialog/state.ts +23 -5
- package/src/components/editor/actions/export-dialog/use-export-dialog.ts +7 -15
- package/src/components/editor/actions/useConfigActions.tsx +24 -0
- package/src/components/editor/ai/add-cell-with-ai.tsx +2 -2
- package/src/components/editor/ai/ai-completion-editor.tsx +2 -2
- package/src/components/editor/chrome/panels/outline/__tests__/useActiveOutline.test.ts +26 -0
- package/src/components/editor/code/__tests__/readonly-python-code.test.tsx +114 -2
- package/src/components/editor/connections/storage/__tests__/__snapshots__/as-code.test.ts.snap +14 -0
- package/src/components/editor/connections/storage/__tests__/as-code.test.ts +20 -0
- package/src/components/editor/connections/storage/add-storage-form.tsx +10 -0
- package/src/components/editor/connections/storage/as-code.ts +19 -1
- package/src/components/editor/connections/storage/schemas.ts +19 -0
- package/src/components/storage/__tests__/storage-snippets.test.ts +88 -0
- package/src/components/storage/components.tsx +2 -0
- package/src/components/storage/storage-snippets.ts +58 -0
- package/src/core/__tests__/mode.test.ts +85 -0
- package/src/core/ai/constants.ts +7 -0
- package/src/core/ai/staged-cells.ts +3 -0
- package/src/core/codemirror/__tests__/setup.test.ts +1 -1
- package/src/core/codemirror/cm.ts +2 -2
- package/src/core/codemirror/code-lens/__tests__/extension.test.ts +4 -5
- package/src/core/codemirror/code-lens/extension.ts +3 -3
- package/src/core/codemirror/find-replace/search-highlight.ts +8 -3
- package/src/core/codemirror/language/languages/sql/__tests__/renderers.test.tsx +44 -0
- package/src/core/codemirror/language/languages/sql/renderers.tsx +3 -3
- package/src/core/codemirror/theme/__tests__/light.test.ts +59 -123
- package/src/core/codemirror/theme/dark.ts +6 -5
- package/src/core/codemirror/theme/light.ts +6 -4
- package/src/core/config/__tests__/config-schema.test.ts +2 -0
- package/src/core/config/config-schema.ts +1 -0
- package/src/core/config/feature-flag.tsx +0 -2
- package/src/core/dom/outline.ts +25 -2
- package/src/core/hotkeys/hotkeys.ts +2 -0
- package/src/core/mode.ts +11 -9
- package/src/core/network/__tests__/api.test.ts +132 -17
- package/src/core/network/__tests__/export-filename.test.ts +43 -0
- package/src/core/network/__tests__/requests-lazy.test.ts +1 -0
- package/src/core/network/api.ts +47 -12
- package/src/core/network/export-filename.ts +37 -0
- package/src/core/network/markdown-export.ts +19 -0
- package/src/core/network/requests-network.ts +29 -5
- package/src/core/network/types.ts +3 -0
- package/src/core/storage/types.ts +1 -0
- package/src/css/app/codemirror.css +11 -7
- package/src/css/globals.css +8 -0
- package/src/plugins/impl/DataEditorPlugin.tsx +2 -9
- package/src/plugins/impl/__tests__/DataEditorPlugin.test.ts +18 -0
- package/src/plugins/impl/chat/chat-ui.tsx +51 -3
- package/src/plugins/impl/data-frames/__tests__/schema.test.ts +30 -0
- package/src/plugins/impl/data-frames/schema.ts +4 -1
- package/src/plugins/impl/image-comparison/ImageComparisonComponent.tsx +20 -1
- package/src/plugins/impl/image-comparison/__tests__/ImageComparisonComponent.test.tsx +58 -0
- package/src/stories/theme.stories.tsx +71 -1
- package/dist/assets/__vite-browser-external-DltVXDv7.js +0 -1
- package/dist/chat-ui-DsZJj75A.js +0 -8369
- package/dist/dist-5o9uz19x.js +0 -5
- package/dist/dist-BpDH0DEP.js +0 -5
- package/dist/dist-BpXhbQXO.js +0 -8
- package/dist/dist-ByadAk_L.js +0 -5
- package/dist/dist-CK0g7JvP.js +0 -8
- package/dist/dist-CfzCtiq2.js +0 -5
- package/dist/dist-D8JtVFrW.js +0 -8
- package/dist/dist-DK2Oocpu.js +0 -6
- package/dist/dist-DnSu977r.js +0 -5
- package/dist/dist-UmxJm6sW.js +0 -8
- package/dist/dist-aYSBQSyM.js +0 -5
- package/dist/dist-kkZ1Ztlh.js +0 -6
- package/dist/dist-ngPczwNi.js +0 -5
- package/dist/dist-wQRyxyTV.js +0 -5
package/src/core/network/api.ts
CHANGED
|
@@ -16,13 +16,45 @@ function getBaseUriWithoutQueryParams(): string {
|
|
|
16
16
|
return url.toString();
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
function
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
if (!
|
|
23
|
-
|
|
19
|
+
function parseContentDispositionFilename(
|
|
20
|
+
contentDisposition: string | null,
|
|
21
|
+
): string | undefined {
|
|
22
|
+
if (!contentDisposition) {
|
|
23
|
+
return undefined;
|
|
24
24
|
}
|
|
25
|
-
|
|
25
|
+
|
|
26
|
+
const rfc5987Match = contentDisposition.match(/filename\*=UTF-8''([^;]+)/i);
|
|
27
|
+
if (rfc5987Match) {
|
|
28
|
+
try {
|
|
29
|
+
return decodeURIComponent(rfc5987Match[1]);
|
|
30
|
+
} catch {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const quotedMatch = contentDisposition.match(/filename="([^"]+)"/i);
|
|
36
|
+
if (quotedMatch) {
|
|
37
|
+
return quotedMatch[1];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const unquotedMatch = contentDisposition.match(/filename=([^;]+)/i);
|
|
41
|
+
if (unquotedMatch) {
|
|
42
|
+
return unquotedMatch[1].trim();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getExportFilename(response: Response): string | undefined {
|
|
49
|
+
const filename = parseContentDispositionFilename(
|
|
50
|
+
response.headers.get("Content-Disposition"),
|
|
51
|
+
);
|
|
52
|
+
if (!filename) {
|
|
53
|
+
Logger.warn(
|
|
54
|
+
"Export response is missing a readable Content-Disposition filename",
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
return filename;
|
|
26
58
|
}
|
|
27
59
|
|
|
28
60
|
/**
|
|
@@ -129,11 +161,14 @@ export const API = {
|
|
|
129
161
|
}
|
|
130
162
|
return Promise.resolve(response.data as T);
|
|
131
163
|
},
|
|
132
|
-
handleExportResponse: async <T extends BlobPart>(
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
164
|
+
handleExportResponse: async <T extends BlobPart>(
|
|
165
|
+
response: {
|
|
166
|
+
data?: T | undefined;
|
|
167
|
+
error?: Record<string, unknown>;
|
|
168
|
+
response: Response;
|
|
169
|
+
},
|
|
170
|
+
opts: { defaultFilename: string },
|
|
171
|
+
): Promise<ExportedFile<T>> => {
|
|
137
172
|
const contents = await API.handleResponse<T>(response);
|
|
138
173
|
const mediaType = response.response.headers.get("Content-Type");
|
|
139
174
|
if (!mediaType) {
|
|
@@ -142,7 +177,7 @@ export const API = {
|
|
|
142
177
|
|
|
143
178
|
return {
|
|
144
179
|
contents,
|
|
145
|
-
filename: getExportFilename(response.response),
|
|
180
|
+
filename: getExportFilename(response.response) ?? opts.defaultFilename,
|
|
146
181
|
mediaType,
|
|
147
182
|
};
|
|
148
183
|
},
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { filenameAtom } from "@/core/saving/file-state";
|
|
4
|
+
import { store } from "@/core/state/jotai";
|
|
5
|
+
import { Filenames } from "@/utils/filenames";
|
|
6
|
+
import { Paths } from "@/utils/paths";
|
|
7
|
+
import {
|
|
8
|
+
DEFAULT_MARKDOWN_FLAVOR,
|
|
9
|
+
MARKDOWN_EXTENSIONS,
|
|
10
|
+
} from "./markdown-export";
|
|
11
|
+
import type { ExportAsMarkdownRequest } from "./types";
|
|
12
|
+
|
|
13
|
+
const DEFAULT_EXPORT_FILENAME = "download";
|
|
14
|
+
|
|
15
|
+
function getNotebookStem(): string | undefined {
|
|
16
|
+
const filename = store.get(filenameAtom);
|
|
17
|
+
if (!filename) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
const basename = Paths.basename(filename);
|
|
21
|
+
return Filenames.withoutExtension(basename);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function getDefaultExportFilename(extension: string): string {
|
|
25
|
+
const stem = getNotebookStem();
|
|
26
|
+
if (stem) {
|
|
27
|
+
return `${stem}.${extension}`;
|
|
28
|
+
}
|
|
29
|
+
return `${DEFAULT_EXPORT_FILENAME}.${extension}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function getDefaultMarkdownExportFilename(
|
|
33
|
+
flavor: ExportAsMarkdownRequest["flavor"],
|
|
34
|
+
): string {
|
|
35
|
+
const extension = MARKDOWN_EXTENSIONS[flavor ?? DEFAULT_MARKDOWN_FLAVOR];
|
|
36
|
+
return getDefaultExportFilename(extension);
|
|
37
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import type { MarkdownExportFlavor } from "./types";
|
|
4
|
+
|
|
5
|
+
export const MARKDOWN_EXTENSIONS = {
|
|
6
|
+
pymdown: "md",
|
|
7
|
+
qmd: "qmd",
|
|
8
|
+
mystmd: "myst.md",
|
|
9
|
+
mdx: "mdx",
|
|
10
|
+
} satisfies Record<MarkdownExportFlavor, string>;
|
|
11
|
+
export const MARKDOWN_SUFFIXES = [
|
|
12
|
+
".myst.md",
|
|
13
|
+
".markdown",
|
|
14
|
+
".qmd",
|
|
15
|
+
".mdx",
|
|
16
|
+
".md",
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
export const DEFAULT_MARKDOWN_FLAVOR: MarkdownExportFlavor = "pymdown";
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
import { once } from "@/utils/once";
|
|
4
4
|
import { getRuntimeManager } from "../runtime/config";
|
|
5
5
|
import { API, createClientWithRuntimeManager } from "./api";
|
|
6
|
+
import {
|
|
7
|
+
getDefaultExportFilename,
|
|
8
|
+
getDefaultMarkdownExportFilename,
|
|
9
|
+
} from "./export-filename";
|
|
6
10
|
import {
|
|
7
11
|
waitForConnectionOpen,
|
|
8
12
|
waitForConnectionOpenIfNotebook,
|
|
@@ -426,7 +430,11 @@ export function createNetworkRequests(): EditRequests & RunRequests {
|
|
|
426
430
|
parseAs: "text",
|
|
427
431
|
params: getParams(),
|
|
428
432
|
})
|
|
429
|
-
.then(
|
|
433
|
+
.then((response) =>
|
|
434
|
+
handleExportResponse(response, {
|
|
435
|
+
defaultFilename: getDefaultExportFilename("html"),
|
|
436
|
+
}),
|
|
437
|
+
);
|
|
430
438
|
},
|
|
431
439
|
exportAsMarkdown: async (request) => {
|
|
432
440
|
return getClient()
|
|
@@ -435,7 +443,11 @@ export function createNetworkRequests(): EditRequests & RunRequests {
|
|
|
435
443
|
parseAs: "text",
|
|
436
444
|
params: getParams(),
|
|
437
445
|
})
|
|
438
|
-
.then(
|
|
446
|
+
.then((response) =>
|
|
447
|
+
handleExportResponse(response, {
|
|
448
|
+
defaultFilename: getDefaultMarkdownExportFilename(request.flavor),
|
|
449
|
+
}),
|
|
450
|
+
);
|
|
439
451
|
},
|
|
440
452
|
exportAsScript: async (request) => {
|
|
441
453
|
return getClient()
|
|
@@ -444,7 +456,11 @@ export function createNetworkRequests(): EditRequests & RunRequests {
|
|
|
444
456
|
parseAs: "text",
|
|
445
457
|
params: getParams(),
|
|
446
458
|
})
|
|
447
|
-
.then(
|
|
459
|
+
.then((response) =>
|
|
460
|
+
handleExportResponse(response, {
|
|
461
|
+
defaultFilename: getDefaultExportFilename("script.py"),
|
|
462
|
+
}),
|
|
463
|
+
);
|
|
448
464
|
},
|
|
449
465
|
exportAsIPYNB: async (request) => {
|
|
450
466
|
return getClient()
|
|
@@ -453,7 +469,11 @@ export function createNetworkRequests(): EditRequests & RunRequests {
|
|
|
453
469
|
parseAs: "text",
|
|
454
470
|
params: getParams(),
|
|
455
471
|
})
|
|
456
|
-
.then(
|
|
472
|
+
.then((response) =>
|
|
473
|
+
handleExportResponse(response, {
|
|
474
|
+
defaultFilename: getDefaultExportFilename("ipynb"),
|
|
475
|
+
}),
|
|
476
|
+
);
|
|
457
477
|
},
|
|
458
478
|
exportAsPDF: async (request) => {
|
|
459
479
|
return getClient()
|
|
@@ -462,7 +482,11 @@ export function createNetworkRequests(): EditRequests & RunRequests {
|
|
|
462
482
|
parseAs: "blob",
|
|
463
483
|
params: getParams(),
|
|
464
484
|
})
|
|
465
|
-
.then(
|
|
485
|
+
.then((response) =>
|
|
486
|
+
handleExportResponse(response, {
|
|
487
|
+
defaultFilename: getDefaultExportFilename("pdf"),
|
|
488
|
+
}),
|
|
489
|
+
);
|
|
466
490
|
},
|
|
467
491
|
autoExportAsHTML: async (request) => {
|
|
468
492
|
return getClient()
|
|
@@ -22,6 +22,9 @@ export type AutoExportAsMarkdownRequest =
|
|
|
22
22
|
schemas["AutoExportAsMarkdownRequest"];
|
|
23
23
|
export type ExportAsHTMLRequest = schemas["ExportAsHTMLRequest"];
|
|
24
24
|
export type ExportAsMarkdownRequest = schemas["ExportAsMarkdownRequest"];
|
|
25
|
+
export type MarkdownExportFlavor = NonNullable<
|
|
26
|
+
ExportAsMarkdownRequest["flavor"]
|
|
27
|
+
>;
|
|
25
28
|
export type ExportAsIPYNBRequest = schemas["ExportAsIPYNBRequest"];
|
|
26
29
|
export type ExportAsScriptRequest = schemas["ExportAsScriptRequest"];
|
|
27
30
|
export type ExportAsPDFRequest = schemas["ExportAsPDFRequest"];
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
@reference "../globals.css";
|
|
2
2
|
|
|
3
3
|
.light .cm-selectionBackground {
|
|
4
|
-
/*
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
/* Keep drawn selections visible when focus moves to find/replace or another editor. */
|
|
5
|
+
background-color: var(
|
|
6
|
+
--cm-selection-background,
|
|
7
|
+
var(--cm-selection-background-light)
|
|
8
|
+
) !important;
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
.dark .cm-selectionBackground {
|
|
11
|
-
background-color:
|
|
12
|
+
background-color: var(
|
|
13
|
+
--cm-selection-background,
|
|
14
|
+
var(--cm-selection-background-dark)
|
|
15
|
+
) !important;
|
|
12
16
|
}
|
|
13
17
|
|
|
14
18
|
.cm-focused.cm-focused {
|
|
@@ -125,7 +129,7 @@
|
|
|
125
129
|
/* -- Codeium -- */
|
|
126
130
|
|
|
127
131
|
.cm-codeium-cycle {
|
|
128
|
-
font-size:
|
|
132
|
+
font-size: 0.5625rem;
|
|
129
133
|
background-color: var(--sky-3);
|
|
130
134
|
padding: 2px;
|
|
131
135
|
border-radius: 2px;
|
|
@@ -133,7 +137,7 @@
|
|
|
133
137
|
}
|
|
134
138
|
|
|
135
139
|
.cm-codeium-cycle-key {
|
|
136
|
-
font-size:
|
|
140
|
+
font-size: 0.5625rem;
|
|
137
141
|
font-family: monospace;
|
|
138
142
|
display: inline-block;
|
|
139
143
|
padding: 2px;
|
package/src/css/globals.css
CHANGED
|
@@ -107,6 +107,14 @@
|
|
|
107
107
|
/* Codemirror editor */
|
|
108
108
|
--cm-background: light-dark(var(--background), #282c34);
|
|
109
109
|
--cm-comment: light-dark(#708090, #6b7280);
|
|
110
|
+
--cm-selection-background-light: #d7d4f0;
|
|
111
|
+
--cm-selection-background-dark: #1177cc80;
|
|
112
|
+
--cm-reactive-reference-color-light: #005f87;
|
|
113
|
+
--cm-reactive-reference-color-dark: #2a7aa5;
|
|
114
|
+
--cm-reactive-reference-border-color: #bad3de;
|
|
115
|
+
--cm-search-match-background-light: #99ff7780;
|
|
116
|
+
--cm-search-match-background-dark: #22bb0070;
|
|
117
|
+
--cm-search-match-selected-background-dark: #6199ff88;
|
|
110
118
|
}
|
|
111
119
|
|
|
112
120
|
.dark,
|
|
@@ -8,10 +8,10 @@ import { LoadingTable } from "@/components/data-table/loading-table";
|
|
|
8
8
|
import { type FieldTypes, toFieldTypes } from "@/components/data-table/types";
|
|
9
9
|
import { Alert, AlertTitle } from "@/components/ui/alert";
|
|
10
10
|
import { DelayMount } from "@/components/utils/delay-mount";
|
|
11
|
-
import { DATA_TYPES } from "@/core/kernel/messages";
|
|
12
11
|
import { useAsyncData } from "@/hooks/useAsyncData";
|
|
13
12
|
import { createPlugin } from "../core/builder";
|
|
14
13
|
import type { Setter } from "../types";
|
|
14
|
+
import { columnToFieldTypesSchema } from "./data-frames/schema";
|
|
15
15
|
import {
|
|
16
16
|
BulkEdit,
|
|
17
17
|
type DataEditorProps,
|
|
@@ -44,14 +44,7 @@ export const DataEditorPlugin = createPlugin<Edits>("marimo-data-editor", {
|
|
|
44
44
|
}),
|
|
45
45
|
label: z.string().nullable(),
|
|
46
46
|
data: z.union([z.string(), z.array(z.object({}).passthrough())]),
|
|
47
|
-
fieldTypes:
|
|
48
|
-
.array(
|
|
49
|
-
z.tuple([
|
|
50
|
-
z.coerce.string(),
|
|
51
|
-
z.tuple([z.enum(DATA_TYPES), z.string()]),
|
|
52
|
-
]),
|
|
53
|
-
)
|
|
54
|
-
.nullish(),
|
|
47
|
+
fieldTypes: columnToFieldTypesSchema.nullish(),
|
|
55
48
|
editableColumns: z.union([z.array(z.string()), z.literal("all")]),
|
|
56
49
|
columnSizingMode: z.enum(["auto", "fit"]).default("auto"), // TODO: Remove this
|
|
57
50
|
}),
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { DataEditorPlugin } from "../DataEditorPlugin";
|
|
5
|
+
|
|
6
|
+
describe("DataEditorPlugin", () => {
|
|
7
|
+
it("normalizes an unrecognized field type", () => {
|
|
8
|
+
const result = DataEditorPlugin.validator.parse({
|
|
9
|
+
initialValue: { edits: [] },
|
|
10
|
+
label: null,
|
|
11
|
+
data: [],
|
|
12
|
+
fieldTypes: [["geom", ["geometry", "geometry"]]],
|
|
13
|
+
editableColumns: "all",
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
expect(result.fieldTypes).toEqual([["geom", ["unknown", "geometry"]]]);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -26,6 +26,11 @@ import {
|
|
|
26
26
|
} from "lucide-react";
|
|
27
27
|
import React, { useEffect, useRef, useState } from "react";
|
|
28
28
|
import { z } from "zod";
|
|
29
|
+
import {
|
|
30
|
+
describeChatAbortReason,
|
|
31
|
+
getAbortReasonFromChunk,
|
|
32
|
+
resolveChatAbortReason,
|
|
33
|
+
} from "@/components/chat/chat-abort";
|
|
29
34
|
import { renderUIMessage } from "@/components/chat/chat-display";
|
|
30
35
|
import {
|
|
31
36
|
convertToFileUIPart,
|
|
@@ -168,6 +173,12 @@ export const Chatbot: React.FC<Props> = (props) => {
|
|
|
168
173
|
// different message_id are stale (from an aborted-but-not-yet-cancelled run
|
|
169
174
|
// on the kernel) and must be dropped
|
|
170
175
|
const activeRequestIdRef = useRef<string | null>(null);
|
|
176
|
+
const lastAbortReasonRef = useRef<string | null>(null);
|
|
177
|
+
const [abortNotice, setAbortNotice] = useState<string | null>(null);
|
|
178
|
+
const clearAbortState = () => {
|
|
179
|
+
lastAbortReasonRef.current = null;
|
|
180
|
+
setAbortNotice(null);
|
|
181
|
+
};
|
|
171
182
|
|
|
172
183
|
const { data: backendMessages } = useAsyncData(async () => {
|
|
173
184
|
const response = await props.get_chat_history({});
|
|
@@ -231,6 +242,7 @@ export const Chatbot: React.FC<Props> = (props) => {
|
|
|
231
242
|
// Client-generated id used to (a) route chunks back to this stream
|
|
232
243
|
// and (b) ask the kernel to cancel just this run on Stop.
|
|
233
244
|
const requestId = generateUUID();
|
|
245
|
+
clearAbortState();
|
|
234
246
|
|
|
235
247
|
const stream = new ReadableStream<UIMessageChunk>({
|
|
236
248
|
start(controller) {
|
|
@@ -297,15 +309,30 @@ export const Chatbot: React.FC<Props> = (props) => {
|
|
|
297
309
|
},
|
|
298
310
|
}),
|
|
299
311
|
messages: initialMessages,
|
|
300
|
-
onFinish: (
|
|
312
|
+
onFinish: ({ messages, isAbort, finishReason }) => {
|
|
301
313
|
setFiles(undefined);
|
|
302
314
|
|
|
303
315
|
if (fileInputRef.current) {
|
|
304
316
|
fileInputRef.current.value = "";
|
|
305
317
|
}
|
|
306
|
-
Logger.debug("Finished streaming message:",
|
|
318
|
+
Logger.debug("Finished streaming message:", { finishReason, isAbort });
|
|
319
|
+
|
|
320
|
+
if (isAbort) {
|
|
321
|
+
const reason = resolveChatAbortReason({
|
|
322
|
+
isAbort: true,
|
|
323
|
+
streamReason: lastAbortReasonRef.current,
|
|
324
|
+
});
|
|
325
|
+
clearAbortState();
|
|
326
|
+
if (reason != null) {
|
|
327
|
+
const description = describeChatAbortReason(reason);
|
|
328
|
+
Logger.debug("Chat stream aborted", { reason, finishReason });
|
|
329
|
+
setAbortNotice(description);
|
|
330
|
+
}
|
|
331
|
+
} else {
|
|
332
|
+
clearAbortState();
|
|
333
|
+
}
|
|
307
334
|
|
|
308
|
-
props.setValue(
|
|
335
|
+
props.setValue(messages);
|
|
309
336
|
},
|
|
310
337
|
onError: (error) => {
|
|
311
338
|
Logger.error("An error occurred:", error);
|
|
@@ -323,6 +350,17 @@ export const Chatbot: React.FC<Props> = (props) => {
|
|
|
323
350
|
if (!parsedMessage.success) {
|
|
324
351
|
return;
|
|
325
352
|
}
|
|
353
|
+
// Only record abort reasons for the active request. Late chunks from a
|
|
354
|
+
// cancelled run must not overwrite the next turn's reason
|
|
355
|
+
if (
|
|
356
|
+
parsedMessage.data.content &&
|
|
357
|
+
parsedMessage.data.message_id === activeRequestIdRef.current
|
|
358
|
+
) {
|
|
359
|
+
const abortReason = getAbortReasonFromChunk(parsedMessage.data.content);
|
|
360
|
+
if (abortReason !== undefined) {
|
|
361
|
+
lastAbortReasonRef.current = abortReason;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
326
364
|
routeIncomingChatChunk(parsedMessage.data, {
|
|
327
365
|
controllerRef: frontendStreamControllerRef,
|
|
328
366
|
activeRequestIdRef,
|
|
@@ -340,6 +378,9 @@ export const Chatbot: React.FC<Props> = (props) => {
|
|
|
340
378
|
setMessages(newMessages);
|
|
341
379
|
|
|
342
380
|
props.setValue(newMessages);
|
|
381
|
+
if (newMessages.length === 0) {
|
|
382
|
+
clearAbortState();
|
|
383
|
+
}
|
|
343
384
|
}
|
|
344
385
|
};
|
|
345
386
|
|
|
@@ -389,6 +430,7 @@ export const Chatbot: React.FC<Props> = (props) => {
|
|
|
389
430
|
props.setValue([]);
|
|
390
431
|
props.delete_chat_history({});
|
|
391
432
|
clearError();
|
|
433
|
+
clearAbortState();
|
|
392
434
|
};
|
|
393
435
|
|
|
394
436
|
return (
|
|
@@ -483,6 +525,12 @@ export const Chatbot: React.FC<Props> = (props) => {
|
|
|
483
525
|
</Button>
|
|
484
526
|
</div>
|
|
485
527
|
)}
|
|
528
|
+
|
|
529
|
+
{abortNotice && !isLoading && !error && (
|
|
530
|
+
<div className="flex justify-center py-2">
|
|
531
|
+
<span className="text-xs text-muted-foreground">{abortNotice}</span>
|
|
532
|
+
</div>
|
|
533
|
+
)}
|
|
486
534
|
</div>
|
|
487
535
|
<form
|
|
488
536
|
onSubmit={async (evt) => {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { columnToFieldTypesSchema } from "../schema";
|
|
5
|
+
|
|
6
|
+
describe("columnToFieldTypesSchema", () => {
|
|
7
|
+
it("keeps known field types", () => {
|
|
8
|
+
const result = columnToFieldTypesSchema.parse([
|
|
9
|
+
["count", ["integer", "int64"]],
|
|
10
|
+
["label", ["string", "object"]],
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
expect(result).toEqual([
|
|
14
|
+
["count", ["integer", "int64"]],
|
|
15
|
+
["label", ["string", "object"]],
|
|
16
|
+
]);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("normalizes an unrecognized field type without discarding other fields", () => {
|
|
20
|
+
const result = columnToFieldTypesSchema.parse([
|
|
21
|
+
["geom", ["geometry", "geometry"]],
|
|
22
|
+
["label", ["string", "object"]],
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
expect(result).toEqual([
|
|
26
|
+
["geom", ["unknown", "geometry"]],
|
|
27
|
+
["label", ["string", "object"]],
|
|
28
|
+
]);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -15,7 +15,10 @@ import {
|
|
|
15
15
|
} from "./utils/operators";
|
|
16
16
|
|
|
17
17
|
export const columnToFieldTypesSchema = z.array(
|
|
18
|
-
z.tuple([
|
|
18
|
+
z.tuple([
|
|
19
|
+
z.coerce.string(),
|
|
20
|
+
z.tuple([z.enum(DATA_TYPES).catch("unknown"), z.string()]),
|
|
21
|
+
]),
|
|
19
22
|
);
|
|
20
23
|
|
|
21
24
|
export const column_id = z
|
|
@@ -49,6 +49,18 @@ const ImageComparisonComponent: React.FC<ImageComparisonData> = ({
|
|
|
49
49
|
minHeight: "2rem",
|
|
50
50
|
};
|
|
51
51
|
|
|
52
|
+
// The slider's width="100%" images resolve against its shadow-DOM wrappers (circular),
|
|
53
|
+
// so size the images explicitly and letterbox with object-fit to keep them aligned.
|
|
54
|
+
// Treat empty strings as "not provided" (normalize_dimension passes them through).
|
|
55
|
+
const imageStyle: React.CSSProperties | undefined =
|
|
56
|
+
width || height
|
|
57
|
+
? {
|
|
58
|
+
width: width || undefined,
|
|
59
|
+
height: height || undefined,
|
|
60
|
+
objectFit: "contain",
|
|
61
|
+
}
|
|
62
|
+
: undefined;
|
|
63
|
+
|
|
52
64
|
// If an image fails to load, the slider collapses to nothing; surface a
|
|
53
65
|
// visible error instead of silently rendering an empty output.
|
|
54
66
|
if (failedSrcs.size > 0) {
|
|
@@ -67,12 +79,18 @@ const ImageComparisonComponent: React.FC<ImageComparisonData> = ({
|
|
|
67
79
|
|
|
68
80
|
return (
|
|
69
81
|
<div style={containerStyle}>
|
|
70
|
-
<ImgComparisonSlider
|
|
82
|
+
<ImgComparisonSlider
|
|
83
|
+
value={value}
|
|
84
|
+
direction={direction}
|
|
85
|
+
// inline-block leaves a baseline gap below the slider; align to top.
|
|
86
|
+
style={{ verticalAlign: "top" }}
|
|
87
|
+
>
|
|
71
88
|
<img
|
|
72
89
|
slot="first"
|
|
73
90
|
src={beforeSrc}
|
|
74
91
|
alt="Before"
|
|
75
92
|
width="100%"
|
|
93
|
+
style={imageStyle}
|
|
76
94
|
onError={() => handleError(beforeSrc)}
|
|
77
95
|
/>
|
|
78
96
|
<img
|
|
@@ -80,6 +98,7 @@ const ImageComparisonComponent: React.FC<ImageComparisonData> = ({
|
|
|
80
98
|
src={afterSrc}
|
|
81
99
|
alt="After"
|
|
82
100
|
width="100%"
|
|
101
|
+
style={imageStyle}
|
|
83
102
|
onError={() => handleError(afterSrc)}
|
|
84
103
|
/>
|
|
85
104
|
</ImgComparisonSlider>
|
|
@@ -68,4 +68,62 @@ describe("ImageComparisonComponent", () => {
|
|
|
68
68
|
expect(error?.textContent).toContain("…");
|
|
69
69
|
expect(error?.textContent).not.toContain("A".repeat(200));
|
|
70
70
|
});
|
|
71
|
+
|
|
72
|
+
it("sizes both images explicitly when dimensions are given", () => {
|
|
73
|
+
const { getAllByAltText } = render(
|
|
74
|
+
<ImageComparisonComponent {...baseProps} width="300px" height="300px" />,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
for (const img of getAllByAltText(/Before|After/)) {
|
|
78
|
+
expect((img as HTMLElement).style.width).toBe("300px");
|
|
79
|
+
expect((img as HTMLElement).style.height).toBe("300px");
|
|
80
|
+
// Letterbox identically so the slider's clip stays aligned.
|
|
81
|
+
expect((img as HTMLElement).style.objectFit).toBe("contain");
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("applies a single dimension to both images when only one is given", () => {
|
|
86
|
+
const { getAllByAltText } = render(
|
|
87
|
+
<ImageComparisonComponent {...baseProps} width="300px" />,
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
for (const img of getAllByAltText(/Before|After/)) {
|
|
91
|
+
expect((img as HTMLElement).style.width).toBe("300px");
|
|
92
|
+
expect((img as HTMLElement).style.objectFit).toBe("contain");
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("applies height-only sizing to both images", () => {
|
|
97
|
+
const { getAllByAltText } = render(
|
|
98
|
+
<ImageComparisonComponent {...baseProps} height="300px" />,
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
for (const img of getAllByAltText(/Before|After/)) {
|
|
102
|
+
expect((img as HTMLElement).style.height).toBe("300px");
|
|
103
|
+
expect((img as HTMLElement).style.objectFit).toBe("contain");
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("treats empty-string dimensions as not provided", () => {
|
|
108
|
+
const { getAllByAltText } = render(
|
|
109
|
+
<ImageComparisonComponent {...baseProps} width="" height="" />,
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
for (const img of getAllByAltText(/Before|After/)) {
|
|
113
|
+
expect((img as HTMLElement).style.width).toBe("");
|
|
114
|
+
expect((img as HTMLElement).style.height).toBe("");
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("leaves images unstyled when no dimensions are given", () => {
|
|
119
|
+
const { getAllByAltText } = render(
|
|
120
|
+
<ImageComparisonComponent {...baseProps} />,
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
for (const img of getAllByAltText(/Before|After/)) {
|
|
124
|
+
// No explicit sizing: the slider keeps its intrinsic-size behavior.
|
|
125
|
+
expect((img as HTMLElement).style.width).toBe("");
|
|
126
|
+
expect((img as HTMLElement).style.height).toBe("");
|
|
127
|
+
}
|
|
128
|
+
});
|
|
71
129
|
});
|