@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,23 +1,32 @@
1
1
  /* Copyright 2024 Marimo. All rights reserved. */
2
2
 
3
- import { useAtom } from "jotai";
3
+ import { useAtom, useAtomValue } from "jotai";
4
4
  import { capitalize } from "lodash-es";
5
5
  import {
6
+ AtSignIcon,
6
7
  BotMessageSquareIcon,
8
+ PaperclipIcon,
7
9
  RefreshCwIcon,
10
+ SendIcon,
11
+ SquareIcon,
8
12
  StopCircleIcon,
9
13
  } from "lucide-react";
10
- import React, { memo, useEffect, useRef, useState } from "react";
14
+ import React, { memo, useEffect, useMemo, useRef, useState } from "react";
11
15
  import useEvent from "react-use-event-hook";
12
16
  import { useAcpClient } from "use-acp";
13
17
  import {
14
18
  ConnectionStatus,
15
19
  PermissionRequest,
16
20
  } from "@/components/chat/acp/common";
17
- import { PromptInput } from "@/components/editor/ai/add-cell-with-ai";
21
+ import {
22
+ type AdditionalCompletions,
23
+ PromptInput,
24
+ } from "@/components/editor/ai/add-cell-with-ai";
18
25
  import { PanelEmptyState } from "@/components/editor/chrome/panels/empty-state";
19
26
  import { Spinner } from "@/components/icons/spinner";
20
27
  import { Button } from "@/components/ui/button";
28
+ import { Input } from "@/components/ui/input";
29
+ import { Tooltip, TooltipProvider } from "@/components/ui/tooltip";
21
30
  import { cn } from "@/utils/cn";
22
31
  import { Logger } from "@/utils/Logger";
23
32
  import { AgentDocs } from "./agent-docs";
@@ -34,27 +43,50 @@ import {
34
43
  } from "./state";
35
44
  import { AgentThread } from "./thread";
36
45
  import "./agent-panel.css";
46
+ import type { Completion } from "@codemirror/autocomplete";
47
+ import type { ReactCodeMirrorRef } from "@uiw/react-codemirror";
37
48
  import type {
38
- ReadTextFileResponse,
49
+ ContentBlock,
39
50
  RequestPermissionResponse,
40
- WriteTextFileResponse,
41
51
  } from "@zed-industries/agent-client-protocol";
52
+ import {
53
+ addContextCompletion,
54
+ CONTEXT_TRIGGER,
55
+ } from "@/components/editor/ai/completion-utils";
56
+ import {
57
+ Select,
58
+ SelectContent,
59
+ SelectGroup,
60
+ SelectItem,
61
+ SelectLabel,
62
+ SelectTrigger,
63
+ } from "@/components/ui/select";
42
64
  import { toast } from "@/components/ui/use-toast";
43
65
  import { useRequestClient } from "@/core/network/requests";
44
66
  import { filenameAtom } from "@/core/saving/file-state";
45
67
  import { store } from "@/core/state/jotai";
46
68
  import { Functions } from "@/utils/functions";
47
69
  import { Paths } from "@/utils/paths";
70
+ import { FileAttachmentPill } from "../chat-components";
71
+ import {
72
+ convertFilesToResourceLinks,
73
+ parseContextFromPrompt,
74
+ } from "./context-utils";
48
75
  import { getAgentPrompt } from "./prompt";
49
76
  import type {
50
77
  AgentConnectionState,
51
78
  AgentPendingPermission,
79
+ AvailableCommands,
52
80
  ExternalAgentSessionId,
53
81
  NotificationEvent,
82
+ SessionMode,
54
83
  } from "./types";
55
84
 
56
85
  const logger = Logger.get("agents");
57
86
 
87
+ // File attachment constants
88
+ const SUPPORTED_ATTACHMENT_TYPES = ["image/*", "text/*"];
89
+
58
90
  interface AgentTitleProps {
59
91
  currentAgentId?: ExternalAgentId;
60
92
  }
@@ -171,35 +203,45 @@ interface EmptyStateProps {
171
203
  }
