@marimo-team/islands 0.19.8-dev5 → 0.19.8-dev50

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 (147) hide show
  1. package/dist/{Combination-Bg-xN8JV.js → Combination-BTMrlhzT.js} +11 -10
  2. package/dist/{ConnectedDataExplorerComponent-DewsKLl2.js → ConnectedDataExplorerComponent-BAeQ8DWw.js} +11 -11
  3. package/dist/{ImageComparisonComponent-Bijp8beW.js → ImageComparisonComponent-DkEXPki_.js} +2 -2
  4. package/dist/{any-language-editor-DZc6NCTp.js → any-language-editor-D0UQItkS.js} +6 -6
  5. package/dist/{architectureDiagram-VXUJARFQ--NkyBn9Y.js → architectureDiagram-VXUJARFQ-DPPYVq8H.js} +4 -4
  6. package/dist/assets/__vite-browser-external-WSlCcXn_.js +1 -0
  7. package/dist/assets/worker-DUYMdbtA.js +73 -0
  8. package/dist/{blockDiagram-VD42YOAC-DEZZaTW0.js → blockDiagram-VD42YOAC-BA5N05Y9.js} +4 -4
  9. package/dist/{button-BWvsJ2Wr.js → button-Cy0ElmIm.js} +2 -2
  10. package/dist/{c4Diagram-YG6GDRKO-Bj7hwWCO.js → c4Diagram-YG6GDRKO-DJLzuGJJ.js} +3 -3
  11. package/dist/{channel-B_QrFrGg.js → channel-Dob5kWXR.js} +1 -1
  12. package/dist/{check-CM_kewwn.js → check-DkNR52Mm.js} +1 -1
  13. package/dist/{chunk-5FQGJX7Z-D5VFKHmt.js → chunk-5FQGJX7Z-BEb20Lzt.js} +3 -3
  14. package/dist/{chunk-ABZYJK2D-SZPYmRzN.js → chunk-ABZYJK2D-BXTC53mt.js} +1 -1
  15. package/dist/{chunk-ATLVNIR6-BI_WwH1o.js → chunk-ATLVNIR6-BJDjUR_c.js} +1 -1
  16. package/dist/{chunk-B4BG7PRW-BlI9Gm1l.js → chunk-B4BG7PRW-DzmUUpfH.js} +4 -4
  17. package/dist/{chunk-DI55MBZ5-BXxemMn5.js → chunk-DI55MBZ5-gTd3J8Tu.js} +4 -4
  18. package/dist/{chunk-EXTU4WIE-CzWtDV99.js → chunk-EXTU4WIE-DyoOs5QX.js} +1 -1
  19. package/dist/{chunk-JA3XYJ7Z-DQ-2ARfa.js → chunk-JA3XYJ7Z-BGnAIbOP.js} +2 -2
  20. package/dist/{chunk-JZLCHNYA-CVfjf2vv.js → chunk-JZLCHNYA-CIRgweVQ.js} +4 -4
  21. package/dist/{chunk-N4CR4FBY-BCZvQ7Jq.js → chunk-N4CR4FBY-DKSvXAIS.js} +5 -5
  22. package/dist/{chunk-QN33PNHL-DY_2Q2zl.js → chunk-QN33PNHL-B6zC8BTi.js} +1 -1
  23. package/dist/{chunk-QXUST7PY-BMCjAVR_.js → chunk-QXUST7PY-C7750n_u.js} +5 -5
  24. package/dist/{chunk-S3R3BYOJ-Ddu0H4Qa.js → chunk-S3R3BYOJ-CBkH6JZZ.js} +1 -1
  25. package/dist/{chunk-TZMSLE5B-C2wVlbMl.js → chunk-TZMSLE5B-DObGL7xi.js} +1 -1
  26. package/dist/{classDiagram-2ON5EDUG-D-g7zbyO.js → classDiagram-2ON5EDUG-B9pkKjjc.js} +9 -9
  27. package/dist/{classDiagram-v2-WZHVMYZB-C7v5zNRD.js → classDiagram-v2-WZHVMYZB-CRhhA0tV.js} +9 -9
  28. package/dist/{click-outside-container-BCN5BtVO.js → click-outside-container-DNfggvIW.js} +1 -1
  29. package/dist/{code-block-37QAKDTI-eUgXqGNG.js → code-block-37QAKDTI-u5kgjqmr.js} +2 -2
  30. package/dist/{compiler-runtime-DHFVbq0b.js → compiler-runtime-B_OLMU9S.js} +1 -1
  31. package/dist/{copy-B59Bw3-w.js → copy-DRaXIb_a.js} +3 -3
  32. package/dist/{dagre-6UL2VRFP-DKIPL74O.js → dagre-6UL2VRFP-C2C2XxsB.js} +6 -6
  33. package/dist/{data-grid-overlay-editor-COyFwFmE.js → data-grid-overlay-editor-BXqtz1ia.js} +4 -4
  34. package/dist/{diagram-PSM6KHXK-CVTrAZaP.js → diagram-PSM6KHXK-DHBY-94p.js} +5 -5
  35. package/dist/{diagram-QEK2KX5R-BqHBzu3x.js → diagram-QEK2KX5R-CgMshOwn.js} +3 -3
  36. package/dist/{diagram-S2PKOQOG-CJD6owcg.js → diagram-S2PKOQOG-F1KPva3Y.js} +3 -3
  37. package/dist/{dist-Co5PD8Fb.js → dist-BBYTEAvO.js} +1 -1
  38. package/dist/{erDiagram-Q2GNP2WA-CqOceSf9.js → erDiagram-Q2GNP2WA-18gGng8V.js} +9 -9
  39. package/dist/{error-banner-C7KLpECd.js → error-banner-D2zjeN_a.js} +5 -5
  40. package/dist/{esm-D4WO8J3G.js → esm-CgRNPmz8.js} +6 -6
  41. package/dist/{flowDiagram-NV44I4VS-K7-DUifo.js → flowDiagram-NV44I4VS-iHFiHYe0.js} +9 -9
  42. package/dist/{ganttDiagram-JELNMOA3-BwUFY9Nu.js → ganttDiagram-JELNMOA3-D7GixxiF.js} +2 -2
  43. package/dist/{gitGraphDiagram-NY62KEGX-CjGRtLb1.js → gitGraphDiagram-NY62KEGX-CJFHytRK.js} +2 -2
  44. package/dist/{glide-data-editor-C3T7HsLi.js → glide-data-editor-BYwb17Bf.js} +13 -13
  45. package/dist/{infoDiagram-WHAUD3N6-DNhmDn-6.js → infoDiagram-WHAUD3N6-B5Lkh3A9.js} +2 -2
  46. package/dist/{journeyDiagram-XKPGCS4Q-BOdK47P8.js → journeyDiagram-XKPGCS4Q-CV_9R9iP.js} +2 -2
  47. package/dist/{kanban-definition-3W4ZIXB7-A0JC9d0g.js → kanban-definition-3W4ZIXB7-Dp21D5Ym.js} +6 -6
  48. package/dist/{katex-DJyOeQ91.js → katex-CX2BKujk.js} +1 -1
  49. package/dist/{katex-Dm9nZf6A.js → katex-Db0k5oV_.js} +1 -1
  50. package/dist/{label-C4PtQcza.js → label-CxU5JNBW.js} +6 -6
  51. package/dist/main.js +2000 -1887
  52. package/dist/mermaid-4DMBBIKO-BhDCqnO1.js +6 -0
  53. package/dist/{mermaid-Bqp2Xw99.js → mermaid-B__BZSXU.js} +39 -39
  54. package/dist/{mhchem-BqdXeZVX.js → mhchem-w1tkUnWr.js} +1 -1
  55. package/dist/{mindmap-definition-VGOIOE7T-CS6nKN_L.js → mindmap-definition-VGOIOE7T-B_5mfdYp.js} +8 -8
  56. package/dist/{number-overlay-editor-Bz_bDJQb.js → number-overlay-editor-D-4WQAGX.js} +2 -2
  57. package/dist/{pieDiagram-ADFJNKIX-DSa60Grk.js → pieDiagram-ADFJNKIX-B-DGEopK.js} +3 -3
  58. package/dist/{quadrantDiagram-AYHSOK5B-CFnMbP2J.js → quadrantDiagram-AYHSOK5B-M_yRSIZn.js} +1 -1
  59. package/dist/{react-DdA8EBol.js → react-Bs6Z0kvn.js} +1 -1
  60. package/dist/{react-dom-DJW8xUDg.js → react-dom-CqtLRVZP.js} +2 -2
  61. package/dist/{react-plotly-jVjTu07w.js → react-plotly-BuRa9xtI.js} +1 -1
  62. package/dist/{react-vega-DgHpnZ04.js → react-vega-3WcLHYC7.js} +2 -2
  63. package/dist/{react-vega-CjiPWyw0.js → react-vega-DLFvGrpJ.js} +1 -1
  64. package/dist/{requirementDiagram-UZGBJVZJ-ytLQrFTk.js → requirementDiagram-UZGBJVZJ-9Wt82hOZ.js} +8 -8
  65. package/dist/{sankeyDiagram-TZEHDZUN-KQqXDoky.js → sankeyDiagram-TZEHDZUN-x_aTXZeN.js} +1 -1
  66. package/dist/{sequenceDiagram-WL72ISMW-ByLI04T5.js → sequenceDiagram-WL72ISMW-CXXmJqiQ.js} +3 -3
  67. package/dist/{slides-component-BVjvNo92.js → slides-component-Dp-y50K9.js} +4 -4
  68. package/dist/{spec-Dmb1KfK3.js → spec-HoYHAQo2.js} +6 -6
  69. package/dist/{stateDiagram-FKZM4ZOC-Dfz8vBbP.js → stateDiagram-FKZM4ZOC-CiSKS_Mx.js} +9 -9
  70. package/dist/{stateDiagram-v2-4FDKWEC3-DRYoLdT5.js → stateDiagram-v2-4FDKWEC3-A43Itnjp.js} +9 -9
  71. package/dist/style.css +1 -1
  72. package/dist/{timeline-definition-IT6M3QCI-CO48XU1B.js → timeline-definition-IT6M3QCI-DR26eWb4.js} +1 -1
  73. package/dist/{types-CzEZ3EWT.js → types-Bb-6p8hv.js} +8 -8
  74. package/dist/{useAsyncData-BjNwqCfS.js → useAsyncData-Dyq3DyOF.js} +3 -3
  75. package/dist/{useDeepCompareMemoize-CfoxVor3.js → useDeepCompareMemoize-BhZZsis0.js} +12 -8
  76. package/dist/{useIframeCapabilities-BBO_R0ww.js → useIframeCapabilities-DurI5SJh.js} +2 -2
  77. package/dist/{useTheme-BYG2SH8J.js → useTheme-SlKl8MlS.js} +5 -6
  78. package/dist/{vega-component-rDX7xwxH.js → vega-component-DCxUyPnb.js} +10 -10
  79. package/dist/{xychartDiagram-PRI3JC2R-CUIfjNVD.js → xychartDiagram-PRI3JC2R-BcVxCRox.js} +4 -4
  80. package/dist/{zod-DITCj31F.js → zod-bjADtMKr.js} +3 -3
  81. package/package.json +18 -18
  82. package/src/components/app-config/ai-config.tsx +11 -2
  83. package/src/components/app-config/optional-features.tsx +1 -1
  84. package/src/components/app-config/user-config-form.tsx +0 -54
  85. package/src/components/chat/__tests__/useFileState.test.tsx +93 -0
  86. package/src/components/chat/acp/__tests__/state.test.ts +69 -0
  87. package/src/components/chat/acp/agent-panel.tsx +26 -77
  88. package/src/components/chat/acp/state.ts +6 -6
  89. package/src/components/chat/chat-components.tsx +114 -1
  90. package/src/components/chat/chat-panel.tsx +79 -134
  91. package/src/components/chat/chat-utils.ts +42 -0
  92. package/src/components/data-table/__tests__/data-table.test.tsx +94 -2
  93. package/src/components/editor/actions/useCellActionButton.tsx +14 -1
  94. package/src/components/editor/ai/add-cell-with-ai.tsx +85 -53
  95. package/src/components/editor/ai/ai-completion-editor.tsx +15 -38
  96. package/src/components/editor/cell/CreateCellButton.tsx +2 -1
  97. package/src/components/editor/cell/code/cell-editor.tsx +12 -0
  98. package/src/components/editor/chrome/panels/packages-panel.tsx +12 -9
  99. package/src/components/editor/database/__tests__/__snapshots__/as-code.test.ts.snap +15 -0
  100. package/src/components/editor/database/__tests__/as-code.test.ts +8 -0
  101. package/src/components/editor/database/as-code.ts +3 -0
  102. package/src/components/editor/database/schemas.ts +9 -0
  103. package/src/components/editor/renderers/cell-array.tsx +2 -1
  104. package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +12 -0
  105. package/src/components/pages/gallery-page.tsx +37 -6
  106. package/src/core/MarimoApp.tsx +12 -8
  107. package/src/core/ai/context/providers/file.ts +1 -1
  108. package/src/core/cells/__tests__/cells.test.ts +120 -0
  109. package/src/core/cells/__tests__/session.test.ts +37 -1
  110. package/src/core/cells/cells.ts +14 -0
  111. package/src/core/cells/session.ts +20 -8
  112. package/src/core/codemirror/language/languages/markdown.ts +7 -0
  113. package/src/core/config/feature-flag.tsx +0 -4
  114. package/src/core/dom/uiregistry.ts +4 -1
  115. package/src/core/islands/__tests__/bridge.test.ts +7 -2
  116. package/src/core/islands/bridge.ts +1 -1
  117. package/src/core/islands/main.ts +7 -0
  118. package/src/core/network/types.ts +2 -2
  119. package/src/core/run-app.tsx +11 -4
  120. package/src/core/static/__tests__/files.test.ts +195 -1
  121. package/src/core/static/files.ts +39 -9
  122. package/src/core/wasm/bridge.ts +1 -1
  123. package/src/core/websocket/useMarimoKernelConnection.tsx +5 -15
  124. package/src/plugins/core/registerReactComponent.tsx +9 -1
  125. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +164 -0
  126. package/src/plugins/impl/anywidget/AnyWidgetPlugin.tsx +93 -168
  127. package/src/plugins/impl/anywidget/__tests__/AnyWidgetPlugin.test.tsx +37 -123
  128. package/src/plugins/impl/anywidget/__tests__/model.test.ts +128 -122
  129. package/src/{utils/__tests__/data-views.test.ts → plugins/impl/anywidget/__tests__/serialization.test.ts} +42 -96
  130. package/src/plugins/impl/anywidget/model.ts +348 -223
  131. package/src/plugins/impl/anywidget/schemas.ts +32 -0
  132. package/src/{utils/data-views.ts → plugins/impl/anywidget/serialization.ts} +13 -36
  133. package/src/plugins/impl/anywidget/types.ts +27 -0
  134. package/src/plugins/impl/chat/chat-ui.tsx +22 -20
  135. package/src/utils/Deferred.ts +21 -0
  136. package/src/utils/__tests__/blob.test.ts +3 -3
  137. package/src/utils/__tests__/id-tree.test.ts +22 -7
  138. package/src/utils/__tests__/mime-types.test.ts +8 -10
  139. package/src/utils/__tests__/url-parser.test.ts +22 -0
  140. package/src/utils/blob.ts +14 -27
  141. package/src/utils/id-tree.tsx +11 -19
  142. package/src/utils/json/base64.ts +38 -8
  143. package/src/utils/mime-types.ts +5 -5
  144. package/src/utils/url-parser.ts +1 -1
  145. package/dist/assets/__vite-browser-external-DRa9CT_O.js +0 -1
  146. package/dist/assets/worker-SqntmiwV.js +0 -73
  147. package/dist/mermaid-4DMBBIKO-o3xNphpD.js +0 -6
