@marimo-team/islands 0.15.2 → 0.15.3

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 (189) hide show
  1. package/dist/{ConnectedDataExplorerComponent-C39nQwtD.js → ConnectedDataExplorerComponent-DfvW3rBn.js} +323 -328
  2. package/dist/{ImageComparisonComponent-BhkiyswP.js → ImageComparisonComponent-XaJshw7d.js} +13 -13
  3. package/dist/{_baseUniq-DdHL34FO.js → _baseUniq-dN9WKF9m.js} +67 -67
  4. package/dist/any-language-editor-CpFniVi-.js +27 -0
  5. package/dist/{arc-BXrety1g.js → arc-BOhn-m2C.js} +1 -1
  6. package/dist/{architectureDiagram-KFL7JDKH-BMy6ywCF.js → architectureDiagram-W76B3OCA-Bpg85ZKv.js} +144 -144
  7. package/dist/assets/{worker-COGufAQn.js → worker-Y-Q4G-N2.js} +30 -26
  8. package/dist/asterisk-DS281yxp.js +271 -0
  9. package/dist/{blockDiagram-ZYB65J3Q-DYT2-nlI.js → blockDiagram-QIGZ2CNN-DS1kOHlW.js} +10 -10
  10. package/dist/{c4Diagram-AAMF2YG6-ZiQzioe6.js → c4Diagram-FPNF74CW-CyRVKssw.js} +8 -8
  11. package/dist/{channel-CeuXqUAU.js → channel-BilGXox7.js} +1 -1
  12. package/dist/{chunk-ANTBXLJU-BvYnIrdq.js → chunk-4BX2VUAB-CZR39zCO.js} +1 -1
  13. package/dist/{chunk-WVR4S24B-DXj8yaUk.js → chunk-55IACEB6-BIH-MYov.js} +1 -1
  14. package/dist/{chunk-GLLZNHP4-CyFsosAe.js → chunk-FMBD7UC4-4PZXFZE8.js} +1 -1
  15. package/dist/{chunk-JBRWN2VN-DA_EEhy2.js → chunk-K7UQS3LO-CEvWKznk.js} +117 -117
  16. package/dist/{chunk-NRVI72HA-BYx2jMlI.js → chunk-QN33PNHL-D5LO5Jq_.js} +1 -1
  17. package/dist/{chunk-FHKO5MBM-DfCztBk8.js → chunk-QZHKN3VN-6gwUonWI.js} +1 -1
  18. package/dist/{chunk-LXBSTHXV-Se7vdY6J.js → chunk-TVAH2DTR-3gm06QdU.js} +7 -7
  19. package/dist/{chunk-OMD6QJNC-CqgcPMgL.js → chunk-TZMSLE5B-Cm8Iy9bO.js} +1 -1
  20. package/dist/{classDiagram-v2-QTMF73CY-B19A3G1l.js → classDiagram-KNZD7YFC-DC529O_z.js} +2 -2
  21. package/dist/{classDiagram-3BZAVTQC-B19A3G1l.js → classDiagram-v2-RKCZMP56-DC529O_z.js} +2 -2
  22. package/dist/{clone-78au0tn1.js → clone-CLoRX3j6.js} +1 -1
  23. package/dist/cose-bilkent-S5V4N54A-qf5DlS6Y.js +2609 -0
  24. package/dist/{cytoscape.esm-BYnVVhJX.js → cytoscape.esm-DfdJODL8.js} +34 -34
  25. package/dist/{dagre-2BBEFEWP-BfEn3ZUV.js → dagre-5GWH7T2D-Ceocls0m.js} +6 -6
  26. package/dist/{data-grid-overlay-editor-CH_qLkV2.js → data-grid-overlay-editor-AqDS_UKe.js} +11 -11
  27. package/dist/{diagram-4IRLE6MV-CL8xidnG.js → diagram-N5W7TBWH-CP66oSiv.js} +59 -60
  28. package/dist/{diagram-RP2FKANI-B1BPcUew.js → diagram-QEK2KX5R-_YD4kxxi.js} +15 -15
  29. package/dist/{diagram-GUPCWM2R-CZ5cfqlq.js → diagram-S2PKOQOG-Cnj8T-OP.js} +10 -10
  30. package/dist/dockerfile-Cm8cRYCN.js +194 -0
  31. package/dist/ebnf-DUPDuY4r.js +78 -0
  32. package/dist/{erDiagram-HZWUO2LU-BEAIww50.js → erDiagram-AWTI2OKA-CGnvoHx6.js} +8 -8
  33. package/dist/fcl-CPC2WYrI.js +103 -0
  34. package/dist/{flowDiagram-THRYKUMA-Czs2UAI2.js → flowDiagram-PVAE7QVJ-DG-pr9R9.js} +9 -9
  35. package/dist/{ganttDiagram-WV7ZQ7D5-ByYIAVFO.js → ganttDiagram-OWAHRB6G-JmChtxvn.js} +34 -34
  36. package/dist/{gitGraphDiagram-OJR772UL-BcpDsiyB.js → gitGraphDiagram-NY62KEGX-D8wLfOPd.js} +4 -4
  37. package/dist/{glide-data-editor-CmN6FVyi.js → glide-data-editor-9nC3iCIZ.js} +33 -33
  38. package/dist/{graph-77W6heli.js → graph-CoRe7vAN.js} +3 -3
  39. package/dist/http-D9LttvKF.js +44 -0
  40. package/dist/{index-Bfk9dnyS.js → index-6qYeHHjQ.js} +33090 -32892
  41. package/dist/{index-BOojn38D.js → index-BpzLh4Qe.js} +7711 -7711
  42. package/dist/{index-CmozKMxx.js → index-BthgsgYX.js} +6 -6
  43. package/dist/{index-pBmAzQJl.js → index-MCx5v1x0.js} +2 -2
  44. package/dist/index-jkm77Jrz.js +98 -0
  45. package/dist/{infoDiagram-6WOFNB3A-CfzLHHVP.js → infoDiagram-STP46IZ2-BlXxvOrR.js} +2 -2
  46. package/dist/{journeyDiagram-FFXJYRFH-ndAcpkGn.js → journeyDiagram-BIP6EPQ6-CNRYs_Fc.js} +24 -26
  47. package/dist/{kanban-definition-KOZQBZVT-DcQYzNvc.js → kanban-definition-6OIFK2YF-B9HeMAuP.js} +14 -14
  48. package/dist/{layout-XySVHJgD.js → layout-m2vOUiW1.js} +81 -81
  49. package/dist/{linear-PbooOqg7.js → linear-DU6Q5CX3.js} +35 -35
  50. package/dist/{main-B5yML0bw.js → main-BD2KGFpU.js} +74594 -68034
  51. package/dist/main.js +1 -1
  52. package/dist/{mermaid-Cg5IX6Nv.js → mermaid-HVCtvbyx.js} +6160 -7493
  53. package/dist/min-DcGMA4e_.js +80 -0
  54. package/dist/mindmap-definition-Q6HEUPPD-BW8UmIDQ.js +785 -0
  55. package/dist/nginx-zDPm3Z74.js +89 -0
  56. package/dist/{number-overlay-editor-DUhfZqtP.js → number-overlay-editor-D8Hl0Syo.js} +19 -19
  57. package/dist/{pieDiagram-DBDJKBY4-DTOlNsja.js → pieDiagram-ADFJNKIX-Bg-3zg5U.js} +17 -17
  58. package/dist/{quadrantDiagram-YPSRARAO-BX2d8VS-.js → quadrantDiagram-LMRXKWRM-BO4IG6Yz.js} +6 -6
  59. package/dist/{react-plotly-Dcyw-3Sa.js → react-plotly-dkvHVuRb.js} +3577 -3577
  60. package/dist/{requirementDiagram-EGVEC5DT-D1T5u-wG.js → requirementDiagram-4UW4RH46-5sdTguSM.js} +7 -7
  61. package/dist/{sankeyDiagram-HRAUVNP4-G6xDfnp-.js → sankeyDiagram-GR3RE2ED-Buhlv9OI.js} +5 -5
  62. package/dist/sequenceDiagram-C3RYC4MD-C3qsM2UP.js +2519 -0
  63. package/dist/{slides-component-BJLlPJSr.js → slides-component-D209A0-s.js} +66 -66
  64. package/dist/solr-BNlsLglM.js +41 -0
  65. package/dist/spreadsheet-C-cy4P5N.js +49 -0
  66. package/dist/{stateDiagram-UUKSUZ4H-CYXbjaom.js → stateDiagram-KXAO66HF-CopJ7G6P.js} +5 -5
  67. package/dist/{stateDiagram-v2-EYPG3UTE-Br1HYKT6.js → stateDiagram-v2-UMBNRL4Z-CejL8AKf.js} +2 -2
  68. package/dist/style.css +1 -1
  69. package/dist/tiddlywiki-5wqsXtSk.js +155 -0
  70. package/dist/tiki-__Kn3CeS.js +181 -0
  71. package/dist/{time-B9SZnSen.js → time-BwSBitlN.js} +58 -58
  72. package/dist/{timeline-definition-3HZDQTIS-DeK_ZRD0.js → timeline-definition-XQNQX7LJ-DzMNTX-C.js} +10 -12
  73. package/dist/{timer-BYwnU4DF.js → timer-B0-z63CM.js} +16 -16
  74. package/dist/{treemap-75Q7IDZK-CKP4vV_0.js → treemap-75Q7IDZK-zeJG07dk.js} +14 -14
  75. package/dist/{vega-component-CpgdqX2d.js → vega-component-CUkiTayd.js} +30 -30
  76. package/dist/{xychartDiagram-FDP5SA34-AMEPsx_R.js → xychartDiagram-6GGTOJPD-DiENNXMS.js} +7 -7
  77. package/package.json +39 -39
  78. package/src/__mocks__/notebook.ts +3 -0
  79. package/src/__mocks__/requests.ts +3 -0
  80. package/src/__tests__/__snapshots__/CellStatus.test.tsx.snap +12 -12
  81. package/src/__tests__/chat-utils.test.ts +26 -211
  82. package/src/components/ai/ai-model-dropdown.tsx +25 -9
  83. package/src/components/app-config/ai-config.tsx +7 -0
  84. package/src/components/chat/chat-components.tsx +71 -0
  85. package/src/components/chat/chat-panel.tsx +481 -291
  86. package/src/components/chat/chat-utils.ts +50 -0
  87. package/src/components/chat/markdown-renderer.tsx +3 -7
  88. package/src/components/chat/tool-call-accordion.tsx +5 -5
  89. package/src/components/datasources/__tests__/utils.test.ts +6 -0
  90. package/src/components/datasources/column-preview.tsx +1 -3
  91. package/src/components/editor/actions/useNotebookActions.tsx +1 -1
  92. package/src/components/editor/ai/add-cell-with-ai.tsx +20 -15
  93. package/src/components/editor/ai/ai-completion-editor.tsx +22 -3
  94. package/src/components/editor/ai/completion-handlers.tsx +2 -4
  95. package/src/components/editor/ai/completion-utils.ts +85 -11
  96. package/src/components/editor/alerts/startup-logs-alert.tsx +72 -0
  97. package/src/components/editor/chrome/panels/datasources-panel.tsx +3 -1
  98. package/src/components/editor/chrome/panels/dependency-graph-panel.tsx +3 -1
  99. package/src/components/editor/chrome/panels/documentation-panel.tsx +3 -1
  100. package/src/components/editor/chrome/panels/error-panel.tsx +3 -1
  101. package/src/components/editor/chrome/panels/file-explorer-panel.tsx +3 -1
  102. package/src/components/editor/chrome/panels/logs-panel.tsx +3 -1
  103. package/src/components/editor/chrome/panels/outline-panel.tsx +3 -1
  104. package/src/components/editor/chrome/panels/packages-panel.tsx +4 -2
  105. package/src/components/editor/chrome/panels/scratchpad-panel.tsx +3 -1
  106. package/src/components/editor/chrome/panels/secrets-panel.tsx +3 -1
  107. package/src/components/editor/chrome/panels/snippets-panel.tsx +3 -1
  108. package/src/components/editor/chrome/panels/tracing-panel.tsx +3 -1
  109. package/src/components/editor/chrome/panels/variable-panel.tsx +3 -1
  110. package/src/components/editor/chrome/wrapper/app-chrome.tsx +38 -28
  111. package/src/components/editor/controls/command-palette-button.tsx +1 -1
  112. package/src/components/editor/controls/command-palette.tsx +5 -4
  113. package/src/components/editor/controls/state.ts +4 -0
  114. package/src/components/editor/package-alert.tsx +108 -58
  115. package/src/components/editor/renderers/CellArray.tsx +2 -0
  116. package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +0 -1
  117. package/src/components/pages/edit-page.tsx +7 -3
  118. package/src/core/ai/chat-utils.ts +26 -43
  119. package/src/core/ai/config.ts +1 -1
  120. package/src/core/ai/context/__tests__/registry.test.ts +277 -3
  121. package/src/core/ai/context/context.ts +11 -1
  122. package/src/core/ai/context/providers/__tests__/cell-output.test.ts +378 -0
  123. package/src/core/ai/context/providers/__tests__/error.test.ts +3 -2
  124. package/src/core/ai/context/providers/__tests__/file.test.ts +119 -0
  125. package/src/core/ai/context/providers/cell-output.ts +349 -0
  126. package/src/core/ai/context/providers/common.ts +5 -1
  127. package/src/core/ai/context/providers/file.ts +287 -0
  128. package/src/core/ai/context/registry.ts +79 -0
  129. package/src/core/ai/state.ts +22 -5
  130. package/src/core/alerts/state.ts +71 -3
  131. package/src/core/cells/cell.ts +2 -2
  132. package/src/core/cells/cells.ts +1 -1
  133. package/src/core/cells/logs.ts +1 -1
  134. package/src/core/cells/runs.ts +6 -5
  135. package/src/core/codemirror/ai/resources.ts +47 -5
  136. package/src/core/codemirror/ai/state.ts +12 -0
  137. package/src/core/codemirror/language/__tests__/sql.test.ts +45 -0
  138. package/src/core/codemirror/markdown/__tests__/commands.test.ts +1 -0
  139. package/src/core/codemirror/theme/dark.ts +1 -1
  140. package/src/core/config/capabilities.ts +1 -1
  141. package/src/core/datasets/__tests__/data-source.test.ts +24 -0
  142. package/src/core/errors/__tests__/errors.test.ts +2 -0
  143. package/src/core/islands/bridge.ts +1 -0
  144. package/src/core/islands/main.ts +1 -0
  145. package/src/core/kernel/messages.ts +12 -6
  146. package/src/core/layout/layout.ts +3 -3
  147. package/src/core/network/requests-network.ts +8 -0
  148. package/src/core/network/requests-static.ts +1 -0
  149. package/src/core/network/requests-toasting.ts +1 -0
  150. package/src/core/network/types.ts +4 -1
  151. package/src/core/wasm/bridge.ts +18 -2
  152. package/src/core/wasm/worker/bootstrap.ts +3 -1
  153. package/src/core/wasm/worker/getMarimoWheel.ts +3 -8
  154. package/src/core/wasm/worker/types.ts +3 -0
  155. package/src/core/websocket/useMarimoWebSocket.tsx +7 -1
  156. package/src/css/app/Cell.css +42 -21
  157. package/src/css/app/codemirror.css +5 -1
  158. package/src/css/globals.css +3 -0
  159. package/src/css/md.css +1 -1
  160. package/src/plugins/impl/MicrophonePlugin.tsx +2 -2
  161. package/src/plugins/impl/chat/ChatPlugin.tsx +2 -9
  162. package/src/plugins/impl/chat/chat-ui.tsx +129 -110
  163. package/src/plugins/impl/chat/types.ts +5 -8
  164. package/src/plugins/impl/code/__tests__/language.test.ts +15 -0
  165. package/src/plugins/impl/code/any-language-editor.tsx +11 -8
  166. package/src/plugins/layout/MimeRenderPlugin.tsx +3 -6
  167. package/src/stories/cell.stories.tsx +6 -0
  168. package/src/stories/layout/vertical/one-column.stories.tsx +215 -0
  169. package/src/theme/useTheme.ts +11 -6
  170. package/src/utils/__tests__/blob.test.ts +37 -0
  171. package/src/utils/arrays.ts +13 -0
  172. package/src/utils/fileToBase64.ts +21 -6
  173. package/src/utils/json/base64.ts +5 -2
  174. package/src/utils/numbers.ts +9 -7
  175. package/dist/any-language-editor-DC5170DQ.js +0 -45
  176. package/dist/asn1-jKiBa2Ya.js +0 -95
  177. package/dist/clojure-CCKyeQKf.js +0 -800
  178. package/dist/css-BkF-NPzE.js +0 -1553
  179. package/dist/index-5ZH_qS8j.js +0 -288
  180. package/dist/index-U4yn89qO.js +0 -341
  181. package/dist/javascript-C2yteZeJ.js +0 -691
  182. package/dist/min-DS5Jz-hg.js +0 -80
  183. package/dist/mindmap-definition-LNHGMQRG-0aOVaMR8.js +0 -3234
  184. package/dist/mllike-BSnXJBGA.js +0 -272
  185. package/dist/pug-CwAQJzGR.js +0 -248
  186. package/dist/python-BkR3uSy8.js +0 -313
  187. package/dist/rpm-IznJm2Xc.js +0 -57
  188. package/dist/sequenceDiagram-WFGC7UMF-DMhHzllb.js +0 -2284
  189. package/dist/ttcn-cfg-Bac_acMi.js +0 -88