172
204
 
173
205
  const EmptyState = memo<EmptyStateProps>(
174
- ({ currentAgentId, connectionState, onConnect, onDisconnect }) => (
175
- <div className="flex flex-col h-full">
176
- <AgentPanelHeader
177
- connectionState={connectionState}
178
- currentAgentId={currentAgentId}
179
- onConnect={onConnect}
180
- onDisconnect={onDisconnect}
181
- hasActiveSession={false}
182
- />
183
- <SessionTabs />
184
- <div className="flex-1 flex items-center justify-center p-6">
185
- <div className="max-w-md w-full space-y-6">
186
- <PanelEmptyState
187
- title="No Agent Sessions"
188
- description="Create a new session to start a conversation"
189
- action={<AgentSelector className="border-y-1 rounded" />}
190
- icon={<BotMessageSquareIcon />}
191
- />
192
- {connectionState.status === "disconnected" && (
193
- <AgentDocs
194
- className="border-t pt-6"
195
- title="Connect to an agent"
196
- description="Start agents by running these commands in your terminal:"
206
+ ({ currentAgentId, connectionState, onConnect, onDisconnect }) => {
207
+ const filename = useAtomValue(filenameAtom);
208
+ return (
209
+ <div className="flex flex-col h-full">
210
+ <AgentPanelHeader
211
+ connectionState={connectionState}
212
+ currentAgentId={currentAgentId}
213
+ onConnect={onConnect}
214
+ onDisconnect={onDisconnect}
215
+ hasActiveSession={false}
216
+ />
217
+ <SessionTabs />
218
+ <div className="flex-1 flex items-center justify-center p-6">
219
+ <div className="max-w-md w-full space-y-6">
220
+ <PanelEmptyState
221
+ title="No Agent Sessions"
222
+ description="Create a new session to start a conversation"
223
+ action={<AgentSelector className="border-y-1 rounded" />}
224
+ icon={<BotMessageSquareIcon />}
197
225
  />
198
- )}
226
+ {connectionState.status === "disconnected" && (
227
+ <AgentDocs
228
+ className="border-t pt-6"
229
+ title="Connect to an agent"
230
+ description={
231
+ <>
232
+ Start agents by running these commands in your terminal:
233
+ <br />
234
+ Note: This must be in the directory{" "}
235
+ {Paths.dirname(filename ?? "")}
236
+ </>
237
+ }
238
+ />
239
+ )}
240
+ </div>
199
241
  </div>
200
242
  </div>
201
- </div>
202
- ),
243
+ );
244
+ },
203
245
  );
204
246
  EmptyState.displayName = "EmptyState";
205
247
 
@@ -248,8 +290,14 @@ interface PromptAreaProps {
248
290
  isLoading: boolean;
249
291
  activeSessionId: ExternalAgentSessionId | null;
250
292
  promptValue: string;
293
+ commands: AvailableCommands | undefined;
251
294
  onPromptValueChange: (value: string) => void;
252
295
  onPromptSubmit: (e: KeyboardEvent | undefined, prompt: string) => void;
296
+ onAddFiles: (files: File[]) => void;
297
+ onStop: () => void;
298
+ fileInputRef: React.RefObject<HTMLInputElement | null>;
299
+ sessionMode?: SessionMode;
300
+ onModeChange?: (mode: string) => void;
253
301
  }
254
302
 
