@marimo-team/frontend 0.25.1-dev25 → 0.25.1-dev27
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-B900B7VD.js → ConnectedDataExplorerComponent-CEk030Wr.js} +1 -1
- package/dist/assets/{ErrorBoundary-DTCcdbpf.js → ErrorBoundary-C7U9phn8.js} +2 -2
- package/dist/assets/{LazyAnyLanguageCodeMirror-B77C3r93.js → LazyAnyLanguageCodeMirror-DRPEmboS.js} +2 -2
- package/dist/assets/{RunButton-DmybFhDa.js → RunButton-DaEo2YLw.js} +8 -8
- package/dist/assets/{abnfDiagram-VCTEODGH-D706b5Vh.js → abnfDiagram-VCTEODGH-BPICToYh.js} +1 -1
- package/dist/assets/add-cell-with-ai-Szu4h2xr.js +76 -0
- package/dist/assets/{agent-panel-Be9y8MKj.js → agent-panel-DOi8FFCM.js} +4 -4
- package/dist/assets/ai-model-dropdown-8njYygAy.js +5 -0
- package/dist/assets/{any-language-editor-CkKOXI8j.js → any-language-editor-BO2KQgqe.js} +1 -1
- package/dist/assets/app-config-button-DPOpo6fD.js +1 -0
- package/dist/assets/{architectureDiagram-5GKGNRK7-BgABE09T.js → architectureDiagram-5GKGNRK7-D0Nj4LZV.js} +1 -1
- package/dist/assets/{blockDiagram-I7D4REHJ-CG0YONwW.js → blockDiagram-I7D4REHJ-DGl4_x69.js} +1 -1
- package/dist/assets/{cache-panel-B-BLtw6A.js → cache-panel-PIZV9skZ.js} +1 -1
- package/dist/assets/{chat-display-BFqI78n0.js → chat-display-CrQhSDTe.js} +1 -1
- package/dist/assets/chat-panel-rZcx6i0J.js +4 -0
- package/dist/assets/{chat-ui-Dl3Zqjqy.js → chat-ui-Cq9S1Vfd.js} +4 -4
- package/dist/assets/{chunk-IMKFNOWR-DK99hdjq.js → chunk-IMKFNOWR-Cf41dfnh.js} +1 -1
- package/dist/assets/{chunk-SHT3W25Y-Blh2fIUo.js → chunk-SHT3W25Y-C5Pw7o8Z.js} +1 -1
- package/dist/assets/{chunk-SVP7TREG-Dpgf3pxv.js → chunk-SVP7TREG-NUl6G0sX.js} +1 -1
- package/dist/assets/{chunk-TICWLB2K-_RDWfgUY.js → chunk-TICWLB2K-nULmAXZ1.js} +1 -1
- package/dist/assets/{classDiagram-ZZMXUADV-BKpaIKnT.js → classDiagram-ZZMXUADV--OufHXVT.js} +1 -1
- package/dist/assets/{classDiagram-v2-VYDZK3BY-BKpaIKnT.js → classDiagram-v2-VYDZK3BY--OufHXVT.js} +1 -1
- package/dist/assets/{code-visibility-DJUsk6Cj.js → code-visibility-Dbkqo9JU.js} +1 -1
- package/dist/assets/{command-palette-2BlqlsLm.js → command-palette-CTE4dAh0.js} +1 -1
- package/dist/assets/{common-pDENtzby.js → common-Br8QwTYp.js} +1 -1
- package/dist/assets/{components-Bw1NdtYp.js → components-BazbSuIT.js} +1 -1
- package/dist/assets/{components-DnklDlz5.js → components-Dr8S9a39.js} +1 -1
- package/dist/assets/{cynefinDiagram-5FMLGOSQ-IK_6lAzx.js → cynefinDiagram-5FMLGOSQ-BnDBzYE4.js} +1 -1
- package/dist/assets/dagre-BjMCWsBB.js +1 -0
- package/dist/assets/{dagre-GXQ25YYZ-Bfxohvj6.js → dagre-GXQ25YYZ-C6HjhdC6.js} +1 -1
- package/dist/assets/{data-grid-overlay-editor-BKs0QVKl.js → data-grid-overlay-editor-BhYanmB4.js} +1 -1
- package/dist/assets/{dependency-graph-panel-DELjFGsZ.js → dependency-graph-panel-KhePinnC.js} +3 -3
- package/dist/assets/{diagram-S7CK7UJ4-l9DRv-IW.js → diagram-S7CK7UJ4-puXb_Rtq.js} +1 -1
- package/dist/assets/{diagram-UQ7AKVKN-B4ELQoq1.js → diagram-UQ7AKVKN-BRNrAZmm.js} +1 -1
- package/dist/assets/{diagram-VX7I27RA-MILYBjDC.js → diagram-VX7I27RA-FYgcY0ll.js} +1 -1
- package/dist/assets/{diagram-Z3DM3KII-qCfcU8HX.js → diagram-Z3DM3KII-0a9IJqv0.js} +1 -1
- package/dist/assets/{documentation-panel--e0UwqDY.js → documentation-panel-CVIeqh1b.js} +1 -1
- package/dist/assets/{ebnfDiagram-PWID7BFC-Cx1BTyML.js → ebnfDiagram-PWID7BFC-mIbU0Xap.js} +1 -1
- package/dist/assets/{edit-page-Dfv_tiSi.js → edit-page-DPcMmMvJ.js} +49 -51
- package/dist/assets/{erDiagram-RLTQ6QDP-CsbQxnLY.js → erDiagram-RLTQ6QDP-Cd4GM-oc.js} +1 -1
- package/dist/assets/{error-panel-85yZnBFY.js → error-panel-BQh3dJvY.js} +1 -1
- package/dist/assets/{feedback-button-uqgaXA0V.js → feedback-button-Bv9N5kfQ.js} +2 -2
- package/dist/assets/{file-explorer-panel-yoPAiHK9.js → file-explorer-panel-Cw-riLBk.js} +1 -1
- package/dist/assets/file-name-input-45TcG8Ds.js +3 -0
- package/dist/assets/filename-CVTfpc8L.js +1 -0
- package/dist/assets/{flowDiagram-HODETNUW-2RSj-EBu.js → flowDiagram-HODETNUW-DoAP7Em-.js} +1 -1
- package/dist/assets/{gallery-page-BHRnnj1G.js → gallery-page-DF-L2Hg7.js} +1 -1
- package/dist/assets/glide-data-editor-CeEFyCyf.js +131 -0
- package/dist/assets/{graphlib-7pGvEFCC.js → graphlib-D4-sEAEu.js} +1 -1
- package/dist/assets/has-BZiXT9H1.js +1 -0
- package/dist/assets/{home-page-Be--NZaJ.js → home-page-D7-nxzVU.js} +2 -2
- package/dist/assets/index-CwgxYBE_.css +2 -0
- package/dist/assets/index-DZqTrlKL.js +62 -0
- package/dist/assets/{infoDiagram-27XIBGKW-DWQXjlZK.js → infoDiagram-27XIBGKW-DisP8YlO.js} +1 -1
- package/dist/assets/{ishikawaDiagram-5VMMS53U-nurNAy7a.js → ishikawaDiagram-5VMMS53U-DrZXbBhn.js} +1 -1
- package/dist/assets/{kanban-definition-UXKFOSKX-DCKI2GRE.js → kanban-definition-UXKFOSKX-DjYaQ7cZ.js} +1 -1
- package/dist/assets/{logs-panel-17Mm6W-I.js → logs-panel-C0JyIjb-.js} +1 -1
- package/dist/assets/{mermaid-BrTpWrcJ.js → mermaid-Cf8lqXqK.js} +4 -4
- package/dist/assets/{mindmap-definition-YA3MSWOX-B8_R5rhs.js → mindmap-definition-YA3MSWOX-BfdtRUZ5.js} +1 -1
- package/dist/assets/{number-overlay-editor-CA0eKP7Q.js → number-overlay-editor-gJYkb7UJ.js} +1 -1
- package/dist/assets/{outline-panel-qla5Heuu.js → outline-panel-PsjDONzX.js} +1 -1
- package/dist/assets/packages-panel-B75bdIo0.js +1 -0
- package/dist/assets/{panels-K0JehZOa.js → panels-C4MoKhQw.js} +9 -9
- package/dist/assets/{pegDiagram-XKGWAZYB-BaQ4Zrey.js → pegDiagram-XKGWAZYB-CYaHdB2n.js} +1 -1
- package/dist/assets/{pieDiagram-E7YTZNPT-C73a2qkL.js → pieDiagram-E7YTZNPT-CVS0Hdon.js} +1 -1
- package/dist/assets/{process-output-BHR3aOKW.js → process-output-BBoXTTZf.js} +1 -1
- package/dist/assets/{railroadDiagram-O6MQD6OU-CLLA0Dpz.js → railroadDiagram-O6MQD6OU-CmjNDTQ_.js} +1 -1
- package/dist/assets/{requirementDiagram-BXWQKSXE-CfpgVLOa.js → requirementDiagram-BXWQKSXE-pveEaBVb.js} +1 -1
- package/dist/assets/{reveal-component-CsbfwgVo.js → reveal-component-BQrEwlGW.js} +3 -3
- package/dist/assets/run-page-ByRNiVEw.js +1 -0
- package/dist/assets/{scratchpad-panel-Bd-qn9UM.js → scratchpad-panel-DSo4DfEm.js} +1 -1
- package/dist/assets/{secrets-panel-DELSeOUU.js → secrets-panel-Twujk_1B.js} +1 -1
- package/dist/assets/{sequenceDiagram-WJ2MYXX4-DQwJ-9W3.js → sequenceDiagram-WJ2MYXX4-yG9WGeGU.js} +1 -1
- package/dist/assets/{session-panel-CXpG5z1x.js → session-panel-Cxg1jbpz.js} +1 -1
- package/dist/assets/{SelectionIndicator-DLJBcP4e.js → shell-BSnvn9t7.js} +1 -1
- package/dist/assets/{snippets-panel-7lMPYaMd.js → snippets-panel-DOmIRweD.js} +1 -1
- package/dist/assets/{stateDiagram-D77RDMKH-DGtwt1IQ.js → stateDiagram-D77RDMKH-HwpM4BWS.js} +1 -1
- package/dist/assets/{stateDiagram-v2-MP3YSRHH-CmnU38wU.js → stateDiagram-v2-MP3YSRHH-Cg5o2AFK.js} +1 -1
- package/dist/assets/{swimlanes-42K2YHIH-YwKe982E.js → swimlanes-42K2YHIH-vnyrUnUj.js} +1 -1
- package/dist/assets/{swimlanesDiagram-VR7AAH4N-BMHziEE8.js → swimlanesDiagram-VR7AAH4N-CDSAC7kd.js} +1 -1
- package/dist/assets/{swiper-component-BBFMKUfK.js → swiper-component-COoqfICh.js} +1 -1
- package/dist/assets/{terminal-CeYepuEI.js → terminal-BvGa3-Zb.js} +1 -1
- package/dist/assets/{timeline-definition-24CTP7MA-BOvihrMx.js → timeline-definition-24CTP7MA-Ci_0VG5-.js} +1 -1
- package/dist/assets/{tracing-CmfAqfDs.js → tracing-D5Z2olGj.js} +1 -1
- package/dist/assets/tracing-panel-DAEyvbPv.js +2 -0
- package/dist/assets/{useCellActionButton-C5p6Bj3G.js → useCellActionButton-C9tGr-T8.js} +1 -1
- package/dist/assets/{vega-component-BYAvxR91.js → vega-component-F6JI94_G.js} +1 -1
- package/dist/assets/{vega-embed-container-Bp3Y84i3.js → vega-embed-container-D5fybnJd.js} +1 -1
- package/dist/assets/{vennDiagram-4TSXK5OY-iTZF9Ucp.js → vennDiagram-4TSXK5OY-5Pnagf9w.js} +1 -1
- package/dist/assets/{wardleyDiagram-VM6X3IG4-iXJdkC6P.js → wardleyDiagram-VM6X3IG4-rIaxW9T4.js} +1 -1
- package/dist/assets/{xychartDiagram-S5SC5T6Z-D69LNZIO.js → xychartDiagram-S5SC5T6Z-78iNa87v.js} +1 -1
- package/dist/index.html +5 -4
- package/package.json +1 -1
- package/src/components/app-config/ai-config.tsx +2 -0
- package/src/components/chat/chat-panel.tsx +19 -8
- package/src/components/editor/__tests__/package-alert.test.tsx +140 -0
- package/src/components/editor/actions/__tests__/pair-with-agent.test.tsx +107 -0
- package/src/components/editor/actions/pair-with-agent.tsx +119 -0
- package/src/components/editor/actions/useNotebookActions.tsx +2 -1
- package/src/components/editor/ai/add-cell-with-ai.tsx +2 -0
- package/src/components/editor/package-alert.tsx +20 -7
- package/dist/assets/add-cell-with-ai-CijhDTDq.js +0 -76
- package/dist/assets/ai-model-dropdown-CRugDEWY.js +0 -3
- package/dist/assets/app-config-button-viNhoFwC.js +0 -1
- package/dist/assets/chat-panel-DPDCLEor.js +0 -4
- package/dist/assets/dagre-D6Z5qgyX.js +0 -1
- package/dist/assets/file-name-input-FJVbG7HS.js +0 -3
- package/dist/assets/glide-data-editor-WIf8uEqW.js +0 -131
- package/dist/assets/has-BmfwILMr.js +0 -1
- package/dist/assets/index--GdsJp-T.js +0 -62
- package/dist/assets/index-DWEk4MKV.css +0 -2
- package/dist/assets/packages-panel-DQpt9t_X.js +0 -1
- package/dist/assets/run-page-DpvoEPLU.js +0 -1
- package/dist/assets/tracing-panel-bFquffKa.js +0 -2
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
4
|
+
import { createStore, Provider } from "jotai";
|
|
5
|
+
import { expect, it, vi } from "vitest";
|
|
6
|
+
import { MockRequestClient } from "@/__mocks__/requests";
|
|
7
|
+
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
8
|
+
import { alertAtom } from "@/core/alerts/state";
|
|
9
|
+
import {
|
|
10
|
+
defaultUserConfig,
|
|
11
|
+
type PackageManagerName,
|
|
12
|
+
} from "@/core/config/config-schema";
|
|
13
|
+
import { userConfigAtom } from "@/core/config/config";
|
|
14
|
+
import { requestClientAtom } from "@/core/network/requests";
|
|
15
|
+
import { sandboxAtom } from "@/core/packages/sandbox-state";
|
|
16
|
+
import { PackageAlert } from "../package-alert";
|
|
17
|
+
|
|
18
|
+
vi.mock("@/hooks/usePackageMetadata", () => ({
|
|
19
|
+
usePackageMetadata: () => ({
|
|
20
|
+
data: { versions: ["1.2.3"], extras: [] },
|
|
21
|
+
error: null,
|
|
22
|
+
isPending: false,
|
|
23
|
+
}),
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
function mountPackageAlert({
|
|
27
|
+
configuredManager,
|
|
28
|
+
sandboxBackend,
|
|
29
|
+
source = "kernel",
|
|
30
|
+
}: {
|
|
31
|
+
configuredManager: PackageManagerName;
|
|
32
|
+
sandboxBackend: "uv" | "pixi" | null;
|
|
33
|
+
source?: "kernel" | "server";
|
|
34
|
+
}) {
|
|
35
|
+
const store = createStore();
|
|
36
|
+
const client = MockRequestClient.create();
|
|
37
|
+
const config = defaultUserConfig();
|
|
38
|
+
store.set(userConfigAtom, {
|
|
39
|
+
...config,
|
|
40
|
+
package_management: { manager: configuredManager },
|
|
41
|
+
});
|
|
42
|
+
store.set(requestClientAtom, client);
|
|
43
|
+
store.set(
|
|
44
|
+
sandboxAtom,
|
|
45
|
+
sandboxBackend
|
|
46
|
+
? {
|
|
47
|
+
backend: sandboxBackend,
|
|
48
|
+
manifest: "",
|
|
49
|
+
filename: "notebook.py",
|
|
50
|
+
}
|
|
51
|
+
: null,
|
|
52
|
+
);
|
|
53
|
+
store.set(alertAtom, {
|
|
54
|
+
...store.get(alertAtom),
|
|
55
|
+
packageAlert: {
|
|
56
|
+
id: "missing-package",
|
|
57
|
+
kind: "missing",
|
|
58
|
+
packages: ["polars"],
|
|
59
|
+
isolated: true,
|
|
60
|
+
source,
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
render(
|
|
64
|
+
<Provider store={store}>
|
|
65
|
+
<TooltipProvider>
|
|
66
|
+
<PackageAlert />
|
|
67
|
+
</TooltipProvider>
|
|
68
|
+
</Provider>,
|
|
69
|
+
);
|
|
70
|
+
return client;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
it.each(["uv", "pixi"] as const)(
|
|
74
|
+
"shows the fixed %s manager for a sandbox",
|
|
75
|
+
(sandboxBackend) => {
|
|
76
|
+
const client = mountPackageAlert({
|
|
77
|
+
configuredManager: sandboxBackend === "uv" ? "pixi" : "uv",
|
|
78
|
+
sandboxBackend,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const managerSelect = screen.getByTestId("install-package-manager-select");
|
|
82
|
+
expect(managerSelect).toHaveValue(sandboxBackend);
|
|
83
|
+
expect(managerSelect).toBeDisabled();
|
|
84
|
+
|
|
85
|
+
fireEvent.click(screen.getByTestId("install-packages-button"));
|
|
86
|
+
expect(client.sendInstallMissingPackages).toHaveBeenCalledWith(
|
|
87
|
+
expect.objectContaining({ manager: sandboxBackend }),
|
|
88
|
+
);
|
|
89
|
+
},
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
it("keeps the configured package manager selectable outside a sandbox", () => {
|
|
93
|
+
mountPackageAlert({ configuredManager: "uv", sandboxBackend: null });
|
|
94
|
+
|
|
95
|
+
const managerSelect = screen.getByTestId("install-package-manager-select");
|
|
96
|
+
expect(managerSelect).toHaveValue("uv");
|
|
97
|
+
expect(managerSelect).not.toBeDisabled();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("installs a selected version in a Pixi sandbox", () => {
|
|
101
|
+
const client = mountPackageAlert({
|
|
102
|
+
configuredManager: "uv",
|
|
103
|
+
sandboxBackend: "pixi",
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
fireEvent.change(screen.getByDisplayValue("latest"), {
|
|
107
|
+
target: { value: "1.2.3" },
|
|
108
|
+
});
|
|
109
|
+
fireEvent.click(screen.getByTestId("install-packages-button"));
|
|
110
|
+
expect(client.sendInstallMissingPackages).toHaveBeenCalledExactlyOnceWith({
|
|
111
|
+
manager: "pixi",
|
|
112
|
+
versions: { polars: "1.2.3" },
|
|
113
|
+
source: "kernel",
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("does not offer version selection for Pixi outside a sandbox", () => {
|
|
118
|
+
mountPackageAlert({ configuredManager: "pixi", sandboxBackend: null });
|
|
119
|
+
|
|
120
|
+
expect(screen.queryByDisplayValue("latest")).not.toBeInTheDocument();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("uses the configured package manager for a server installation", () => {
|
|
124
|
+
const client = mountPackageAlert({
|
|
125
|
+
configuredManager: "uv",
|
|
126
|
+
sandboxBackend: "pixi",
|
|
127
|
+
source: "server",
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
expect(
|
|
131
|
+
screen.queryByTestId("install-package-manager-select"),
|
|
132
|
+
).not.toBeInTheDocument();
|
|
133
|
+
|
|
134
|
+
fireEvent.click(screen.getByTestId("install-packages-button"));
|
|
135
|
+
expect(client.sendInstallMissingPackages).toHaveBeenCalledExactlyOnceWith({
|
|
136
|
+
manager: "uv",
|
|
137
|
+
versions: { polars: "" },
|
|
138
|
+
source: "server",
|
|
139
|
+
});
|
|
140
|
+
});
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
4
|
+
import { Provider } from "jotai";
|
|
5
|
+
import type React from "react";
|
|
6
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
7
|
+
import { ModalProvider } from "@/components/modal/ImperativeModal";
|
|
8
|
+
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
9
|
+
import { store } from "@/core/state/jotai";
|
|
10
|
+
import { PairWithAgentBanner, PairWithAgentButton } from "../pair-with-agent";
|
|
11
|
+
|
|
12
|
+
const mocks = vi.hoisted(() => ({
|
|
13
|
+
isWasm: vi.fn(() => false),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
vi.mock("@/core/wasm/utils", () => ({
|
|
17
|
+
isWasm: mocks.isWasm,
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
function wrapper({ children }: { children: React.ReactNode }) {
|
|
21
|
+
return (
|
|
22
|
+
<Provider store={store}>
|
|
23
|
+
<TooltipProvider>
|
|
24
|
+
<ModalProvider>{children}</ModalProvider>
|
|
25
|
+
</TooltipProvider>
|
|
26
|
+
</Provider>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function stubTokenFetch() {
|
|
31
|
+
vi.stubGlobal(
|
|
32
|
+
"fetch",
|
|
33
|
+
vi.fn().mockResolvedValue(new Response(JSON.stringify({ token: null }))),
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
describe("PairWithAgent", () => {
|
|
38
|
+
afterEach(() => {
|
|
39
|
+
vi.unstubAllGlobals();
|
|
40
|
+
mocks.isWasm.mockReset();
|
|
41
|
+
mocks.isWasm.mockReturnValue(false);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("shows local agent options", () => {
|
|
45
|
+
render(<PairWithAgentBanner />, { wrapper });
|
|
46
|
+
|
|
47
|
+
expect(
|
|
48
|
+
screen.getByRole("group", { name: "Supported agents" }),
|
|
49
|
+
).toBeVisible();
|
|
50
|
+
expect(screen.getByRole("figure", { name: "Claude Code" })).toBeVisible();
|
|
51
|
+
expect(screen.getByRole("figure", { name: "Codex" })).toBeVisible();
|
|
52
|
+
expect(screen.getByRole("figure", { name: "Cursor" })).toBeVisible();
|
|
53
|
+
expect(screen.getByRole("figure", { name: "Gemini CLI" })).toBeVisible();
|
|
54
|
+
expect(screen.getByRole("figure", { name: "OpenCode" })).toBeVisible();
|
|
55
|
+
expect(
|
|
56
|
+
screen.getByRole("figure", { name: "GitHub Copilot" }),
|
|
57
|
+
).toBeVisible();
|
|
58
|
+
expect(
|
|
59
|
+
screen.getByRole("figure", { name: "Any local agent" }),
|
|
60
|
+
).toBeVisible();
|
|
61
|
+
expect(
|
|
62
|
+
screen.getByRole("button", {
|
|
63
|
+
name: "Learn more.",
|
|
64
|
+
}),
|
|
65
|
+
).toBeVisible();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("opens the pairing dialog from the banner description", () => {
|
|
69
|
+
stubTokenFetch();
|
|
70
|
+
render(<PairWithAgentBanner />, { wrapper });
|
|
71
|
+
|
|
72
|
+
fireEvent.click(
|
|
73
|
+
screen.getByRole("button", {
|
|
74
|
+
name: "Learn more.",
|
|
75
|
+
}),
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
expect(
|
|
79
|
+
screen.getByRole("dialog", { name: "Pair with an agent" }),
|
|
80
|
+
).toBeVisible();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("opens the pairing dialog and preserves a passed click handler", () => {
|
|
84
|
+
const onClick = vi.fn();
|
|
85
|
+
stubTokenFetch();
|
|
86
|
+
render(<PairWithAgentButton onClick={onClick} />, { wrapper });
|
|
87
|
+
|
|
88
|
+
fireEvent.click(screen.getByRole("button", { name: "Connect your agent" }));
|
|
89
|
+
|
|
90
|
+
expect(onClick).toHaveBeenCalledOnce();
|
|
91
|
+
expect(
|
|
92
|
+
screen.getByRole("dialog", { name: "Pair with an agent" }),
|
|
93
|
+
).toBeVisible();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("hides pairing controls in WASM", () => {
|
|
97
|
+
mocks.isWasm.mockReturnValue(true);
|
|
98
|
+
|
|
99
|
+
const { container, rerender } = render(<PairWithAgentButton />, {
|
|
100
|
+
wrapper,
|
|
101
|
+
});
|
|
102
|
+
expect(container).toBeEmptyDOMElement();
|
|
103
|
+
|
|
104
|
+
rerender(<PairWithAgentBanner />);
|
|
105
|
+
expect(container).toBeEmptyDOMElement();
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { BotIcon } from "lucide-react";
|
|
4
|
+
import React from "react";
|
|
5
|
+
import { AiProviderIcon } from "@/components/ai/ai-provider-icon";
|
|
6
|
+
import { useImperativeModal } from "@/components/modal/ImperativeModal";
|
|
7
|
+
import { Button, type ButtonProps } from "@/components/ui/button";
|
|
8
|
+
import { isWasm } from "@/core/wasm/utils";
|
|
9
|
+
import { cn } from "@/utils/cn";
|
|
10
|
+
import { PairWithAgentModal } from "./pair-with-agent-modal";
|
|
11
|
+
|
|
12
|
+
const FEATURED_AGENTS = [
|
|
13
|
+
{ id: "claude", label: "Claude Code" },
|
|
14
|
+
{ id: "codex", label: "Codex" },
|
|
15
|
+
{ id: "cursor", label: "Cursor" },
|
|
16
|
+
{ id: "gemini", label: "Gemini CLI" },
|
|
17
|
+
{ id: "opencode", label: "OpenCode" },
|
|
18
|
+
{ id: "github", label: "GitHub Copilot" },
|
|
19
|
+
{ id: "openai-compatible", label: "Any local agent" },
|
|
20
|
+
] as const satisfies ReadonlyArray<{
|
|
21
|
+
id: React.ComponentProps<typeof AiProviderIcon>["provider"];
|
|
22
|
+
label: string;
|
|
23
|
+
}>;
|
|
24
|
+
|
|
25
|
+
export const usePairWithAgentModal = () => {
|
|
26
|
+
const { openModal, closeModal } = useImperativeModal();
|
|
27
|
+
|
|
28
|
+
return React.useCallback(
|
|
29
|
+
() => openModal(<PairWithAgentModal onClose={closeModal} />),
|
|
30
|
+
[openModal, closeModal],
|
|
31
|
+
);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const PairWithAgentButton: React.FC<
|
|
35
|
+
ButtonProps & { label?: string }
|
|
36
|
+
> = ({ className, label = "Connect your agent", onClick, ...props }) => {
|
|
37
|
+
const openPairWithAgentModal = usePairWithAgentModal();
|
|
38
|
+
|
|
39
|
+
if (isWasm()) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<Button
|
|
45
|
+
type="button"
|
|
46
|
+
variant="link"
|
|
47
|
+
size="xs"
|
|
48
|
+
className={cn("h-auto gap-1 px-0", className)}
|
|
49
|
+
onClick={(event) => {
|
|
50
|
+
onClick?.(event);
|
|
51
|
+
if (!event.defaultPrevented) {
|
|
52
|
+
openPairWithAgentModal();
|
|
53
|
+
}
|
|
54
|
+
}}
|
|
55
|
+
{...props}
|
|
56
|
+
>
|
|
57
|
+
{label}
|
|
58
|
+
</Button>
|
|
59
|
+
);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const PairWithAgentBanner: React.FC<{
|
|
63
|
+
label?: string;
|
|
64
|
+
className?: string;
|
|
65
|
+
}> = ({ label = "Connect your agent", className }) => {
|
|
66
|
+
const openPairWithAgentModal = usePairWithAgentModal();
|
|
67
|
+
|
|
68
|
+
if (isWasm()) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<div
|
|
74
|
+
className={cn(
|
|
75
|
+
"rounded-lg border border-(--blue-6) bg-(--blue-2)/50 p-3",
|
|
76
|
+
className,
|
|
77
|
+
)}
|
|
78
|
+
>
|
|
79
|
+
<div className="flex items-center gap-2 text-sm font-semibold">
|
|
80
|
+
{label}
|
|
81
|
+
<fieldset
|
|
82
|
+
className="m-0 flex min-w-0 -space-x-1.5 border-0 p-0 mb-0.5"
|
|
83
|
+
aria-label="Supported agents"
|
|
84
|
+
>
|
|
85
|
+
{FEATURED_AGENTS.map(({ id, label }) => (
|
|
86
|
+
<figure
|
|
87
|
+
key={id}
|
|
88
|
+
title={label}
|
|
89
|
+
aria-label={label}
|
|
90
|
+
className="flex size-6 items-center justify-center rounded-full border bg-background shadow-xs"
|
|
91
|
+
>
|
|
92
|
+
{id === "openai-compatible" ? (
|
|
93
|
+
<BotIcon className="size-3.5" aria-hidden={true} />
|
|
94
|
+
) : (
|
|
95
|
+
<AiProviderIcon
|
|
96
|
+
provider={id}
|
|
97
|
+
className="size-3.5"
|
|
98
|
+
aria-hidden={true}
|
|
99
|
+
/>
|
|
100
|
+
)}
|
|
101
|
+
</figure>
|
|
102
|
+
))}
|
|
103
|
+
</fieldset>
|
|
104
|
+
</div>
|
|
105
|
+
<div className="mt-1 text-xs leading-relaxed">
|
|
106
|
+
<p className="inline">
|
|
107
|
+
Use your AI coding agent to pair-program on this notebook.
|
|
108
|
+
</p>
|
|
109
|
+
<button
|
|
110
|
+
type="button"
|
|
111
|
+
onClick={openPairWithAgentModal}
|
|
112
|
+
className="text-link hover:underline ml-1"
|
|
113
|
+
>
|
|
114
|
+
Learn more.
|
|
115
|
+
</button>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
);
|
|
119
|
+
};
|
|
@@ -297,11 +297,12 @@ export function useNotebookActions({
|
|
|
297
297
|
|
|
298
298
|
{
|
|
299
299
|
icon: <SparklesIcon size={14} strokeWidth={1.5} />,
|
|
300
|
-
label: "
|
|
300
|
+
label: "Connect your agent",
|
|
301
301
|
hidden: isWasm(),
|
|
302
302
|
handle: async () => {
|
|
303
303
|
openModal(<PairWithAgentModal onClose={closeModal} />);
|
|
304
304
|
},
|
|
305
|
+
additionalKeywords: ["connect"],
|
|
305
306
|
},
|
|
306
307
|
|
|
307
308
|
{
|
|
@@ -29,6 +29,7 @@ import useEvent from "react-use-event-hook";
|
|
|
29
29
|
import { DefaultChatTransport } from "ai";
|
|
30
30
|
import { z } from "zod";
|
|
31
31
|
import { AIModelDropdown } from "@/components/ai/ai-model-dropdown";
|
|
32
|
+
import { PairWithAgentButton } from "@/components/editor/actions/pair-with-agent";
|
|
32
33
|
import {
|
|
33
34
|
AddContextButton,
|
|
34
35
|
AttachFileButton,
|
|
@@ -301,6 +302,7 @@ export const AddCellWithAI: React.FC<{
|
|
|
301
302
|
showAddCustomModelDocs={true}
|
|
302
303
|
/>
|
|
303
304
|
{languageDropdown}
|
|
305
|
+
<PairWithAgentButton />
|
|
304
306
|
</div>
|
|
305
307
|
<div className="flex flex-row items-center">
|
|
306
308
|
{files.length > 0 && (
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
3
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
4
|
+
import { useAtomValue } from "jotai";
|
|
4
5
|
import {
|
|
5
6
|
BoxIcon,
|
|
6
7
|
CheckIcon,
|
|
@@ -34,6 +35,7 @@ import {
|
|
|
34
35
|
import { useResolvedMarimoConfig } from "@/core/config/config";
|
|
35
36
|
import type { PackageOperationStatus } from "@/core/kernel/messages";
|
|
36
37
|
import { useRequestClient } from "@/core/network/requests";
|
|
38
|
+
import { sandboxAtom } from "@/core/packages/sandbox-state";
|
|
37
39
|
import { RESTART_REQUIRED_DESCRIPTION } from "@/core/packages/toast-components";
|
|
38
40
|
import { isWasm } from "@/core/wasm/utils";
|
|
39
41
|
import { usePackageMetadata } from "@/hooks/usePackageMetadata";
|
|
@@ -104,6 +106,7 @@ export const PackageAlert: React.FC = () => {
|
|
|
104
106
|
const { packageAlert } = useAlerts();
|
|
105
107
|
const { clearPackageAlert } = useAlertActions();
|
|
106
108
|
const [userConfig] = useResolvedMarimoConfig();
|
|
109
|
+
const sandbox = useAtomValue(sandboxAtom);
|
|
107
110
|
const [desiredPackageVersions, setDesiredPackageVersions] = useState<
|
|
108
111
|
Record<string, string>
|
|
109
112
|
>({});
|
|
@@ -132,8 +135,14 @@ export const PackageAlert: React.FC = () => {
|
|
|
132
135
|
return null;
|
|
133
136
|
}
|
|
134
137
|
|
|
138
|
+
const sandboxPackageManager =
|
|
139
|
+
packageAlert.source === "server" ? null : sandbox?.backend;
|
|
140
|
+
const packageManager =
|
|
141
|
+
sandboxPackageManager ?? userConfig.package_management.manager;
|
|
135
142
|
const doesSupportVersioning =
|
|
136
|
-
|
|
143
|
+
packageAlert.source === "server" ||
|
|
144
|
+
sandbox !== null ||
|
|
145
|
+
packageManager !== "pixi";
|
|
137
146
|
|
|
138
147
|
if (isMissingPackageAlert(packageAlert)) {
|
|
139
148
|
return (
|
|
@@ -207,7 +216,7 @@ export const PackageAlert: React.FC = () => {
|
|
|
207
216
|
{packageAlert.isolated ? (
|
|
208
217
|
<>
|
|
209
218
|
<InstallPackagesButton
|
|
210
|
-
manager={
|
|
219
|
+
manager={packageManager}
|
|
211
220
|
packages={packageAlert.packages.map((pkg) => {
|
|
212
221
|
const parsed = parsePackageSpecifier(pkg);
|
|
213
222
|
const currentExtras =
|
|
@@ -219,10 +228,12 @@ export const PackageAlert: React.FC = () => {
|
|
|
219
228
|
source={packageAlert.source}
|
|
220
229
|
/>
|
|
221
230
|
|
|
222
|
-
{!isWasm() && (
|
|
231
|
+
{!isWasm() && packageAlert.source !== "server" && (
|
|
223
232
|
<>
|
|
224
233
|
<span className="px-2 text-sm">with</span>{" "}
|
|
225
|
-
<PackageManagerForm
|
|
234
|
+
<PackageManagerForm
|
|
235
|
+
fixedManager={sandboxPackageManager ?? undefined}
|
|
236
|
+
/>
|
|
226
237
|
</>
|
|
227
238
|
)}
|
|
228
239
|
</>
|
|
@@ -449,7 +460,9 @@ const InstallPackagesButton = ({
|
|
|
449
460
|
);
|
|
450
461
|
};
|
|
451
462
|
|
|
452
|
-
const PackageManagerForm: React.FC
|
|
463
|
+
const PackageManagerForm: React.FC<{
|
|
464
|
+
fixedManager?: PackageManagerName;
|
|
465
|
+
}> = ({ fixedManager }) => {
|
|
453
466
|
const [config, setConfig] = useResolvedMarimoConfig();
|
|
454
467
|
const { saveUserConfig } = useRequestClient();
|
|
455
468
|
|
|
@@ -490,8 +503,8 @@ const PackageManagerForm: React.FC = () => {
|
|
|
490
503
|
<NativeSelect
|
|
491
504
|
data-testid="install-package-manager-select"
|
|
492
505
|
onChange={(e) => field.onChange(e.target.value)}
|
|
493
|
-
value={field.value}
|
|
494
|
-
disabled={field.disabled}
|
|
506
|
+
value={fixedManager ?? field.value}
|
|
507
|
+
disabled={fixedManager !== undefined || field.disabled}
|
|
495
508
|
className="inline-flex mr-2"
|
|
496
509
|
>
|
|
497
510
|
{PackageManagerNames.map((option) => (
|