@@ -2,6 +2,10 @@
2
2
 
3
3
  import type { Completion } from "@codemirror/autocomplete";
4
4
  import type { Resource } from "@marimo-team/codemirror-mcp";
5
+ import type { FileUIPart } from "ai";
6
+ import { Memoize } from "typescript-memoize";
7
+ import { Logger } from "@/utils/Logger";
8
+ import { MultiMap } from "@/utils/multi-map";
5
9
  import type { TypedString } from "@/utils/typed";
6
10
 
7
11
  /**
@@ -41,6 +45,12 @@ export abstract class AIContextProvider<
41
45
  /** Format completion */
42
46
  abstract formatCompletion(item: T): Completion;
43
47
 
48
+ /** Get attachments for context items (optional, async) */
49
+ async getAttachments(_items: T[]): Promise<FileUIPart[]> {
50
+ // Default implementation returns no attachments
51
+ return [];
52
+ }
53
+
44
54
  asURI(id: string): ContextLocatorId {
45
55
  return `${this.contextType}://${id}` as ContextLocatorId;
46
56
  }
@@ -118,6 +128,7 @@ export class AIContextRegistry<T extends AIContextItem> {
118
128
  );
119
129
  }
120
130
 
131
+ @Memoize()
121
132
  getAllItems(): T[] {
122
133
  return [...this.providers].flatMap((provider) => provider.getItems());
123
134
  }