255
303
  const PromptArea = memo<PromptAreaProps>(
@@ -257,36 +305,193 @@ const PromptArea = memo<PromptAreaProps>(
257
305
  isLoading,
258
306
  activeSessionId,
259
307
  promptValue,
308
+ commands,
260
309
  onPromptValueChange,
261
310
  onPromptSubmit,
262
- }) => (
263
- <div
264
- className={cn(
265
- "px-3 py-2 border-t bg-background flex-shrink-0 min-h-[80px]",
266
- (isLoading || !activeSessionId) && "opacity-50 pointer-events-none",
267
- )}
268
- >
269
- <PromptInput
270
- value={promptValue}
271
- onChange={isLoading ? Functions.NOOP : onPromptValueChange}
272
- onSubmit={onPromptSubmit}
273
- onClose={Functions.NOOP}
274
- placeholder={isLoading ? "Processing..." : "Ask your AI agent..."}
275
- className={isLoading ? "opacity-50 pointer-events-none" : ""}
276
- maxHeight="120px"
277
- />
278
- </div>
279
- ),
311
+ onAddFiles,
312
+ onStop,
313
+ fileInputRef,
314
+ sessionMode,
315
+ onModeChange,
316
+ }) => {
317
+ const inputRef = useRef<ReactCodeMirrorRef | null>(null);
318
+ const promptCompletions: AdditionalCompletions | undefined = useMemo(() => {
319
+ if (!commands) {
320
+ return undefined;
321
+ }
322
+ // sentence has to begin with '/' to trigger autocomplete
323
+ return {
324
+ triggerCompletionRegex: /^\/(\w+)?/,
325
+ completions: commands.map(
326
+ (prompt): Completion => ({
327
+ label: `/${prompt.name}`,
328
+ info: prompt.description,
329
+ }),
330
+ ),
331
+ };
332
+ }, [commands]);
333
+
334
+ const handleSendClick = useEvent(() => {
335
+ if (promptValue.trim()) {
336
+ onPromptSubmit(undefined, promptValue);
337
+ }
338
+ });
339
+
340
+ const handleAddContext = useEvent(() => {
341
+ // For now, just append @ to the current value
342
+ addContextCompletion(inputRef);
343
+ });
344
+
345
+ return (
346
+ <div className="border-t bg-background flex-shrink-0">
347
+ <div
348
+ className={cn(
349
+ "px-3 py-2 min-h-[80px]",
350
+ (isLoading || !activeSessionId) && "opacity-50 pointer-events-none",
351
+ )}
352
+ >
353
+ <PromptInput
354
+ inputRef={inputRef}
355
+ value={promptValue}
356
+ onChange={isLoading ? Functions.NOOP : onPromptValueChange}
357
+ onSubmit={onPromptSubmit}
358
+ additionalCompletions={promptCompletions}
359
+ onClose={Functions.NOOP}
360
+ onAddFiles={onAddFiles}
361
+ placeholder={
362
+ isLoading
363
+ ? "Processing..."
364
+ : `Ask anything, ${CONTEXT_TRIGGER} to include context about tables or dataframes`
365
+ }
366
+ className={isLoading ? "opacity-50 pointer-events-none" : ""}
367
+ maxHeight="120px"
368
+ />
369
+ </div>
370
+ <TooltipProvider>
371
+ <div className="px-3 py-2 border-t border-border/20 flex flex-row items-center justify-between">
372
+ <div className="flex items-center gap-2">
373
+ {sessionMode && onModeChange && (
374
+ <ModeSelector
375
+ sessionMode={sessionMode}
376
+ onModeChange={onModeChange}
377
+ />
378
+ )}
379
+ </div>
380
+ <div className="flex flex-row">
381
+ <Tooltip content="Add context">
382
+ <Button variant="text" size="icon" onClick={handleAddContext}>
383
+ <AtSignIcon className="h-3.5 w-3.5" />
384
+ </Button>
385
+ </Tooltip>
386
+ <>
387
+ <Tooltip content="Attach a file">
388
+ <Button
389
+ variant="text"
390
+ size="icon"
391
+ className="cursor-pointer"
392
+ onClick={() => fileInputRef.current?.click()}
393
+ title="Attach a file"
394
+ >
395
+ <PaperclipIcon className="h-3.5 w-3.5" />
396
+ </Button>
397
+ </Tooltip>
398
+ <Input
399
+ ref={fileInputRef}
400
+ type="file"
401
+ multiple={true}
402
+ hidden={true}
403
+ onChange={(event) => {
404
+ if (event.target.files) {
405
+ onAddFiles([...event.target.files]);
406
+ }
407
+ }}
408
+ accept={SUPPORTED_ATTACHMENT_TYPES.join(",")}
409
+ />
410
+ </>
411
+ <Tooltip content={isLoading ? "Stop" : "Submit"}>
412
+ <Button
413
+ variant="text"
414
+ size="sm"
415
+ className="h-6 w-6 p-0 hover:bg-muted/30 cursor-pointer"
416
+ onClick={isLoading ? onStop : handleSendClick}
417
+ disabled={isLoading ? false : !promptValue.trim()}
418
+ >
419
+ {isLoading ? (
420
+ <SquareIcon className="h-3 w-3 fill-current" />
421
+ ) : (
422
+ <SendIcon className="h-3 w-3" />
423
+ )}
424
+ </Button>
425
+ </Tooltip>
426
+ </div>
427
+ </div>
428
+ </TooltipProvider>
429
+ </div>
430
+ );
431
+ },
280
432
  );
