@marimo-team/frontend 0.15.5 → 0.16.0

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 (223) hide show
  1. package/dist/assets/{ConnectedDataExplorerComponent-Cn5-l2X1.js → ConnectedDataExplorerComponent-BErMbWvG.js} +1 -1
  2. package/dist/assets/{ImageComparisonComponent-CEXMKKA4.js → ImageComparisonComponent-fTHv1Ih0.js} +1 -1
  3. package/dist/assets/{VegaLite-Bt14Ds9k.js → VegaLite-Bdi-TyfY.js} +6 -6
  4. package/dist/assets/_baseEach-CNBxBxvS.js +1 -0
  5. package/dist/assets/_baseMap-D1WHjKrd.js +1 -0
  6. package/dist/assets/_baseUniq-CCgDNtZb.js +1 -0
  7. package/dist/assets/_createAggregator-DcD0kTA5.js +1 -0
  8. package/dist/assets/agent-panel-Crv430aI.js +268 -0
  9. package/dist/assets/agent-panel-D92Mfy1i.css +1 -0
  10. package/dist/assets/{any-language-editor-DiwNT6zp.js → any-language-editor-CQh552Wu.js} +1 -1
  11. package/dist/assets/architectureDiagram-W76B3OCA-BAJeBxzt.js +36 -0
  12. package/dist/assets/{between-horizontal-start-FyewyCGn.js → between-horizontal-start-Boxgxbt_.js} +1 -1
  13. package/dist/assets/{blockDiagram-QIGZ2CNN-BrOkAf_c.js → blockDiagram-QIGZ2CNN-CL-1svEK.js} +1 -1
  14. package/dist/assets/{c4Diagram-FPNF74CW-BHPzDxE2.js → c4Diagram-FPNF74CW-BbEqbCTl.js} +5 -5
  15. package/dist/assets/channel-_2eNSz0n.js +1 -0
  16. package/dist/assets/chat-panel-CXh5Wl6C.js +3 -0
  17. package/dist/assets/{chunk-4BX2VUAB-DLxaCNYh.js → chunk-4BX2VUAB-C--8TXeE.js} +1 -1
  18. package/dist/assets/{chunk-55IACEB6-DdzvO3HR.js → chunk-55IACEB6-Bj00HDqq.js} +1 -1
  19. package/dist/assets/{chunk-FMBD7UC4-R5o-nSiG.js → chunk-FMBD7UC4-C-lhB6hN.js} +1 -1
  20. package/dist/assets/{chunk-K7UQS3LO-DxaMrGgG.js → chunk-K7UQS3LO-B-pGTXPt.js} +1 -1
  21. package/dist/assets/{chunk-QN33PNHL-DqS9-FYm.js → chunk-QN33PNHL-DqUzGhvm.js} +1 -1
  22. package/dist/assets/{chunk-QZHKN3VN-BZ-TzajS.js → chunk-QZHKN3VN-TntJHfSk.js} +1 -1
  23. package/dist/assets/{chunk-TVAH2DTR-BsgP2dyv.js → chunk-TVAH2DTR-HUJb1psV.js} +1 -1
  24. package/dist/assets/{chunk-TZMSLE5B-D-h3ahXI.js → chunk-TZMSLE5B-BK3C__t3.js} +1 -1
  25. package/dist/assets/{circle-play-CQtRZ-rT.js → circle-play-DBLOv1Yu.js} +1 -1
  26. package/dist/assets/classDiagram-KNZD7YFC-BGmh9POF.js +1 -0
  27. package/dist/assets/classDiagram-v2-RKCZMP56-BGmh9POF.js +1 -0
  28. package/dist/assets/{clear-button-BY6Z_ViL.js → clear-button-BeoFbEKH.js} +1 -1
  29. package/dist/assets/clone-BFDSPAj3.js +1 -0
  30. package/dist/assets/command-palette-CXZiSv0I.js +1 -0
  31. package/dist/assets/common-C7oJcmCT.js +1 -0
  32. package/dist/assets/{compile-Ct_jzdKr.js → compile-7L0MwhyI.js} +1 -1
  33. package/dist/assets/cose-bilkent-S5V4N54A-BMkGLcVC.js +1 -0
  34. package/dist/assets/dagre-5GWH7T2D-BJtRienS.js +4 -0
  35. package/dist/assets/{data-grid-overlay-editor-BN_wulc3.js → data-grid-overlay-editor-DBkmGtNs.js} +1 -1
  36. package/dist/assets/datasources-panel-B7FbYLiy.js +1 -0
  37. package/dist/assets/{dependency-graph-panel-BOmSCZf7.js → dependency-graph-panel-DEdOxp2X.js} +4 -4
  38. package/dist/assets/diagram-N5W7TBWH-CmECY3nb.js +24 -0
  39. package/dist/assets/diagram-QEK2KX5R-DMOVSNKD.js +43 -0
  40. package/dist/assets/diagram-S2PKOQOG-BiJ96PNQ.js +24 -0
  41. package/dist/assets/{documentation-panel-BxjJO_Gw.js → documentation-panel-xULhaEv3.js} +1 -1
  42. package/dist/assets/edit-page-BrYda9VE.js +129 -0
  43. package/dist/assets/{ellipsis-vertical-UHbmjI2n.js → ellipsis-vertical-BBqXIlc2.js} +1 -1
  44. package/dist/assets/{empty-state-BIBXzY_0.js → empty-state-B3dA3G5P.js} +1 -1
  45. package/dist/assets/{erDiagram-AWTI2OKA-E84mAle_.js → erDiagram-AWTI2OKA-MP1DiFRo.js} +1 -1
  46. package/dist/assets/{error-panel-MEvQ6K7h.js → error-panel-Cc1sv-Ag.js} +1 -1
  47. package/dist/assets/file-explorer-panel-Bw59Kva1.js +1 -0
  48. package/dist/assets/{flowDiagram-PVAE7QVJ-DfbIRSAW.js → flowDiagram-PVAE7QVJ-BX7caPp7.js} +1 -1
  49. package/dist/assets/{ganttDiagram-OWAHRB6G-DR4HZ1z_.js → ganttDiagram-OWAHRB6G-B462g4Yf.js} +3 -3
  50. package/dist/assets/gitGraphDiagram-NY62KEGX-CGgvZ9-9.js +65 -0
  51. package/dist/assets/{glide-data-editor-nNmo1lPq.js → glide-data-editor-C0gUFZON.js} +4 -4
  52. package/dist/assets/graph-CHRVBzY5.js +1 -0
  53. package/dist/assets/{home-page-9eW6qida.js → home-page-Fb2osjys.js} +3 -3
  54. package/dist/assets/{index-DMomwMcN.js → index-BVgAenPd.js} +1 -1
  55. package/dist/assets/{index-B8llrTSo.js → index-BY93Ejhl.js} +1 -1
  56. package/dist/assets/{index-BFSnz7iM.js → index-C-8WADat.js} +1 -1
  57. package/dist/assets/{index-CPN7TRA1.js → index-C-GhZ7ti.js} +1 -1
  58. package/dist/assets/{index-DyLSuOH1.js → index-C1v_Z9et.js} +1 -1
  59. package/dist/assets/{index-VPWqq2Pg.js → index-C4Tn5NvJ.js} +1 -1
  60. package/dist/assets/{index-BAH034Ue.js → index-C77h_TXN.js} +1 -1
  61. package/dist/assets/{index-Dt9UWeWn.js → index-CQDrxQ0j.js} +1 -1
  62. package/dist/assets/{index-DWOaniGT.js → index-CWMgowgL.js} +1 -1
  63. package/dist/assets/{index-B1_GXGaP.js → index-Clbi_Yaq.js} +1 -1
  64. package/dist/assets/{index-B7yXbrLa.js → index-CpTPJo4k.js} +1 -1
  65. package/dist/assets/{index-CknhX2Vy.css → index-Cx0bsY1w.css} +1 -1
  66. package/dist/assets/{index-DqzMPAC8.js → index-D1vmG6DS.js} +2 -2
  67. package/dist/assets/{index-c6If577Q.js → index-D9UKkrr2.js} +1 -1
  68. package/dist/assets/{index-CB2pnVQG.js → index-DEQvTChO.js} +1 -1
  69. package/dist/assets/{index-OC46250R.js → index-DKEudB02.js} +205 -197
  70. package/dist/assets/{index-CSgxTUzD.js → index-DRMm6SNo.js} +1 -1
  71. package/dist/assets/{index-Bq516OmX.js → index-DoRmcrKM.js} +1 -1
  72. package/dist/assets/{index-DSU75csX.js → index-lYa_leQE.js} +1 -1
  73. package/dist/assets/{index-BLu5CX6z.js → index-vmICa5KN.js} +1 -1
  74. package/dist/assets/{index-uacyUula.js → index-z9bohSQJ.js} +1 -1
  75. package/dist/assets/infoDiagram-STP46IZ2-CVyrdLc8.js +2 -0
  76. package/dist/assets/isEmpty-DU_ogP_D.js +1 -0
  77. package/dist/assets/{journeyDiagram-BIP6EPQ6-BBiFyygf.js → journeyDiagram-BIP6EPQ6-C6EgLP_Q.js} +1 -1
  78. package/dist/assets/{kanban-definition-6OIFK2YF-DhgA6Nt6.js → kanban-definition-6OIFK2YF-BXzYO1yj.js} +4 -4
  79. package/dist/assets/layout-jihVw5-i.js +1 -0
  80. package/dist/assets/linear-C4blANlC.js +1 -0
  81. package/dist/assets/{links-CbvGxbsJ.js → links-D59GIweI.js} +3 -3
  82. package/dist/assets/{logs-panel-B9SmTZAW.js → logs-panel-D401qzZh.js} +1 -1
  83. package/dist/assets/markdown-renderer-Cd9eYyaL.js +263 -0
  84. package/dist/assets/{agent-panel-DpQ6muj-.css → markdown-renderer-ClyzDMmG.css} +1 -1
  85. package/dist/assets/mermaid-BEVuRz_O.js +1 -0
  86. package/dist/assets/{mermaid.core-4nVOEVX3.js → mermaid.core-CaSnaLH0.js} +41 -41
  87. package/dist/assets/min-DUMu_zeK.js +1 -0
  88. package/dist/assets/{mindmap-definition-Q6HEUPPD-CVLQNn1q.js → mindmap-definition-Q6HEUPPD-BXUM5MT2.js} +2 -2
  89. package/dist/assets/{number-overlay-editor-CzRzXLcd.js → number-overlay-editor-4uWXGlPG.js} +1 -1
  90. package/dist/assets/{outline-panel-uvsS-YEQ.js → outline-panel-DIzkvm2I.js} +1 -1
  91. package/dist/assets/packages-panel-CJL0MVlj.js +1 -0
  92. package/dist/assets/{pieDiagram-ADFJNKIX-C5IQ5DBZ.js → pieDiagram-ADFJNKIX-Dxt5PVNo.js} +3 -3
  93. package/dist/assets/{quadrantDiagram-LMRXKWRM-CFXFnQxx.js → quadrantDiagram-LMRXKWRM-D4pUaA31.js} +1 -1
  94. package/dist/assets/{react-plotly-mzdv02_Y.js → react-plotly-cJZ0VWBq.js} +1 -1
  95. package/dist/assets/{requirementDiagram-4UW4RH46-D9bPC89T.js → requirementDiagram-4UW4RH46-DVRTjgas.js} +1 -1
  96. package/dist/assets/run-page-BUEnMC9w.js +1 -0
  97. package/dist/assets/sankeyDiagram-GR3RE2ED-CVFnD9C-.js +10 -0
  98. package/dist/assets/scratchpad-panel-BIgRENkI.js +1 -0
  99. package/dist/assets/secrets-panel-xY5-V_BD.js +1 -0
  100. package/dist/assets/{sequenceDiagram-C3RYC4MD-6N7_hY4k.js → sequenceDiagram-C3RYC4MD-_lY4ZN_S.js} +4 -4
  101. package/dist/assets/{slides-component-EcjC8sDK.js → slides-component-Xjymwj7X.js} +1 -1
  102. package/dist/assets/snippets-panel-CTPYW41n.js +1 -0
  103. package/dist/assets/sortBy-BNZKwiq_.js +1 -0
  104. package/dist/assets/state-C4NiC9tO.js +1 -0
  105. package/dist/assets/stateDiagram-KXAO66HF-Da0JQWCn.js +1 -0
  106. package/dist/assets/stateDiagram-v2-UMBNRL4Z-D5lYZOOt.js +1 -0
  107. package/dist/assets/storage-CMdLzB_c.js +26 -0
  108. package/dist/assets/terminal-BPwTkXae.js +10 -0
  109. package/dist/assets/time-Dv5_Ouz_.js +1 -0
  110. package/dist/assets/{timeline-definition-XQNQX7LJ-BEaynAiY.js → timeline-definition-XQNQX7LJ-Dxh5Zu2e.js} +1 -1
  111. package/dist/assets/tracing-BCIurUfa.js +2 -0
  112. package/dist/assets/{tracing-panel-BmuHLPrY.js → tracing-panel-DAzrzNmm.js} +2 -2
  113. package/dist/assets/{trash-UBqfK4mR.js → trash-Dc6DSjz_.js} +1 -1
  114. package/dist/assets/{tree-XiEycetl.js → tree-jheoerAX.js} +1 -1
  115. package/dist/assets/{treemap-75Q7IDZK-CnuVFbBG.js → treemap-75Q7IDZK-IgpxeGaf.js} +21 -21
  116. package/dist/assets/{ts-tags-CloPe9IY.js → ts-tags-DxCDHihD.js} +1 -1
  117. package/dist/assets/variable-panel-DYAiLBmF.js +1 -0
  118. package/dist/assets/{vega-component-DsTH4tuX.js → vega-component-BpfpiPKI.js} +1 -1
  119. package/dist/assets/{xychartDiagram-6GGTOJPD-Dcz3O-A3.js → xychartDiagram-6GGTOJPD-CmNigJ31.js} +1 -1
  120. package/dist/index.html +2 -2
  121. package/package.json +8 -4
  122. package/src/__tests__/mocks.ts +43 -0
  123. package/src/components/app-config/user-config-form.tsx +32 -0
  124. package/src/components/chat/acp/__tests__/__snapshots__/prompt.test.ts.snap +55 -23
  125. package/src/components/chat/acp/__tests__/context-utils.test.ts +222 -0
  126. package/src/components/chat/acp/__tests__/prompt.test.ts +1 -1
  127. package/src/components/chat/acp/__tests__/state.test.ts +2 -6
  128. package/src/components/chat/acp/agent-docs.tsx +33 -6
  129. package/src/components/chat/acp/agent-panel.css +0 -18
  130. package/src/components/chat/acp/agent-panel.tsx +397 -72
  131. package/src/components/chat/acp/agent-selector.tsx +7 -1
  132. package/src/components/chat/acp/blocks.tsx +40 -10
  133. package/src/components/chat/acp/common.tsx +10 -2
  134. package/src/components/chat/acp/context-utils.ts +127 -0
  135. package/src/components/chat/acp/prompt.ts +34 -10
  136. package/src/components/chat/acp/state.ts +1 -1
  137. package/src/components/chat/acp/types.ts +8 -0
  138. package/src/components/chat/chat-panel.tsx +23 -88
  139. package/src/components/chat/chat-utils.ts +127 -1
  140. package/src/components/chat/markdown-renderer.css +39 -0
  141. package/src/components/chat/markdown-renderer.tsx +7 -38
  142. package/src/components/chat/tool-call-accordion.tsx +113 -23
  143. package/src/components/editor/Cell.tsx +6 -0
  144. package/src/components/editor/actions/name-cell-input.tsx +6 -1
  145. package/src/components/editor/actions/useCellActionButton.tsx +3 -1
  146. package/src/components/editor/ai/__tests__/completion-utils.test.ts +178 -1
  147. package/src/components/editor/ai/add-cell-with-ai.tsx +68 -66
  148. package/src/components/editor/ai/ai-completion-editor.tsx +29 -26
  149. package/src/components/editor/ai/completion-handlers.tsx +44 -6
  150. package/src/components/editor/ai/completion-utils.ts +92 -0
  151. package/src/components/editor/ai/transport/chat-transport.tsx +36 -0
  152. package/src/components/editor/cell/StagedAICell.tsx +51 -0
  153. package/src/components/editor/cell/cell-actions.tsx +2 -1
  154. package/src/components/terminal/__tests__/state.test.ts +207 -0
  155. package/src/components/terminal/hooks.ts +41 -0
  156. package/src/components/terminal/state.ts +75 -0
  157. package/src/components/terminal/terminal.tsx +334 -13
  158. package/src/components/terminal/theme.tsx +56 -0
  159. package/src/core/ai/__tests__/staged-cells.test.ts +356 -0
  160. package/src/core/ai/staged-cells.ts +208 -0
  161. package/src/core/cells/cells.ts +1 -1
  162. package/src/core/codemirror/lsp/federated-lsp.ts +1 -1
  163. package/src/core/islands/main.ts +2 -2
  164. package/src/core/kernel/messages.ts +8 -12
  165. package/src/core/saving/__tests__/filename.test.ts +37 -0
  166. package/src/core/static/__tests__/download-html.test.ts +43 -1
  167. package/src/core/websocket/useMarimoWebSocket.tsx +2 -2
  168. package/src/css/app/Cell.css +11 -0
  169. package/src/plugins/core/RenderHTML.tsx +36 -2
  170. package/src/plugins/core/__test__/RenderHTML.test.ts +72 -0
  171. package/src/plugins/core/registerReactComponent.tsx +28 -0
  172. package/src/plugins/impl/FileBrowserPlugin.tsx +8 -2
  173. package/src/stories/cell.stories.tsx +1 -1
  174. package/src/stories/layout/vertical/one-column.stories.tsx +1 -1
  175. package/src/utils/__tests__/cell-urls.test.ts +29 -0
  176. package/src/utils/__tests__/filenames.test.ts +18 -0
  177. package/src/utils/__tests__/path.test.ts +38 -0
  178. package/src/utils/__tests__/urls.test.ts +56 -1
  179. package/src/utils/errors.ts +9 -0
  180. package/dist/assets/_baseEach-C1FLm7WW.js +0 -1
  181. package/dist/assets/_baseMap-DBVArUYD.js +0 -1
  182. package/dist/assets/_baseUniq-Dk7ZPJ3N.js +0 -1
  183. package/dist/assets/_createAggregator-Bn38fDd3.js +0 -1
  184. package/dist/assets/agent-panel-COUYnuIK.js +0 -475
  185. package/dist/assets/architectureDiagram-W76B3OCA-DBzWQKKu.js +0 -36
  186. package/dist/assets/channel-CjhbjOv4.js +0 -1
  187. package/dist/assets/chat-panel-BPXKoTnZ.js +0 -7
  188. package/dist/assets/chat-panel-Brrs_eeH.css +0 -1
  189. package/dist/assets/classDiagram-KNZD7YFC-DHs5cFzy.js +0 -1
  190. package/dist/assets/classDiagram-v2-RKCZMP56-DHs5cFzy.js +0 -1
  191. package/dist/assets/clone-DM1YNjEn.js +0 -1
  192. package/dist/assets/command-palette-S0bzQp7v.js +0 -1
  193. package/dist/assets/common-B8U9k2Ly.js +0 -1
  194. package/dist/assets/cose-bilkent-S5V4N54A-wz1Sfx7j.js +0 -1
  195. package/dist/assets/dagre-5GWH7T2D-BfpcVBgq.js +0 -4
  196. package/dist/assets/datasources-panel-DfuURLJw.js +0 -1
  197. package/dist/assets/diagram-N5W7TBWH-Bf0oqqQh.js +0 -24
  198. package/dist/assets/diagram-QEK2KX5R-ZTc3qikh.js +0 -43
  199. package/dist/assets/diagram-S2PKOQOG-tLScBy7Z.js +0 -24
  200. package/dist/assets/edit-page-DJ8kJZ9w.js +0 -129
  201. package/dist/assets/file-explorer-panel-CzNUJ63G.js +0 -1
  202. package/dist/assets/gitGraphDiagram-NY62KEGX-C1t6QtVa.js +0 -65
  203. package/dist/assets/graph-CssCVWIq.js +0 -1
  204. package/dist/assets/index-DcCIe7np.js +0 -28
  205. package/dist/assets/infoDiagram-STP46IZ2-CwiAoz9f.js +0 -2
  206. package/dist/assets/layout-DpQrxGW-.js +0 -1
  207. package/dist/assets/linear-NsreOeBF.js +0 -1
  208. package/dist/assets/mermaid-DSt0r6IQ.js +0 -1
  209. package/dist/assets/min-D259kI3t.js +0 -1
  210. package/dist/assets/packages-panel-xMz9W2hW.js +0 -1
  211. package/dist/assets/run-page-Bb68qdhQ.js +0 -1
  212. package/dist/assets/sankeyDiagram-GR3RE2ED-BSJOau8E.js +0 -10
  213. package/dist/assets/scratchpad-panel-BF4BO-U4.js +0 -1
  214. package/dist/assets/secrets-panel-CdIX44dQ.js +0 -1
  215. package/dist/assets/snippets-panel-Dco9h0rb.js +0 -1
  216. package/dist/assets/sortBy-aLGA-PGK.js +0 -1
  217. package/dist/assets/stateDiagram-KXAO66HF-Bd68WT3b.js +0 -1
  218. package/dist/assets/stateDiagram-v2-UMBNRL4Z-BXz_GSwb.js +0 -1
  219. package/dist/assets/storage-CGlP4lCF.js +0 -26
  220. package/dist/assets/terminal-CxkHubcu.js +0 -9
  221. package/dist/assets/time-D2nr1UgQ.js +0 -1
  222. package/dist/assets/tracing-kTqHxa7q.js +0 -2
  223. package/dist/assets/variable-panel-noTnH-AQ.js +0 -1