@@ -177,4 +188,72 @@ export class AIContextRegistry<T extends AIContextItem> {
177
188
  })
178
189
  .join("\n\n");
179
190
  }
191
+
192
+ /**
193
+ * Get attachments for mentioned items
194
+ */
195
+ async getAttachmentsForContext(
196
+ contextIds: ContextLocatorId[],
197
+ ): Promise<FileUIPart[]> {
198
+ const allItems = new Map<ContextLocatorId, T>(
199
+ this.getAllItems().map((item) => [item.uri as ContextLocatorId, item]),
200
+ );
201
+
202
+ const contextInfo: T[] = [];
203
+ for (const contextId of contextIds) {
204
+ const item = allItems.get(contextId);
205
+ if (item) {
206
+ contextInfo.push(item);
207
+ }
208
+ }
209
+
210
+ if (contextInfo.length === 0) {
211
+ return [];
212
+ }
213
+
214
+ // Group items by provider type to batch attachment requests
215
+ const itemsByProvider = new MultiMap<string, T>();
216
+ for (const item of contextInfo) {
217
+ const providerType = item.type;
218
+ itemsByProvider.add(providerType, item);
219
+ }
220
+
221
+ // Collect attachments from all providers
222
+ const attachmentPromises = [...itemsByProvider.entries()].map(
223
+ async ([providerType, items]) => {
224
+ const provider = this.getProvider(providerType);
225
+ if (!provider) {
226
+ return [];
227
+ }
228
+ try {
229
+ return await provider.getAttachments(items);
230
+ } catch (error) {
231
+ Logger.error("Error getting attachments from provider", error);
232
+ return [];
233
+ }
234
+ },
235
+ );
236
+
237
+ const attachmentResults = await Promise.all(attachmentPromises);
238
+ const results = attachmentResults.flat();
239
+
240
+ // Print attachments to the console with a rich image preview
241
+ if (import.meta.env.DEV) {
242
+ for (const attachment of results) {
243
+ // If it's an image, print a rich preview
244
+ if (
245
+ /^data:image\/(png|jpeg|jpg|gif|svg\+xml);base64,/.test(
246
+ attachment.url,
247
+ )
248
+ ) {
249
+ Logger.debug(
250
+ "%c ",
251
+ `font-size:1px;padding:140px 180px;background:url('${attachment.url}') no-repeat;background-size:contain;`,
252
+ );
253
+ }
254
+ }
255
+ }
256
+
257
+ return results;
258
+ }
180
259
  }