281
433
  PromptArea.displayName = "PromptArea";
282
434
 
435
+ interface ModeSelectorProps {
436
+ sessionMode: SessionMode;
437
+ onModeChange: (mode: string) => void;
438
+ }
439
+
440
+ const ModeSelector = memo<ModeSelectorProps>(
441
+ ({ sessionMode, onModeChange }) => {
442
+ const availableModes = sessionMode?.availableModes || [];
443
+ const currentModeId = sessionMode?.currentModeId;
444
+ if (availableModes.length === 0) {
445
+ return null;
446
+ }
447
+
448
+ const modeOptions = availableModes.map((mode) => ({
449
+ value: mode.id,
450
+ label: mode.name,
451
+ subtitle: mode.description ?? "",
452
+ }));
453
+ const currentMode = modeOptions.find((opt) => opt.value === currentModeId);
454
+
455
+ return (
456
+ <Select value={currentModeId} onValueChange={onModeChange}>
457
+ <SelectTrigger className="h-6 text-xs border-border shadow-none! ring-0! bg-muted hover:bg-muted/30 py-0 px-2 gap-1 capitalize">
458
+ {currentMode?.label ?? currentModeId}
459
+ </SelectTrigger>
460
+ <SelectContent>
461
+ <SelectGroup>
462
+ <SelectLabel>Agent Mode</SelectLabel>
463
+ {modeOptions.map((option) => (
464
+ <SelectItem
465
+ key={option.value}
466
+ value={option.value}
467
+ className="text-xs"
468
+ >
469
+ <div className="flex flex-col">
470
+ {option.label}
471
+ {option.subtitle && (
472
+ <div className="text-muted-foreground text-xs pt-1 block">
473
+ {option.subtitle}
474
+ </div>
475
+ )}
476
+ </div>
477
+ </SelectItem>
478
+ ))}
479
+ </SelectGroup>
480
+ </SelectContent>
481
+ </Select>
482
+ );
483
+ },
484
+ );
485
+ ModeSelector.displayName = "ModeSelector";
486
+
283
487
  interface ChatContentProps {
284
488
  hasNotifications: boolean;
489
+ agentId: ExternalAgentId | undefined;
285
490
  connectionState: AgentConnectionState;
286
491
  sessionId: ExternalAgentSessionId | null;
287
492
  notifications: NotificationEvent[];
288
493
  pendingPermission: AgentPendingPermission;
289
- onResolvePermission: (option: unknown) => void;
494
+ onResolvePermission: (option: RequestPermissionResponse) => void;
290
495
  onRetryConnection?: () => void;
291
496
  onRetryLastAction?: () => void;
292
497
  onDismissError?: (errorId: string) => void;
@@ -295,6 +500,7 @@ interface ChatContentProps {
295
500
  const ChatContent = memo<ChatContentProps>(
296
501
  ({
297
502
  hasNotifications,
503
+ agentId,
298
504
  connectionState,
299
505
  notifications,
300
506
  pendingPermission,
@@ -306,6 +512,7 @@ const ChatContent = memo<ChatContentProps>(
306
512
  }) => {
307
513
  const [isScrolledToBottom, setIsScrolledToBottom] = useState(true);
308
514
  const scrollContainerRef = useRef<HTMLDivElement>(null);
515
+ const isDisconnected = connectionState.status === "disconnected";
309
516
 
310
517
  // Scroll handler to determine if we're at the bottom of the chat
311
518
  const handleScroll = useEvent(() => {
@@ -375,12 +582,30 @@ const ChatContent = memo<ChatContentProps>(
375
582
  />
376
583
  </div>
377
584
  ) : (
378
- <div className="flex items-center justify-center h-full min-h-[200px]">
585
+ <div className="flex items-center justify-center h-full min-h-[200px] flex-col">
379
586
  <PanelEmptyState
380
587
  title="Waiting for agent"
381
588
  description="Your AI agent will appear here when active"
382
589
  icon={<BotMessageSquareIcon />}
383
590
  />
591
+ {isDisconnected && agentId && (
592
+ <AgentDocs
593
+ className="border-t pt-6 px-5"
594
+ title="Make sure you're connected to an agent"
595
+ description="Run this command in your terminal:"
596
+ agents={[agentId]}
597
+ />
598
+ )}
599
+ {isDisconnected && (
600
+ <Button
601
+ variant="outline"
602
+ onClick={onRetryConnection}
603
+ type="button"
604
+ className="mt-4"
605
+ >
606
+ Retry
607
+ </Button>
608
+ )}
384
609
  </div>
385
610
  )}
386
611
  </div>
@@ -408,6 +633,8 @@ function getCwd() {
408
633
  const AgentPanel: React.FC = () => {
409
634
  const [isLoading, setIsLoading] = useState(false);
410
635
  const [promptValue, setPromptValue] = useState("");
636
+ const [files, setFiles] = useState<File[]>();
637
+ const fileInputRef = useRef<HTMLInputElement>(null);
411
638
 
412
639
  const [selectedTab] = useAtom(selectedTabAtom);
413
640
  const [sessionState, setSessionState] = useAtom(agentSessionStateAtom);
@@ -421,7 +648,7 @@ const AgentPanel: React.FC = () => {
421
648
  const acpClient = useAcpClient({
422
649
  wsUrl,
423
650
  clientOptions: {
424
- readTextFile: (request): Promise<ReadTextFileResponse> => {
651
+ readTextFile: (request) => {
425
652
  logger.debug("Agent requesting file read", {
426
653
  path: request.path,
427
654
  });
@@ -429,7 +656,7 @@ const AgentPanel: React.FC = () => {
429
656
  content: response.contents || "",
430
657
  }));
431
658
  },
432
- writeTextFile: (request): Promise<WriteTextFileResponse> => {
659
+ writeTextFile: (request) => {
433
660
  logger.debug("Agent requesting file write", {
434
661
  path: request.path,
435
662
  contentLength: request.content.length,
@@ -437,7 +664,7 @@ const AgentPanel: React.FC = () => {
437
664
  return sendUpdateFile({
438
665
  path: request.path,
439
666
  contents: request.content,
440
- }).then(() => null);
667
+ }).then(() => ({}));
441
668
  },
442
669
  },
443
670
  autoConnect: false, // We'll manage connection manually based on active session
@@ -449,11 +676,25 @@ const AgentPanel: React.FC = () => {
449
676
  connectionState,
450
677
  notifications,
451
678
  pendingPermission,
679
+ availableCommands,
452
680
  resolvePermission,
681
+ sessionMode,
453
682
  activeSessionId,
454
683
  agent,
455
684
  } = acpClient;
456
685
 
686
+ useEffect(() => {
687
+ agent?.initialize({
688
+ protocolVersion: 1,
689
+ clientCapabilities: {
690
+ fs: {
691
+ readTextFile: true,
692
+ writeTextFile: true,
693
+ },
694
+ },
695
+ });
696
+ }, [agent]);
697
+
457
698
  // Auto-connect to agent when we have an active session, but only once per session
458
699
  useEffect(() => {
459
700
  if (wsUrl === NO_WS_SET) {
@@ -581,6 +822,7 @@ const AgentPanel: React.FC = () => {
581
822
  });
582
823
  setIsLoading(true);
583
824
  setPromptValue("");
825
+ setFiles(undefined);
584
826
 
585
827
  // Update session title with first message if it's still the default
586
828
  if (selectedTab?.title.startsWith("New ")) {
@@ -597,26 +839,48 @@ const AgentPanel: React.FC = () => {
597
839
  return;
598
840
  }
599
841
 
842
+ const promptBlocks: ContentBlock[] = [{ type: "text", text: prompt }];
843
+
844
+ // Parse context from the prompt
845
+ const { contextBlocks, attachmentBlocks } =
846
+ await parseContextFromPrompt(prompt);
847
+ promptBlocks.push(...contextBlocks);
848
+ promptBlocks.push(...attachmentBlocks);
849
+
850
+ // Add manually uploaded files as resource links
851
+ if (files && files.length > 0) {
852
+ const fileResourceLinks = await convertFilesToResourceLinks(files);
853
+ promptBlocks.push(...fileResourceLinks);
854
+ }
855
+
856
+ const hasGivenRules = notifications.some(
857
+ (notification) =>
858
+ notification.type === "session_notification" &&
859
+ notification.data.update.sessionUpdate === "user_message_chunk",
860
+ );
861
+ if (!hasGivenRules) {
862
+ promptBlocks.push(
863
+ {
864
+ type: "resource_link",
865
+ uri: filename,
866
+ mimeType: "text/x-python",
867
+ name: filename,
868
+ },
869
+ {
870
+ type: "resource",
871
+ resource: {
872
+ uri: "marimo_rules.md",
873
+ mimeType: "text/markdown",
874
+ text: getAgentPrompt(filename),
875
+ },
876
+ },
877
+ );
878
+ }
879
+
600
880
  try {
601
881
  await agent.prompt({
602
882
  sessionId: activeSessionId,
603
- prompt: [
604
- { type: "text", text: prompt },
605
- {
606
- type: "resource_link",
607
- uri: filename,
608
- mimeType: "text/x-python",
609
- name: filename,
610
- },
611
- {
612
- type: "resource",
613
- resource: {
614
- uri: "marimo_rules.md",
615
- mimeType: "text/markdown",
616
- text: getAgentPrompt(filename),
617
- },
618
- },
619
- ],
883
+ prompt: promptBlocks,
620
884
  });
621
885
  } catch (error) {
622
886
  logger.error("Failed to send prompt", { error });
@@ -635,6 +899,21 @@ const AgentPanel: React.FC = () => {
635
899
  setIsLoading(false);
636
900
  });
637
901
 
902
+ // Handler for adding files
903
+ const handleAddFiles = useEvent((newFiles: File[]) => {
904
+ if (newFiles.length === 0) {
905
+ return;
906
+ }
907
+ setFiles((prev) => [...(prev ?? []), ...newFiles]);
908
+ });
909
+
910
+ // Handler for removing files
911
+ const handleRemoveFile = useEvent((fileToRemove: File) => {
912
+ if (files) {
913
+ setFiles(files.filter((f) => f !== fileToRemove));
914
+ }
915
+ });
916
+
638
917
  // Handler for manual connect
639
918
  const handleManualConnect = useEvent(() => {
640
919
  logger.debug("Manual connect requested", {
@@ -652,6 +931,33 @@ const AgentPanel: React.FC = () => {
652
931
  disconnect();
653
932
  });
654
933
 
934
+ const handleModeChange = useEvent((mode: string) => {
935
+ logger.debug("Mode change requested", {
936
+ sessionId: activeSessionId,
937
+ mode,
938
+ });
939
+ if (!agent) {
940
+ toast({
941
+ title: "Agent not connected",
942
+ description: "Please connect to an agent to change the mode",
943
+ variant: "danger",
944
+ });
945
+ return;
946
+ }
947
+ if (!agent.setSessionMode) {
948
+ toast({
949
+ title: "Mode change not supported",
950
+ description: "The agent does not support mode changes",
951
+ variant: "danger",
952
+ });
953
+ return;
954
+ }
955
+ void agent.setSessionMode?.({
956
+ sessionId: activeSessionId as string,
957
+ modeId: mode,
958
+ });
959
+ });
960
+
655
961
  const hasNotifications = notifications.length > 0;
656
962
  const hasActiveSessions = sessionState.sessions.length > 0;
657
963
 
@@ -680,6 +986,7 @@ const AgentPanel: React.FC = () => {
680
986
  <SessionTabs />
681
987
 
682
988
  <ChatContent
989
+ agentId={selectedTab?.agentId}
683
990
  sessionId={activeSessionId}
684
991
  hasNotifications={hasNotifications}
685
992
  connectionState={connectionState}
@@ -690,7 +997,7 @@ const AgentPanel: React.FC = () => {
690
997
  sessionId: activeSessionId,
691
998
  option,
692
999
  });
693
- resolvePermission(option as RequestPermissionResponse);
1000
+ resolvePermission(option);
694
1001
  }}
695
1002
  onRetryConnection={handleManualConnect}
696
1003
  />
@@ -701,12 +1008,30 @@ const AgentPanel: React.FC = () => {
701
1008
  onStop={handleStop}
702
1009
  />
703
1010
 
1011
+ {files && files.length > 0 && (
1012
+ <div className="flex flex-row gap-1 flex-wrap p-3 border-t">
1013
+ {files.map((file) => (
1014
+ <FileAttachmentPill
1015
+ file={file}
1016
+ key={file.name}
1017
+ onRemove={() => handleRemoveFile(file)}
1018
+ />
1019
+ ))}
1020
+ </div>
1021
+ )}
1022
+
704
1023
  <PromptArea
705
1024
  isLoading={isLoading}
706
1025
  activeSessionId={activeSessionId}
707
1026
  promptValue={promptValue}
708
1027
  onPromptValueChange={setPromptValue}
709
1028
  onPromptSubmit={handlePromptSubmit}
1029
+ onAddFiles={handleAddFiles}
1030
+ onStop={handleStop}
1031
+ fileInputRef={fileInputRef}
1032
+ commands={availableCommands}
1033
+ sessionMode={sessionMode}
1034
+ onModeChange={handleModeChange}
710
1035
  />
711
1036
  </div>
712
1037
  );
@@ -13,7 +13,9 @@ import {
13
13
  DropdownMenuSeparator,
14
14
  DropdownMenuTrigger,
15
15
  } from "@/components/ui/dropdown-menu";
16
+ import { useFilename } from "@/core/saving/filename";
16
17
  import { cn } from "@/utils/cn";
18
+ import { Paths } from "@/utils/paths";
17
19
  import { AgentDocs } from "./agent-docs";
18
20
  import {
19
21
  type AgentSession,
@@ -79,6 +81,7 @@ AgentMenuItem.displayName = "AgentMenuItem";
79
81
 
80
82
  export const AgentSelector: React.FC<AgentSelectorProps> = memo(
81
83
  ({ onSessionCreated, className }) => {
84
+ const filename = useFilename();
82
85
  const [sessionState, setSessionState] = useAtom(agentSessionStateAtom);
83
86
  const setActiveTab = useSetAtom(selectedTabAtom);
84
87
  const [isOpen, setIsOpen] = useState(false);
@@ -123,7 +126,10 @@ export const AgentSelector: React.FC<AgentSelectorProps> = memo(
123
126
  <div className="px-2 py-2">
124
127
  <div className="text-xs font-medium text-muted-foreground mb-3">
125
128
  To start an external agent, run the following command in your
126
- terminal:
129
+ terminal.
130
+ <br />
131
+ Note: This must be in the directory{" "}
132
+ {Paths.dirname(filename ?? "")}
127
133
  </div>
128
134
  <AgentDocs
129
135
  agents={AVAILABLE_AGENTS.map((agent) => agent.id)}