@marimo-team/islands 0.25.1-dev26 → 0.25.1-dev28
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/{ErrorBoundary-pWWhtr0s.js → ErrorBoundary-CE6-1WC1.js} +1 -1
- package/dist/{code-visibility-Ba_KMLhg.js → code-visibility--u2Q2Zfx.js} +1 -1
- package/dist/{common-Cqwp2VSj.js → common-hHL7iKzA.js} +1 -1
- package/dist/{data-grid-overlay-editor-CpH8-A59.js → data-grid-overlay-editor-v7VZO5up.js} +1 -1
- package/dist/{feedback-button-CJxD9fy8.js → feedback-button-C3Ya1IXo.js} +2 -2
- package/dist/{glide-data-editor-cm-NQofk.js → glide-data-editor-BKOLh2Tv.js} +3 -3
- package/dist/{globals-BquX8N9R.js → globals-DOJr_y24.js} +1 -1
- package/dist/main.js +6 -6
- package/dist/{number-overlay-editor-3AkAzj7F.js → number-overlay-editor-DkEFJYK7.js} +1 -1
- package/dist/{reveal-component-rC5k5pu3.js → reveal-component-rbEv9ppg.js} +2 -2
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/__mocks__/common.ts +3 -3
- package/src/__tests__/CellStatus.test.tsx +1 -1
- package/src/__tests__/setup.ts +7 -7
- package/src/components/app-config/ai-config.tsx +2 -0
- package/src/components/chat/chat-panel.tsx +19 -8
- package/src/components/data-table/column-summary/chart-spec-model.tsx +4 -4
- package/src/components/dependency-graph/elements.ts +2 -2
- 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/ai/staged-cell-submission.ts +2 -2
- package/src/components/editor/ai/transport/chat-transport.tsx +1 -1
- package/src/components/editor/connections/database/as-code.ts +44 -44
- package/src/components/editor/connections/storage/as-code.ts +3 -3
- package/src/components/editor/file-tree/requesting-tree.tsx +16 -13
- package/src/components/editor/navigation/clipboard.ts +2 -2
- package/src/components/editor/output/ansi-reduce.ts +13 -13
- package/src/components/slides/__tests__/minimap-actions.test.tsx +7 -7
- package/src/components/slides/__tests__/minimap.test.ts +12 -12
- package/src/components/slides/__tests__/slide-scroll-hint.test.tsx +8 -8
- package/src/core/ai/context/__tests__/registry.test.ts +31 -28
- package/src/core/ai/context/providers/cell-output.ts +8 -8
- package/src/core/ai/context/providers/datasource.ts +10 -7
- package/src/core/ai/context/providers/error.ts +7 -7
- package/src/core/ai/context/providers/file.ts +8 -8
- package/src/core/ai/context/providers/tables.ts +7 -7
- package/src/core/ai/context/providers/variable.ts +7 -7
- package/src/core/ai/context/registry.ts +18 -18
- package/src/core/ai/ids/ids.ts +5 -5
- package/src/core/ai/model-registry.ts +12 -9
- package/src/core/ai/staged-cells.ts +6 -6
- package/src/core/ai/tools/base.ts +6 -6
- package/src/core/ai/tools/edit-notebook-tool.ts +6 -6
- package/src/core/ai/tools/registry.ts +4 -4
- package/src/core/ai/tools/run-cells-tool.ts +7 -7
- package/src/core/ai/tools/sample-tool.ts +6 -6
- package/src/core/codemirror/cells/debugger-decorations.ts +13 -7
- package/src/core/codemirror/cells/line-timing-decorations.ts +9 -9
- package/src/core/codemirror/cells/traceback-decorations.ts +7 -4
- package/src/core/codemirror/code-lens/extension.ts +13 -13
- package/src/core/codemirror/copilot/__tests__/language-server.test.ts +6 -6
- package/src/core/codemirror/copilot/__tests__/transport.test.ts +12 -12
- package/src/core/codemirror/copilot/language-server.ts +13 -13
- package/src/core/codemirror/copilot/transport.ts +1 -1
- package/src/core/codemirror/find-replace/search-highlight.ts +5 -5
- package/src/core/codemirror/go-to-definition/underline.ts +3 -3
- package/src/core/codemirror/keymaps/vim.ts +3 -3
- package/src/core/codemirror/language/languages/markdown.ts +8 -8
- package/src/core/codemirror/language/languages/python.ts +7 -7
- package/src/core/codemirror/language/languages/sql/completion-builder.ts +5 -5
- package/src/core/codemirror/language/languages/sql/completion-store.ts +4 -4
- package/src/core/codemirror/language/languages/sql/sql.ts +21 -21
- package/src/core/codemirror/lsp/__tests__/transports.test.ts +10 -10
- package/src/core/codemirror/lsp/federated-lsp.ts +29 -27
- package/src/core/codemirror/lsp/notebook-lsp.ts +21 -21
- package/src/core/codemirror/misc/__tests__/dnd.test.ts +10 -10
- package/src/core/codemirror/misc/__tests__/paste.test.ts +7 -4
- package/src/core/codemirror/reactive-references/extension.ts +3 -3
- package/src/core/codemirror/rtc/loro/awareness.ts +12 -12
- package/src/core/codemirror/rtc/loro/sync.ts +5 -5
- package/src/core/codemirror/vim/cursor-visibility.ts +3 -3
- package/src/core/dom/ui-element.ts +5 -5
- package/src/core/dom/uiregistry.ts +10 -10
- package/src/core/hotkeys/hotkeys.ts +9 -9
- package/src/core/islands/__tests__/bridge.test.ts +4 -4
- package/src/core/islands/__tests__/test-utils.tsx +4 -4
- package/src/core/islands/bridge.ts +85 -82
- package/src/core/islands/components/web-components.tsx +7 -7
- package/src/core/islands/worker/__tests__/controller.test.ts +1 -1
- package/src/core/islands/worker/controller.ts +6 -3
- package/src/core/islands/worker-factory.ts +2 -2
- package/src/core/kernel/RuntimeState.ts +4 -4
- package/src/core/lsp/__tests__/transport.test.ts +19 -19
- package/src/core/lsp/transport.ts +5 -5
- package/src/core/network/CachingRequestRegistry.ts +2 -2
- package/src/core/network/DeferredRequestRegistry.ts +4 -4
- package/src/core/runtime/runtime.ts +25 -20
- package/src/core/static/virtual-file-tracker.ts +5 -5
- package/src/core/wasm/__tests__/bridge.test.ts +2 -2
- package/src/core/wasm/bridge.ts +130 -126
- package/src/core/wasm/router.ts +5 -5
- package/src/core/wasm/store.ts +4 -4
- package/src/core/wasm/worker/bootstrap.ts +8 -5
- package/src/core/wasm/worker/message-buffer.ts +3 -3
- package/src/core/websocket/transports/__tests__/ws.test.ts +8 -8
- package/src/core/websocket/transports/basic.ts +15 -8
- package/src/core/websocket/transports/sse.ts +7 -7
- package/src/core/websocket/transports/transport.ts +3 -3
- package/src/core/websocket/transports/ws.ts +9 -7
- package/src/hooks/__tests__/useOverflowDetection.test.tsx +5 -5
- package/src/hooks/__tests__/useResizeObserver.test.tsx +12 -12
- package/src/plugins/core/registerReactComponent.tsx +5 -5
- package/src/plugins/core/sidebar-element.tsx +2 -2
- package/src/plugins/impl/ButtonPlugin.tsx +3 -3
- package/src/plugins/impl/CheckboxPlugin.tsx +3 -3
- package/src/plugins/impl/CodeEditorPlugin.tsx +3 -3
- package/src/plugins/impl/DatePickerPlugin.tsx +3 -3
- package/src/plugins/impl/DateRangePlugin.tsx +3 -3
- package/src/plugins/impl/DateTimePickerPlugin.tsx +3 -3
- package/src/plugins/impl/DictPlugin.tsx +3 -3
- package/src/plugins/impl/DropdownPlugin.tsx +3 -3
- package/src/plugins/impl/FileUploadPlugin.tsx +3 -3
- package/src/plugins/impl/MatrixPlugin.tsx +4 -4
- package/src/plugins/impl/MicrophonePlugin.tsx +3 -3
- package/src/plugins/impl/MultiselectPlugin.tsx +3 -3
- package/src/plugins/impl/NumberPlugin.tsx +3 -3
- package/src/plugins/impl/RadioPlugin.tsx +3 -3
- package/src/plugins/impl/RangeSliderPlugin.tsx +3 -3
- package/src/plugins/impl/RefreshPlugin.tsx +3 -3
- package/src/plugins/impl/SliderPlugin.tsx +3 -3
- package/src/plugins/impl/SwitchPlugin.tsx +3 -3
- package/src/plugins/impl/TabsPlugin.tsx +3 -3
- package/src/plugins/impl/TextAreaPlugin.tsx +3 -3
- package/src/plugins/impl/TextInputPlugin.tsx +3 -3
- package/src/plugins/impl/__tests__/MatrixPlugin.test.tsx +2 -2
- package/src/plugins/impl/anywidget/model.ts +10 -10
- package/src/plugins/impl/anywidget/registry.ts +12 -9
- package/src/plugins/impl/anywidget/runtime.ts +11 -9
- package/src/plugins/impl/anywidget/widget-binding.ts +6 -6
- package/src/plugins/impl/matplotlib/MatplotlibPlugin.tsx +3 -3
- package/src/plugins/impl/matplotlib/__tests__/matplotlib-renderer.test.ts +2 -2
- package/src/plugins/impl/matplotlib/matplotlib-renderer.ts +2 -2
- package/src/plugins/impl/mpl-interactive/mpl-websocket-shim.ts +10 -10
- package/src/plugins/impl/panel/utils.ts +5 -5
- package/src/plugins/impl/plotly/PlotlyPlugin.tsx +3 -3
- package/src/plugins/impl/vega/VegaPlugin.tsx +3 -3
- package/src/plugins/impl/vega/__tests__/use-vega-container-remeasure.test.ts +7 -7
- package/src/plugins/layout/AccordionPlugin.tsx +3 -3
- package/src/plugins/layout/CalloutPlugin.tsx +3 -3
- package/src/plugins/layout/ImageComparisonPlugin.tsx +5 -3
- package/src/plugins/layout/JsonOutputPlugin.tsx +3 -3
- package/src/plugins/layout/MimeRenderPlugin.tsx +3 -3
- package/src/plugins/layout/NavigationMenuPlugin.tsx +3 -3
- package/src/plugins/layout/OutlinePlugin.tsx +3 -3
- package/src/plugins/layout/ProgressPlugin.tsx +3 -3
- package/src/plugins/layout/RoutesPlugin.tsx +3 -3
- package/src/plugins/layout/StatPlugin.tsx +3 -3
- package/src/plugins/layout/TexPlugin.tsx +3 -3
- package/src/plugins/layout/carousel/CarouselPlugin.tsx +4 -4
- package/src/plugins/layout/mermaid/MermaidPlugin.tsx +3 -3
- package/src/utils/Deferred.ts +6 -6
- package/src/utils/async-capture-tracker.ts +10 -7
- package/src/utils/errors.ts +5 -5
- package/src/utils/id-tree.tsx +83 -80
- package/src/utils/lru.ts +1 -1
- package/src/utils/multi-map.ts +14 -12
- package/src/utils/paths.ts +6 -6
- package/src/utils/pluralize.ts +3 -3
- package/src/utils/progress.ts +6 -6
- package/src/utils/python-poet/poet.ts +14 -14
- package/src/utils/routes.ts +4 -2
- package/src/utils/semaphore.ts +6 -6
- package/src/utils/storage/storage.ts +6 -6
- package/src/utils/storage/typed.ts +16 -12
- package/src/utils/time.ts +9 -9
- package/src/utils/timed-cache.ts +4 -4
- package/src/utils/tracer.ts +9 -6
package/package.json
CHANGED
package/src/__mocks__/common.ts
CHANGED
|
@@ -101,9 +101,9 @@ export const SetupMocks = {
|
|
|
101
101
|
const unobserve = vi.fn();
|
|
102
102
|
const disconnect = vi.fn();
|
|
103
103
|
global.ResizeObserver = class MockResizeObserver {
|
|
104
|
-
observe = observe;
|
|
105
|
-
unobserve = unobserve;
|
|
106
|
-
disconnect = disconnect;
|
|
104
|
+
public observe = observe;
|
|
105
|
+
public unobserve = unobserve;
|
|
106
|
+
public disconnect = disconnect;
|
|
107
107
|
} as unknown as typeof ResizeObserver;
|
|
108
108
|
return { observe, unobserve, disconnect };
|
|
109
109
|
},
|
package/src/__tests__/setup.ts
CHANGED
|
@@ -9,29 +9,29 @@ import "blob-polyfill";
|
|
|
9
9
|
// if we need to test ResizeObserver functionality
|
|
10
10
|
// we can use a library like "resize-observer-polyfill"
|
|
11
11
|
globalThis.ResizeObserver ??= class {
|
|
12
|
-
observe(_target: Element) {
|
|
12
|
+
public observe(_target: Element) {
|
|
13
13
|
/* noop */
|
|
14
14
|
}
|
|
15
|
-
unobserve(_target: Element) {
|
|
15
|
+
public unobserve(_target: Element) {
|
|
16
16
|
/* noop */
|
|
17
17
|
}
|
|
18
|
-
disconnect() {
|
|
18
|
+
public disconnect() {
|
|
19
19
|
/* noop */
|
|
20
20
|
}
|
|
21
21
|
} as never;
|
|
22
22
|
|
|
23
23
|
// mock implementation because jsdom doesn't support IntersectionObserver
|
|
24
24
|
globalThis.IntersectionObserver ??= class {
|
|
25
|
-
observe(_target: Element) {
|
|
25
|
+
public observe(_target: Element) {
|
|
26
26
|
/* noop */
|
|
27
27
|
}
|
|
28
|
-
unobserve(_target: Element) {
|
|
28
|
+
public unobserve(_target: Element) {
|
|
29
29
|
/* noop */
|
|
30
30
|
}
|
|
31
|
-
disconnect() {
|
|
31
|
+
public disconnect() {
|
|
32
32
|
/* noop */
|
|
33
33
|
}
|
|
34
|
-
takeRecords() {
|
|
34
|
+
public takeRecords() {
|
|
35
35
|
return [];
|
|
36
36
|
}
|
|
37
37
|
} as never;
|
|
@@ -59,6 +59,7 @@ import {
|
|
|
59
59
|
listModelsForAiSettings,
|
|
60
60
|
} from "../ai/ai-utils";
|
|
61
61
|
import { getTagColour } from "../ai/display-helpers";
|
|
62
|
+
import { PairWithAgentBanner } from "../editor/actions/pair-with-agent";
|
|
62
63
|
import {
|
|
63
64
|
Accordion,
|
|
64
65
|
AccordionContent,
|
|
@@ -2005,6 +2006,7 @@ export const AiConfig: React.FC<AiConfigProps> = ({
|
|
|
2005
2006
|
{aiEnabled !== false && (
|
|
2006
2007
|
<>
|
|
2007
2008
|
<TabsContent value="ai-providers">
|
|
2009
|
+
<PairWithAgentBanner className="mb-4" />
|
|
2008
2010
|
<AiProvidersConfig
|
|
2009
2011
|
form={form}
|
|
2010
2012
|
config={config}
|
|
@@ -64,6 +64,10 @@ import { cn } from "@/utils/cn";
|
|
|
64
64
|
import { Logger } from "@/utils/Logger";
|
|
65
65
|
import { AIModelDropdown } from "../ai/ai-model-dropdown";
|
|
66
66
|
import { useOpenSettingsToTab } from "../app-config/state";
|
|
67
|
+
import {
|
|
68
|
+
PairWithAgentBanner,
|
|
69
|
+
PairWithAgentButton,
|
|
70
|
+
} from "../editor/actions/pair-with-agent";
|
|
67
71
|
import { PairWithAgentModal } from "../editor/actions/pair-with-agent-modal";
|
|
68
72
|
import { PromptInput } from "../editor/ai/add-cell-with-ai";
|
|
69
73
|
import {
|
|
@@ -163,6 +167,9 @@ const ChatHeader: React.FC<ChatHeaderProps> = ({
|
|
|
163
167
|
</Button>
|
|
164
168
|
</Tooltip>
|
|
165
169
|
<div className="flex items-center gap-2">
|
|
170
|
+
<Tooltip content="Connect your agent">
|
|
171
|
+
<PairWithAgentButton className="mr-1" label="Connect" />
|
|
172
|
+
</Tooltip>
|
|
166
173
|
<MCPStatusIndicator />
|
|
167
174
|
<Tooltip content="AI Settings">
|
|
168
175
|
<Button
|
|
@@ -493,7 +500,7 @@ const PairWithAgentCallout: React.FC<{
|
|
|
493
500
|
onClick={onPairWithAgent}
|
|
494
501
|
>
|
|
495
502
|
<SparklesIcon className="h-3.5 w-3.5 shrink-0" />
|
|
496
|
-
<span>
|
|
503
|
+
<span>Connect your own agent to this notebook</span>
|
|
497
504
|
<ArrowRightIcon className="h-3 w-3 shrink-0" />
|
|
498
505
|
</Button>
|
|
499
506
|
);
|
|
@@ -509,13 +516,17 @@ const ChatPanel = () => {
|
|
|
509
516
|
title="Chat with AI"
|
|
510
517
|
description="No AI provider configured or Chat model not selected"
|
|
511
518
|
action={
|
|
512
|
-
<
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
+
<div className="flex flex-col gap-3">
|
|
520
|
+
<Button
|
|
521
|
+
variant="outline"
|
|
522
|
+
size="sm"
|
|
523
|
+
className="self-start"
|
|
524
|
+
onClick={() => handleClick("ai", "ai-providers")}
|
|
525
|
+
>
|
|
526
|
+
Edit AI settings
|
|
527
|
+
</Button>
|
|
528
|
+
<PairWithAgentBanner />
|
|
529
|
+
</div>
|
|
519
530
|
}
|
|
520
531
|
icon={<BotMessageSquareIcon />}
|
|
521
532
|
/>
|
|
@@ -137,14 +137,14 @@ export class ColumnChartSpecModel<T> {
|
|
|
137
137
|
private legacySourceName: "data_0" | "source_0";
|
|
138
138
|
|
|
139
139
|
private readonly fieldTypes: FieldTypes;
|
|
140
|
-
readonly stats: Record<ColumnName, Partial<ColumnHeaderStats>>;
|
|
141
|
-
readonly binValues: Record<ColumnName, BinValues>;
|
|
142
|
-
readonly valueCounts: Record<ColumnName, ValueCounts>;
|
|
140
|
+
public readonly stats: Record<ColumnName, Partial<ColumnHeaderStats>>;
|
|
141
|
+
public readonly binValues: Record<ColumnName, BinValues>;
|
|
142
|
+
public readonly valueCounts: Record<ColumnName, ValueCounts>;
|
|
143
143
|
private readonly opts: {
|
|
144
144
|
includeCharts: boolean;
|
|
145
145
|
};
|
|
146
146
|
|
|
147
|
-
constructor(
|
|
147
|
+
public constructor(
|
|
148
148
|
data: T[] | string,
|
|
149
149
|
fieldTypes: FieldTypes,
|
|
150
150
|
stats: Record<ColumnName, Partial<ColumnHeaderStats>>,
|
|
@@ -225,7 +225,7 @@ export class VerticalElementsBuilder implements ElementsBuilder {
|
|
|
225
225
|
};
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
-
createElements(
|
|
228
|
+
public createElements(
|
|
229
229
|
cellIds: CellId[],
|
|
230
230
|
cellAtoms: Atom<CellData>[],
|
|
231
231
|
variables: Variables,
|
|
@@ -311,7 +311,7 @@ export class TreeElementsBuilder implements ElementsBuilder {
|
|
|
311
311
|
};
|
|
312
312
|
}
|
|
313
313
|
|
|
314
|
-
createElements(
|
|
314
|
+
public createElements(
|
|
315
315
|
cellIds: CellId[],
|
|
316
316
|
cellAtoms: Atom<CellData>[],
|
|
317
317
|
variables: Variables,
|
|
@@ -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 && (
|
|
@@ -14,13 +14,13 @@ interface SubmissionAttempt {
|
|
|
14
14
|
export class StagedCellSubmissionController {
|
|
15
15
|
private activeAttempt: SubmissionAttempt | null = null;
|
|
16
16
|
|
|
17
|
-
cancel() {
|
|
17
|
+
public cancel() {
|
|
18
18
|
if (this.activeAttempt) {
|
|
19
19
|
this.activeAttempt.cancelled = true;
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
async run<T>({ prepare, submit, onError }: SubmissionCallbacks<T>) {
|
|
23
|
+
public async run<T>({ prepare, submit, onError }: SubmissionCallbacks<T>) {
|
|
24
24
|
if (this.activeAttempt !== null) {
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
@@ -15,7 +15,7 @@ export class StreamingChunkTransport<
|
|
|
15
15
|
> extends DefaultChatTransport<UI_MESSAGE> {
|
|
16
16
|
private onChunkReceived: (chunk: UIMessageChunk) => void;
|
|
17
17
|
|
|
18
|
-
constructor(
|
|
18
|
+
public constructor(
|
|
19
19
|
options: HttpChatTransportInitOptions<UI_MESSAGE>,
|
|
20
20
|
onChunkReceived: (chunk: UIMessageChunk) => void,
|
|
21
21
|
) {
|