@@ -1,13 +1,15 @@
1
1
  /* Copyright 2024 Marimo. All rights reserved. */
2
2
 
3
- import type { Message as AIMessage } from "@ai-sdk/react";
3
+ import type { UIMessage } from "@ai-sdk/react";
4
+ import type { FileUIPart } from "ai";
4
5
  import { atom } from "jotai";
5
6
  import { atomWithStorage } from "jotai/utils";
7
+ import { uniqueBy } from "@/utils/arrays";
6
8
  import { adaptForLocalStorage } from "@/utils/storage";
7
9
  import type { TypedString } from "@/utils/typed";
8
10
  import type { CellId } from "../cells/ids";
9
11
 
10
- const KEY = "marimo:ai:chatState:v4";
12
+ const KEY = "marimo:ai:chatState:v5";
11
13
 
12
14
  export type ChatId = TypedString<"ChatId">;
13
15
 
@@ -27,13 +29,14 @@ export interface Message {
27
29
  role: "user" | "assistant" | "data" | "system";
28
30
  content: string;
29
31
  timestamp: number;
30
- parts?: AIMessage["parts"];
32
+ parts?: UIMessage["parts"];
33
+ attachments?: FileUIPart[];
31
34
  }
32
35
 
33
36
  export interface Chat {
34
37
  id: ChatId;
35
38
  title: string;
36
- messages: Message[];
39
+ messages: UIMessage[];
37
40
  createdAt: number;
38
41
  updatedAt: number;
39
42
  }
