@marimo-team/islands 0.15.2 → 0.15.4

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 (211) hide show
  1. package/dist/{ConnectedDataExplorerComponent-C39nQwtD.js → ConnectedDataExplorerComponent-B68gXlbY.js} +323 -328
  2. package/dist/{ImageComparisonComponent-BhkiyswP.js → ImageComparisonComponent-Cw1oA8Tn.js} +13 -13
  3. package/dist/{_baseUniq-DdHL34FO.js → _baseUniq-CQrhBg_9.js} +67 -67
  4. package/dist/any-language-editor-pzUl6lxp.js +27 -0
  5. package/dist/{arc-BXrety1g.js → arc-BOhn-m2C.js} +1 -1
  6. package/dist/{architectureDiagram-KFL7JDKH-BMy6ywCF.js → architectureDiagram-W76B3OCA-DdYf2VnU.js} +144 -144
  7. package/dist/assets/{worker-COGufAQn.js → worker-BcG8m3h5.js} +33 -29
  8. package/dist/asterisk-DS281yxp.js +271 -0
  9. package/dist/{blockDiagram-ZYB65J3Q-DYT2-nlI.js → blockDiagram-QIGZ2CNN-DmmYotkP.js} +10 -10
  10. package/dist/{c4Diagram-AAMF2YG6-ZiQzioe6.js → c4Diagram-FPNF74CW-Dyz4zMHJ.js} +8 -8
  11. package/dist/{channel-CeuXqUAU.js → channel-CCL8jXAe.js} +1 -1
  12. package/dist/{chunk-ANTBXLJU-BvYnIrdq.js → chunk-4BX2VUAB-BfKwWLfJ.js} +1 -1
  13. package/dist/{chunk-WVR4S24B-DXj8yaUk.js → chunk-55IACEB6-CFQU7zSp.js} +1 -1
  14. package/dist/{chunk-GLLZNHP4-CyFsosAe.js → chunk-FMBD7UC4-DDjZzUcl.js} +1 -1
  15. package/dist/{chunk-JBRWN2VN-DA_EEhy2.js → chunk-K7UQS3LO-nBRjBU1H.js} +117 -117
  16. package/dist/{chunk-NRVI72HA-BYx2jMlI.js → chunk-QN33PNHL-B-g8sJzl.js} +1 -1
  17. package/dist/{chunk-FHKO5MBM-DfCztBk8.js → chunk-QZHKN3VN-B7QSJS3J.js} +1 -1
  18. package/dist/{chunk-LXBSTHXV-Se7vdY6J.js → chunk-TVAH2DTR-pGXll4d1.js} +7 -7
  19. package/dist/{chunk-OMD6QJNC-CqgcPMgL.js → chunk-TZMSLE5B-Dx9h-1mv.js} +1 -1
  20. package/dist/{classDiagram-v2-QTMF73CY-B19A3G1l.js → classDiagram-KNZD7YFC-BXryI7DY.js} +2 -2
  21. package/dist/{classDiagram-3BZAVTQC-B19A3G1l.js → classDiagram-v2-RKCZMP56-BXryI7DY.js} +2 -2
  22. package/dist/{clone-78au0tn1.js → clone-DqwV7ges.js} +1 -1
  23. package/dist/cose-bilkent-S5V4N54A-Di6FNMXz.js +2609 -0
  24. package/dist/{cytoscape.esm-BYnVVhJX.js → cytoscape.esm-DfdJODL8.js} +34 -34
  25. package/dist/{dagre-2BBEFEWP-BfEn3ZUV.js → dagre-5GWH7T2D-BTZPMTey.js} +6 -6
  26. package/dist/{data-grid-overlay-editor-CH_qLkV2.js → data-grid-overlay-editor-ryatXXby.js} +11 -11
  27. package/dist/{diagram-4IRLE6MV-CL8xidnG.js → diagram-N5W7TBWH-D79_zdOu.js} +59 -60
  28. package/dist/{diagram-RP2FKANI-B1BPcUew.js → diagram-QEK2KX5R-DX2A_SD0.js} +15 -15
  29. package/dist/{diagram-GUPCWM2R-CZ5cfqlq.js → diagram-S2PKOQOG-DM6VMTrJ.js} +10 -10
  30. package/dist/dockerfile-BoowzQlp.js +194 -0
  31. package/dist/ebnf-DUPDuY4r.js +78 -0
  32. package/dist/{erDiagram-HZWUO2LU-BEAIww50.js → erDiagram-AWTI2OKA-BBirxtlI.js} +8 -8
  33. package/dist/fcl-CPC2WYrI.js +103 -0
  34. package/dist/{flowDiagram-THRYKUMA-Czs2UAI2.js → flowDiagram-PVAE7QVJ-DyVweEMs.js} +9 -9
  35. package/dist/{ganttDiagram-WV7ZQ7D5-ByYIAVFO.js → ganttDiagram-OWAHRB6G-DTB7FX7r.js} +34 -34
  36. package/dist/{gitGraphDiagram-OJR772UL-BcpDsiyB.js → gitGraphDiagram-NY62KEGX-BrbIb5pD.js} +4 -4
  37. package/dist/{glide-data-editor-CmN6FVyi.js → glide-data-editor-DhMX4nmM.js} +33 -33
  38. package/dist/{graph-77W6heli.js → graph-CuLSrclI.js} +3 -3
  39. package/dist/http-D9LttvKF.js +44 -0
  40. package/dist/{index-BOojn38D.js → index-BNgdUQ2e.js} +7711 -7711
  41. package/dist/index-DIy6LHLJ.js +98 -0
  42. package/dist/{index-CmozKMxx.js → index-Df2dsx1t.js} +6 -6
  43. package/dist/{index-pBmAzQJl.js → index-MCx5v1x0.js} +2 -2
  44. package/dist/{index-Bfk9dnyS.js → index-cz_xaKvT.js} +33090 -32892
  45. package/dist/{infoDiagram-6WOFNB3A-CfzLHHVP.js → infoDiagram-STP46IZ2-CCBHc7-K.js} +2 -2
  46. package/dist/{journeyDiagram-FFXJYRFH-ndAcpkGn.js → journeyDiagram-BIP6EPQ6-LhGSj54j.js} +24 -26
  47. package/dist/{kanban-definition-KOZQBZVT-DcQYzNvc.js → kanban-definition-6OIFK2YF-aegTMFS6.js} +14 -14
  48. package/dist/{layout-XySVHJgD.js → layout-BEARWMhl.js} +81 -81
  49. package/dist/{linear-PbooOqg7.js → linear-fbJq6cdO.js} +35 -35
  50. package/dist/{main-B5yML0bw.js → main-HerZgEhd.js} +76533 -69945
  51. package/dist/main.js +1 -1
  52. package/dist/{mermaid-Cg5IX6Nv.js → mermaid-DxPYK0KX.js} +6160 -7493
  53. package/dist/min-DBJkhObB.js +80 -0
  54. package/dist/mindmap-definition-Q6HEUPPD-A3Fh5XDZ.js +785 -0
  55. package/dist/nginx-zDPm3Z74.js +89 -0
  56. package/dist/{number-overlay-editor-DUhfZqtP.js → number-overlay-editor-USMrY6k3.js} +19 -19
  57. package/dist/{pieDiagram-DBDJKBY4-DTOlNsja.js → pieDiagram-ADFJNKIX-Q9uFlCV0.js} +17 -17
  58. package/dist/{quadrantDiagram-YPSRARAO-BX2d8VS-.js → quadrantDiagram-LMRXKWRM-BuPh-qpK.js} +6 -6
  59. package/dist/{react-plotly-Dcyw-3Sa.js → react-plotly-HSqJPRfa.js} +3577 -3577
  60. package/dist/{requirementDiagram-EGVEC5DT-D1T5u-wG.js → requirementDiagram-4UW4RH46-CHROYNU_.js} +7 -7
  61. package/dist/{sankeyDiagram-HRAUVNP4-G6xDfnp-.js → sankeyDiagram-GR3RE2ED-DkUqHP2d.js} +5 -5
  62. package/dist/sequenceDiagram-C3RYC4MD-YoPTMplP.js +2519 -0
  63. package/dist/{slides-component-BJLlPJSr.js → slides-component-D7CHSR00.js} +66 -66
  64. package/dist/solr-BNlsLglM.js +41 -0
  65. package/dist/spreadsheet-C-cy4P5N.js +49 -0
  66. package/dist/{stateDiagram-UUKSUZ4H-CYXbjaom.js → stateDiagram-KXAO66HF-DEN00mVU.js} +5 -5
  67. package/dist/{stateDiagram-v2-EYPG3UTE-Br1HYKT6.js → stateDiagram-v2-UMBNRL4Z-DlQqSUAa.js} +2 -2
  68. package/dist/style.css +1 -1
  69. package/dist/tiddlywiki-5wqsXtSk.js +155 -0
  70. package/dist/tiki-__Kn3CeS.js +181 -0
  71. package/dist/{time-B9SZnSen.js → time-BtVcKqeD.js} +58 -58
  72. package/dist/{timeline-definition-3HZDQTIS-DeK_ZRD0.js → timeline-definition-XQNQX7LJ-DEteLt8D.js} +10 -12
  73. package/dist/{timer-BYwnU4DF.js → timer-B0-z63CM.js} +16 -16
  74. package/dist/{treemap-75Q7IDZK-CKP4vV_0.js → treemap-75Q7IDZK-8S6podme.js} +14 -14
  75. package/dist/{vega-component-CpgdqX2d.js → vega-component-D35L45kI.js} +30 -30
  76. package/dist/{xychartDiagram-FDP5SA34-AMEPsx_R.js → xychartDiagram-6GGTOJPD-DKwGThyy.js} +7 -7
  77. package/package.json +44 -41
  78. package/src/__mocks__/notebook.ts +3 -0
  79. package/src/__mocks__/requests.ts +3 -0
  80. package/src/__tests__/__snapshots__/CellStatus.test.tsx.snap +12 -12
  81. package/src/__tests__/chat-utils.test.ts +26 -211
  82. package/src/components/ai/ai-model-dropdown.tsx +25 -9
  83. package/src/components/ai/ai-provider-icon.tsx +5 -1
  84. package/src/components/app-config/ai-config.tsx +7 -0
  85. package/src/components/chat/acp/__tests__/__snapshots__/prompt.test.ts.snap +304 -0
  86. package/src/components/chat/acp/__tests__/atoms.test.ts +56 -0
  87. package/src/components/chat/acp/__tests__/prompt.test.ts +12 -0
  88. package/src/components/chat/acp/__tests__/state.test.ts +621 -0
  89. package/src/components/chat/acp/agent-docs.tsx +78 -0
  90. package/src/components/chat/acp/agent-panel.css +23 -0
  91. package/src/components/chat/acp/agent-panel.tsx +715 -0
  92. package/src/components/chat/acp/agent-selector.tsx +138 -0
  93. package/src/components/chat/acp/blocks.tsx +664 -0
  94. package/src/components/chat/acp/common.tsx +198 -0
  95. package/src/components/chat/acp/prompt.ts +284 -0
  96. package/src/components/chat/acp/scroll-to-bottom-button.tsx +50 -0
  97. package/src/components/chat/acp/session-tabs.tsx +138 -0
  98. package/src/components/chat/acp/state.ts +263 -0
  99. package/src/components/chat/acp/thread.tsx +121 -0
  100. package/src/components/chat/acp/types.ts +63 -0
  101. package/src/components/chat/acp/utils.ts +45 -0
  102. package/src/components/chat/chat-components.tsx +71 -0
  103. package/src/components/chat/chat-panel.tsx +481 -291
  104. package/src/components/chat/chat-utils.ts +50 -0
  105. package/src/components/chat/markdown-renderer.tsx +3 -7
  106. package/src/components/chat/tool-call-accordion.tsx +6 -6
  107. package/src/components/datasources/__tests__/utils.test.ts +6 -0
  108. package/src/components/datasources/column-preview.tsx +1 -3
  109. package/src/components/editor/actions/useNotebookActions.tsx +1 -1
  110. package/src/components/editor/ai/add-cell-with-ai.tsx +20 -15
  111. package/src/components/editor/ai/ai-completion-editor.tsx +22 -3
  112. package/src/components/editor/ai/completion-handlers.tsx +2 -4
  113. package/src/components/editor/ai/completion-utils.ts +85 -11
  114. package/src/components/editor/alerts/startup-logs-alert.tsx +72 -0
  115. package/src/components/editor/chrome/panels/datasources-panel.tsx +3 -1
  116. package/src/components/editor/chrome/panels/dependency-graph-panel.tsx +3 -1
  117. package/src/components/editor/chrome/panels/documentation-panel.tsx +3 -1
  118. package/src/components/editor/chrome/panels/error-panel.tsx +3 -1
  119. package/src/components/editor/chrome/panels/file-explorer-panel.tsx +3 -1
  120. package/src/components/editor/chrome/panels/logs-panel.tsx +3 -1
  121. package/src/components/editor/chrome/panels/outline-panel.tsx +3 -1
  122. package/src/components/editor/chrome/panels/packages-panel.tsx +4 -2
  123. package/src/components/editor/chrome/panels/scratchpad-panel.tsx +3 -1
  124. package/src/components/editor/chrome/panels/secrets-panel.tsx +3 -1
  125. package/src/components/editor/chrome/panels/snippets-panel.tsx +3 -1
  126. package/src/components/editor/chrome/panels/tracing-panel.tsx +3 -1
  127. package/src/components/editor/chrome/panels/variable-panel.tsx +3 -1
  128. package/src/components/editor/chrome/types.ts +10 -0
  129. package/src/components/editor/chrome/wrapper/app-chrome.tsx +55 -31
  130. package/src/components/editor/controls/command-palette-button.tsx +1 -1
  131. package/src/components/editor/controls/command-palette.tsx +5 -4
  132. package/src/components/editor/controls/state.ts +4 -0
  133. package/src/components/editor/package-alert.tsx +108 -58
  134. package/src/components/editor/renderers/CellArray.tsx +2 -0
  135. package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +0 -1
  136. package/src/components/pages/edit-page.tsx +7 -3
  137. package/src/core/ai/chat-utils.ts +26 -43
  138. package/src/core/ai/config.ts +1 -1
  139. package/src/core/ai/context/__tests__/registry.test.ts +277 -3
  140. package/src/core/ai/context/context.ts +11 -1
  141. package/src/core/ai/context/providers/__tests__/cell-output.test.ts +378 -0
  142. package/src/core/ai/context/providers/__tests__/error.test.ts +3 -2
  143. package/src/core/ai/context/providers/__tests__/file.test.ts +119 -0
  144. package/src/core/ai/context/providers/cell-output.ts +349 -0
  145. package/src/core/ai/context/providers/common.ts +5 -1
  146. package/src/core/ai/context/providers/file.ts +287 -0
  147. package/src/core/ai/context/registry.ts +79 -0
  148. package/src/core/ai/state.ts +22 -5
  149. package/src/core/alerts/state.ts +71 -3
  150. package/src/core/cells/cell.ts +2 -2
  151. package/src/core/cells/cells.ts +1 -1
  152. package/src/core/cells/logs.ts +1 -1
  153. package/src/core/cells/runs.ts +6 -5
  154. package/src/core/codemirror/ai/resources.ts +47 -5
  155. package/src/core/codemirror/ai/state.ts +12 -0
  156. package/src/core/codemirror/language/__tests__/sql.test.ts +45 -0
  157. package/src/core/codemirror/markdown/__tests__/commands.test.ts +1 -0
  158. package/src/core/codemirror/theme/dark.ts +1 -1
  159. package/src/core/config/capabilities.ts +1 -1
  160. package/src/core/config/feature-flag.tsx +2 -0
  161. package/src/core/datasets/__tests__/data-source.test.ts +24 -0
  162. package/src/core/errors/__tests__/errors.test.ts +2 -0
  163. package/src/core/islands/bridge.ts +1 -0
  164. package/src/core/islands/main.ts +1 -0
  165. package/src/core/kernel/messages.ts +12 -6
  166. package/src/core/layout/layout.ts +3 -3
  167. package/src/core/network/requests-network.ts +8 -0
  168. package/src/core/network/requests-static.ts +1 -0
  169. package/src/core/network/requests-toasting.ts +1 -0
  170. package/src/core/network/types.ts +4 -1
  171. package/src/core/wasm/bridge.ts +18 -2
  172. package/src/core/wasm/worker/bootstrap.ts +3 -1
  173. package/src/core/wasm/worker/getMarimoWheel.ts +3 -8
  174. package/src/core/wasm/worker/types.ts +3 -0
  175. package/src/core/websocket/useMarimoWebSocket.tsx +7 -1
  176. package/src/css/app/Cell.css +42 -21
  177. package/src/css/app/codemirror.css +5 -1
  178. package/src/css/globals.css +3 -0
  179. package/src/css/md.css +1 -1
  180. package/src/plugins/impl/MicrophonePlugin.tsx +2 -2
  181. package/src/plugins/impl/chat/ChatPlugin.tsx +2 -9
  182. package/src/plugins/impl/chat/chat-ui.tsx +129 -110
  183. package/src/plugins/impl/chat/types.ts +5 -8
  184. package/src/plugins/impl/code/__tests__/language.test.ts +15 -0
  185. package/src/plugins/impl/code/any-language-editor.tsx +11 -8
  186. package/src/plugins/impl/vega/vega.css +121 -0
  187. package/src/plugins/layout/MimeRenderPlugin.tsx +3 -6
  188. package/src/stories/cell.stories.tsx +6 -0
  189. package/src/stories/layout/vertical/one-column.stories.tsx +215 -0
  190. package/src/theme/useTheme.ts +11 -6
  191. package/src/utils/Logger.ts +5 -6
  192. package/src/utils/__tests__/blob.test.ts +37 -0
  193. package/src/utils/arrays.ts +13 -0
  194. package/src/utils/fileToBase64.ts +21 -6
  195. package/src/utils/json/base64.ts +5 -2
  196. package/src/utils/numbers.ts +9 -7
  197. package/dist/any-language-editor-DC5170DQ.js +0 -45
  198. package/dist/asn1-jKiBa2Ya.js +0 -95
  199. package/dist/clojure-CCKyeQKf.js +0 -800
  200. package/dist/css-BkF-NPzE.js +0 -1553
  201. package/dist/index-5ZH_qS8j.js +0 -288
  202. package/dist/index-U4yn89qO.js +0 -341
  203. package/dist/javascript-C2yteZeJ.js +0 -691
  204. package/dist/min-DS5Jz-hg.js +0 -80
  205. package/dist/mindmap-definition-LNHGMQRG-0aOVaMR8.js +0 -3234
  206. package/dist/mllike-BSnXJBGA.js +0 -272
  207. package/dist/pug-CwAQJzGR.js +0 -248
  208. package/dist/python-BkR3uSy8.js +0 -313
  209. package/dist/rpm-IznJm2Xc.js +0 -57
  210. package/dist/sequenceDiagram-WFGC7UMF-DMhHzllb.js +0 -2284
  211. package/dist/ttcn-cfg-Bac_acMi.js +0 -88
