@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.
- package/dist/assets/{ConnectedDataExplorerComponent-Cn5-l2X1.js → ConnectedDataExplorerComponent-BErMbWvG.js} +1 -1
- package/dist/assets/{ImageComparisonComponent-CEXMKKA4.js → ImageComparisonComponent-fTHv1Ih0.js} +1 -1
- package/dist/assets/{VegaLite-Bt14Ds9k.js → VegaLite-Bdi-TyfY.js} +6 -6
- package/dist/assets/_baseEach-CNBxBxvS.js +1 -0
- package/dist/assets/_baseMap-D1WHjKrd.js +1 -0
- package/dist/assets/_baseUniq-CCgDNtZb.js +1 -0
- package/dist/assets/_createAggregator-DcD0kTA5.js +1 -0
- package/dist/assets/agent-panel-Crv430aI.js +268 -0
- package/dist/assets/agent-panel-D92Mfy1i.css +1 -0
- package/dist/assets/{any-language-editor-DiwNT6zp.js → any-language-editor-CQh552Wu.js} +1 -1
- package/dist/assets/architectureDiagram-W76B3OCA-BAJeBxzt.js +36 -0
- package/dist/assets/{between-horizontal-start-FyewyCGn.js → between-horizontal-start-Boxgxbt_.js} +1 -1
- package/dist/assets/{blockDiagram-QIGZ2CNN-BrOkAf_c.js → blockDiagram-QIGZ2CNN-CL-1svEK.js} +1 -1
- package/dist/assets/{c4Diagram-FPNF74CW-BHPzDxE2.js → c4Diagram-FPNF74CW-BbEqbCTl.js} +5 -5
- package/dist/assets/channel-_2eNSz0n.js +1 -0
- package/dist/assets/chat-panel-CXh5Wl6C.js +3 -0
- package/dist/assets/{chunk-4BX2VUAB-DLxaCNYh.js → chunk-4BX2VUAB-C--8TXeE.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-DdzvO3HR.js → chunk-55IACEB6-Bj00HDqq.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-R5o-nSiG.js → chunk-FMBD7UC4-C-lhB6hN.js} +1 -1
- package/dist/assets/{chunk-K7UQS3LO-DxaMrGgG.js → chunk-K7UQS3LO-B-pGTXPt.js} +1 -1
- package/dist/assets/{chunk-QN33PNHL-DqS9-FYm.js → chunk-QN33PNHL-DqUzGhvm.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-BZ-TzajS.js → chunk-QZHKN3VN-TntJHfSk.js} +1 -1
- package/dist/assets/{chunk-TVAH2DTR-BsgP2dyv.js → chunk-TVAH2DTR-HUJb1psV.js} +1 -1
- package/dist/assets/{chunk-TZMSLE5B-D-h3ahXI.js → chunk-TZMSLE5B-BK3C__t3.js} +1 -1
- package/dist/assets/{circle-play-CQtRZ-rT.js → circle-play-DBLOv1Yu.js} +1 -1
- package/dist/assets/classDiagram-KNZD7YFC-BGmh9POF.js +1 -0
- package/dist/assets/classDiagram-v2-RKCZMP56-BGmh9POF.js +1 -0
- package/dist/assets/{clear-button-BY6Z_ViL.js → clear-button-BeoFbEKH.js} +1 -1
- package/dist/assets/clone-BFDSPAj3.js +1 -0
- package/dist/assets/command-palette-CXZiSv0I.js +1 -0
- package/dist/assets/common-C7oJcmCT.js +1 -0
- package/dist/assets/{compile-Ct_jzdKr.js → compile-7L0MwhyI.js} +1 -1
- package/dist/assets/cose-bilkent-S5V4N54A-BMkGLcVC.js +1 -0
- package/dist/assets/dagre-5GWH7T2D-BJtRienS.js +4 -0
- package/dist/assets/{data-grid-overlay-editor-BN_wulc3.js → data-grid-overlay-editor-DBkmGtNs.js} +1 -1
- package/dist/assets/datasources-panel-B7FbYLiy.js +1 -0
- package/dist/assets/{dependency-graph-panel-BOmSCZf7.js → dependency-graph-panel-DEdOxp2X.js} +4 -4
- package/dist/assets/diagram-N5W7TBWH-CmECY3nb.js +24 -0
- package/dist/assets/diagram-QEK2KX5R-DMOVSNKD.js +43 -0
- package/dist/assets/diagram-S2PKOQOG-BiJ96PNQ.js +24 -0
- package/dist/assets/{documentation-panel-BxjJO_Gw.js → documentation-panel-xULhaEv3.js} +1 -1
- package/dist/assets/edit-page-BrYda9VE.js +129 -0
- package/dist/assets/{ellipsis-vertical-UHbmjI2n.js → ellipsis-vertical-BBqXIlc2.js} +1 -1
- package/dist/assets/{empty-state-BIBXzY_0.js → empty-state-B3dA3G5P.js} +1 -1
- package/dist/assets/{erDiagram-AWTI2OKA-E84mAle_.js → erDiagram-AWTI2OKA-MP1DiFRo.js} +1 -1
- package/dist/assets/{error-panel-MEvQ6K7h.js → error-panel-Cc1sv-Ag.js} +1 -1
- package/dist/assets/file-explorer-panel-Bw59Kva1.js +1 -0
- package/dist/assets/{flowDiagram-PVAE7QVJ-DfbIRSAW.js → flowDiagram-PVAE7QVJ-BX7caPp7.js} +1 -1
- package/dist/assets/{ganttDiagram-OWAHRB6G-DR4HZ1z_.js → ganttDiagram-OWAHRB6G-B462g4Yf.js} +3 -3
- package/dist/assets/gitGraphDiagram-NY62KEGX-CGgvZ9-9.js +65 -0
- package/dist/assets/{glide-data-editor-nNmo1lPq.js → glide-data-editor-C0gUFZON.js} +4 -4
- package/dist/assets/graph-CHRVBzY5.js +1 -0
- package/dist/assets/{home-page-9eW6qida.js → home-page-Fb2osjys.js} +3 -3
- package/dist/assets/{index-DMomwMcN.js → index-BVgAenPd.js} +1 -1
- package/dist/assets/{index-B8llrTSo.js → index-BY93Ejhl.js} +1 -1
- package/dist/assets/{index-BFSnz7iM.js → index-C-8WADat.js} +1 -1
- package/dist/assets/{index-CPN7TRA1.js → index-C-GhZ7ti.js} +1 -1
- package/dist/assets/{index-DyLSuOH1.js → index-C1v_Z9et.js} +1 -1
- package/dist/assets/{index-VPWqq2Pg.js → index-C4Tn5NvJ.js} +1 -1
- package/dist/assets/{index-BAH034Ue.js → index-C77h_TXN.js} +1 -1
- package/dist/assets/{index-Dt9UWeWn.js → index-CQDrxQ0j.js} +1 -1
- package/dist/assets/{index-DWOaniGT.js → index-CWMgowgL.js} +1 -1
- package/dist/assets/{index-B1_GXGaP.js → index-Clbi_Yaq.js} +1 -1
- package/dist/assets/{index-B7yXbrLa.js → index-CpTPJo4k.js} +1 -1
- package/dist/assets/{index-CknhX2Vy.css → index-Cx0bsY1w.css} +1 -1
- package/dist/assets/{index-DqzMPAC8.js → index-D1vmG6DS.js} +2 -2
- package/dist/assets/{index-c6If577Q.js → index-D9UKkrr2.js} +1 -1
- package/dist/assets/{index-CB2pnVQG.js → index-DEQvTChO.js} +1 -1
- package/dist/assets/{index-OC46250R.js → index-DKEudB02.js} +205 -197
- package/dist/assets/{index-CSgxTUzD.js → index-DRMm6SNo.js} +1 -1
- package/dist/assets/{index-Bq516OmX.js → index-DoRmcrKM.js} +1 -1
- package/dist/assets/{index-DSU75csX.js → index-lYa_leQE.js} +1 -1
- package/dist/assets/{index-BLu5CX6z.js → index-vmICa5KN.js} +1 -1
- package/dist/assets/{index-uacyUula.js → index-z9bohSQJ.js} +1 -1
- package/dist/assets/infoDiagram-STP46IZ2-CVyrdLc8.js +2 -0
- package/dist/assets/isEmpty-DU_ogP_D.js +1 -0
- package/dist/assets/{journeyDiagram-BIP6EPQ6-BBiFyygf.js → journeyDiagram-BIP6EPQ6-C6EgLP_Q.js} +1 -1
- package/dist/assets/{kanban-definition-6OIFK2YF-DhgA6Nt6.js → kanban-definition-6OIFK2YF-BXzYO1yj.js} +4 -4
- package/dist/assets/layout-jihVw5-i.js +1 -0
- package/dist/assets/linear-C4blANlC.js +1 -0
- package/dist/assets/{links-CbvGxbsJ.js → links-D59GIweI.js} +3 -3
- package/dist/assets/{logs-panel-B9SmTZAW.js → logs-panel-D401qzZh.js} +1 -1
- package/dist/assets/markdown-renderer-Cd9eYyaL.js +263 -0
- package/dist/assets/{agent-panel-DpQ6muj-.css → markdown-renderer-ClyzDMmG.css} +1 -1
- package/dist/assets/mermaid-BEVuRz_O.js +1 -0
- package/dist/assets/{mermaid.core-4nVOEVX3.js → mermaid.core-CaSnaLH0.js} +41 -41
- package/dist/assets/min-DUMu_zeK.js +1 -0
- package/dist/assets/{mindmap-definition-Q6HEUPPD-CVLQNn1q.js → mindmap-definition-Q6HEUPPD-BXUM5MT2.js} +2 -2
- package/dist/assets/{number-overlay-editor-CzRzXLcd.js → number-overlay-editor-4uWXGlPG.js} +1 -1
- package/dist/assets/{outline-panel-uvsS-YEQ.js → outline-panel-DIzkvm2I.js} +1 -1
- package/dist/assets/packages-panel-CJL0MVlj.js +1 -0
- package/dist/assets/{pieDiagram-ADFJNKIX-C5IQ5DBZ.js → pieDiagram-ADFJNKIX-Dxt5PVNo.js} +3 -3
- package/dist/assets/{quadrantDiagram-LMRXKWRM-CFXFnQxx.js → quadrantDiagram-LMRXKWRM-D4pUaA31.js} +1 -1
- package/dist/assets/{react-plotly-mzdv02_Y.js → react-plotly-cJZ0VWBq.js} +1 -1
- package/dist/assets/{requirementDiagram-4UW4RH46-D9bPC89T.js → requirementDiagram-4UW4RH46-DVRTjgas.js} +1 -1
- package/dist/assets/run-page-BUEnMC9w.js +1 -0
- package/dist/assets/sankeyDiagram-GR3RE2ED-CVFnD9C-.js +10 -0
- package/dist/assets/scratchpad-panel-BIgRENkI.js +1 -0
- package/dist/assets/secrets-panel-xY5-V_BD.js +1 -0
- package/dist/assets/{sequenceDiagram-C3RYC4MD-6N7_hY4k.js → sequenceDiagram-C3RYC4MD-_lY4ZN_S.js} +4 -4
- package/dist/assets/{slides-component-EcjC8sDK.js → slides-component-Xjymwj7X.js} +1 -1
- package/dist/assets/snippets-panel-CTPYW41n.js +1 -0
- package/dist/assets/sortBy-BNZKwiq_.js +1 -0
- package/dist/assets/state-C4NiC9tO.js +1 -0
- package/dist/assets/stateDiagram-KXAO66HF-Da0JQWCn.js +1 -0
- package/dist/assets/stateDiagram-v2-UMBNRL4Z-D5lYZOOt.js +1 -0
- package/dist/assets/storage-CMdLzB_c.js +26 -0
- package/dist/assets/terminal-BPwTkXae.js +10 -0
- package/dist/assets/time-Dv5_Ouz_.js +1 -0
- package/dist/assets/{timeline-definition-XQNQX7LJ-BEaynAiY.js → timeline-definition-XQNQX7LJ-Dxh5Zu2e.js} +1 -1
- package/dist/assets/tracing-BCIurUfa.js +2 -0
- package/dist/assets/{tracing-panel-BmuHLPrY.js → tracing-panel-DAzrzNmm.js} +2 -2
- package/dist/assets/{trash-UBqfK4mR.js → trash-Dc6DSjz_.js} +1 -1
- package/dist/assets/{tree-XiEycetl.js → tree-jheoerAX.js} +1 -1
- package/dist/assets/{treemap-75Q7IDZK-CnuVFbBG.js → treemap-75Q7IDZK-IgpxeGaf.js} +21 -21
- package/dist/assets/{ts-tags-CloPe9IY.js → ts-tags-DxCDHihD.js} +1 -1
- package/dist/assets/variable-panel-DYAiLBmF.js +1 -0
- package/dist/assets/{vega-component-DsTH4tuX.js → vega-component-BpfpiPKI.js} +1 -1
- package/dist/assets/{xychartDiagram-6GGTOJPD-Dcz3O-A3.js → xychartDiagram-6GGTOJPD-CmNigJ31.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +8 -4
- package/src/__tests__/mocks.ts +43 -0
- package/src/components/app-config/user-config-form.tsx +32 -0
- package/src/components/chat/acp/__tests__/__snapshots__/prompt.test.ts.snap +55 -23
- package/src/components/chat/acp/__tests__/context-utils.test.ts +222 -0
- package/src/components/chat/acp/__tests__/prompt.test.ts +1 -1
- package/src/components/chat/acp/__tests__/state.test.ts +2 -6
- package/src/components/chat/acp/agent-docs.tsx +33 -6
- package/src/components/chat/acp/agent-panel.css +0 -18
- package/src/components/chat/acp/agent-panel.tsx +397 -72
- package/src/components/chat/acp/agent-selector.tsx +7 -1
- package/src/components/chat/acp/blocks.tsx +40 -10
- package/src/components/chat/acp/common.tsx +10 -2
- package/src/components/chat/acp/context-utils.ts +127 -0
- package/src/components/chat/acp/prompt.ts +34 -10
- package/src/components/chat/acp/state.ts +1 -1
- package/src/components/chat/acp/types.ts +8 -0
- package/src/components/chat/chat-panel.tsx +23 -88
- package/src/components/chat/chat-utils.ts +127 -1
- package/src/components/chat/markdown-renderer.css +39 -0
- package/src/components/chat/markdown-renderer.tsx +7 -38
- package/src/components/chat/tool-call-accordion.tsx +113 -23
- package/src/components/editor/Cell.tsx +6 -0
- package/src/components/editor/actions/name-cell-input.tsx +6 -1
- package/src/components/editor/actions/useCellActionButton.tsx +3 -1
- package/src/components/editor/ai/__tests__/completion-utils.test.ts +178 -1
- package/src/components/editor/ai/add-cell-with-ai.tsx +68 -66
- package/src/components/editor/ai/ai-completion-editor.tsx +29 -26
- package/src/components/editor/ai/completion-handlers.tsx +44 -6
- package/src/components/editor/ai/completion-utils.ts +92 -0
- package/src/components/editor/ai/transport/chat-transport.tsx +36 -0
- package/src/components/editor/cell/StagedAICell.tsx +51 -0
- package/src/components/editor/cell/cell-actions.tsx +2 -1
- package/src/components/terminal/__tests__/state.test.ts +207 -0
- package/src/components/terminal/hooks.ts +41 -0
- package/src/components/terminal/state.ts +75 -0
- package/src/components/terminal/terminal.tsx +334 -13
- package/src/components/terminal/theme.tsx +56 -0
- package/src/core/ai/__tests__/staged-cells.test.ts +356 -0
- package/src/core/ai/staged-cells.ts +208 -0
- package/src/core/cells/cells.ts +1 -1
- package/src/core/codemirror/lsp/federated-lsp.ts +1 -1
- package/src/core/islands/main.ts +2 -2
- package/src/core/kernel/messages.ts +8 -12
- package/src/core/saving/__tests__/filename.test.ts +37 -0
- package/src/core/static/__tests__/download-html.test.ts +43 -1
- package/src/core/websocket/useMarimoWebSocket.tsx +2 -2
- package/src/css/app/Cell.css +11 -0
- package/src/plugins/core/RenderHTML.tsx +36 -2
- package/src/plugins/core/__test__/RenderHTML.test.ts +72 -0
- package/src/plugins/core/registerReactComponent.tsx +28 -0
- package/src/plugins/impl/FileBrowserPlugin.tsx +8 -2
- package/src/stories/cell.stories.tsx +1 -1
- package/src/stories/layout/vertical/one-column.stories.tsx +1 -1
- package/src/utils/__tests__/cell-urls.test.ts +29 -0
- package/src/utils/__tests__/filenames.test.ts +18 -0
- package/src/utils/__tests__/path.test.ts +38 -0
- package/src/utils/__tests__/urls.test.ts +56 -1
- package/src/utils/errors.ts +9 -0
- package/dist/assets/_baseEach-C1FLm7WW.js +0 -1
- package/dist/assets/_baseMap-DBVArUYD.js +0 -1
- package/dist/assets/_baseUniq-Dk7ZPJ3N.js +0 -1
- package/dist/assets/_createAggregator-Bn38fDd3.js +0 -1
- package/dist/assets/agent-panel-COUYnuIK.js +0 -475
- package/dist/assets/architectureDiagram-W76B3OCA-DBzWQKKu.js +0 -36
- package/dist/assets/channel-CjhbjOv4.js +0 -1
- package/dist/assets/chat-panel-BPXKoTnZ.js +0 -7
- package/dist/assets/chat-panel-Brrs_eeH.css +0 -1
- package/dist/assets/classDiagram-KNZD7YFC-DHs5cFzy.js +0 -1
- package/dist/assets/classDiagram-v2-RKCZMP56-DHs5cFzy.js +0 -1
- package/dist/assets/clone-DM1YNjEn.js +0 -1
- package/dist/assets/command-palette-S0bzQp7v.js +0 -1
- package/dist/assets/common-B8U9k2Ly.js +0 -1
- package/dist/assets/cose-bilkent-S5V4N54A-wz1Sfx7j.js +0 -1
- package/dist/assets/dagre-5GWH7T2D-BfpcVBgq.js +0 -4
- package/dist/assets/datasources-panel-DfuURLJw.js +0 -1
- package/dist/assets/diagram-N5W7TBWH-Bf0oqqQh.js +0 -24
- package/dist/assets/diagram-QEK2KX5R-ZTc3qikh.js +0 -43
- package/dist/assets/diagram-S2PKOQOG-tLScBy7Z.js +0 -24
- package/dist/assets/edit-page-DJ8kJZ9w.js +0 -129
- package/dist/assets/file-explorer-panel-CzNUJ63G.js +0 -1
- package/dist/assets/gitGraphDiagram-NY62KEGX-C1t6QtVa.js +0 -65
- package/dist/assets/graph-CssCVWIq.js +0 -1
- package/dist/assets/index-DcCIe7np.js +0 -28
- package/dist/assets/infoDiagram-STP46IZ2-CwiAoz9f.js +0 -2
- package/dist/assets/layout-DpQrxGW-.js +0 -1
- package/dist/assets/linear-NsreOeBF.js +0 -1
- package/dist/assets/mermaid-DSt0r6IQ.js +0 -1
- package/dist/assets/min-D259kI3t.js +0 -1
- package/dist/assets/packages-panel-xMz9W2hW.js +0 -1
- package/dist/assets/run-page-Bb68qdhQ.js +0 -1
- package/dist/assets/sankeyDiagram-GR3RE2ED-BSJOau8E.js +0 -10
- package/dist/assets/scratchpad-panel-BF4BO-U4.js +0 -1
- package/dist/assets/secrets-panel-CdIX44dQ.js +0 -1
- package/dist/assets/snippets-panel-Dco9h0rb.js +0 -1
- package/dist/assets/sortBy-aLGA-PGK.js +0 -1
- package/dist/assets/stateDiagram-KXAO66HF-Bd68WT3b.js +0 -1
- package/dist/assets/stateDiagram-v2-UMBNRL4Z-BXz_GSwb.js +0 -1
- package/dist/assets/storage-CGlP4lCF.js +0 -26
- package/dist/assets/terminal-CxkHubcu.js +0 -9
- package/dist/assets/time-D2nr1UgQ.js +0 -1
- package/dist/assets/tracing-kTqHxa7q.js +0 -2
- package/dist/assets/variable-panel-noTnH-AQ.js +0 -1
|
@@ -10,6 +10,7 @@ import type { ReactCodeMirrorRef } from "@uiw/react-codemirror";
|
|
|
10
10
|
import type { FileUIPart } from "ai";
|
|
11
11
|
import { getAIContextRegistry } from "@/core/ai/context/context";
|
|
12
12
|
import { getCodes } from "@/core/codemirror/copilot/getCodes";
|
|
13
|
+
import type { LanguageAdapterType } from "@/core/codemirror/language/types";
|
|
13
14
|
import type { AiCompletionRequest } from "@/core/network/types";
|
|
14
15
|
import { store } from "@/core/state/jotai";
|
|
15
16
|
import { Logger } from "@/utils/Logger";
|
|
@@ -145,3 +146,94 @@ export function addContextCompletion(
|
|
|
145
146
|
startCompletion(inputRef.current.view);
|
|
146
147
|
}
|
|
147
148
|
}
|
|
149
|
+
|
|
150
|
+
export interface AiCompletion {
|
|
151
|
+
language: LanguageAdapterType;
|
|
152
|
+
code: string;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Extracts code blocks (delimited by triple backticks) and their language ("python", "sql", "markdown").
|
|
157
|
+
* Defaults to "python" if no language is specified or no code blocks are found.
|
|
158
|
+
* Returns an array of AiCompletion objects.
|
|
159
|
+
*/
|
|
160
|
+
export function codeToCells(code: string): AiCompletion[] {
|
|
161
|
+
if (code.trim().length === 0) {
|
|
162
|
+
return [];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// If there are no backticks, assume code is in 1 cell and python
|
|
166
|
+
if (!code.includes("```")) {
|
|
167
|
+
return [{ language: "python", code: code }];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// If code has opening backticks, get the code after it
|
|
171
|
+
const cells: AiCompletion[] = [];
|
|
172
|
+
let start = 0;
|
|
173
|
+
|
|
174
|
+
let openIndex = code.indexOf("```", start);
|
|
175
|
+
while (openIndex !== -1) {
|
|
176
|
+
const newlineIndex = code.indexOf("\n", openIndex);
|
|
177
|
+
if (newlineIndex === -1) {
|
|
178
|
+
// If there's no newline after opening backticks, treat everything after as code
|
|
179
|
+
const remaining = code.slice(openIndex + 3);
|
|
180
|
+
const firstSpace = remaining.indexOf(" ");
|
|
181
|
+
const language =
|
|
182
|
+
firstSpace === -1 ? remaining : remaining.slice(0, firstSpace);
|
|
183
|
+
const finalLanguage =
|
|
184
|
+
language === "markdown"
|
|
185
|
+
? "markdown"
|
|
186
|
+
: language === "sql"
|
|
187
|
+
? "sql"
|
|
188
|
+
: "python";
|
|
189
|
+
// Extract code after the language identifier
|
|
190
|
+
const codeContent =
|
|
191
|
+
firstSpace === -1 ? "" : remaining.slice(firstSpace + 1);
|
|
192
|
+
if (codeContent.trim()) {
|
|
193
|
+
cells.push({ language: finalLanguage, code: codeContent.trim() });
|
|
194
|
+
}
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
let language = code.slice(openIndex + 3, newlineIndex).trim() || "";
|
|
199
|
+
language =
|
|
200
|
+
language === "markdown"
|
|
201
|
+
? "markdown"
|
|
202
|
+
: language === "sql"
|
|
203
|
+
? "sql"
|
|
204
|
+
: "python";
|
|
205
|
+
const codeStart = newlineIndex + 1;
|
|
206
|
+
|
|
207
|
+
const closeIndex = code.indexOf("```", codeStart);
|
|
208
|
+
if (closeIndex === -1) {
|
|
209
|
+
// If there's no closing backticks, treat everything after the opening as code
|
|
210
|
+
const codeContent = code.slice(codeStart).replace(/\n+$/, "");
|
|
211
|
+
if (codeContent.trim()) {
|
|
212
|
+
cells.push({
|
|
213
|
+
language: language as LanguageAdapterType,
|
|
214
|
+
code: codeContent,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Remove trailing newlines
|
|
221
|
+
const codeContent = code.slice(codeStart, closeIndex).replace(/\n+$/, "");
|
|
222
|
+
if (codeContent.trim()) {
|
|
223
|
+
cells.push({
|
|
224
|
+
language: language as LanguageAdapterType,
|
|
225
|
+
code: codeContent,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
start = closeIndex + 3;
|
|
230
|
+
openIndex = code.indexOf("```", start);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// If no cells found, assume code is in 1 cell and python
|
|
234
|
+
if (cells.length === 0) {
|
|
235
|
+
cells.push({ language: "python", code: code });
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return cells;
|
|
239
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
DefaultChatTransport,
|
|
5
|
+
type HttpChatTransportInitOptions,
|
|
6
|
+
type UIMessage,
|
|
7
|
+
type UIMessageChunk,
|
|
8
|
+
} from "ai";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Thin wrapper around the DefaultChatTransport that calls a callback when a chunk is received.
|
|
12
|
+
*/
|
|
13
|
+
export class StreamingChunkTransport<
|
|
14
|
+
UI_MESSAGE extends UIMessage,
|
|
15
|
+
> extends DefaultChatTransport<UI_MESSAGE> {
|
|
16
|
+
constructor(
|
|
17
|
+
options: HttpChatTransportInitOptions<UI_MESSAGE> = {},
|
|
18
|
+
private onChunkReceived: (chunk: UIMessageChunk) => void,
|
|
19
|
+
) {
|
|
20
|
+
super(options);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
protected override processResponseStream(
|
|
24
|
+
stream: ReadableStream<Uint8Array>,
|
|
25
|
+
): ReadableStream<UIMessageChunk> {
|
|
26
|
+
const onChunkReceived = this.onChunkReceived;
|
|
27
|
+
return super.processResponseStream(stream).pipeThrough(
|
|
28
|
+
new TransformStream<UIMessageChunk, UIMessageChunk>({
|
|
29
|
+
async transform(chunk, controller) {
|
|
30
|
+
onChunkReceived(chunk);
|
|
31
|
+
controller.enqueue(chunk);
|
|
32
|
+
},
|
|
33
|
+
}),
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { useAtomValue, useStore } from "jotai";
|
|
4
|
+
import { stagedAICellsAtom, useStagedCells } from "@/core/ai/staged-cells";
|
|
5
|
+
import type { CellId } from "@/core/cells/ids";
|
|
6
|
+
import { cn } from "@/utils/cn";
|
|
7
|
+
import { CompletionActionsCellFooter } from "../ai/completion-handlers";
|
|
8
|
+
|
|
9
|
+
export const StagedAICellBackground: React.FC<{
|
|
10
|
+
cellId: CellId;
|
|
11
|
+
className?: string;
|
|
12
|
+
}> = ({ cellId, className }) => {
|
|
13
|
+
const stagedAICells = useAtomValue(stagedAICellsAtom);
|
|
14
|
+
|
|
15
|
+
if (!stagedAICells.has(cellId)) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return <div className={cn("mo-ai-generated-cell", className)} />;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const StagedAICellFooter: React.FC<{ cellId: CellId }> = ({
|
|
23
|
+
cellId,
|
|
24
|
+
}) => {
|
|
25
|
+
const store = useStore();
|
|
26
|
+
const stagedAICells = useAtomValue(stagedAICellsAtom);
|
|
27
|
+
const { deleteStagedCell, removeStagedCell } = useStagedCells(store);
|
|
28
|
+
|
|
29
|
+
if (!stagedAICells.has(cellId)) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const handleAcceptCompletion = () => {
|
|
34
|
+
removeStagedCell(cellId);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const handleDeclineCompletion = () => {
|
|
38
|
+
deleteStagedCell(cellId);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div className="flex items-center justify-end gap-1.5 w-full pb-1 pt-2">
|
|
43
|
+
<CompletionActionsCellFooter
|
|
44
|
+
isLoading={false}
|
|
45
|
+
onAccept={handleAcceptCompletion}
|
|
46
|
+
onDecline={handleDeclineCompletion}
|
|
47
|
+
size="xs"
|
|
48
|
+
/>
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
};
|
|
@@ -56,7 +56,8 @@ const CellActionsDropdownInternal = (
|
|
|
56
56
|
ref: React.Ref<CellActionsDropdownHandle>,
|
|
57
57
|
) => {
|
|
58
58
|
const [open, setOpen] = useState(false);
|
|
59
|
-
const
|
|
59
|
+
const closePopover = () => setOpen(false);
|
|
60
|
+
const actions = useCellActionButtons({ cell: props, closePopover });
|
|
60
61
|
|
|
61
62
|
// store the last focused element so we can restore it when the popover closes
|
|
62
63
|
const restoreFocus = useRestoreFocus();
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { exportedForTesting } from "../state";
|
|
5
|
+
|
|
6
|
+
const { reducer, initialState } = exportedForTesting;
|
|
7
|
+
|
|
8
|
+
describe("terminal state", () => {
|
|
9
|
+
describe("initialState", () => {
|
|
10
|
+
it("should return initial state with empty pendingCommands and isReady false", () => {
|
|
11
|
+
const state = initialState();
|
|
12
|
+
expect(state).toEqual({
|
|
13
|
+
pendingCommands: [],
|
|
14
|
+
isReady: false,
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe("reducer", () => {
|
|
20
|
+
it("should add a command to pendingCommands", () => {
|
|
21
|
+
const state = initialState();
|
|
22
|
+
const text = "ls -la";
|
|
23
|
+
|
|
24
|
+
const newState = reducer(state, { type: "addCommand", payload: text });
|
|
25
|
+
|
|
26
|
+
expect(newState.pendingCommands).toHaveLength(1);
|
|
27
|
+
expect(newState.pendingCommands[0]).toMatchObject({
|
|
28
|
+
text: "ls -la",
|
|
29
|
+
timestamp: expect.any(Number),
|
|
30
|
+
});
|
|
31
|
+
expect(newState.pendingCommands[0].id).toBeDefined();
|
|
32
|
+
expect(newState.isReady).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("should add multiple commands to pendingCommands", () => {
|
|
36
|
+
const state = initialState();
|
|
37
|
+
|
|
38
|
+
let newState = reducer(state, { type: "addCommand", payload: "ls -la" });
|
|
39
|
+
newState = reducer(newState, { type: "addCommand", payload: "cd /home" });
|
|
40
|
+
newState = reducer(newState, { type: "addCommand", payload: "pwd" });
|
|
41
|
+
|
|
42
|
+
expect(newState.pendingCommands).toHaveLength(3);
|
|
43
|
+
expect(newState.pendingCommands[0].text).toBe("ls -la");
|
|
44
|
+
expect(newState.pendingCommands[1].text).toBe("cd /home");
|
|
45
|
+
expect(newState.pendingCommands[2].text).toBe("pwd");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should remove a command by id", () => {
|
|
49
|
+
const state = initialState();
|
|
50
|
+
|
|
51
|
+
let newState = reducer(state, { type: "addCommand", payload: "ls -la" });
|
|
52
|
+
newState = reducer(newState, { type: "addCommand", payload: "cd /home" });
|
|
53
|
+
newState = reducer(newState, { type: "addCommand", payload: "pwd" });
|
|
54
|
+
|
|
55
|
+
const commandToRemove = newState.pendingCommands[1];
|
|
56
|
+
newState = reducer(newState, {
|
|
57
|
+
type: "removeCommand",
|
|
58
|
+
payload: commandToRemove.id,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
expect(newState.pendingCommands).toHaveLength(2);
|
|
62
|
+
expect(newState.pendingCommands[0].text).toBe("ls -la");
|
|
63
|
+
expect(newState.pendingCommands[1].text).toBe("pwd");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("should not remove anything if command id does not exist", () => {
|
|
67
|
+
const state = initialState();
|
|
68
|
+
|
|
69
|
+
let newState = reducer(state, { type: "addCommand", payload: "ls -la" });
|
|
70
|
+
newState = reducer(newState, { type: "addCommand", payload: "cd /home" });
|
|
71
|
+
|
|
72
|
+
const originalLength = newState.pendingCommands.length;
|
|
73
|
+
newState = reducer(newState, {
|
|
74
|
+
type: "removeCommand",
|
|
75
|
+
payload: "non-existent-id",
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
expect(newState.pendingCommands).toHaveLength(originalLength);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("should set isReady to true", () => {
|
|
82
|
+
const state = initialState();
|
|
83
|
+
|
|
84
|
+
const newState = reducer(state, { type: "setReady", payload: true });
|
|
85
|
+
|
|
86
|
+
expect(newState.isReady).toBe(true);
|
|
87
|
+
expect(newState.pendingCommands).toEqual([]);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("should set isReady to false", () => {
|
|
91
|
+
const state = { ...initialState(), isReady: true };
|
|
92
|
+
|
|
93
|
+
const newState = reducer(state, { type: "setReady", payload: false });
|
|
94
|
+
|
|
95
|
+
expect(newState.isReady).toBe(false);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("should clear all pending commands", () => {
|
|
99
|
+
const state = initialState();
|
|
100
|
+
|
|
101
|
+
let newState = reducer(state, { type: "addCommand", payload: "ls -la" });
|
|
102
|
+
newState = reducer(newState, { type: "addCommand", payload: "cd /home" });
|
|
103
|
+
newState = reducer(newState, { type: "addCommand", payload: "pwd" });
|
|
104
|
+
|
|
105
|
+
expect(newState.pendingCommands).toHaveLength(3);
|
|
106
|
+
|
|
107
|
+
newState = reducer(newState, {
|
|
108
|
+
type: "clearCommands",
|
|
109
|
+
payload: undefined,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
expect(newState.pendingCommands).toHaveLength(0);
|
|
113
|
+
expect(newState.isReady).toBe(false);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("should preserve other state when adding commands", () => {
|
|
117
|
+
const state = { ...initialState(), isReady: true };
|
|
118
|
+
|
|
119
|
+
const newState = reducer(state, {
|
|
120
|
+
type: "addCommand",
|
|
121
|
+
payload: "ls -la",
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
expect(newState.isReady).toBe(true);
|
|
125
|
+
expect(newState.pendingCommands).toHaveLength(1);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("should preserve other state when removing commands", () => {
|
|
129
|
+
const state = { ...initialState(), isReady: true };
|
|
130
|
+
|
|
131
|
+
let newState = reducer(state, { type: "addCommand", payload: "ls -la" });
|
|
132
|
+
newState = reducer(newState, {
|
|
133
|
+
type: "removeCommand",
|
|
134
|
+
payload: newState.pendingCommands[0].id,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
expect(newState.isReady).toBe(true);
|
|
138
|
+
expect(newState.pendingCommands).toHaveLength(0);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("should preserve other state when setting ready", () => {
|
|
142
|
+
const state = initialState();
|
|
143
|
+
|
|
144
|
+
let newState = reducer(state, { type: "addCommand", payload: "ls -la" });
|
|
145
|
+
newState = reducer(newState, { type: "addCommand", payload: "cd /home" });
|
|
146
|
+
newState = reducer(newState, { type: "setReady", payload: true });
|
|
147
|
+
|
|
148
|
+
expect(newState.isReady).toBe(true);
|
|
149
|
+
expect(newState.pendingCommands).toHaveLength(2);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("should preserve other state when clearing commands", () => {
|
|
153
|
+
const state = { ...initialState(), isReady: true };
|
|
154
|
+
|
|
155
|
+
let newState = reducer(state, { type: "addCommand", payload: "ls -la" });
|
|
156
|
+
newState = reducer(newState, {
|
|
157
|
+
type: "clearCommands",
|
|
158
|
+
payload: undefined,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
expect(newState.isReady).toBe(true);
|
|
162
|
+
expect(newState.pendingCommands).toHaveLength(0);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe("command properties", () => {
|
|
167
|
+
it("should generate unique ids for commands", () => {
|
|
168
|
+
const state = initialState();
|
|
169
|
+
|
|
170
|
+
let newState = reducer(state, {
|
|
171
|
+
type: "addCommand",
|
|
172
|
+
payload: "command1",
|
|
173
|
+
});
|
|
174
|
+
newState = reducer(newState, { type: "addCommand", payload: "command2" });
|
|
175
|
+
|
|
176
|
+
const ids = newState.pendingCommands.map((cmd) => cmd.id);
|
|
177
|
+
expect(ids[0]).not.toBe(ids[1]);
|
|
178
|
+
expect(ids[0]).toBeDefined();
|
|
179
|
+
expect(ids[1]).toBeDefined();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("should set timestamp when adding commands", () => {
|
|
183
|
+
const state = initialState();
|
|
184
|
+
const beforeTime = Date.now();
|
|
185
|
+
|
|
186
|
+
const newState = reducer(state, {
|
|
187
|
+
type: "addCommand",
|
|
188
|
+
payload: "ls -la",
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const afterTime = Date.now();
|
|
192
|
+
const command = newState.pendingCommands[0];
|
|
193
|
+
|
|
194
|
+
expect(command.timestamp).toBeGreaterThanOrEqual(beforeTime);
|
|
195
|
+
expect(command.timestamp).toBeLessThanOrEqual(afterTime);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("should preserve text when adding commands", () => {
|
|
199
|
+
const state = initialState();
|
|
200
|
+
const text = "echo 'Hello World'";
|
|
201
|
+
|
|
202
|
+
const newState = reducer(state, { type: "addCommand", payload: text });
|
|
203
|
+
|
|
204
|
+
expect(newState.pendingCommands[0].text).toBe(text);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { useChromeActions } from "../editor/chrome/state";
|
|
4
|
+
import { useTerminalActions } from "./state";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Hook for sending commands to the terminal programmatically.
|
|
8
|
+
* This will:
|
|
9
|
+
* 1. Open the terminal if it's not already open
|
|
10
|
+
* 2. Wait for the terminal to be connected
|
|
11
|
+
* 3. Send the command text to the terminal
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* function CopyButton({ command }: { command: string }) {
|
|
16
|
+
* const { sendCommand } = useTerminalCommands();
|
|
17
|
+
*
|
|
18
|
+
* return (
|
|
19
|
+
* <button onClick={() => sendCommand(command)}>
|
|
20
|
+
* Copy to Terminal
|
|
21
|
+
* </button>
|
|
22
|
+
* );
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export function useTerminalCommands() {
|
|
27
|
+
const { addCommand } = useTerminalActions();
|
|
28
|
+
const { setIsTerminalOpen } = useChromeActions();
|
|
29
|
+
|
|
30
|
+
const sendCommand = (text: string) => {
|
|
31
|
+
// First, ensure the terminal is open
|
|
32
|
+
setIsTerminalOpen(true);
|
|
33
|
+
|
|
34
|
+
// Add the command to the queue
|
|
35
|
+
addCommand(text);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
sendCommand,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { useAtomValue } from "jotai";
|
|
4
|
+
import { createReducerAndAtoms } from "@/utils/createReducer";
|
|
5
|
+
import { generateUUID } from "@/utils/uuid";
|
|
6
|
+
|
|
7
|
+
export interface TerminalCommand {
|
|
8
|
+
id: string;
|
|
9
|
+
text: string;
|
|
10
|
+
timestamp: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface TerminalState {
|
|
14
|
+
pendingCommands: TerminalCommand[];
|
|
15
|
+
isReady: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function initialState(): TerminalState {
|
|
19
|
+
return {
|
|
20
|
+
pendingCommands: [],
|
|
21
|
+
isReady: false,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const {
|
|
26
|
+
reducer,
|
|
27
|
+
createActions,
|
|
28
|
+
valueAtom: terminalStateAtom,
|
|
29
|
+
useActions,
|
|
30
|
+
} = createReducerAndAtoms(initialState, {
|
|
31
|
+
addCommand: (state, text: string) => {
|
|
32
|
+
const command: TerminalCommand = {
|
|
33
|
+
id: generateUUID(),
|
|
34
|
+
text,
|
|
35
|
+
timestamp: Date.now(),
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
...state,
|
|
40
|
+
pendingCommands: [...state.pendingCommands, command],
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
removeCommand: (state, commandId: string) => ({
|
|
44
|
+
...state,
|
|
45
|
+
pendingCommands: state.pendingCommands.filter(
|
|
46
|
+
(cmd) => cmd.id !== commandId,
|
|
47
|
+
),
|
|
48
|
+
}),
|
|
49
|
+
setReady: (state, isReady: boolean) => ({
|
|
50
|
+
...state,
|
|
51
|
+
isReady,
|
|
52
|
+
}),
|
|
53
|
+
clearCommands: (state) => ({
|
|
54
|
+
...state,
|
|
55
|
+
pendingCommands: [],
|
|
56
|
+
}),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* React hook to get the terminal state.
|
|
61
|
+
*/
|
|
62
|
+
export const useTerminalState = () => useAtomValue(terminalStateAtom);
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* React hook to get the terminal actions.
|
|
66
|
+
*/
|
|
67
|
+
export function useTerminalActions() {
|
|
68
|
+
return useActions();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const exportedForTesting = {
|
|
72
|
+
reducer,
|
|
73
|
+
createActions,
|
|
74
|
+
initialState,
|
|
75
|
+
};
|