@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
|
@@ -309,6 +309,26 @@ describe("generateStorageCode", () => {
|
|
|
309
309
|
});
|
|
310
310
|
});
|
|
311
311
|
|
|
312
|
+
describe("Hugging Face", () => {
|
|
313
|
+
it("default connection", () => {
|
|
314
|
+
expect(
|
|
315
|
+
generateStorageCode({ type: "huggingface" }, { library: "fsspec" }),
|
|
316
|
+
).toMatchSnapshot();
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it("with token from secrets", () => {
|
|
320
|
+
expect(
|
|
321
|
+
generateStorageCode(
|
|
322
|
+
{
|
|
323
|
+
type: "huggingface",
|
|
324
|
+
token: prefixSecret("HF_TOKEN"),
|
|
325
|
+
},
|
|
326
|
+
{ library: "fsspec" },
|
|
327
|
+
),
|
|
328
|
+
).toMatchSnapshot();
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
|
|
312
332
|
describe("invalid cases", () => {
|
|
313
333
|
it("throws for empty S3 bucket", () => {
|
|
314
334
|
expect(() =>
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
CoreWeaveStorageSchema,
|
|
18
18
|
GCSStorageSchema,
|
|
19
19
|
GoogleDriveStorageSchema,
|
|
20
|
+
HuggingfaceStorageSchema,
|
|
20
21
|
S3StorageSchema,
|
|
21
22
|
type StorageConnection,
|
|
22
23
|
} from "./schemas";
|
|
@@ -79,6 +80,15 @@ const STORAGE_PROVIDERS = [
|
|
|
79
80
|
preferred: "fsspec",
|
|
80
81
|
},
|
|
81
82
|
},
|
|
83
|
+
{
|
|
84
|
+
name: "Hugging Face Hub",
|
|
85
|
+
schema: HuggingfaceStorageSchema,
|
|
86
|
+
protocol: "hf",
|
|
87
|
+
storageLibraries: {
|
|
88
|
+
libraries: ["huggingface_hub"],
|
|
89
|
+
preferred: "huggingface_hub",
|
|
90
|
+
},
|
|
91
|
+
},
|
|
82
92
|
] satisfies StorageProviderSchema[];
|
|
83
93
|
|
|
84
94
|
const StorageProviderSelector: React.FC<{
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
StorageConnectionSchema,
|
|
11
11
|
} from "./schemas";
|
|
12
12
|
|
|
13
|
-
export type StorageLibrary = "obstore" | "fsspec";
|
|
13
|
+
export type StorageLibrary = "obstore" | "fsspec" | "huggingface_hub";
|
|
14
14
|
|
|
15
15
|
export interface StorageCodeOptions {
|
|
16
16
|
library: StorageLibrary;
|
|
@@ -20,6 +20,7 @@ export interface StorageCodeOptions {
|
|
|
20
20
|
export const StorageLibraryDisplayNames: Record<StorageLibrary, string> = {
|
|
21
21
|
obstore: "obstore",
|
|
22
22
|
fsspec: "fsspec",
|
|
23
|
+
huggingface_hub: "huggingface_hub",
|
|
23
24
|
};
|
|
24
25
|
|
|
25
26
|
class SecretContainer {
|
|
@@ -245,6 +246,20 @@ function generateGDriveCode(
|
|
|
245
246
|
return { imports, code };
|
|
246
247
|
}
|
|
247
248
|
|
|
249
|
+
function generateHuggingfaceCode(
|
|
250
|
+
connection: Extract<StorageConnection, { type: "huggingface" }>,
|
|
251
|
+
secrets: SecretContainer,
|
|
252
|
+
): { imports: Set<string>; code: string } {
|
|
253
|
+
const imports = new Set(["from huggingface_hub import HfApi"]);
|
|
254
|
+
|
|
255
|
+
if (!connection.token) {
|
|
256
|
+
return { imports, code: "hf = HfApi()" };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const token = secrets.print("token", connection.token);
|
|
260
|
+
return { imports, code: `hf = HfApi(token=${token})` };
|
|
261
|
+
}
|
|
262
|
+
|
|
248
263
|
export function generateStorageCode(
|
|
249
264
|
connection: StorageConnection,
|
|
250
265
|
options: StorageCodeOptions,
|
|
@@ -273,6 +288,9 @@ export function generateStorageCode(
|
|
|
273
288
|
isEmbedded: options.isEmbedded,
|
|
274
289
|
});
|
|
275
290
|
break;
|
|
291
|
+
case "huggingface":
|
|
292
|
+
result = generateHuggingfaceCode(connection, secrets);
|
|
293
|
+
break;
|
|
276
294
|
default:
|
|
277
295
|
assertNever(connection);
|
|
278
296
|
}
|
|
@@ -220,12 +220,31 @@ export const GoogleDriveStorageSchema = z
|
|
|
220
220
|
})
|
|
221
221
|
.describe(FieldOptions.of({ direction: "two-columns" }));
|
|
222
222
|
|
|
223
|
+
export const HuggingfaceStorageSchema = z
|
|
224
|
+
.object({
|
|
225
|
+
type: z.literal("huggingface"),
|
|
226
|
+
token: z
|
|
227
|
+
.string()
|
|
228
|
+
.optional()
|
|
229
|
+
.describe(
|
|
230
|
+
FieldOptions.of({
|
|
231
|
+
label: "Access Token",
|
|
232
|
+
description:
|
|
233
|
+
"Leave empty to use the HF_TOKEN environment variable or cached login",
|
|
234
|
+
inputType: "password",
|
|
235
|
+
optionRegex: "(hf.?token|hugging.?face.?token|hub.?token)",
|
|
236
|
+
}),
|
|
237
|
+
),
|
|
238
|
+
})
|
|
239
|
+
.describe(FieldOptions.of({ direction: "two-columns" }));
|
|
240
|
+
|
|
223
241
|
export const StorageConnectionSchema = z.discriminatedUnion("type", [
|
|
224
242
|
S3StorageSchema,
|
|
225
243
|
GCSStorageSchema,
|
|
226
244
|
AzureStorageSchema,
|
|
227
245
|
CoreWeaveStorageSchema,
|
|
228
246
|
GoogleDriveStorageSchema,
|
|
247
|
+
HuggingfaceStorageSchema,
|
|
229
248
|
]);
|
|
230
249
|
|
|
231
250
|
export type StorageConnection = z.infer<typeof StorageConnectionSchema>;
|
|
@@ -44,6 +44,94 @@ describe("read-file snippet", () => {
|
|
|
44
44
|
`);
|
|
45
45
|
});
|
|
46
46
|
|
|
47
|
+
it("huggingface backend (dataset)", () => {
|
|
48
|
+
expect(
|
|
49
|
+
readSnippet.getCode(
|
|
50
|
+
makeCtx({
|
|
51
|
+
backendType: "huggingface",
|
|
52
|
+
entry: {
|
|
53
|
+
path: "datasets/scikit-learn/Fish/Fish.csv",
|
|
54
|
+
kind: "file",
|
|
55
|
+
size: 100,
|
|
56
|
+
lastModified: null,
|
|
57
|
+
},
|
|
58
|
+
}),
|
|
59
|
+
),
|
|
60
|
+
).toMatchInlineSnapshot(`
|
|
61
|
+
"from huggingface_hub import hf_hub_download
|
|
62
|
+
|
|
63
|
+
local_path = hf_hub_download(
|
|
64
|
+
repo_id="scikit-learn/Fish",
|
|
65
|
+
filename="Fish.csv",
|
|
66
|
+
repo_type="dataset",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
with open(local_path, "rb") as f:
|
|
70
|
+
_data = f.read()
|
|
71
|
+
_data"
|
|
72
|
+
`);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("huggingface backend (model)", () => {
|
|
76
|
+
expect(
|
|
77
|
+
readSnippet.getCode(
|
|
78
|
+
makeCtx({
|
|
79
|
+
backendType: "huggingface",
|
|
80
|
+
entry: {
|
|
81
|
+
path: "google-bert/bert-base-uncased/config.json",
|
|
82
|
+
kind: "file",
|
|
83
|
+
size: 100,
|
|
84
|
+
lastModified: null,
|
|
85
|
+
},
|
|
86
|
+
}),
|
|
87
|
+
),
|
|
88
|
+
).toMatchInlineSnapshot(`
|
|
89
|
+
"from huggingface_hub import hf_hub_download
|
|
90
|
+
|
|
91
|
+
local_path = hf_hub_download(
|
|
92
|
+
repo_id="google-bert/bert-base-uncased",
|
|
93
|
+
filename="config.json",
|
|
94
|
+
repo_type="model",
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
with open(local_path, "rb") as f:
|
|
98
|
+
_data = f.read()
|
|
99
|
+
_data"
|
|
100
|
+
`);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("huggingface backend returns null for unparsable paths", () => {
|
|
104
|
+
expect(
|
|
105
|
+
readSnippet.getCode(
|
|
106
|
+
makeCtx({
|
|
107
|
+
backendType: "huggingface",
|
|
108
|
+
entry: {
|
|
109
|
+
path: "buckets/my-bucket/file.csv",
|
|
110
|
+
kind: "file",
|
|
111
|
+
size: 100,
|
|
112
|
+
lastModified: null,
|
|
113
|
+
},
|
|
114
|
+
}),
|
|
115
|
+
),
|
|
116
|
+
).toBeNull();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("huggingface backend does not misparse a dataset/space root as a model repo", () => {
|
|
120
|
+
// These paths are missing a filename segment, so they aren't valid
|
|
121
|
+
// dataset/space repo files. They must not fall through and be treated
|
|
122
|
+
// as the model repo "datasets/scikit-learn" or "spaces/gradio".
|
|
123
|
+
for (const path of ["datasets/scikit-learn/Fish", "spaces/gradio/demo"]) {
|
|
124
|
+
expect(
|
|
125
|
+
readSnippet.getCode(
|
|
126
|
+
makeCtx({
|
|
127
|
+
backendType: "huggingface",
|
|
128
|
+
entry: { path, kind: "file", size: 100, lastModified: null },
|
|
129
|
+
}),
|
|
130
|
+
),
|
|
131
|
+
).toBeNull();
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
47
135
|
it("returns null for directories", () => {
|
|
48
136
|
expect(
|
|
49
137
|
readSnippet.getCode(
|
|
@@ -9,6 +9,7 @@ import CoreweaveDarkIcon from "@marimo-team/llm-info/icons/coreweave-dark.svg?in
|
|
|
9
9
|
import { DatabaseZapIcon, GlobeIcon, HardDriveIcon } from "lucide-react";
|
|
10
10
|
import GoogleCloudIcon from "@/components/databases/icons/google-cloud-storage.svg?inline";
|
|
11
11
|
import GoogleDriveIcon from "@/components/databases/icons/google-drive.svg?inline";
|
|
12
|
+
import HuggingfaceIcon from "@/components/databases/icons/huggingface.svg?inline";
|
|
12
13
|
import { GitHubIcon } from "@/components/icons/github";
|
|
13
14
|
import type { KnownStorageProtocol } from "@/core/storage/types";
|
|
14
15
|
import { useTheme } from "@/theme/useTheme";
|
|
@@ -28,6 +29,7 @@ const PROTOCOL_ICONS: Record<KnownStorageProtocol, IconEntry> = {
|
|
|
28
29
|
file: HardDriveIcon,
|
|
29
30
|
"in-memory": DatabaseZapIcon,
|
|
30
31
|
gdrive: { src: GoogleDriveIcon },
|
|
32
|
+
hf: { src: HuggingfaceIcon },
|
|
31
33
|
github: GitHubIcon,
|
|
32
34
|
};
|
|
33
35
|
|
|
@@ -27,6 +27,50 @@ function escapeForPythonString(value: string): string {
|
|
|
27
27
|
return JSON.stringify(value).slice(1, -1);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
interface ParsedHfRepoPath {
|
|
31
|
+
repoType: "model" | "dataset" | "space";
|
|
32
|
+
repoId: string;
|
|
33
|
+
filename: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Namespaced prefixes are reserved: they can never be the first segment of a
|
|
37
|
+
// model repo id, so a path like "datasets/org" that is missing a filename
|
|
38
|
+
// must not fall through and be misparsed as the model repo "datasets/org".
|
|
39
|
+
const RESERVED_PREFIXES = new Set(["datasets", "spaces", "buckets"]);
|
|
40
|
+
|
|
41
|
+
function parseHfRepoPath(path: string): ParsedHfRepoPath | null {
|
|
42
|
+
const parts = path.split("/").filter(Boolean);
|
|
43
|
+
if (parts[0] === "datasets" && parts.length >= 4) {
|
|
44
|
+
return {
|
|
45
|
+
repoType: "dataset",
|
|
46
|
+
repoId: `${parts[1]}/${parts[2]}`,
|
|
47
|
+
filename: parts.slice(3).join("/"),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (parts[0] === "spaces" && parts.length >= 4) {
|
|
51
|
+
return {
|
|
52
|
+
repoType: "space",
|
|
53
|
+
repoId: `${parts[1]}/${parts[2]}`,
|
|
54
|
+
filename: parts.slice(3).join("/"),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
if (parts.length >= 3 && !RESERVED_PREFIXES.has(parts[0])) {
|
|
58
|
+
return {
|
|
59
|
+
repoType: "model",
|
|
60
|
+
repoId: `${parts[0]}/${parts[1]}`,
|
|
61
|
+
filename: parts.slice(2).join("/"),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function hfHubDownloadSnippet(parsed: ParsedHfRepoPath): string {
|
|
68
|
+
const repoId = escapeForPythonString(parsed.repoId);
|
|
69
|
+
const filename = escapeForPythonString(parsed.filename);
|
|
70
|
+
const repoType = escapeForPythonString(parsed.repoType);
|
|
71
|
+
return `from huggingface_hub import hf_hub_download\n\nlocal_path = hf_hub_download(\n repo_id="${repoId}",\n filename="${filename}",\n repo_type="${repoType}",\n)`;
|
|
72
|
+
}
|
|
73
|
+
|
|
30
74
|
export const STORAGE_SNIPPETS: StorageSnippet[] = [
|
|
31
75
|
{
|
|
32
76
|
id: "read-file",
|
|
@@ -37,6 +81,13 @@ export const STORAGE_SNIPPETS: StorageSnippet[] = [
|
|
|
37
81
|
return null;
|
|
38
82
|
}
|
|
39
83
|
const path = escapeForPythonString(ctx.entry.path);
|
|
84
|
+
if (ctx.backendType === "huggingface") {
|
|
85
|
+
const parsed = parseHfRepoPath(ctx.entry.path);
|
|
86
|
+
if (!parsed) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
return `${hfHubDownloadSnippet(parsed)}\n\nwith open(local_path, "rb") as f:\n _data = f.read()\n_data`;
|
|
90
|
+
}
|
|
40
91
|
if (ctx.backendType === "obstore") {
|
|
41
92
|
return `_data = ${ctx.variableName}.get("${path}").bytes()\n_data`;
|
|
42
93
|
}
|
|
@@ -52,6 +103,13 @@ export const STORAGE_SNIPPETS: StorageSnippet[] = [
|
|
|
52
103
|
return null;
|
|
53
104
|
}
|
|
54
105
|
const path = escapeForPythonString(ctx.entry.path);
|
|
106
|
+
if (ctx.backendType === "huggingface") {
|
|
107
|
+
const parsed = parseHfRepoPath(ctx.entry.path);
|
|
108
|
+
if (!parsed) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
return `${hfHubDownloadSnippet(parsed)}\nlocal_path`;
|
|
112
|
+
}
|
|
55
113
|
if (ctx.backendType === "obstore") {
|
|
56
114
|
if (NOT_SIGNABLE_PROTOCOLS.has(ctx.protocol)) {
|
|
57
115
|
return null;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { CellId } from "@/core/cells/ids";
|
|
5
|
+
import { runDuringPresentMode, viewStateAtom } from "@/core/mode";
|
|
6
|
+
import { store } from "@/core/state/jotai";
|
|
7
|
+
|
|
8
|
+
const requestAnimationFrameMock = vi.fn((callback: FrameRequestCallback) => {
|
|
9
|
+
callback(0);
|
|
10
|
+
return 0;
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
async function runAfterRender(fn: () => void | Promise<void>): Promise<void> {
|
|
14
|
+
const result = runDuringPresentMode(fn);
|
|
15
|
+
await vi.runAllTimersAsync();
|
|
16
|
+
return result;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe("runDuringPresentMode", () => {
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
vi.useFakeTimers();
|
|
22
|
+
vi.stubGlobal("requestAnimationFrame", requestAnimationFrameMock);
|
|
23
|
+
requestAnimationFrameMock.mockClear();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
vi.useRealTimers();
|
|
28
|
+
vi.unstubAllGlobals();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("runs in present mode and restores the captured view state", async () => {
|
|
32
|
+
const state = { mode: "edit" as const, cellAnchor: CellId.create() };
|
|
33
|
+
store.set(viewStateAtom, state);
|
|
34
|
+
|
|
35
|
+
await runAfterRender(() => {
|
|
36
|
+
expect(store.get(viewStateAtom)).toEqual({
|
|
37
|
+
...state,
|
|
38
|
+
mode: "present",
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
expect(store.get(viewStateAtom)).toEqual(state);
|
|
43
|
+
expect(requestAnimationFrameMock).toHaveBeenCalledTimes(2);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("restores the captured view state when the callback rejects", async () => {
|
|
47
|
+
const state = { mode: "edit" as const, cellAnchor: CellId.create() };
|
|
48
|
+
const error = new Error("capture failed");
|
|
49
|
+
store.set(viewStateAtom, state);
|
|
50
|
+
|
|
51
|
+
const result = runDuringPresentMode(() => Promise.reject(error));
|
|
52
|
+
const rejection = expect(result).rejects.toBe(error);
|
|
53
|
+
await vi.runAllTimersAsync();
|
|
54
|
+
await rejection;
|
|
55
|
+
|
|
56
|
+
expect(store.get(viewStateAtom)).toEqual(state);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("runs directly when already in present mode", async () => {
|
|
60
|
+
const state = { mode: "present" as const, cellAnchor: CellId.create() };
|
|
61
|
+
store.set(viewStateAtom, state);
|
|
62
|
+
|
|
63
|
+
await runDuringPresentMode(() => {
|
|
64
|
+
expect(store.get(viewStateAtom)).toEqual(state);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
expect(store.get(viewStateAtom)).toEqual(state);
|
|
68
|
+
expect(requestAnimationFrameMock).not.toHaveBeenCalled();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it.each(["read", "home", "gallery"] as const)(
|
|
72
|
+
"runs directly without changing %s mode",
|
|
73
|
+
async (mode) => {
|
|
74
|
+
const state = { mode, cellAnchor: CellId.create() };
|
|
75
|
+
store.set(viewStateAtom, state);
|
|
76
|
+
|
|
77
|
+
await runDuringPresentMode(() => {
|
|
78
|
+
expect(store.get(viewStateAtom)).toEqual(state);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
expect(store.get(viewStateAtom)).toEqual(state);
|
|
82
|
+
expect(requestAnimationFrameMock).not.toHaveBeenCalled();
|
|
83
|
+
},
|
|
84
|
+
);
|
|
85
|
+
});
|
|
@@ -176,14 +176,17 @@ export function useStagedCells(store: JotaiStore) {
|
|
|
176
176
|
case "message-metadata":
|
|
177
177
|
case "tool-input-available":
|
|
178
178
|
case "tool-output-available":
|
|
179
|
+
case "tool-approval-response":
|
|
179
180
|
case "reasoning-start":
|
|
180
181
|
case "reasoning-delta":
|
|
181
182
|
case "reasoning-end":
|
|
182
183
|
case "file":
|
|
184
|
+
case "reasoning-file":
|
|
183
185
|
case "source-document":
|
|
184
186
|
case "source-url":
|
|
185
187
|
case "tool-input-start":
|
|
186
188
|
case "tool-input-delta":
|
|
189
|
+
case "custom":
|
|
187
190
|
Logger.debug(chunk.type, { chunk });
|
|
188
191
|
break;
|
|
189
192
|
default:
|
|
@@ -68,7 +68,7 @@ function getOpts() {
|
|
|
68
68
|
diagnosticsConfig: {},
|
|
69
69
|
hotkeys: new OverridingHotkeyProvider({}),
|
|
70
70
|
theme: "light",
|
|
71
|
-
displayConfig: { reference_highlighting: false },
|
|
71
|
+
displayConfig: { reference_highlighting: false, code_lens: true },
|
|
72
72
|
inlineAiTooltip: false,
|
|
73
73
|
} as const;
|
|
74
74
|
}
|
|
@@ -82,7 +82,7 @@ export interface CodeMirrorSetupOpts {
|
|
|
82
82
|
hotkeys: HotkeyProvider;
|
|
83
83
|
lspConfig: LSPConfig;
|
|
84
84
|
diagnosticsConfig: DiagnosticsConfig;
|
|
85
|
-
displayConfig: Pick<DisplayConfig, "reference_highlighting">;
|
|
85
|
+
displayConfig: Pick<DisplayConfig, "reference_highlighting" | "code_lens">;
|
|
86
86
|
inlineAiTooltip: boolean;
|
|
87
87
|
/**
|
|
88
88
|
* CSS selector for the element that CodeMirror tooltips (completions, hover,
|
|
@@ -200,7 +200,7 @@ export const setupCodeMirror = (opts: CodeMirrorSetupOpts): Extension[] => {
|
|
|
200
200
|
displayConfig.reference_highlighting ?? true,
|
|
201
201
|
),
|
|
202
202
|
// Inline icons linking datasources/buckets/caches to their panels
|
|
203
|
-
codeLensBundle(cellId),
|
|
203
|
+
codeLensBundle(cellId, displayConfig.code_lens ?? true),
|
|
204
204
|
];
|
|
205
205
|
};
|
|
206
206
|
|
|
@@ -103,7 +103,7 @@ describe("codeLensBundle", () => {
|
|
|
103
103
|
|
|
104
104
|
beforeEach(() => {
|
|
105
105
|
vi.useFakeTimers();
|
|
106
|
-
mockFlags({
|
|
106
|
+
mockFlags({ cache_panel: true });
|
|
107
107
|
seedStore({});
|
|
108
108
|
});
|
|
109
109
|
|
|
@@ -141,9 +141,8 @@ describe("codeLensBundle", () => {
|
|
|
141
141
|
return [...v.dom.querySelectorAll<HTMLElement>(".mo-code-lens")];
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
it("returns an empty extension when
|
|
145
|
-
|
|
146
|
-
expect(codeLensBundle(cellId("cell1"))).toEqual([]);
|
|
144
|
+
it("returns an empty extension when disabled via `display.code_lens`", () => {
|
|
145
|
+
expect(codeLensBundle(cellId("cell1"), false)).toEqual([]);
|
|
147
146
|
});
|
|
148
147
|
|
|
149
148
|
it("renders a lens at a dataframe declaration", async () => {
|
|
@@ -293,7 +292,7 @@ describe("codeLensBundle", () => {
|
|
|
293
292
|
);
|
|
294
293
|
|
|
295
294
|
it("does not render cache lenses when the cache panel is disabled", async () => {
|
|
296
|
-
mockFlags({
|
|
295
|
+
mockFlags({ cache_panel: false });
|
|
297
296
|
const v = await mount("@mo.cache\ndef f():\n return 1");
|
|
298
297
|
|
|
299
298
|
expect(lenses(v)).toHaveLength(0);
|
|
@@ -281,10 +281,10 @@ const codeLensTheme = EditorView.baseTheme({
|
|
|
281
281
|
/**
|
|
282
282
|
* Inline icons linking datasource/bucket variables and `mo.cache` /
|
|
283
283
|
* `mo.persistent_cache` calls to their panels.
|
|
284
|
-
*
|
|
284
|
+
* Configurable via the `display.code_lens` user config (on by default).
|
|
285
285
|
*/
|
|
286
|
-
export function codeLensBundle(cellId: CellId): Extension {
|
|
287
|
-
if (!
|
|
286
|
+
export function codeLensBundle(cellId: CellId, enabled = true): Extension {
|
|
287
|
+
if (!enabled) {
|
|
288
288
|
return [];
|
|
289
289
|
}
|
|
290
290
|
return [
|
|
@@ -140,11 +140,16 @@ export const searchHighlighter = ViewPlugin.fromClass(
|
|
|
140
140
|
);
|
|
141
141
|
|
|
142
142
|
export const highlightTheme = EditorView.baseTheme({
|
|
143
|
-
"&light .cm-searchMatch": {
|
|
144
|
-
|
|
143
|
+
"&light .cm-searchMatch": {
|
|
144
|
+
backgroundColor: "var(--cm-search-match-background-light)",
|
|
145
|
+
},
|
|
146
|
+
"&dark .cm-searchMatch": {
|
|
147
|
+
backgroundColor: "var(--cm-search-match-background-dark)",
|
|
148
|
+
},
|
|
145
149
|
|
|
146
150
|
"&light .cm-searchMatch-selected": { backgroundColor: "transparent" },
|
|
147
151
|
"&dark .cm-searchMatch.cm-searchMatch-selected": {
|
|
148
|
-
backgroundColor:
|
|
152
|
+
backgroundColor:
|
|
153
|
+
"var(--cm-search-match-selected-background-dark) !important",
|
|
149
154
|
},
|
|
150
155
|
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { render } from "@testing-library/react";
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
import type {
|
|
6
|
+
DataTable,
|
|
7
|
+
DataTableColumn,
|
|
8
|
+
DataType,
|
|
9
|
+
} from "@/core/kernel/messages";
|
|
10
|
+
import { renderColumnInfo, renderTableInfo } from "../renderers";
|
|
11
|
+
|
|
12
|
+
const geometryColumn = {
|
|
13
|
+
name: "geom",
|
|
14
|
+
type: "geometry" as DataType,
|
|
15
|
+
external_type: "geometry",
|
|
16
|
+
sample_values: [],
|
|
17
|
+
} as DataTableColumn;
|
|
18
|
+
|
|
19
|
+
describe("SQL renderers", () => {
|
|
20
|
+
it("renders table information with an unrecognized column type", () => {
|
|
21
|
+
const table: DataTable = {
|
|
22
|
+
name: "shapes",
|
|
23
|
+
type: "table",
|
|
24
|
+
source: "local",
|
|
25
|
+
source_type: "local",
|
|
26
|
+
num_rows: null,
|
|
27
|
+
num_columns: 1,
|
|
28
|
+
variable_name: null,
|
|
29
|
+
columns: [geometryColumn],
|
|
30
|
+
primary_keys: [],
|
|
31
|
+
indexes: [],
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const result = render(renderTableInfo(table));
|
|
35
|
+
|
|
36
|
+
expect(result.container.querySelector("svg")).not.toBeNull();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("renders column information with an unrecognized column type", () => {
|
|
40
|
+
const result = render(renderColumnInfo(geometryColumn));
|
|
41
|
+
|
|
42
|
+
expect(result.container.querySelector("svg")).not.toBeNull();
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
TableIcon,
|
|
14
14
|
ViewIcon,
|
|
15
15
|
} from "@/components/databases/namespace-icons";
|
|
16
|
-
import { DATA_TYPE_ICON } from "@/components/datasets/icons";
|
|
16
|
+
import { DATA_TYPE_ICON, resolveDataType } from "@/components/datasets/icons";
|
|
17
17
|
import { Badge } from "@/components/ui/badge";
|
|
18
18
|
import {
|
|
19
19
|
type ConnectionName,
|
|
@@ -149,7 +149,7 @@ export const renderTableInfo = (table: DataTable): React.ReactNode => {
|
|
|
149
149
|
);
|
|
150
150
|
|
|
151
151
|
const columnItems = table.columns.map((column) => {
|
|
152
|
-
const TypeIcon = DATA_TYPE_ICON[column.type];
|
|
152
|
+
const TypeIcon = DATA_TYPE_ICON[resolveDataType(column.type)];
|
|
153
153
|
return (
|
|
154
154
|
<div
|
|
155
155
|
key={column.name}
|
|
@@ -288,7 +288,7 @@ export const renderTableInfo = (table: DataTable): React.ReactNode => {
|
|
|
288
288
|
};
|
|
289
289
|
|
|
290
290
|
export const renderColumnInfo = (column: DataTableColumn): React.ReactNode => {
|
|
291
|
-
const TypeIcon = DATA_TYPE_ICON[column.type];
|
|
291
|
+
const TypeIcon = DATA_TYPE_ICON[resolveDataType(column.type)];
|
|
292
292
|
|
|
293
293
|
const typeBadge = (
|
|
294
294
|
<Badge
|