@@ -43,6 +46,20 @@ export interface ChatState {
43
46
  activeChatId: ChatId | null;
44
47
  }
45
48
 
49
+ function removeEmptyChats(chatState: Map<ChatId, Chat>): Map<ChatId, Chat> {
50
+ const result = new Map<ChatId, Chat>();
51
+
52
+ // Dedupe messages with the same id
53
+ for (const [chatId, chat] of chatState.entries()) {
54
+ if (chat.messages.length === 0) {
55
+ continue;
56
+ }
57
+ const dedupedMessages = uniqueBy(chat.messages, (message) => message.id);
58
+ result.set(chatId, { ...chat, messages: dedupedMessages });
59
+ }
60
+ return result;
61
+ }
62
+
46
63
  export const chatStateAtom = atomWithStorage<ChatState>(
47
64
  KEY,
48
65
  {
@@ -51,7 +68,7 @@ export const chatStateAtom = atomWithStorage<ChatState>(
51
68
  },
52
69
  adaptForLocalStorage({
53
70
  toSerializable: (value: ChatState) => ({
54
- chats: [...value.chats.entries()],
71
+ chats: [...removeEmptyChats(value.chats).entries()],
55
72
  activeChatId: value.activeChatId,
56
73
  }),
57
74
  fromSerializable: (value) => ({
@@ -16,6 +16,13 @@ export interface MissingPackageAlert {
16
16
  export interface InstallingPackageAlert {
17
17
  kind: "installing";
18
18
  packages: PackageInstallationStatus;
19
+ logs?: { [key: string]: string } | null;
20
+ log_status?: "append" | "start" | "done" | null;
21
+ }
22
+
23
+ export interface StartupLogsAlert {
24
+ content: string;
25
+ status: "append" | "start" | "done";
19
26
  }
20
27
 
21
28
  export function isMissingPackageAlert(
@@ -39,26 +46,87 @@ interface AlertState {
39
46
  | Identified<MissingPackageAlert>
40
47
  | Identified<InstallingPackageAlert>
41
48
  | null;
49
+ startupLogsAlert: StartupLogsAlert | null;
50
+ packageLogs: { [packageName: string]: string };
42
51
  }
43
52
 
44
53
  const { valueAtom: alertAtom, useActions } = createReducerAndAtoms(
45
- () => ({ packageAlert: null }) as AlertState,
54
+ () =>
55
+ ({
56
+ packageAlert: null,
57
+ startupLogsAlert: null,
58
+ packageLogs: {},
59
+ }) as AlertState,
46
60
  {
47
61
  addPackageAlert: (
48
62
  state,
49
63
  alert: MissingPackageAlert | InstallingPackageAlert,
50
64
  ) => {
65
+ const newPackageLogs = { ...state.packageLogs };
66
+
67
+ // Handle streaming logs for installing package alerts
68
+ if (isInstallingPackageAlert(alert) && alert.logs && alert.log_status) {
69
+ for (const [packageName, newContent] of Object.entries(alert.logs)) {
70
+ switch (alert.log_status) {
71
+ case "start":
72
+ // Start new log for this package
73
+ newPackageLogs[packageName] = newContent;
74
+
75
+ break;
76
+
77
+ case "append": {
78
+ // Append to existing log
79
+ const prevContent = newPackageLogs[packageName] || "";
80
+ newPackageLogs[packageName] = prevContent + newContent;
81
+
82
+ break;
83
+ }
84
+ case "done": {
85
+ // Append final content and mark as done
86
+ const prevContent = newPackageLogs[packageName] || "";
87
+ newPackageLogs[packageName] = prevContent + newContent;
88
+
89
+ break;
90
+ }
91
+ // No default
92
+ }
93
+ }
94
+ }
95
+
96
+ const existingAlert = state.packageAlert;
97
+ const alertId = existingAlert?.id || generateUUID();
98
+
51
99
  return {
52
100
  ...state,
53
- packageAlert: { id: generateUUID(), ...alert },
101
+ packageAlert: { id: alertId, ...alert },
102
+ packageLogs: newPackageLogs,
54
103
  };
55
104
  },
56
105
 
57
106
  clearPackageAlert: (state, id: string) => {
58
107
  return state.packageAlert !== null && state.packageAlert.id === id
59
- ? { ...state, packageAlert: null }
108
+ ? { ...state, packageAlert: null, packageLogs: {} }
60
109
  : state;
61
110
  },
111
+
112
+ addStartupLog: (
113
+ state,
114
+ logData: { content: string; status: "append" | "start" | "done" },
115
+ ) => {
116
+ const prevContent = state.startupLogsAlert?.content || "";
117
+ return {
118
+ ...state,
119
+ startupLogsAlert: {
120
+ ...state.startupLogsAlert,
121
+ content: prevContent + logData.content,
122
+ status: logData.status,
123
+ },
124
+ };
125
+ },
126
+
127
+ clearStartupLogsAlert: (state) => {
128
+ return { ...state, startupLogsAlert: null };
129
+ },
62
130
  },
63
131
  );
64
132
 
@@ -47,7 +47,7 @@ export function transitionCell(
47
47
  case "idle":
48
48
  if (cell.runStartTimestamp) {
49
49
  nextCell.runElapsedTimeMs = Time.fromSeconds(
50
- (message.timestamp - cell.runStartTimestamp) as Seconds,
50
+ ((message.timestamp ?? 0) - cell.runStartTimestamp) as Seconds,
51
51
  ).toMilliseconds();
52
52
  nextCell.runStartTimestamp = null;
53
53
  nextCell.staleInputs = false;
@@ -210,7 +210,7 @@ export function outputIsStale(
210
210
  status === "running" &&
211
211
  output !== null &&
212
212
  runStartTimestamp !== null &&
213
- output.timestamp > runStartTimestamp;
213
+ (output.timestamp ?? 0) > runStartTimestamp;
214
214
 
215
215
  // If loading and output has not been received while running
216
216
  if (loading && !outputReceivedWhileRunning) {
@@ -1372,7 +1372,7 @@ const {
1372
1372
 
1373
1373
  function isCellCodeHidden(state: NotebookState, cellId: CellId): boolean {
1374
1374
  return (
1375
- state.cellData[cellId].config.hide_code &&
1375
+ Boolean(state.cellData[cellId].config.hide_code) &&
1376
1376
  !state.untouchedNewCells.has(cellId)
1377
1377
  );
1378
1378
  }
@@ -56,7 +56,7 @@ export function getCellLogsForMessage(cell: CellMessage): CellLog[] {
56
56
  CellLogLogger.log({
57
57
  level: "stderr",
58
58
  cellId: cell.cell_id as CellId,
59
- timestamp: cell.timestamp,
59
+ timestamp: cell.timestamp ?? 0,
60
60
  message: JSON.stringify(error),
61
61
  });
62
62
  });
@@ -45,6 +45,7 @@ const {
45
45
  opts: { cellOperation: CellMessage; code: string },
46
46
  ): RunsState => {
47
47
  const { cellOperation, code } = opts;
48
+ const timestamp = cellOperation.timestamp ?? 0;
48
49
  const runId = cellOperation.run_id as RunId | undefined;
49
50
  if (!runId) {
50
51
  return state;
@@ -84,11 +85,11 @@ const {
84
85
  code: code.slice(0, MAX_CODE_LENGTH),
85
86
  elapsedTime: 0,
86
87
  status: status,
87
- startTime: cellOperation.timestamp,
88
+ startTime: timestamp,
88
89
  },
89
90
  ],
90
91
  ]),
91
- runStartTime: cellOperation.timestamp,
92
+ runStartTime: timestamp,
92
93
  };
93
94
 
94
95
  // Manage run history size
@@ -129,12 +130,12 @@ const {
129
130
 
130
131
  const startTime =
131
132
  cellOperation.status === "running"
132
- ? cellOperation.timestamp
133
+ ? timestamp
133
134
  : existingCellRun.startTime;
134
135
 
135
136
  const elapsedTime =
136
137
  status === "success" || status === "error"
137
- ? cellOperation.timestamp - existingCellRun.startTime
138
+ ? timestamp - existingCellRun.startTime
138
139
  : undefined;
139
140
 
140
141
  nextCellRuns.set(cellOperation.cell_id as CellId, {
@@ -149,7 +150,7 @@ const {
149
150
  code: code.slice(0, MAX_CODE_LENGTH),
150
151
  elapsedTime: 0,
151
152
  status: status,
152
- startTime: cellOperation.timestamp,
153
+ startTime: timestamp,
153
154
  });
154
155
  }
155
156
 
@@ -12,30 +12,72 @@ import {
12
12
  resourcesField,
13
13
  resourceTheme,
14
14
  } from "@marimo-team/codemirror-mcp";
15
- import { getAIContextRegistry } from "@/core/ai/context/context";
15
+ import {
16
+ getAIContextRegistry,
17
+ getFileContextProvider,
18
+ } from "@/core/ai/context/context";
16
19
  import type { AIContextItem } from "@/core/ai/context/registry";
17
20
  import type { JotaiStore } from "@/core/state/jotai";
18
21
  import { Logger } from "@/utils/Logger";
22
+ import { contextCallbacks } from "./state";
23
+
24
+ const NONE_RESOURCE_TYPE = "_none_";
25
+ const NONE_RESOURCE = [
26
+ {
27
+ uri: "",
28
+ name: "No resources",
29
+ type: NONE_RESOURCE_TYPE,
30
+ data: {},
31
+ },
32
+ ];
33
+ const NONE_RESOURCE_FORMAT_COMPLETION = {
34
+ info: "Variables, dataframes, and tables will appear here.",
35
+ apply: () => {
36
+ return;
37
+ },
38
+ };
39
+
40
+ export function resourceExtension(opts: {
41
+ language: Language;
42
+ store: JotaiStore;
43
+ onAddFiles?: (files: File[]) => void;
44
+ }): Extension[] {
45
+ const { language, store, onAddFiles } = opts;
19
46
 
20
- export function resourceExtension(
21
- language: Language,
22
- store: JotaiStore,
23
- ): Extension[] {
24
47
  return [
25
48
  language.data.of({
49
+ // Resource completion for static resources (variables, tables, etc.)
26
50
  autocomplete: resourceCompletion(
27
51
  async (): Promise<Resource[]> => {
28
52
  const registry = getAIContextRegistry(store);
29
53
  const resources = registry.getAllItems();
54
+ if (resources.length === 0) {
55
+ return NONE_RESOURCE;
56
+ }
30
57
  return resources;
31
58
  },
32
59
  (resource) => {
60
+ if (resource.type === NONE_RESOURCE_TYPE) {
61
+ return NONE_RESOURCE_FORMAT_COMPLETION;
62
+ }
63
+
33
64
  const registry = getAIContextRegistry(store);
34
65
  const provider = registry.getProvider(resource.type);
35
66
  return provider?.formatCompletion(resource as AIContextItem) || {};
36
67
  },
37
68
  ),
38
69
  }),
70
+ contextCallbacks.of({
71
+ addAttachment: (attachment) => onAddFiles?.([attachment]),
72
+ }),
73
+ // Dynamic file completion
74
+ ...(onAddFiles
75
+ ? [
76
+ language.data.of({
77
+ autocomplete: getFileContextProvider().createCompletionSource(),
78
+ }),
79
+ ]
80
+ : []),
39
81
  resourceDecorations,
40
82
  resourceInputFilter,
41
83
  resourcesField.init(() => {
@@ -0,0 +1,12 @@
1
+ /* Copyright 2024 Marimo. All rights reserved. */
2
+
3
+ import { singleFacet } from "../facet";
4
+
5
+ interface ContextCallbacks {
6
+ addAttachment?: (attachment: File) => void;
7
+ }
8
+
9
+ /**
10
+ * State for completion callbacks
11
+ */
12
+ export const contextCallbacks = singleFacet<ContextCallbacks>();
@@ -664,6 +664,9 @@ describe("tablesCompletionSource", () => {
664
664
  source: "duckdb",
665
665
  source_type: "local",
666
666
  type: "table",
667
+ num_columns: 0,
668
+ num_rows: 0,
669
+ variable_name: null,
667
670
  columns: [
668
671
  {
669
672
  name: "id",
@@ -690,6 +693,9 @@ describe("tablesCompletionSource", () => {
690
693
  source: "duckdb",
691
694
  source_type: "local",
692
695
  type: "table",
696
+ num_columns: 0,
697
+ num_rows: 0,
698
+ variable_name: null,
693
699
  columns: [
694
700
  {
695
701
  name: "order_id",
@@ -763,6 +769,9 @@ describe("tablesCompletionSource", () => {
763
769
  source: "postgres",
764
770
  source_type: "local",
765
771
  type: "table",
772
+ num_columns: 0,
773
+ num_rows: 0,
774
+ variable_name: null,
766
775
  columns: [
767
776
  {
768
777
  name: "col1",
@@ -788,6 +797,9 @@ describe("tablesCompletionSource", () => {
788
797
  source: "postgres",
789
798
  source_type: "local",
790
799
  type: "table",
800
+ num_columns: 0,
801
+ num_rows: 0,
802
+ variable_name: null,
791
803
  columns: [
792
804
  {
793
805
  name: "col2",
@@ -856,6 +868,9 @@ describe("tablesCompletionSource", () => {
856
868
  source: "postgres",
857
869
  source_type: "local",
858
870
  type: "table",
871
+ num_columns: 0,
872
+ num_rows: 0,
873
+ variable_name: null,
859
874
  columns: [
860
875
  {
861
876
  name: "col1",
@@ -875,6 +890,9 @@ describe("tablesCompletionSource", () => {
875
890
  source: "postgres",
876
891
  source_type: "local",
877
892
  type: "table",
893
+ num_columns: 0,
894
+ num_rows: 0,
895
+ variable_name: null,
878
896
  columns: [
879
897
  {
880
898
  name: "col2",
@@ -900,6 +918,9 @@ describe("tablesCompletionSource", () => {
900
918
  source: "postgres",
901
919
  source_type: "local",
902
920
  type: "table",
921
+ num_columns: 0,
922
+ num_rows: 0,
923
+ variable_name: null,
903
924
  columns: [
904
925
  {
905
926
  name: "col2",
@@ -925,6 +946,9 @@ describe("tablesCompletionSource", () => {
925
946
  source: "postgres",
926
947
  source_type: "local",
927
948
  type: "table",
949
+ num_columns: 0,
950
+ num_rows: 0,
951
+ variable_name: null,
928
952
  columns: [
929
953
  {
930
954
  name: "col2",
@@ -1002,6 +1026,9 @@ describe("tablesCompletionSource", () => {
1002
1026
  source: "postgres",
1003
1027
  source_type: "local",
1004
1028
  type: "table",
1029
+ num_columns: 0,
1030
+ num_rows: 0,
1031
+ variable_name: null,
1005
1032
  columns: [
1006
1033
  {
1007
1034
  name: "id",
@@ -1070,6 +1097,9 @@ describe("tablesCompletionSource", () => {
1070
1097
  source: "postgres",
1071
1098
  source_type: "local",
1072
1099
  type: "table",
1100
+ num_columns: 0,
1101
+ num_rows: 0,
1102
+ variable_name: null,
1073
1103
  columns: [],
1074
1104
  },
1075
1105
  ],
@@ -1109,6 +1139,9 @@ describe("tablesCompletionSource", () => {
1109
1139
  source: "postgres",
1110
1140
  source_type: "local",
1111
1141
  type: "table",
1142
+ num_columns: 0,
1143
+ num_rows: 0,
1144
+ variable_name: null,
1112
1145
  columns: [
1113
1146
  {
1114
1147
  name: "id",
@@ -1134,6 +1167,9 @@ describe("tablesCompletionSource", () => {
1134
1167
  source: "postgres",
1135
1168
  source_type: "local",
1136
1169
  type: "table",
1170
+ num_columns: 0,
1171
+ num_rows: 0,
1172
+ variable_name: null,
1137
1173
  columns: [
1138
1174
  {
1139
1175
  name: "order_id",
@@ -1279,6 +1315,9 @@ describe("tablesCompletionSource", () => {
1279
1315
  source: "postgres",
1280
1316
  source_type: "local",
1281
1317
  type: "table",
1318
+ num_columns: 0,
1319
+ num_rows: 0,
1320
+ variable_name: null,
1282
1321
  columns: [
1283
1322
  {
1284
1323
  name: "id",
@@ -1469,6 +1508,9 @@ describe("tablesCompletionSource", () => {
1469
1508
  source: "duckdb",
1470
1509
  source_type: "local",
1471
1510
  type: "table",
1511
+ num_columns: 0,
1512
+ num_rows: 0,
1513
+ variable_name: null,
1472
1514
  columns: [
1473
1515
  {
1474
1516
  name: "col1",
@@ -1613,6 +1655,9 @@ const mockConnection: DataSourceConnection = {
1613
1655
  source: "duckdb",
1614
1656
  source_type: "local",
1615
1657
  type: "table",
1658
+ num_columns: 0,
1659
+ num_rows: 0,
1660
+ variable_name: null,
1616
1661
  columns: [
1617
1662
  {
1618
1663
  name: "col1",
@@ -267,6 +267,7 @@ describe("insertImage", () => {
267
267
 
268
268
  mockRequestClient.sendCreateFileOrFolder.mockResolvedValueOnce({
269
269
  success: true,
270
+ message: null,
270
271
  info: {
271
272
  path: "nested/public/hello.png",
272
273
  name: "hello.png",
@@ -8,7 +8,7 @@ export const darkTheme = [
8
8
  createTheme({
9
9
  variant: "dark",
10
10
  settings: {
11
- background: "#282c34",
11
+ background: "var(--cm-background)",
12
12
  foreground: "#abb2bf",
13
13
  caret: "#528bff",
14
14
  selection: "#3E4451",
@@ -11,5 +11,5 @@ export const capabilitiesAtom = atom<Capabilities>({
11
11
  });
12
12
 
13
13
  export function hasCapability(key: keyof Capabilities): boolean {
14
- return store.get(capabilitiesAtom)[key];
14
+ return store.get(capabilitiesAtom)[key] ?? false;
15
15
  }