@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.
Files changed (185) hide show
  1. package/dist/{ConnectedDataExplorerComponent-f9vLjQiF.js → ConnectedDataExplorerComponent-BBZRR1-C.js} +16 -16
  2. package/dist/{ErrorBoundary-DhLqwSWA.js → ErrorBoundary-CptUP1_t.js} +3 -3
  3. package/dist/{ImageComparisonComponent-C3y7L3eJ.js → ImageComparisonComponent-fdY0LUuf.js} +38 -27
  4. package/dist/{any-language-editor-DifE-fl7.js → any-language-editor-BnIS9Mvg.js} +21 -21
  5. package/dist/assets/__vite-browser-external-DUz7vvG0.js +1 -0
  6. package/dist/assets/{worker-CDEKsBgX.js → worker-Cl1KJG2_.js} +2 -2
  7. package/dist/{button-BSm8IXRU.js → button-8aS1QcHr.js} +4 -2
  8. package/dist/{capabilities-C6OQ5YBO.js → capabilities-D23lgbH_.js} +1 -1
  9. package/dist/chat-ui-DH9M4L-6.js +8620 -0
  10. package/dist/{check-DP7U0H0G.js → check-D-BaEjEh.js} +1 -1
  11. package/dist/{common-BcQ4kHDP.js → common-DSVkRiDF.js} +652 -652
  12. package/dist/{copy-DNq8q3zw.js → copy-BkA3t7rJ.js} +2 -2
  13. package/dist/{defaultLocale-BpsHxBd7.js → defaultLocale-BsfI21d_.js} +26 -26
  14. package/dist/{diagram-PSM6KHXK-D4ftnaua.js → diagram-PSM6KHXK-D3GsWNcQ.js} +1 -1
  15. package/dist/{dist-BpeiYpG2.js → dist-3F1XDSYc.js} +2 -2
  16. package/dist/{dist-BP39XM8u.js → dist-B-upo54Y.js} +1 -1
  17. package/dist/{dist-BdqCKQ1O.js → dist-B1W12VzQ.js} +2 -2
  18. package/dist/{dist-3NCqaWjt.js → dist-B6au1PTB.js} +2 -2
  19. package/dist/{dist-BCX980mU.js → dist-BDwn6WIk.js} +2 -2
  20. package/dist/dist-BDzVdDo2.js +8 -0
  21. package/dist/dist-BH2Tq1S1.js +5 -0
  22. package/dist/dist-BIhBrzqg.js +5 -0
  23. package/dist/dist-BLCsvXTG.js +6 -0
  24. package/dist/dist-BNVtuFdd.js +5 -0
  25. package/dist/{dist-BOMbzyb3.js → dist-BNeJ8P-4.js} +3 -3
  26. package/dist/{dist-LpLXa9Lb.js → dist-BT96P4oR.js} +2 -2
  27. package/dist/{dist-CFSb_Frm.js → dist-BUzxp0Xf.js} +1 -1
  28. package/dist/{dist-JGehap7K.js → dist-Ba1WFESi.js} +5 -5
  29. package/dist/{dist-CRmSjkLP.js → dist-BrKNRKC8.js} +2 -2
  30. package/dist/{dist-BIEt-mlF.js → dist-BvvQUFUK.js} +1 -1
  31. package/dist/{dist-pqIwD8VD.js → dist-BxR-g1ig.js} +2 -2
  32. package/dist/{dist-DviUBLUp.js → dist-CBraAJlT.js} +4 -4
  33. package/dist/{dist-BwhXBwMd.js → dist-CDBV8n0I.js} +1 -1
  34. package/dist/dist-CEJ6Lowi.js +5 -0
  35. package/dist/{dist-CSlJrrAw.js → dist-CWYsUX_O.js} +1 -1
  36. package/dist/dist-CYoa69kj.js +5 -0
  37. package/dist/{dist-DwqBJ014.js → dist-CdKRSqjo.js} +2 -2
  38. package/dist/{dist-De-GlSAs.js → dist-Cds3J-EE.js} +15 -9
  39. package/dist/dist-CnZyXFq3.js +8 -0
  40. package/dist/dist-D-4HaJKc.js +8 -0
  41. package/dist/{dist-C18MUWTj.js → dist-D8SX5Qdg.js} +2 -2
  42. package/dist/{dist-D3bdsa1d.js → dist-DFQ91Mx1.js} +4 -4
  43. package/dist/{dist-BPF0gTRZ.js → dist-DGQmYLkh.js} +1 -1
  44. package/dist/dist-DIGyvbg3.js +5 -0
  45. package/dist/dist-DIhaoCL4.js +5 -0
  46. package/dist/{dist-C2_4pjuS.js → dist-DK0pFEmc.js} +1 -1
  47. package/dist/{dist-BTdrwBWP.js → dist-DQj6A7SE.js} +3 -3
  48. package/dist/{dist-DIlxLFXG.js → dist-DarwE0ZO.js} +11 -11
  49. package/dist/{dist-DaXJPX8F.js → dist-Ddb0VFS3.js} +2 -2
  50. package/dist/dist-DgQleHmb.js +8 -0
  51. package/dist/{dist-DZwp8lro.js → dist-DgkJegw4.js} +3 -3
  52. package/dist/{dist-BO5HRiTf.js → dist-DjQRRttD.js} +2 -2
  53. package/dist/dist-JaoLzZmt.js +5 -0
  54. package/dist/{dist-B2jmQKKx.js → dist-PQDZaPEo.js} +2 -2
  55. package/dist/{dist-4lVIWUT3.js → dist-Slyx4vXr.js} +1 -1
  56. package/dist/{dist-DgjbnqTg.js → dist-dJeh9ef4.js} +2 -2
  57. package/dist/dist-pEX0t8ZV.js +6 -0
  58. package/dist/{dist-w_gWE5Wf.js → dist-v-kAJfMq.js} +1 -1
  59. package/dist/{error-banner-B_GQfxxD.js → error-banner-dmkbm21G.js} +2 -2
  60. package/dist/{esm-DzZVaNH4.js → esm-D8vjxCyn.js} +3 -3
  61. package/dist/{esm-DKOv6P2M.js → esm-DEQU-LUO.js} +22 -22
  62. package/dist/{extends-w2_S44bV.js → extends-DDOVg_b8.js} +5 -5
  63. package/dist/{formats-Dwq1THhr.js → formats-xc-Msa0v.js} +3 -3
  64. package/dist/{ganttDiagram-JELNMOA3-CXXCN7LN.js → ganttDiagram-JELNMOA3-D9ywbC5g.js} +3 -3
  65. package/dist/{glide-data-editor-B9K6-MJe.js → glide-data-editor-CyTNN635.js} +9 -9
  66. package/dist/{html-to-image-CZ1kLKkq.js → html-to-image-DrEMf75G.js} +2219 -2209
  67. package/dist/{input-C5K4VBvB.js → input-B4ddy8Jy.js} +6 -6
  68. package/dist/{label-DjoYwqmj.js → label-CzHncDvS.js} +2 -2
  69. package/dist/{linear-H_vsH9ao.js → linear-BOGUO8Cd.js} +2 -2
  70. package/dist/{loader-DX-IDS7n.js → loader-C7SfUdOD.js} +10 -10
  71. package/dist/main.js +729 -733
  72. package/dist/{mermaid-hv6IckVw.js → mermaid-CpL8y5pw.js} +9 -9
  73. package/dist/{precisionRound-CnHPY_5v.js → precisionRound-Bq50n-bG.js} +1 -1
  74. package/dist/{process-output-bVfbcx5_.js → process-output-B9IHQuus.js} +3 -3
  75. package/dist/{quadrantDiagram-AYHSOK5B-BqDB9tUC.js → quadrantDiagram-AYHSOK5B-DD4QBdSv.js} +2 -2
  76. package/dist/{react-vega-BazDALhI.js → react-vega-Cg17A6rO.js} +8017 -4777
  77. package/dist/{reveal-component-9QGZq7uF.js → reveal-component-CUzZw4ZH.js} +19 -19
  78. package/dist/{spec-CQKL_D3i.js → spec-uMKNvMRZ.js} +4 -4
  79. package/dist/{strings-DIy9NAmg.js → strings-BUDhKc0s.js} +299 -299
  80. package/dist/style.css +1 -1
  81. package/dist/{swiper-component-CEdAYjb7.js → swiper-component-dl1H9WIW.js} +2 -2
  82. package/dist/{time-Bb4A7I6a.js → time-CQnohwNM.js} +2 -2
  83. package/dist/{toDate-CqhBWjvO.js → toDate-BiyuFdnT.js} +3 -3
  84. package/dist/{tooltip-f-qR0iUx.js → tooltip-CkCWlUOf.js} +3 -3
  85. package/dist/{types-DicH9Oi0.js → types-uENjXsiC.js} +1 -1
  86. package/dist/{useAsyncData-CYOQb1EW.js → useAsyncData-DqJLysjC.js} +1 -1
  87. package/dist/{useDateFormatter-Bt2qr_XS.js → useDateFormatter-BeN3fHMH.js} +2 -2
  88. package/dist/{useDeepCompareMemoize-CSHQeQEI.js → useDeepCompareMemoize-CvlmHAmy.js} +1 -1
  89. package/dist/{useIframeCapabilities-kA09ykNq.js → useIframeCapabilities-pLC-ceDP.js} +1 -1
  90. package/dist/{useLifecycle-CMJGqBaY.js → useLifecycle-D4N02q82.js} +3 -3
  91. package/dist/{useTheme-DYfby65G.js → useTheme-Dur2p3iC.js} +4 -3
  92. package/dist/{vega-component-C7nO5zcf.js → vega-component-Cft458Sr.js} +13 -13
  93. package/dist/{vega-embed-container-DAQAMjiO.js → vega-embed-container-S9gRu4TZ.js} +3 -3
  94. package/dist/{vega-loader.browser-DmpxCJqx.js → vega-loader.browser-CmLRI3wk.js} +104 -111
  95. package/dist/{xychartDiagram-PRI3JC2R-BSkNxYNw.js → xychartDiagram-PRI3JC2R-D9DgEtKn.js} +2 -2
  96. package/dist/{zod-BUcyXSdR.js → zod-yTHHzJiv.js} +37 -38
  97. package/package.json +6 -6
  98. package/src/__mocks__/requests.ts +5 -0
  99. package/src/components/app-config/user-config-form.tsx +26 -0
  100. package/src/components/chat/__tests__/chat-abort.test.ts +51 -0
  101. package/src/components/chat/chat-abort.ts +44 -0
  102. package/src/components/chat/chat-display.tsx +17 -0
  103. package/src/components/chat/chat-panel.tsx +85 -37
  104. package/src/components/chat/chat-utils.ts +1 -1
  105. package/src/components/data-table/charts/__tests__/schemas.test.ts +35 -0
  106. package/src/components/data-table/charts/schemas.ts +5 -2
  107. package/src/components/databases/icons/huggingface.svg +8 -0
  108. package/src/components/datasets/__tests__/icons.test.ts +21 -0
  109. package/src/components/datasets/icons.tsx +10 -0
  110. package/src/components/datasources/__tests__/components.test.tsx +29 -0
  111. package/src/components/datasources/components.tsx +8 -3
  112. package/src/components/editor/actions/export-dialog/__tests__/export-dialog.test.tsx +35 -0
  113. package/src/components/editor/actions/export-dialog/__tests__/state.test.ts +61 -0
  114. package/src/components/editor/actions/export-dialog/export-command.ts +4 -9
  115. package/src/components/editor/actions/export-dialog/export-dialog.tsx +27 -23
  116. package/src/components/editor/actions/export-dialog/format-notice.tsx +28 -0
  117. package/src/components/editor/actions/export-dialog/state.ts +23 -5
  118. package/src/components/editor/actions/export-dialog/use-export-dialog.ts +7 -15
  119. package/src/components/editor/actions/useConfigActions.tsx +24 -0
  120. package/src/components/editor/ai/add-cell-with-ai.tsx +2 -2
  121. package/src/components/editor/ai/ai-completion-editor.tsx +2 -2
  122. package/src/components/editor/chrome/panels/outline/__tests__/useActiveOutline.test.ts +26 -0
  123. package/src/components/editor/code/__tests__/readonly-python-code.test.tsx +114 -2
  124. package/src/components/editor/connections/storage/__tests__/__snapshots__/as-code.test.ts.snap +14 -0
  125. package/src/components/editor/connections/storage/__tests__/as-code.test.ts +20 -0
  126. package/src/components/editor/connections/storage/add-storage-form.tsx +10 -0
  127. package/src/components/editor/connections/storage/as-code.ts +19 -1
  128. package/src/components/editor/connections/storage/schemas.ts +19 -0
  129. package/src/components/storage/__tests__/storage-snippets.test.ts +88 -0
  130. package/src/components/storage/components.tsx +2 -0
  131. package/src/components/storage/storage-snippets.ts +58 -0
  132. package/src/core/__tests__/mode.test.ts +85 -0
  133. package/src/core/ai/constants.ts +7 -0
  134. package/src/core/ai/staged-cells.ts +3 -0
  135. package/src/core/codemirror/__tests__/setup.test.ts +1 -1
  136. package/src/core/codemirror/cm.ts +2 -2
  137. package/src/core/codemirror/code-lens/__tests__/extension.test.ts +4 -5
  138. package/src/core/codemirror/code-lens/extension.ts +3 -3
  139. package/src/core/codemirror/find-replace/search-highlight.ts +8 -3
  140. package/src/core/codemirror/language/languages/sql/__tests__/renderers.test.tsx +44 -0
  141. package/src/core/codemirror/language/languages/sql/renderers.tsx +3 -3
  142. package/src/core/codemirror/theme/__tests__/light.test.ts +59 -123
  143. package/src/core/codemirror/theme/dark.ts +6 -5
  144. package/src/core/codemirror/theme/light.ts +6 -4
  145. package/src/core/config/__tests__/config-schema.test.ts +2 -0
  146. package/src/core/config/config-schema.ts +1 -0
  147. package/src/core/config/feature-flag.tsx +0 -2
  148. package/src/core/dom/outline.ts +25 -2
  149. package/src/core/hotkeys/hotkeys.ts +2 -0
  150. package/src/core/mode.ts +11 -9
  151. package/src/core/network/__tests__/api.test.ts +132 -17
  152. package/src/core/network/__tests__/export-filename.test.ts +43 -0
  153. package/src/core/network/__tests__/requests-lazy.test.ts +1 -0
  154. package/src/core/network/api.ts +47 -12
  155. package/src/core/network/export-filename.ts +37 -0
  156. package/src/core/network/markdown-export.ts +19 -0
  157. package/src/core/network/requests-network.ts +29 -5
  158. package/src/core/network/types.ts +3 -0
  159. package/src/core/storage/types.ts +1 -0
  160. package/src/css/app/codemirror.css +11 -7
  161. package/src/css/globals.css +8 -0
  162. package/src/plugins/impl/DataEditorPlugin.tsx +2 -9
  163. package/src/plugins/impl/__tests__/DataEditorPlugin.test.ts +18 -0
  164. package/src/plugins/impl/chat/chat-ui.tsx +51 -3
  165. package/src/plugins/impl/data-frames/__tests__/schema.test.ts +30 -0
  166. package/src/plugins/impl/data-frames/schema.ts +4 -1
  167. package/src/plugins/impl/image-comparison/ImageComparisonComponent.tsx +20 -1
  168. package/src/plugins/impl/image-comparison/__tests__/ImageComparisonComponent.test.tsx +58 -0
  169. package/src/stories/theme.stories.tsx +71 -1
  170. package/dist/assets/__vite-browser-external-DltVXDv7.js +0 -1
  171. package/dist/chat-ui-DsZJj75A.js +0 -8369
  172. package/dist/dist-5o9uz19x.js +0 -5
  173. package/dist/dist-BpDH0DEP.js +0 -5
  174. package/dist/dist-BpXhbQXO.js +0 -8
  175. package/dist/dist-ByadAk_L.js +0 -5
  176. package/dist/dist-CK0g7JvP.js +0 -8
  177. package/dist/dist-CfzCtiq2.js +0 -5
  178. package/dist/dist-D8JtVFrW.js +0 -8
  179. package/dist/dist-DK2Oocpu.js +0 -6
  180. package/dist/dist-DnSu977r.js +0 -5
  181. package/dist/dist-UmxJm6sW.js +0 -8
  182. package/dist/dist-aYSBQSyM.js +0 -5
  183. package/dist/dist-kkZ1Ztlh.js +0 -6
  184. package/dist/dist-ngPczwNi.js +0 -5
  185. 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
+ });
@@ -0,0 +1,7 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ /**
4
+ * Throttle wait (ms) for AI SDK React hooks (`useChat` / `useCompletion`).
5
+ * Limits UI re-renders while streaming tokens.
6
+ */
7
+ export const AI_SDK_UI_THROTTLE_MS = 100;
@@ -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({ editor_code_lens: true, cache_panel: true });
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 the flag is off", () => {
145
- mockFlags({ editor_code_lens: false });
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({ editor_code_lens: true, cache_panel: false });
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
- * Gated behind the `editor_code_lens` feature flag.
284
+ * Configurable via the `display.code_lens` user config (on by default).
285
285
  */
286
- export function codeLensBundle(cellId: CellId): Extension {
287
- if (!getFeatureFlag("editor_code_lens")) {
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": { backgroundColor: "#99ff7780" },
144
- "&dark .cm-searchMatch": { backgroundColor: "#22bb0070" },
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: "#6199ff88 !important",
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