@@ -1,7 +1,15 @@
1
1
  /* Copyright 2024 Marimo. All rights reserved. */
2
2
 
3
- import type { FileUIPart, UIMessage } from "ai";
3
+ import type { components } from "@marimo-team/marimo-api";
4
+ import type { FileUIPart, ToolUIPart, UIMessage } from "ai";
5
+ import type {
6
+ InvokeAiToolRequest,
7
+ InvokeAiToolResponse,
8
+ } from "@/core/network/types";
9
+ import type { ChatMessage } from "@/plugins/impl/chat/types";
4
10
  import { blobToString } from "@/utils/fileToBase64";
11
+ import { Logger } from "@/utils/Logger";
12
+ import { getAICompletionBodyWithAttachments } from "../editor/ai/completion-utils";
5
13
 
6
14
  export function generateChatTitle(message: string): string {
7
15
  return message.length > 50 ? `${message.slice(0, 50)}...` : message;
@@ -48,3 +56,121 @@ export function isLastMessageReasoning(messages: UIMessage[]): boolean {
48
56
  const lastPart = parts[parts.length - 1];
49
57
  return lastPart.type === "reasoning";
50
58
  }
59
+
60
+ function stringifyTextParts(parts: UIMessage["parts"]): string {
61
+ return parts
62
+ .map((part) => (part.type === "text" ? part.text : ""))
63
+ .join("\n");
64
+ }
65
+
66
+ export async function buildCompletionRequestBody(
67
+ messages: UIMessage[],
68
+ ): Promise<{
69
+ messages: ChatMessage[];
70
+ context?: (null | components["schemas"]["AiCompletionContext"]) | undefined;
71
+ includeOtherCode: string;
72
+ selectedText?: string | null | undefined;
73
+ }> {
74
+ const input = stringifyTextParts(messages.flatMap((m) => m.parts));
75
+ const completionBody = await getAICompletionBodyWithAttachments({ input });
76
+
77
+ // Map from UIMessage to our ChatMessage type
78
+ // If it's the last message, add the attachments from the completion body
79
+ function toChatMessage(message: UIMessage, isLast: boolean): ChatMessage {
80
+ // Clone parts to avoid mutating the original message
81
+ const parts = [...message.parts];
82
+ if (isLast) {
83
+ parts.push(...completionBody.attachments);
84
+ }
85
+ return {
86
+ role: message.role,
87
+ content: stringifyTextParts(message.parts), // This is no longer used in the backend
88
+ parts,
89
+ };
90
+ }
91
+
92
+ return {
93
+ ...completionBody.body,
94
+ messages: messages.map((m, idx) =>
95
+ toChatMessage(m, idx === messages.length - 1),
96
+ ),
97
+ };
98
+ }
99
+
100
+ interface AddToolResult {
101
+ tool: string;
102
+ toolCallId: string;
103
+ output: unknown;
104
+ }
105
+
106
+ export async function handleToolCall({
107
+ invokeAiTool,
108
+ addToolResult,
109
+ toolCall,
110
+ }: {
111
+ invokeAiTool: (request: InvokeAiToolRequest) => Promise<InvokeAiToolResponse>;
112
+ addToolResult: (result: AddToolResult) => Promise<void>;
113
+ toolCall: {
114
+ toolName: string;
115
+ toolCallId: string;
116
+ input: Record<string, never>;
117
+ };
118
+ }) {
119
+ try {
120
+ const response = await invokeAiTool({
121
+ toolName: toolCall.toolName,
122
+ arguments: toolCall.input,
123
+ });
124
+ addToolResult({
125
+ tool: toolCall.toolName,
126
+ toolCallId: toolCall.toolCallId,
127
+ output: response.result || response.error,
128
+ });
129
+ } catch (error) {
130
+ Logger.error("Tool call failed:", error);
131
+ addToolResult({
132
+ tool: toolCall.toolName,
133
+ toolCallId: toolCall.toolCallId,
134
+ output: `Error: ${error instanceof Error ? error.message : String(error)}`,
135
+ });
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Checks if we should send a message automatically based on the messages.
141
+ * We only want to send a message if we have completed tool calls and there is no reply yet.
142
+ */
143
+ export function hasPendingToolCalls(messages: UIMessage[]): boolean {
144
+ if (messages.length === 0) {
145
+ return false;
146
+ }
147
+
148
+ const lastMessage = messages[messages.length - 1];
149
+ const parts = lastMessage.parts;
150
+
151
+ if (parts.length === 0) {
152
+ return false;
153
+ }
154
+
155
+ // Only auto-send if the last message is an assistant message
156
+ // Because assistant messages are the ones that can have tool calls
157
+ if (lastMessage.role !== "assistant") {
158
+ return false;
159
+ }
160
+
161
+ const toolParts = parts.filter((part) =>
162
+ part.type.startsWith("tool-"),
163
+ ) as ToolUIPart[];
164
+
165
+ const hasCompletedToolCalls = toolParts.some(
166
+ (part) => part.state === "output-available",
167
+ );
168
+
169
+ // Check if the last part has any text content
170
+ const lastPart = parts[parts.length - 1];
171
+ const hasTextContent =
172
+ lastPart.type === "text" && lastPart.text?.trim().length > 0;
173
+
174
+ // Only auto-send if we have completed tool calls and there is no reply yet
175
+ return hasCompletedToolCalls && !hasTextContent;
176
+ }
@@ -1,4 +1,10 @@
1
+ @source "../../../node_modules/streamdown/dist/index.js";
2
+
1
3
  .mo-markdown-renderer {
4
+ /* Make headers a bit smaller */
5
+ --text-2xl: 1rem;
6
+ --text-3xl: 1.2rem;
7
+
2
8
  h1,
3
9
  h2,
4
10
  h3,
@@ -12,4 +18,37 @@
12
18
  margin-top: 8px;
13
19
  margin-bottom: 12px;
14
20
  }
21
+
22
+ pre {
23
+ width: 100%;
24
+ }
25
+
26
+ [data-code-block-container="true"] {
27
+ margin-top: 0 !important;
28
+ }
29
+
30
+ [data-code-block-header="true"] {
31
+ padding: 4px !important;
32
+ }
33
+
34
+ code {
35
+ font-size: inherit !important;
36
+ }
37
+
38
+ li.task-list-item {
39
+ list-style-type: none;
40
+ }
41
+
42
+ li {
43
+ padding-left: 6px;
44
+
45
+ > input {
46
+ margin-right: 6px;
47
+ }
48
+ }
49
+
50
+ ul,
51
+ ol {
52
+ padding-left: 10px;
53
+ }
15
54
  }
@@ -3,10 +3,8 @@
3
3
  import { EditorView } from "@codemirror/view";
4
4
  import { useAtomValue } from "jotai";
5
5
  import { BetweenHorizontalStartIcon } from "lucide-react";
6
- import { marked } from "marked";
7
- import { memo, Suspense, useEffect, useMemo, useState } from "react";
8
- import Markdown, { type Components } from "react-markdown";
9
- import remarkGfm from "remark-gfm";
6
+ import { memo, Suspense, useEffect, useState } from "react";
7
+ import { Streamdown, type StreamdownProps } from "streamdown";
10
8
  import { Button, type ButtonProps } from "@/components/ui/button";
11
9
  import { maybeAddMarimoImport } from "@/core/cells/add-missing-import";
12
10
  import { useCellActions } from "@/core/cells/cells";
@@ -171,6 +169,8 @@ const CopyButton: React.FC<ButtonProps> = ({ onClick, ...props }) => {
171
169
  );
172
170
  };
173
171
 
172
+ type Components = StreamdownProps["components"];
173
+
174
174
  const COMPONENTS: Components = {
175
175
  code: ({ children, className }) => {
176
176
  const language = className?.replace("language-", "");
@@ -186,42 +186,11 @@ const COMPONENTS: Components = {
186
186
  },
187
187
  };
188
188
 
189
- function parseMarkdownIntoBlocks(markdown: string): string[] {
190
- const tokens = marked.lexer(markdown);
191
- return tokens.map((token) => token.raw);
192
- }
193
-
194
- const PLUGINS = [remarkGfm];
195
-
196
- const MemoizedMarkdownBlock = memo(
197
- ({ content }: { content: string }) => {
198
- return (
199
- <Markdown
200
- components={COMPONENTS}
201
- remarkPlugins={PLUGINS}
202
- className="mo-markdown-renderer prose dark:prose-invert max-w-none prose-pre:pl-0"
203
- >
204
- {content}
205
- </Markdown>
206
- );
207
- },
208
- (prevProps, nextProps) => prevProps.content === nextProps.content,
209
- );
210
-
211
- MemoizedMarkdownBlock.displayName = "MemoizedMarkdownBlock";
212
-
213
189
  export const MarkdownRenderer = memo(({ content }: { content: string }) => {
214
- const blocks = useMemo(() => parseMarkdownIntoBlocks(content), [content]);
215
-
216
190
  return (
217
- <>
218
- {blocks.map((block, index) => (
219
- <MemoizedMarkdownBlock
220
- content={block}
221
- key={`markdown-block-${index}`}
222
- />
223
- ))}
224
- </>
191
+ <Streamdown components={COMPONENTS} className="mo-markdown-renderer">
192
+ {content}
193
+ </Streamdown>
225
194
  );
226
195
  });
227
196
  MarkdownRenderer.displayName = "MarkdownRenderer";
@@ -1,13 +1,16 @@
1
1
  /* Copyright 2024 Marimo. All rights reserved. */
2
2
 
3
3
  import type { ToolUIPart } from "ai";
4
+ import { isEmpty } from "lodash-es";
4
5
  import {
5
6
  CheckCircleIcon,
7
+ InfoIcon,
6
8
  Loader2,
7
9
  WrenchIcon,
8
10
  XCircleIcon,
9
11
  } from "lucide-react";
10
12
  import React from "react";
13
+ import { z } from "zod";
11
14
  import {
12
15
  Accordion,
13
16
  AccordionContent,
@@ -16,12 +19,101 @@ import {
16
19
  } from "@/components/ui/accordion";
17
20
  import { cn } from "@/utils/cn";
18
21
 
22
+ // Zod schema matching the Python SuccessResult dataclass
23
+ const SuccessResultSchema = z
24
+ .object({
25
+ status: z.string().default("success"),
26
+ auth_required: z.boolean().default(false),
27
+ action_url: z.any(),
28
+ next_steps: z.any(),
29
+ meta: z.any(),
30
+ message: z.string().nullish(),
31
+ })
32
+ .passthrough();
33
+
34
+ type SuccessResult = z.infer<typeof SuccessResultSchema>;
35
+
36
+ const PrettySuccessResult: React.FC<{ data: SuccessResult }> = ({ data }) => {
37
+ const {
38
+ status,
39
+ auth_required,
40
+ action_url: _action_url,
41
+ meta: _meta,
42
+ next_steps: _next_steps,
43
+ message,
44
+ ...rest
45
+ } = data;
46
+
47
+ return (
48
+ <div className="py-1 flex flex-col gap-1">
49
+ {/* Status */}
50
+ <div className="flex items-center gap-2">
51
+ <span className="text-xs font-medium text-[var(--grass-11)] capitalize">
52
+ {status}
53
+ </span>
54
+ {auth_required && (
55
+ <span className="text-xs px-2 py-0.5 bg-[var(--amber-2)] text-[var(--amber-11)] rounded-full">
56
+ Auth Required
57
+ </span>
58
+ )}
59
+ </div>
60
+
61
+ {/* Message */}
62
+ {message && (
63
+ <div className="flex items-start gap-2">
64
+ <InfoIcon className="h-3 w-3 text-[var(--blue-11)] mt-0.5 flex-shrink-0" />
65
+ <div className="text-xs text-foreground">{message}</div>
66
+ </div>
67
+ )}
68
+
69
+ {/* Data */}
70
+ {rest && (
71
+ <div className="flex flex-col gap-1">
72
+ {Object.entries(rest).map(([key, value]) => {
73
+ if (isEmpty(value)) {
74
+ return null;
75
+ }
76
+ return (
77
+ <div key={key}>
78
+ <div className="text-xs font-medium text-muted-foreground mb-1 capitalize">
79
+ {key}:
80
+ </div>
81
+ <pre className="bg-[var(--slate-2)] p-1 text-muted-foreground border border-[var(--slate-4)] rounded text-xs overflow-auto scrollbar-thin max-h-64">
82
+ {JSON.stringify(value, null, 2)}
83
+ </pre>
84
+ </div>
85
+ );
86
+ })}
87
+ </div>
88
+ )}
89
+ </div>
90
+ );
91
+ };
92
+
93
+ const ResultRenderer: React.FC<{ result: unknown }> = ({ result }) => {
94
+ // Try to parse the result with our Zod schema
95
+ const parseResult = SuccessResultSchema.safeParse(result);
96
+
97
+ if (parseResult.success) {
98
+ // If it matches the SuccessResult schema, show the pretty UI
99
+ return <PrettySuccessResult data={parseResult.data} />;
100
+ }
101
+
102
+ // Otherwise, fall back to the current JSON viewer
103
+ return (
104
+ <div className="text-xs font-medium text-muted-foreground mb-1">
105
+ {typeof result === "string" ? result : JSON.stringify(result, null, 2)}
106
+ </div>
107
+ );
108
+ };
109
+
19
110
  interface ToolCallAccordionProps {
20
111
  toolName: string;
21
112
  result: unknown;
22
113
  error?: string;
23
114
  index?: number;
24
115
  state?: ToolUIPart["state"];
116
+ className?: string;
25
117
  }
26
118
 
27
119
  export const ToolCallAccordion: React.FC<ToolCallAccordionProps> = ({
@@ -30,6 +122,7 @@ export const ToolCallAccordion: React.FC<ToolCallAccordionProps> = ({
30
122
  error,
31
123
  index = 0,
32
124
  state,
125
+ className,
33
126
  }) => {
34
127
  const hasResult = state === "output-available" && (result || error);
35
128
  const status = error ? "error" : hasResult ? "success" : "loading";
@@ -39,9 +132,9 @@ export const ToolCallAccordion: React.FC<ToolCallAccordionProps> = ({
39
132
  case "loading":
40
133
  return <Loader2 className="h-3 w-3 animate-spin" />;
41
134
  case "error":
42
- return <XCircleIcon className="h-3 w-3 text-destructive" />;
135
+ return <XCircleIcon className="h-3 w-3 text-[var(--red-11)]" />;
43
136
  case "success":
44
- return <CheckCircleIcon className="h-3 w-3 text-green-600" />;
137
+ return <CheckCircleIcon className="h-3 w-3 text-[var(--grass-11)]" />;
45
138
  default:
46
139
  return <WrenchIcon className="h-3 w-3" />;
47
140
  }
@@ -49,13 +142,13 @@ export const ToolCallAccordion: React.FC<ToolCallAccordionProps> = ({
49
142
 
50
143
  const getStatusText = () => {
51
144
  if (status === "loading") {
52
- return "Running: ";
145
+ return "Running";
53
146
  }
54
147
  if (error) {
55
- return "Failed: ";
148
+ return "Failed";
56
149
  }
57
150
  if (hasResult) {
58
- return "Done: ";
151
+ return "Done";
59
152
  }
60
153
  return "Tool call";
61
154
  };
@@ -65,20 +158,22 @@ export const ToolCallAccordion: React.FC<ToolCallAccordionProps> = ({
65
158
  key={`tool-${index}`}
66
159
  type="single"
67
160
  collapsible={true}
68
- className="w-full"
161
+ className={cn("w-full", className)}
69
162
  >
70
163
  <AccordionItem value="tool-call" className="border-0">
71
164
  <AccordionTrigger
72
165
  className={cn(
73
- "h-6 text-xs border-border shadow-none! ring-0! bg-muted hover:bg-muted/30 py-0 px-2 gap-1 rounded-sm [&[data-state=open]>svg]:rotate-180",
74
- status === "error" && "text-destructive/80",
75
- status === "success" && "text-green-600/80",
166
+ "h-6 text-xs border-border shadow-none! ring-0! bg-muted/60 hover:bg-muted py-0 px-2 gap-1 rounded-sm [&[data-state=open]>svg]:rotate-180 hover:no-underline",
167
+ status === "error" && "text-[var(--red-11)]/80",
168
+ status === "success" && "text-[var(--grass-11)]/80",
76
169
  )}
77
170
  >
78
171
  <span className="flex items-center gap-1">
79
172
  {getStatusIcon()}
80
- {getStatusText()}:{" "}
81
- <code className="font-mono text-xs">{toolName}</code>
173
+ {getStatusText()}:
174
+ <code className="font-mono text-xs">
175
+ {formatToolName(toolName)}
176
+ </code>
82
177
  </span>
83
178
  </AccordionTrigger>
84
179
  <AccordionContent className="pb-2 px-2">
@@ -86,25 +181,16 @@ export const ToolCallAccordion: React.FC<ToolCallAccordionProps> = ({
86
181
  {hasResult && (
87
182
  <div className="space-y-3">
88
183
  {result !== undefined && result !== null && (
89
- <div>
90
- <div className="text-xs font-medium text-muted-foreground mt-2 mb-1">
91
- Result:
92
- </div>
93
- <div className="text-xs font-medium text-muted-foreground mb-1">
94
- {typeof result === "string"
95
- ? result
96
- : JSON.stringify(result, null, 2)}
97
- </div>
98
- </div>
184
+ <ResultRenderer result={result} />
99
185
  )}
100
186
 
101
187
  {/* Error */}
102
188
  {error && (
103
189
  <div>
104
- <div className="text-xs font-medium text-destructive mb-1">
190
+ <div className="text-xs font-medium text-[var(--red-11)] mb-1">
105
191
  Error:
106
192
  </div>
107
- <div className="bg-destructive/10 border border-destructive/20 rounded-md p-3 text-sm text-destructive">
193
+ <div className="bg-[var(--red-2)] border border-[var(--red-6)] rounded-md p-3 text-sm text-[var(--red-11)]">
108
194
  {error}
109
195
  </div>
110
196
  </div>
@@ -116,3 +202,7 @@ export const ToolCallAccordion: React.FC<ToolCallAccordionProps> = ({
116
202
  </Accordion>
117
203
  );
118
204
  };
205
+
206
+ function formatToolName(toolName: string) {
207
+ return toolName.replace("tool-", "");
208
+ }
@@ -72,6 +72,10 @@ import { CollapsedCellBanner, CollapseToggle } from "./cell/collapse";
72
72
  import { DeleteButton } from "./cell/DeleteButton";
73
73
  import { PendingDeleteConfirmation } from "./cell/PendingDeleteConfirmation";
74
74
  import { RunButton } from "./cell/RunButton";
75
+ import {
76
+ StagedAICellBackground,
77
+ StagedAICellFooter,
78
+ } from "./cell/StagedAICell";
75
79
  import { useDeleteCellCallback } from "./cell/useDeleteCell";
76
80
  import { useRunCell } from "./cell/useRunCells";
77
81
  import { HideCodeButton } from "./code/readonly-python-code";
@@ -571,6 +575,7 @@ const EditableCellComponent = ({
571
575
  >
572
576
  {cellOutput === "above" && outputArea}
573
577
  <div className={cn("tray")} data-hidden={isMarkdownCodeHidden}>
578
+ <StagedAICellBackground cellId={cellId} />
574
579
  <div className="absolute right-2 -top-4 z-10">
575
580
  <CellToolbar
576
581
  edited={cellData.edited}
@@ -729,6 +734,7 @@ const EditableCellComponent = ({
729
734
  />
730
735
  <PendingDeleteConfirmation cellId={cellId} />
731
736
  </div>
737
+ <StagedAICellFooter cellId={cellId} />
732
738
  {isCollapsed && (
733
739
  <CollapsedCellBanner
734
740
  onClick={() => actions.expandCell({ cellId })}
@@ -19,12 +19,14 @@ interface Props
19
19
  value: string;
20
20
  onChange: (newName: string) => void;
21
21
  placeholder?: string;
22
+ onEnterKey?: () => void;
22
23
  }
23
24
 
24
25
  export const NameCellInput: React.FC<Props> = ({
25
26
  value,
26
27
  onChange,
27
28
  placeholder,
29
+ onEnterKey,
28
30
  ...props
29
31
  }) => {
30
32
  const ref = useRef<HTMLInputElement>(null);
@@ -53,7 +55,10 @@ export const NameCellInput: React.FC<Props> = ({
53
55
  ref={ref}
54
56
  placeholder={placeholder}
55
57
  className="shadow-none! hover:shadow-none focus:shadow-none focus-visible:shadow-none"
56
- onKeyDown={Events.onEnter(Events.stopPropagation())}
58
+ onKeyDown={Events.onEnter((e) => {
59
+ Events.stopPropagation()(e);
60
+ onEnterKey?.();
61
+ })}
57
62
  {...props}
58
63
  />
59
64
  );
@@ -73,9 +73,10 @@ export interface CellActionButtonProps
73
73
 
74
74
  interface Props {
75
75
  cell: CellActionButtonProps | null;
76
+ closePopover?: () => void;
76
77
  }
77
78
 
78
- export function useCellActionButtons({ cell }: Props) {
79
+ export function useCellActionButtons({ cell, closePopover }: Props) {
79
80
  const {
80
81
  createNewCell: createCell,
81
82
  updateCellConfig,
@@ -177,6 +178,7 @@ export function useCellActionButtons({ cell }: Props) {
177
178
  placeholder={"cell name"}
178
179
  value={name}
179
180
  onChange={(newName) => updateCellName({ cellId, name: newName })}
181
+ onEnterKey={() => closePopover?.()}
180
182
  />
181
183
  ),
182
184
  },