@@ -1,10 +1,10 @@
1
1
  /* Copyright 2024 Marimo. All rights reserved. */
2
2
 
3
- import { useChat } from "@ai-sdk/react";
3
+ import { type UIMessage, useChat } from "@ai-sdk/react";
4
4
  import { ChatBubbleIcon } from "@radix-ui/react-icons";
5
5
  import { PopoverAnchor } from "@radix-ui/react-popover";
6
6
  import type { ReactCodeMirrorRef } from "@uiw/react-codemirror";
7
- import type { Message } from "ai/react";
7
+ import { DefaultChatTransport, type FileUIPart } from "ai";
8
8
  import { startCase } from "lodash-es";
9
9
  import {
10
10
  BotMessageSquareIcon,
@@ -18,6 +18,7 @@ import {
18
18
  X,
19
19
  } from "lucide-react";
20
20
  import React, { useEffect, useRef, useState } from "react";
21
+ import { convertToFileUIPart } from "@/components/chat/chat-utils";
21
22
  import {
22
23
  type AdditionalCompletions,
23
24
  PromptInput,
@@ -49,12 +50,7 @@ import { Logger } from "@/utils/Logger";
49
50
  import { Objects } from "@/utils/objects";
50
51
  import { ErrorBanner } from "../common/error-banner";
51
52
  import type { PluginFunctions } from "./ChatPlugin";
52
- import type {
53
- ChatAttachment,
54
- ChatConfig,
55
- ChatMessage,
56
- ChatRole,
57
- } from "./types";
53
+ import type { ChatConfig, ChatMessage } from "./types";
58
54
 
59
55
  interface Props extends PluginFunctions {
60
56
  prompts: string[];
@@ -67,8 +63,9 @@ interface Props extends PluginFunctions {
67
63
  }
68
64
 
69
65
  export const Chatbot: React.FC<Props> = (props) => {
66
+ const [input, setInput] = useState("");
70
67
  const [config, setConfig] = useState<ChatConfig>(props.config);
71
- const [files, setFiles] = useState<FileList | undefined>(undefined);
68
+ const [files, setFiles] = useState<File[] | undefined>(undefined);
72
69
  const fileInputRef = useRef<HTMLInputElement>(null);
73
70
  const formRef = useRef<HTMLFormElement>(null);
74
71
  const codeMirrorInputRef = useRef<ReactCodeMirrorRef>(null);
@@ -76,75 +73,86 @@ export const Chatbot: React.FC<Props> = (props) => {
76
73
 
77
74
  const { data: initialMessages } = useAsyncData(async () => {
78
75
  const chatMessages = await props.get_chat_history({});
79
- const messages: Message[] = chatMessages.messages.map((message, idx) => ({
76
+ const messages: UIMessage[] = chatMessages.messages.map((message, idx) => ({
80
77
  id: idx.toString(),
81
78
  role: message.role,
82
- content: message.content,
83
- experimental_attachments: message.attachments,
79
+ parts: message.parts ?? [],
84
80
  }));
85
81
  return messages;
86
82
  }, []);
87
83
 
88
84
  const {
89
85
  messages,
86
+ sendMessage,
90
87
  setMessages,
91
- input,
92
- setInput,
93
- handleSubmit,
94
88
  status,
95
89
  stop,
96
90
  error,
97
- reload,
91
+ regenerate,
98
92
  } = useChat({
99
- keepLastMessageOnError: true,
100
- streamProtocol: "text",
101
- fetch: async (_url, request) => {
102
- const body = JSON.parse(request?.body as string) as {
103
- messages: Message[];
104
- };
105
- try {
106
- const response = await props.send_prompt({
107
- messages: body.messages.map((m) => ({
108
- role: m.role as ChatRole,
109
- content: m.content,
110
- attachments: m.experimental_attachments,
111
- })),
112
- config: {
113
- max_tokens: config.max_tokens,
114
- temperature: config.temperature,
115
- top_p: config.top_p,
116
- top_k: config.top_k,
117
- frequency_penalty: config.frequency_penalty,
118
- presence_penalty: config.presence_penalty,
119
- },
120
- });
121
- return new Response(response);
122
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
123
- } catch (error: any) {
124
- // HACK: strip the error message to clean up the response
125
- const strippedError = error.message
126
- .split("failed with exception ")
127
- .pop();
128
- return new Response(strippedError, { status: 400 });
129
- }
130
- },
131
- initialMessages: initialMessages,
132
- onFinish: (message, { usage, finishReason }) => {
93
+ transport: new DefaultChatTransport({
94
+ fetch: async (
95
+ request: RequestInfo | URL,
96
+ init: RequestInit | undefined,
97
+ ) => {
98
+ if (init === undefined) {
99
+ return fetch(request);
100
+ }
101
+
102
+ const body = JSON.parse(init.body as unknown as string) as {
103
+ messages: UIMessage[];
104
+ };
105
+ try {
106
+ const messages = body.messages.map((m) => ({
107
+ role: m.role,
108
+ content: m.parts
109
+ ?.map((p) => ("text" in p ? p.text : ""))
110
+ .join("\n"),
111
+ parts: m.parts,
112
+ }));
113
+ const response = await props.send_prompt({
114
+ messages: messages,
115
+ config: {
116
+ max_tokens: config.max_tokens,
117
+ temperature: config.temperature,
118
+ top_p: config.top_p,
119
+ top_k: config.top_k,
120
+ frequency_penalty: config.frequency_penalty,
121
+ presence_penalty: config.presence_penalty,
122
+ },
123
+ });
124
+ // Update local state with AI response
125
+ setMessages((prev) => [
126
+ ...prev,
127
+ {
128
+ id: Date.now().toString(),
129
+ role: "assistant",
130
+ parts: [{ type: "text", text: response }],
131
+ },
132
+ ]);
133
+ return new Response(response);
134
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
135
+ } catch (error: any) {
136
+ // HACK: strip the error message to clean up the response
137
+ const strippedError = error.message
138
+ .split("failed with exception ")
139
+ .pop();
140
+ return new Response(strippedError, { status: 400 });
141
+ }
142
+ },
143
+ }),
144
+ messages: initialMessages,
145
+ onFinish: (message) => {
133
146
  setFiles(undefined);
134
147
 
135
148
  if (fileInputRef.current) {
136
149
  fileInputRef.current.value = "";
137
150
  }
138
151
  Logger.debug("Finished streaming message:", message);
139
- Logger.debug("Token usage:", usage);
140
- Logger.debug("Finish reason:", finishReason);
141
152
  },
142
153
  onError: (error) => {
143
154
  Logger.error("An error occurred:", error);
144
155
  },
145
- onResponse: (response) => {
146
- Logger.debug("Received HTTP response from server:", response);
147
- },
148
156
  });
149
157
 
150
158
  const isLoading = status === "submitted" || status === "streaming";
@@ -157,12 +165,12 @@ export const Chatbot: React.FC<Props> = (props) => {
157
165
  }
158
166
  };
159
167
 
160
- const renderAttachment = (attachment: ChatAttachment) => {
161
- if (attachment.contentType?.startsWith("image")) {
168
+ const renderAttachment = (attachment: FileUIPart) => {
169
+ if (attachment.mediaType?.startsWith("image")) {
162
170
  return (
163
171
  <img
164
172
  src={attachment.url}
165
- alt={attachment.name || "Attachment"}
173
+ alt={attachment.filename || "Attachment"}
166
174
  className="object-contain rounded-sm"
167
175
  width={100}
168
176
  height={100}
@@ -177,18 +185,20 @@ export const Chatbot: React.FC<Props> = (props) => {
177
185
  rel="noopener noreferrer"
178
186
  className="text-background hover:underline"
179
187
  >
180
- {attachment.name || "Attachment"}
188
+ {attachment.filename || "Attachment"}
181
189
  </a>
182
190
  );
183
191
  };
184
192
 
185
- const renderMessage = (message: Message) => {
193
+ const renderMessage = (message: UIMessage) => {
194
+ const textParts = message.parts?.filter((p) => p.type === "text");
195
+ const textContent = textParts?.map((p) => p.text).join("\n");
186
196
  const content =
187
197
  message.role === "assistant"
188
- ? renderHTML({ html: message.content })
189
- : message.content;
198
+ ? renderHTML({ html: textContent })
199
+ : textContent;
190
200
 
191
- const attachments = message.experimental_attachments;
201
+ const attachments = message.parts?.filter((p) => p.type === "file");
192
202
 
193
203
  return (
194
204
  <>
@@ -204,7 +214,7 @@ export const Chatbot: React.FC<Props> = (props) => {
204
214
  size: "icon",
205
215
  })}
206
216
  href={attachment.url}
207
- download={attachment.name}
217
+ download={attachment.filename}
208
218
  >
209
219
  <DownloadIcon className="size-3" />
210
220
  </a>
@@ -274,50 +284,53 @@ export const Chatbot: React.FC<Props> = (props) => {
274
284
  </p>
275
285
  </div>
276
286
  )}
277
- {messages.map((message) => (
278
- <div
279
- key={message.id}
280
- className={cn(
281
- "flex flex-col group gap-2",
282
- message.role === "user" ? "items-end" : "items-start",
283
- )}
284
- >
287
+ {messages.map((message) => {
288
+ const textContent = message.parts
289
+ ?.filter((p) => p.type === "text")
290
+ .map((p) => p.text)
291
+ .join("\n");
292
+
293
+ return (
285
294
  <div
286
- className={`max-w-[80%] p-3 rounded-lg ${
287
- message.role === "user"
288
- ? "bg-(--sky-11) text-(--slate-1)"
289
- : "bg-(--slate-4) text-(--slate-12)"
290
- }`}
295
+ key={message.id}
296
+ className={cn(
297
+ "flex flex-col group gap-2",
298
+ message.role === "user" ? "items-end" : "items-start",
299
+ )}
291
300
  >
292
- <p
293
- className={cn(message.role === "user" && "whitespace-pre-wrap")}
301
+ <div
302
+ className={`max-w-[80%] p-3 rounded-lg ${
303
+ message.role === "user"
304
+ ? "bg-(--sky-11) text-(--slate-1) whitespace-pre-wrap"
305
+ : "bg-(--slate-4) text-(--slate-12)"
306
+ }`}
294
307
  >
295
308
  {renderMessage(message)}
296
- </p>
297
- </div>
298
- <div className="flex justify-end text-xs gap-2 invisible group-hover:visible">
299
- <button
300
- type="button"
301
- onClick={async () => {
302
- await copyToClipboard(message.content);
303
- toast({
304
- title: "Copied to clipboard",
305
- });
306
- }}
307
- className="text-xs text-(--slate-9) hover:text-(--slate-11)"
308
- >
309
- <ClipboardIcon className="h-3 w-3" />
310
- </button>
311
- <button
312
- type="button"
313
- onClick={() => handleDelete(message.id)}
314
- className="text-xs text-(--slate-9) hover:text-(--slate-11)"
315
- >
316
- <Trash2Icon className="h-3 w-3 text-(--red-9)" />
317
- </button>
309
+ </div>
310
+ <div className="flex justify-end text-xs gap-2 invisible group-hover:visible">
311
+ <button
312
+ type="button"
313
+ onClick={async () => {
314
+ await copyToClipboard(textContent);
315
+ toast({
316
+ title: "Copied to clipboard",
317
+ });
318
+ }}
319
+ className="text-xs text-(--slate-9) hover:text-(--slate-11)"
320
+ >
321
+ <ClipboardIcon className="h-3 w-3" />
322
+ </button>
323
+ <button
324
+ type="button"
325
+ onClick={() => handleDelete(message.id)}
326
+ className="text-xs text-(--slate-9) hover:text-(--slate-11)"
327
+ >
328
+ <Trash2Icon className="h-3 w-3 text-(--red-9)" />
329
+ </button>
330
+ </div>
318
331
  </div>
319
- </div>
320
- ))}
332
+ );
333
+ })}
321
334
 
322
335
  {isLoading && (
323
336
  <div className="flex items-center justify-center space-x-2 mb-4">
@@ -336,17 +349,23 @@ export const Chatbot: React.FC<Props> = (props) => {
336
349
  {error && (
337
350
  <div className="flex items-center justify-center space-x-2 mb-4">
338
351
  <ErrorBanner error={error} />
339
- <Button variant="outline" size="sm" onClick={() => reload()}>
352
+ <Button variant="outline" size="sm" onClick={() => regenerate()}>
340
353
  Retry
341
354
  </Button>
342
355
  </div>
343
356
  )}
344
357
  </div>
345
-
346
358
  <form
347
- onSubmit={(evt) => {
348
- handleSubmit(evt, {
349
- experimental_attachments: files,
359
+ onSubmit={async (evt) => {
360
+ evt.preventDefault();
361
+
362
+ const fileParts = files
363
+ ? await convertToFileUIPart(files)
364
+ : undefined;
365
+
366
+ sendMessage({
367
+ role: "user",
368
+ parts: [{ type: "text", text: input }, ...(fileParts ?? [])],
350
369
  });
351
370
  }}
352
371
  ref={formRef}
@@ -388,7 +407,7 @@ export const Chatbot: React.FC<Props> = (props) => {
388
407
  {files && files.length === 1 && (
389
408
  <span
390
409
  title={files[0].name}
391
- className="text-sm text-(--slate-11) truncate shrink-0 w-24"
410
+ className="text-sm text-(--slate-11) truncate shrink-0 w-fit max-w-24"
392
411
  >
393
412
  {files[0].name}
394
413
  </span>
@@ -439,7 +458,7 @@ export const Chatbot: React.FC<Props> = (props) => {
439
458
  }
440
459
  onChange={(event) => {
441
460
  if (event.target.files) {
442
- setFiles(event.target.files);
461
+ setFiles([...event.target.files]);
443
462
  }
444
463
  }}
445
464
  />
@@ -1,16 +1,13 @@
1
1
  /* Copyright 2024 Marimo. All rights reserved. */
2
+
3
+ import type { UIMessage } from "ai";
4
+
2
5
  export type ChatRole = "system" | "user" | "assistant";
3
6
 
4
7
  export interface ChatMessage {
5
8
  role: ChatRole;
6
- content: string;
7
- attachments?: ChatAttachment[];
8
- }
9
-
10
- export interface ChatAttachment {
11
- name?: string;
12
- contentType?: string;
13
- url: string;
9
+ content: string; // TODO: Deprecate content
10
+ parts: UIMessage["parts"];
14
11
  }
15
12
 
16
13
  export interface SendMessageRequest {
@@ -0,0 +1,15 @@
1
+ /* Copyright 2024 Marimo. All rights reserved. */
2
+
3
+ import { langs } from "@uiw/codemirror-extensions-langs";
4
+ import { describe, expect, it } from "vitest";
5
+ import { LANGUAGE_MAP } from "../any-language-editor";
6
+
7
+ describe("Codemirror Languages", () => {
8
+ const codemirrorLanguages = Object.keys(langs);
9
+
10
+ it("LANGUAGE_MAP should have all the languages in CodeMirror", () => {
11
+ for (const language of Object.values(LANGUAGE_MAP)) {
12
+ expect(codemirrorLanguages).toContain(language);
13
+ }
14
+ });
15
+ });
@@ -1,10 +1,6 @@
1
1
  /* Copyright 2024 Marimo. All rights reserved. */
2
2
 
3
- import {
4
- type LanguageName,
5
- langs,
6
- loadLanguage,
7
- } from "@uiw/codemirror-extensions-langs";
3
+ import { langs, loadLanguage } from "@uiw/codemirror-extensions-langs";
8
4
  import ReactCodeMirror, {
9
5
  type Extension,
10
6
  type ReactCodeMirrorProps,
@@ -15,6 +11,12 @@ import type { ResolvedTheme } from "@/theme/useTheme";
15
11
  import { Logger } from "@/utils/Logger";
16
12
  import { ErrorBanner } from "../common/error-banner";
17
13
 
14
+ export const LANGUAGE_MAP: Record<string, string> = {
15
+ python: "py",
16
+ javascript: "js",
17
+ typescript: "ts",
18
+ };
19
+
18
20
  /**
19
21
  * A code editor that supports any language.
20
22
  *
@@ -28,6 +30,9 @@ const AnyLanguageCodeMirror: React.FC<
28
30
  showCopyButton?: boolean;
29
31
  }
30
32
  > = ({ language, showCopyButton, extensions = [], ...props }) => {
33
+ // Maybe normalize the language to the extension
34
+ language = LANGUAGE_MAP[language || ""] || language;
35
+
31
36
  const isNotSupported = language && !(language in langs);
32
37
  if (isNotSupported) {
33
38
  Logger.warn(`Language ${language} not found in CodeMirror.`);
@@ -37,9 +42,7 @@ const AnyLanguageCodeMirror: React.FC<
37
42
  if (!language) {
38
43
  return extensions;
39
44
  }
40
- return [loadLanguage(language as LanguageName), ...extensions].filter(
41
- Boolean,
42
- );
45
+ return [loadLanguage(language), ...extensions].filter(Boolean);
43
46
  }, [language, extensions]);
44
47
 
45
48
  return (
@@ -29,3 +29,124 @@
29
29
  right: 2px;
30
30
  }
31
31
  }
32
+
33
+ /**
34
+ * Due to a bug when defining our own VegaPlugin where these styles are not applied,
35
+ * we provide the default styles for vega-actions.
36
+ * https://github.com/vega/vega-embed/blob/8f4ec8e136951a7e34b358e4e9b6037582a6ccdd/vega-embed.scss
37
+ */
38
+
39
+ .vega-embed.has-actions {
40
+ padding-right: 38px;
41
+ }
42
+ .vega-embed details:not([open]) > :not(summary) {
43
+ display: none !important;
44
+ }
45
+ .vega-embed summary {
46
+ list-style: none;
47
+ position: absolute;
48
+ top: 0;
49
+ right: 0;
50
+ padding: 6px;
51
+ z-index: 1000;
52
+ background: white;
53
+ box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.1);
54
+ color: #1b1e23;
55
+ border: 1px solid #aaa;
56
+ border-radius: 999px;
57
+ opacity: 0.2;
58
+ transition: opacity 0.4s ease-in;
59
+ cursor: pointer;
60
+ line-height: 0px;
61
+ }
62
+ .vega-embed summary::-webkit-details-marker {
63
+ display: none;
64
+ }
65
+ .vega-embed summary:active {
66
+ box-shadow: #aaa 0px 0px 0px 1px inset;
67
+ }
68
+ .vega-embed summary svg {
69
+ width: 14px;
70
+ height: 14px;
71
+ }
72
+ .vega-embed details[open] summary {
73
+ opacity: 0.7;
74
+ }
75
+ .vega-embed:hover summary,
76
+ .vega-embed:focus-within summary {
77
+ opacity: 1 !important;
78
+ transition: opacity 0.2s ease;
79
+ }
80
+ .vega-embed .vega-actions {
81
+ position: absolute;
82
+ z-index: 1001;
83
+ top: 35px;
84
+ /* right: -9px; Remove right for our own styles */
85
+ display: flex;
86
+ flex-direction: column;
87
+ padding-bottom: 8px;
88
+ padding-top: 8px;
89
+ border-radius: 4px;
90
+ box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.2);
91
+ border: 1px solid #d9d9d9;
92
+ background: white;
93
+ animation-duration: 0.15s;
94
+ animation-name: scale-in;
95
+ animation-timing-function: cubic-bezier(0.2, 0, 0.13, 1.5);
96
+ text-align: left;
97
+ }
98
+ .vega-embed .vega-actions a {
99
+ padding: 8px 16px;
100
+ font-family: sans-serif;
101
+ font-size: 14px;
102
+ font-weight: 600;
103
+ white-space: nowrap;
104
+ color: #434a56;
105
+ text-decoration: none;
106
+ }
107
+ .vega-embed .vega-actions a:hover,
108
+ .vega-embed .vega-actions a:focus {
109
+ background-color: #f7f7f9;
110
+ color: black;
111
+ }
112
+ .vega-embed .vega-actions::before,
113
+ .vega-embed .vega-actions::after {
114
+ content: "";
115
+ display: inline-block;
116
+ position: absolute;
117
+ }
118
+ .vega-embed .vega-actions::before {
119
+ left: auto;
120
+ /* right: 14px; */
121
+ top: -16px;
122
+ border: 8px solid #000 0;
123
+ border-bottom-color: #d9d9d9;
124
+ }
125
+ .vega-embed .vega-actions::after {
126
+ left: auto;
127
+ /* right: 15px; */
128
+ top: -14px;
129
+ border: 7px solid #000 0;
130
+ border-bottom-color: #fff;
131
+ }
132
+ .vega-embed .chart-wrapper.fit-x {
133
+ width: 100%;
134
+ }
135
+ .vega-embed .chart-wrapper.fit-y {
136
+ height: 100%;
137
+ }
138
+ .vega-embed-wrapper {
139
+ max-width: 100%;
140
+ overflow: auto;
141
+ padding-right: 14px;
142
+ }
143
+ @keyframes scale-in {
144
+ from {
145
+ opacity: 0;
146
+ transform: scale(0.6);
147
+ }
148
+ to {
149
+ opacity: 1;
150
+ transform: scale(1);
151
+ }
152
+ }
@@ -19,12 +19,9 @@ export class MimeRendererPlugin implements IStatelessPlugin<Data> {
19
19
 
20
20
  validator = z.object({
21
21
  mime: z.string().transform((val) => val as OutputMessage["mimetype"]),
22
- data: z.union([
23
- z.string(),
24
- z.null(),
25
- z.record(z.unknown()),
26
- z.array(z.any()),
27
- ]),
22
+ data: z
23
+ .union([z.string(), z.null(), z.record(z.unknown()), z.array(z.any())])
24
+ .transform((val) => val as OutputMessage["data"]),
28
25
  });
29
26
 
30
27
  render({ data }: IStatelessPluginProps<Data>): JSX.Element {
@@ -1,4 +1,5 @@
1
1
  /* Copyright 2024 Marimo. All rights reserved. */
2
+
2
3
  import type { Meta, StoryObj } from "@storybook/react-vite";
3
4
  import { createStore, Provider } from "jotai";
4
5
  import { createRef } from "react";
@@ -9,6 +10,8 @@ import {
9
10
  } from "@/core/cells/types";
10
11
  import { defaultUserConfig } from "@/core/config/config-schema";
11
12
  import { connectionAtom } from "@/core/network/connection";
13
+ import { requestClientAtom } from "@/core/network/requests";
14
+ import { resolveRequestClient } from "@/core/network/resolve.ts";
12
15
  import type { CellConfig } from "@/core/network/types";
13
16
  import { WebSocketState } from "@/core/websocket/types";
14
17
  import { MultiColumn } from "@/utils/id-tree";
@@ -78,6 +81,7 @@ const Cell: React.FC<{
78
81
  const store = createStore();
79
82
  store.set(notebookAtom, notebook);
80
83
  store.set(connectionAtom, { state: WebSocketState.OPEN });
84
+ store.set(requestClientAtom, resolveRequestClient());
81
85
  return (
82
86
  <Provider store={store}>
83
87
  <TooltipProvider>
@@ -219,6 +223,7 @@ export const Disabled: Story = {
219
223
  config: {
220
224
  disabled: true,
221
225
  hide_code: false,
226
+ column: null,
222
227
  },
223
228
  output: {
224
229
  channel: "output",
@@ -299,6 +304,7 @@ export const DisabledAndStaleStatus: Story = {
299
304
  overrides={{
300
305
  runElapsedTimeMs: 20 as Milliseconds,
301
306
  config: {
307
+ column: null,
302
308
  disabled: true,
303
309
  hide_code: false,
304
310
  },