@@ -3,12 +3,8 @@
3
3
  import { useAtom, useAtomValue } from "jotai";
4
4
  import { capitalize } from "lodash-es";
5
5
  import {
6
- AtSignIcon,
7
6
  BotMessageSquareIcon,
8
- PaperclipIcon,
9
7
  RefreshCwIcon,
10
- SendIcon,
11
- SquareIcon,
12
8
  StopCircleIcon,
13
9
  } from "lucide-react";
14
10
  import React, { memo, useEffect, useMemo, useRef, useState } from "react";
@@ -25,8 +21,7 @@ import {
25
21
  import { PanelEmptyState } from "@/components/editor/chrome/panels/empty-state";
26
22
  import { Spinner } from "@/components/icons/spinner";
27
23
  import { Button } from "@/components/ui/button";
28
- import { Input } from "@/components/ui/input";
29
- import { Tooltip, TooltipProvider } from "@/components/ui/tooltip";
24
+ import { TooltipProvider } from "@/components/ui/tooltip";
30
25
  import { cn } from "@/utils/cn";
31
26
  import { Logger } from "@/utils/Logger";
32
27
  import { AgentDocs } from "./agent-docs";
@@ -70,7 +65,13 @@ import { store } from "@/core/state/jotai";
70
65
  import { ErrorBanner } from "@/plugins/impl/common/error-banner";
71
66
  import { Functions } from "@/utils/functions";
72
67
  import { Paths } from "@/utils/paths";
73
- import { FileAttachmentPill } from "../chat-components";
68
+ import {
69
+ AddContextButton,
70
+ AttachFileButton,
71
+ FileAttachmentPill,
72
+ SendButton,
73
+ } from "../chat-components";
74
+ import { useFileState } from "../chat-utils";
74
75
  import { ReadyToChatBlock } from "./blocks";
75
76
  import {
76
77
  convertFilesToResourceLinks,
@@ -89,9 +90,6 @@ import type {
89
90
 
90
91
  const logger = Logger.get("agents");
91
92
 
92
- // File attachment constants
93
- const SUPPORTED_ATTACHMENT_TYPES = ["image/*", "text/*"];
94
-
95
93
  interface AgentTitleProps {
96
94
  currentAgentId?: ExternalAgentId;
97
95
  }
@@ -392,55 +390,21 @@ const PromptArea = memo<PromptAreaProps>(
392
390
  )}
393
391
  </div>
394
392
  <div className="flex flex-row">
395
- <Tooltip content="Add context">
396
- <Button
397
- variant="text"
398
- size="icon"
399
- onClick={handleAddContext}
400
- disabled={isLoading}
401
- >
402
- <AtSignIcon className="h-3.5 w-3.5" />
403
- </Button>
404
- </Tooltip>
405
- <Tooltip content="Attach a file">
406
- <Button
407
- variant="text"
408
- size="icon"
409
- className="cursor-pointer"
410
- onClick={() => fileInputRef.current?.click()}
411
- title="Attach a file"
412
- disabled={isLoading}
413
- >
414
- <PaperclipIcon className="h-3.5 w-3.5" />
415
- </Button>
416
- </Tooltip>
417
- <Input
418
- ref={fileInputRef}
419
- type="file"
420
- multiple={true}
421
- hidden={true}
422
- onChange={(event) => {
423
- if (event.target.files) {
424
- onAddFiles([...event.target.files]);
425
- }
426
- }}
427
- accept={SUPPORTED_ATTACHMENT_TYPES.join(",")}
393
+ <AddContextButton
394
+ handleAddContext={handleAddContext}
395
+ isLoading={isLoading}
396
+ />
397
+ <AttachFileButton
398
+ fileInputRef={fileInputRef}
399
+ isLoading={isLoading}
400
+ onAddFiles={onAddFiles}
401
+ />
402
+ <SendButton
403
+ isLoading={isLoading}
404
+ onStop={onStop}
405
+ onSendClick={handleSendClick}
406
+ isEmpty={!promptValue.trim()}
428
407
  />
429
- <Tooltip content={isLoading ? "Stop" : "Submit"}>
430
- <Button
431
- variant="text"
432
- size="sm"
433
- className="h-6 w-6 p-0 hover:bg-muted/30 cursor-pointer"
434
- onClick={isLoading ? onStop : handleSendClick}
435
- disabled={isLoading ? false : !promptValue.trim()}
436
- >
437
- {isLoading ? (
438
- <SquareIcon className="h-3 w-3 fill-current" />
439
- ) : (
440
- <SendIcon className="h-3 w-3" />
441
- )}
442
- </Button>
443
- </Tooltip>
444
408
  </div>
445
409
  </div>
446
410
  </TooltipProvider>
@@ -664,7 +628,7 @@ const AgentPanel: React.FC = () => {
664
628
  const [isLoading, setIsLoading] = useState(false);
665
629
  const [error, setError] = useState<Error | string | null>(null);
666
630
  const [promptValue, setPromptValue] = useState("");
667
- const [files, setFiles] = useState<File[]>();
631
+ const { files, addFiles, clearFiles, removeFile } = useFileState();
668
632
  const [sessionModels, setSessionModels] = useState<SessionModelState | null>(
669
633
  null,
670
634
  );
@@ -891,7 +855,7 @@ const AgentPanel: React.FC = () => {
891
855
  });
892
856
  setIsLoading(true);
893
857
  setPromptValue("");
894
- setFiles(undefined);
858
+ clearFiles();
895
859
 
896
860
  // Update session title with first message if it's still the default
897
861
  if (selectedTab?.title.startsWith("New ")) {
@@ -967,21 +931,6 @@ const AgentPanel: React.FC = () => {
967
931
  setIsLoading(false);
968
932
  });
969
933
 
970
- // Handler for adding files
971
- const handleAddFiles = useEvent((newFiles: File[]) => {
972
- if (newFiles.length === 0) {
973
- return;
974
- }
975
- setFiles((prev) => [...(prev ?? []), ...newFiles]);
976
- });
977
-
978
- // Handler for removing files
979
- const handleRemoveFile = useEvent((fileToRemove: File) => {
980
- if (files) {
981
- setFiles(files.filter((f) => f !== fileToRemove));
982
- }
983
- });
984
-
985
934
  // Handler for manual connect
986
935
  const handleManualConnect = useEvent(() => {
987
936
  logger.debug("Manual connect requested", {
@@ -1148,7 +1097,7 @@ const AgentPanel: React.FC = () => {
1148
1097
  <FileAttachmentPill
1149
1098
  file={file}
1150
1099
  key={file.name}
1151
- onRemove={() => handleRemoveFile(file)}
1100
+ onRemove={() => removeFile(file)}
1152
1101
  />
1153
1102
  ))}
1154
1103
  </div>
@@ -1160,7 +1109,7 @@ const AgentPanel: React.FC = () => {
1160
1109
  promptValue={promptValue}
1161
1110
  onPromptValueChange={setPromptValue}
1162
1111
  onPromptSubmit={handlePromptSubmit}
1163
- onAddFiles={handleAddFiles}
1112
+ onAddFiles={addFiles}
1164
1113
  onStop={handleStop}
1165
1114
  fileInputRef={fileInputRef}
1166
1115
  commands={availableCommands}
@@ -233,13 +233,17 @@ export function getAgentDisplayName(agentId: ExternalAgentId): string {
233
233
  }
234
234
 
235
235
  export function getAgentWebSocketUrl(agentId: ExternalAgentId): string {
236
- return AGENT_CONFIG[agentId].webSocketUrl;
236
+ const port = AGENT_CONFIG[agentId].port;
237
+ // Use the current page's hostname so the agent is reachable when
238
+ // marimo is accessed remotely (e.g. via direct IP or reverse proxy).
239
+ const hostname = window.location.hostname;
240
+ const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
241
+ return `${protocol}//${hostname}:${port}/message` as const;
237
242
  }
238
243
 
239
244
  interface AgentConfig {
240
245
  port: number;
241
246
  command: string;
242
- webSocketUrl: string;
243
247
  sessionSupport: SessionSupportType;
244
248
  }
245
249
 
@@ -247,25 +251,21 @@ const AGENT_CONFIG: Record<ExternalAgentId, AgentConfig> = {
247
251
  claude: {
248
252
  port: 3017,
249
253
  command: "npx @zed-industries/claude-code-acp",
250
- webSocketUrl: "ws://localhost:3017/message",
251
254
  sessionSupport: "single",
252
255
  },
253
256
  gemini: {
254
257
  port: 3019,
255
258
  command: "npx @google/gemini-cli --experimental-acp",
256
- webSocketUrl: "ws://localhost:3019/message",
257
259
  sessionSupport: "single",
258
260
  },
259
261
  codex: {
260
262
  port: 3021,
261
263
  command: "npx @zed-industries/codex-acp",
262
- webSocketUrl: "ws://localhost:3021/message",
263
264
  sessionSupport: "single",
264
265
  },
265
266
  opencode: {
266
267
  port: 3023,
267
268
  command: "npx opencode-ai acp",
268
- webSocketUrl: "ws://localhost:3023/message",
269
269
  sessionSupport: "single",
270
270
  },
271
271
  };
@@ -1,9 +1,23 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
3
  import type { FileUIPart } from "ai";
4
- import { FileIcon, FileTextIcon, ImageIcon, XIcon } from "lucide-react";
4
+ import {
5
+ AtSignIcon,
6
+ FileIcon,
7
+ FileTextIcon,
8
+ ImageIcon,
9
+ PaperclipIcon,
10
+ SendHorizontalIcon,
11
+ StopCircleIcon,
12
+ XIcon,
13
+ } from "lucide-react";
5
14
  import { useState } from "react";
6
15
  import { cn } from "@/utils/cn";
16
+ import { Spinner } from "../icons/spinner";
17
+ import { Button } from "../ui/button";
18
+ import { Input } from "../ui/input";
19
+ import { Tooltip } from "../ui/tooltip";
20
+ import { SUPPORTED_ATTACHMENT_TYPES } from "./chat-utils";
7
21
 
8
22
  export const AttachmentRenderer = ({
9
23
  attachment,
@@ -58,6 +72,105 @@ export const FileAttachmentPill = ({
58
72
  );
59
73
  };
60
74
 
75
+ export const SendButton = ({
76
+ isLoading,
77
+ onStop,
78
+ onSendClick,
79
+ isEmpty,
80
+ showStopLabel = false, // Show a stop label and spinner instead of just the stop icon when loading
81
+ }: {
82
+ isLoading: boolean;
83
+ onStop: () => void;
84
+ onSendClick: () => void;
85
+ isEmpty: boolean;
86
+ showStopLabel?: boolean;
87
+ }) => {
88
+ const loadingContent = showStopLabel ? (
89
+ <div className="flex flex-row items-center gap-1 px-1.5">
90
+ <span className="text-xs text-error">Stop</span>
91
+ <Spinner size="small" />
92
+ </div>
93
+ ) : (
94
+ <StopCircleIcon className="size-4 text-error" />
95
+ );
96
+
97
+ return (
98
+ <Tooltip content={isLoading ? "Stop" : "Submit"}>
99
+ <Button
100
+ variant="text"
101
+ size="sm"
102
+ className="h-6 min-w-6 p-0 hover:bg-muted/30 cursor-pointer"
103
+ onClick={isLoading ? onStop : onSendClick}
104
+ disabled={isLoading ? false : isEmpty}
105
+ >
106
+ {isLoading ? (
107
+ loadingContent
108
+ ) : (
109
+ <SendHorizontalIcon className="h-3.5 w-3.5" />
110
+ )}
111
+ </Button>
112
+ </Tooltip>
113
+ );
114
+ };
115
+
116
+ export const AddContextButton = ({
117
+ handleAddContext,
118
+ isLoading,
119
+ }: {
120
+ handleAddContext: () => void;
121
+ isLoading: boolean;
122
+ }) => {
123
+ return (
124
+ <Tooltip content="Add context">
125
+ <Button
126
+ variant="text"
127
+ size="icon"
128
+ onClick={handleAddContext}
129
+ disabled={isLoading}
130
+ >
131
+ <AtSignIcon className="h-3.5 w-3.5" />
132
+ </Button>
133
+ </Tooltip>
134
+ );
135
+ };
136
+
137
+ export const AttachFileButton = ({
138
+ fileInputRef,
139
+ isLoading,
140
+ onAddFiles,
141
+ }: {
142
+ fileInputRef: React.RefObject<HTMLInputElement | null>;
143
+ isLoading: boolean;
144
+ onAddFiles: (files: File[]) => void;
145
+ }) => {
146
+ return (
147
+ <>
148
+ <Tooltip content="Attach a file">
149
+ <Button
150
+ variant="text"
151
+ size="icon"
152
+ onClick={() => fileInputRef.current?.click()}
153
+ disabled={isLoading}
154
+ >
155
+ <PaperclipIcon className="h-3.5 w-3.5" />
156
+ </Button>
157
+ </Tooltip>
158
+ <Input
159
+ ref={fileInputRef}
160
+ type="file"
161
+ multiple={true}
162
+ hidden={true}
163
+ onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
164
+ if (event.target.files) {
165
+ onAddFiles([...event.target.files]);
166
+ }
167
+ }}
168
+ accept={SUPPORTED_ATTACHMENT_TYPES.join(",")}
169
+ />
170
+ </>
171
+ );
172
+ };
173
+
61
174
  function renderFileIcon(file: File): React.ReactNode {
62
175
  const classNames = "h-3 w-3 mt-0.5";
63
176