@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.
- package/dist/{ConnectedDataExplorerComponent-C39nQwtD.js → ConnectedDataExplorerComponent-B68gXlbY.js} +323 -328
- package/dist/{ImageComparisonComponent-BhkiyswP.js → ImageComparisonComponent-Cw1oA8Tn.js} +13 -13
- package/dist/{_baseUniq-DdHL34FO.js → _baseUniq-CQrhBg_9.js} +67 -67
- package/dist/any-language-editor-pzUl6lxp.js +27 -0
- package/dist/{arc-BXrety1g.js → arc-BOhn-m2C.js} +1 -1
- package/dist/{architectureDiagram-KFL7JDKH-BMy6ywCF.js → architectureDiagram-W76B3OCA-DdYf2VnU.js} +144 -144
- package/dist/assets/{worker-COGufAQn.js → worker-BcG8m3h5.js} +33 -29
- package/dist/asterisk-DS281yxp.js +271 -0
- package/dist/{blockDiagram-ZYB65J3Q-DYT2-nlI.js → blockDiagram-QIGZ2CNN-DmmYotkP.js} +10 -10
- package/dist/{c4Diagram-AAMF2YG6-ZiQzioe6.js → c4Diagram-FPNF74CW-Dyz4zMHJ.js} +8 -8
- package/dist/{channel-CeuXqUAU.js → channel-CCL8jXAe.js} +1 -1
- package/dist/{chunk-ANTBXLJU-BvYnIrdq.js → chunk-4BX2VUAB-BfKwWLfJ.js} +1 -1
- package/dist/{chunk-WVR4S24B-DXj8yaUk.js → chunk-55IACEB6-CFQU7zSp.js} +1 -1
- package/dist/{chunk-GLLZNHP4-CyFsosAe.js → chunk-FMBD7UC4-DDjZzUcl.js} +1 -1
- package/dist/{chunk-JBRWN2VN-DA_EEhy2.js → chunk-K7UQS3LO-nBRjBU1H.js} +117 -117
- package/dist/{chunk-NRVI72HA-BYx2jMlI.js → chunk-QN33PNHL-B-g8sJzl.js} +1 -1
- package/dist/{chunk-FHKO5MBM-DfCztBk8.js → chunk-QZHKN3VN-B7QSJS3J.js} +1 -1
- package/dist/{chunk-LXBSTHXV-Se7vdY6J.js → chunk-TVAH2DTR-pGXll4d1.js} +7 -7
- package/dist/{chunk-OMD6QJNC-CqgcPMgL.js → chunk-TZMSLE5B-Dx9h-1mv.js} +1 -1
- package/dist/{classDiagram-v2-QTMF73CY-B19A3G1l.js → classDiagram-KNZD7YFC-BXryI7DY.js} +2 -2
- package/dist/{classDiagram-3BZAVTQC-B19A3G1l.js → classDiagram-v2-RKCZMP56-BXryI7DY.js} +2 -2
- package/dist/{clone-78au0tn1.js → clone-DqwV7ges.js} +1 -1
- package/dist/cose-bilkent-S5V4N54A-Di6FNMXz.js +2609 -0
- package/dist/{cytoscape.esm-BYnVVhJX.js → cytoscape.esm-DfdJODL8.js} +34 -34
- package/dist/{dagre-2BBEFEWP-BfEn3ZUV.js → dagre-5GWH7T2D-BTZPMTey.js} +6 -6
- package/dist/{data-grid-overlay-editor-CH_qLkV2.js → data-grid-overlay-editor-ryatXXby.js} +11 -11
- package/dist/{diagram-4IRLE6MV-CL8xidnG.js → diagram-N5W7TBWH-D79_zdOu.js} +59 -60
- package/dist/{diagram-RP2FKANI-B1BPcUew.js → diagram-QEK2KX5R-DX2A_SD0.js} +15 -15
- package/dist/{diagram-GUPCWM2R-CZ5cfqlq.js → diagram-S2PKOQOG-DM6VMTrJ.js} +10 -10
- package/dist/dockerfile-BoowzQlp.js +194 -0
- package/dist/ebnf-DUPDuY4r.js +78 -0
- package/dist/{erDiagram-HZWUO2LU-BEAIww50.js → erDiagram-AWTI2OKA-BBirxtlI.js} +8 -8
- package/dist/fcl-CPC2WYrI.js +103 -0
- package/dist/{flowDiagram-THRYKUMA-Czs2UAI2.js → flowDiagram-PVAE7QVJ-DyVweEMs.js} +9 -9
- package/dist/{ganttDiagram-WV7ZQ7D5-ByYIAVFO.js → ganttDiagram-OWAHRB6G-DTB7FX7r.js} +34 -34
- package/dist/{gitGraphDiagram-OJR772UL-BcpDsiyB.js → gitGraphDiagram-NY62KEGX-BrbIb5pD.js} +4 -4
- package/dist/{glide-data-editor-CmN6FVyi.js → glide-data-editor-DhMX4nmM.js} +33 -33
- package/dist/{graph-77W6heli.js → graph-CuLSrclI.js} +3 -3
- package/dist/http-D9LttvKF.js +44 -0
- package/dist/{index-BOojn38D.js → index-BNgdUQ2e.js} +7711 -7711
- package/dist/index-DIy6LHLJ.js +98 -0
- package/dist/{index-CmozKMxx.js → index-Df2dsx1t.js} +6 -6
- package/dist/{index-pBmAzQJl.js → index-MCx5v1x0.js} +2 -2
- package/dist/{index-Bfk9dnyS.js → index-cz_xaKvT.js} +33090 -32892
- package/dist/{infoDiagram-6WOFNB3A-CfzLHHVP.js → infoDiagram-STP46IZ2-CCBHc7-K.js} +2 -2
- package/dist/{journeyDiagram-FFXJYRFH-ndAcpkGn.js → journeyDiagram-BIP6EPQ6-LhGSj54j.js} +24 -26
- package/dist/{kanban-definition-KOZQBZVT-DcQYzNvc.js → kanban-definition-6OIFK2YF-aegTMFS6.js} +14 -14
- package/dist/{layout-XySVHJgD.js → layout-BEARWMhl.js} +81 -81
- package/dist/{linear-PbooOqg7.js → linear-fbJq6cdO.js} +35 -35
- package/dist/{main-B5yML0bw.js → main-HerZgEhd.js} +76533 -69945
- package/dist/main.js +1 -1
- package/dist/{mermaid-Cg5IX6Nv.js → mermaid-DxPYK0KX.js} +6160 -7493
- package/dist/min-DBJkhObB.js +80 -0
- package/dist/mindmap-definition-Q6HEUPPD-A3Fh5XDZ.js +785 -0
- package/dist/nginx-zDPm3Z74.js +89 -0
- package/dist/{number-overlay-editor-DUhfZqtP.js → number-overlay-editor-USMrY6k3.js} +19 -19
- package/dist/{pieDiagram-DBDJKBY4-DTOlNsja.js → pieDiagram-ADFJNKIX-Q9uFlCV0.js} +17 -17
- package/dist/{quadrantDiagram-YPSRARAO-BX2d8VS-.js → quadrantDiagram-LMRXKWRM-BuPh-qpK.js} +6 -6
- package/dist/{react-plotly-Dcyw-3Sa.js → react-plotly-HSqJPRfa.js} +3577 -3577
- package/dist/{requirementDiagram-EGVEC5DT-D1T5u-wG.js → requirementDiagram-4UW4RH46-CHROYNU_.js} +7 -7
- package/dist/{sankeyDiagram-HRAUVNP4-G6xDfnp-.js → sankeyDiagram-GR3RE2ED-DkUqHP2d.js} +5 -5
- package/dist/sequenceDiagram-C3RYC4MD-YoPTMplP.js +2519 -0
- package/dist/{slides-component-BJLlPJSr.js → slides-component-D7CHSR00.js} +66 -66
- package/dist/solr-BNlsLglM.js +41 -0
- package/dist/spreadsheet-C-cy4P5N.js +49 -0
- package/dist/{stateDiagram-UUKSUZ4H-CYXbjaom.js → stateDiagram-KXAO66HF-DEN00mVU.js} +5 -5
- package/dist/{stateDiagram-v2-EYPG3UTE-Br1HYKT6.js → stateDiagram-v2-UMBNRL4Z-DlQqSUAa.js} +2 -2
- package/dist/style.css +1 -1
- package/dist/tiddlywiki-5wqsXtSk.js +155 -0
- package/dist/tiki-__Kn3CeS.js +181 -0
- package/dist/{time-B9SZnSen.js → time-BtVcKqeD.js} +58 -58
- package/dist/{timeline-definition-3HZDQTIS-DeK_ZRD0.js → timeline-definition-XQNQX7LJ-DEteLt8D.js} +10 -12
- package/dist/{timer-BYwnU4DF.js → timer-B0-z63CM.js} +16 -16
- package/dist/{treemap-75Q7IDZK-CKP4vV_0.js → treemap-75Q7IDZK-8S6podme.js} +14 -14
- package/dist/{vega-component-CpgdqX2d.js → vega-component-D35L45kI.js} +30 -30
- package/dist/{xychartDiagram-FDP5SA34-AMEPsx_R.js → xychartDiagram-6GGTOJPD-DKwGThyy.js} +7 -7
- package/package.json +44 -41
- package/src/__mocks__/notebook.ts +3 -0
- package/src/__mocks__/requests.ts +3 -0
- package/src/__tests__/__snapshots__/CellStatus.test.tsx.snap +12 -12
- package/src/__tests__/chat-utils.test.ts +26 -211
- package/src/components/ai/ai-model-dropdown.tsx +25 -9
- package/src/components/ai/ai-provider-icon.tsx +5 -1
- package/src/components/app-config/ai-config.tsx +7 -0
- package/src/components/chat/acp/__tests__/__snapshots__/prompt.test.ts.snap +304 -0
- package/src/components/chat/acp/__tests__/atoms.test.ts +56 -0
- package/src/components/chat/acp/__tests__/prompt.test.ts +12 -0
- package/src/components/chat/acp/__tests__/state.test.ts +621 -0
- package/src/components/chat/acp/agent-docs.tsx +78 -0
- package/src/components/chat/acp/agent-panel.css +23 -0
- package/src/components/chat/acp/agent-panel.tsx +715 -0
- package/src/components/chat/acp/agent-selector.tsx +138 -0
- package/src/components/chat/acp/blocks.tsx +664 -0
- package/src/components/chat/acp/common.tsx +198 -0
- package/src/components/chat/acp/prompt.ts +284 -0
- package/src/components/chat/acp/scroll-to-bottom-button.tsx +50 -0
- package/src/components/chat/acp/session-tabs.tsx +138 -0
- package/src/components/chat/acp/state.ts +263 -0
- package/src/components/chat/acp/thread.tsx +121 -0
- package/src/components/chat/acp/types.ts +63 -0
- package/src/components/chat/acp/utils.ts +45 -0
- package/src/components/chat/chat-components.tsx +71 -0
- package/src/components/chat/chat-panel.tsx +481 -291
- package/src/components/chat/chat-utils.ts +50 -0
- package/src/components/chat/markdown-renderer.tsx +3 -7
- package/src/components/chat/tool-call-accordion.tsx +6 -6
- package/src/components/datasources/__tests__/utils.test.ts +6 -0
- package/src/components/datasources/column-preview.tsx +1 -3
- package/src/components/editor/actions/useNotebookActions.tsx +1 -1
- package/src/components/editor/ai/add-cell-with-ai.tsx +20 -15
- package/src/components/editor/ai/ai-completion-editor.tsx +22 -3
- package/src/components/editor/ai/completion-handlers.tsx +2 -4
- package/src/components/editor/ai/completion-utils.ts +85 -11
- package/src/components/editor/alerts/startup-logs-alert.tsx +72 -0
- package/src/components/editor/chrome/panels/datasources-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/dependency-graph-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/documentation-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/error-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/file-explorer-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/logs-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/outline-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/packages-panel.tsx +4 -2
- package/src/components/editor/chrome/panels/scratchpad-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/secrets-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/snippets-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/tracing-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/variable-panel.tsx +3 -1
- package/src/components/editor/chrome/types.ts +10 -0
- package/src/components/editor/chrome/wrapper/app-chrome.tsx +55 -31
- package/src/components/editor/controls/command-palette-button.tsx +1 -1
- package/src/components/editor/controls/command-palette.tsx +5 -4
- package/src/components/editor/controls/state.ts +4 -0
- package/src/components/editor/package-alert.tsx +108 -58
- package/src/components/editor/renderers/CellArray.tsx +2 -0
- package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +0 -1
- package/src/components/pages/edit-page.tsx +7 -3
- package/src/core/ai/chat-utils.ts +26 -43
- package/src/core/ai/config.ts +1 -1
- package/src/core/ai/context/__tests__/registry.test.ts +277 -3
- package/src/core/ai/context/context.ts +11 -1
- package/src/core/ai/context/providers/__tests__/cell-output.test.ts +378 -0
- package/src/core/ai/context/providers/__tests__/error.test.ts +3 -2
- package/src/core/ai/context/providers/__tests__/file.test.ts +119 -0
- package/src/core/ai/context/providers/cell-output.ts +349 -0
- package/src/core/ai/context/providers/common.ts +5 -1
- package/src/core/ai/context/providers/file.ts +287 -0
- package/src/core/ai/context/registry.ts +79 -0
- package/src/core/ai/state.ts +22 -5
- package/src/core/alerts/state.ts +71 -3
- package/src/core/cells/cell.ts +2 -2
- package/src/core/cells/cells.ts +1 -1
- package/src/core/cells/logs.ts +1 -1
- package/src/core/cells/runs.ts +6 -5
- package/src/core/codemirror/ai/resources.ts +47 -5
- package/src/core/codemirror/ai/state.ts +12 -0
- package/src/core/codemirror/language/__tests__/sql.test.ts +45 -0
- package/src/core/codemirror/markdown/__tests__/commands.test.ts +1 -0
- package/src/core/codemirror/theme/dark.ts +1 -1
- package/src/core/config/capabilities.ts +1 -1
- package/src/core/config/feature-flag.tsx +2 -0
- package/src/core/datasets/__tests__/data-source.test.ts +24 -0
- package/src/core/errors/__tests__/errors.test.ts +2 -0
- package/src/core/islands/bridge.ts +1 -0
- package/src/core/islands/main.ts +1 -0
- package/src/core/kernel/messages.ts +12 -6
- package/src/core/layout/layout.ts +3 -3
- package/src/core/network/requests-network.ts +8 -0
- package/src/core/network/requests-static.ts +1 -0
- package/src/core/network/requests-toasting.ts +1 -0
- package/src/core/network/types.ts +4 -1
- package/src/core/wasm/bridge.ts +18 -2
- package/src/core/wasm/worker/bootstrap.ts +3 -1
- package/src/core/wasm/worker/getMarimoWheel.ts +3 -8
- package/src/core/wasm/worker/types.ts +3 -0
- package/src/core/websocket/useMarimoWebSocket.tsx +7 -1
- package/src/css/app/Cell.css +42 -21
- package/src/css/app/codemirror.css +5 -1
- package/src/css/globals.css +3 -0
- package/src/css/md.css +1 -1
- package/src/plugins/impl/MicrophonePlugin.tsx +2 -2
- package/src/plugins/impl/chat/ChatPlugin.tsx +2 -9
- package/src/plugins/impl/chat/chat-ui.tsx +129 -110
- package/src/plugins/impl/chat/types.ts +5 -8
- package/src/plugins/impl/code/__tests__/language.test.ts +15 -0
- package/src/plugins/impl/code/any-language-editor.tsx +11 -8
- package/src/plugins/impl/vega/vega.css +121 -0
- package/src/plugins/layout/MimeRenderPlugin.tsx +3 -6
- package/src/stories/cell.stories.tsx +6 -0
- package/src/stories/layout/vertical/one-column.stories.tsx +215 -0
- package/src/theme/useTheme.ts +11 -6
- package/src/utils/Logger.ts +5 -6
- package/src/utils/__tests__/blob.test.ts +37 -0
- package/src/utils/arrays.ts +13 -0
- package/src/utils/fileToBase64.ts +21 -6
- package/src/utils/json/base64.ts +5 -2
- package/src/utils/numbers.ts +9 -7
- package/dist/any-language-editor-DC5170DQ.js +0 -45
- package/dist/asn1-jKiBa2Ya.js +0 -95
- package/dist/clojure-CCKyeQKf.js +0 -800
- package/dist/css-BkF-NPzE.js +0 -1553
- package/dist/index-5ZH_qS8j.js +0 -288
- package/dist/index-U4yn89qO.js +0 -341
- package/dist/javascript-C2yteZeJ.js +0 -691
- package/dist/min-DS5Jz-hg.js +0 -80
- package/dist/mindmap-definition-LNHGMQRG-0aOVaMR8.js +0 -3234
- package/dist/mllike-BSnXJBGA.js +0 -272
- package/dist/pug-CwAQJzGR.js +0 -248
- package/dist/python-BkR3uSy8.js +0 -313
- package/dist/rpm-IznJm2Xc.js +0 -57
- package/dist/sequenceDiagram-WFGC7UMF-DMhHzllb.js +0 -2284
- package/dist/ttcn-cfg-Bac_acMi.js +0 -88
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { atom } from "jotai";
|
|
4
|
+
import { atomWithStorage } from "jotai/utils";
|
|
5
|
+
import { capitalize } from "lodash-es";
|
|
6
|
+
import type { TypedString } from "@/utils/typed";
|
|
7
|
+
import { generateUUID } from "@/utils/uuid";
|
|
8
|
+
import type { ExternalAgentSessionId, SessionSupportType } from "./types";
|
|
9
|
+
|
|
10
|
+
// Types
|
|
11
|
+
export type TabId = TypedString<"TabId">;
|
|
12
|
+
export type ExternalAgentId = "claude" | "gemini";
|
|
13
|
+
|
|
14
|
+
// No agents support loading sessions, so we limit to 1, otherwise
|
|
15
|
+
// this is confusing to the user when switching between sessions
|
|
16
|
+
const MAX_SESSIONS = 1;
|
|
17
|
+
|
|
18
|
+
export interface AgentSession {
|
|
19
|
+
tabId: TabId;
|
|
20
|
+
agentId: ExternalAgentId;
|
|
21
|
+
title: string;
|
|
22
|
+
createdAt: number;
|
|
23
|
+
lastUsedAt: number;
|
|
24
|
+
// Store the actual agent session ID for resumption
|
|
25
|
+
externalAgentSessionId: ExternalAgentSessionId | null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface AgentSessionState {
|
|
29
|
+
sessions: AgentSession[];
|
|
30
|
+
activeTabId: TabId | null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Constants
|
|
34
|
+
const STORAGE_KEY = "marimo:acp:sessions:v1";
|
|
35
|
+
|
|
36
|
+
// Atoms
|
|
37
|
+
export const agentSessionStateAtom = atomWithStorage<AgentSessionState>(
|
|
38
|
+
STORAGE_KEY,
|
|
39
|
+
{
|
|
40
|
+
sessions: [],
|
|
41
|
+
activeTabId: null,
|
|
42
|
+
},
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
export const selectedTabAtom = atom(
|
|
46
|
+
(get) => {
|
|
47
|
+
const state = get(agentSessionStateAtom);
|
|
48
|
+
if (!state.activeTabId) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
return (
|
|
52
|
+
state.sessions.find((session) => session.tabId === state.activeTabId) ||
|
|
53
|
+
null
|
|
54
|
+
);
|
|
55
|
+
},
|
|
56
|
+
(_get, set, activeTabId: TabId | null) => {
|
|
57
|
+
set(agentSessionStateAtom, (prev) => ({
|
|
58
|
+
...prev,
|
|
59
|
+
activeTabId: activeTabId,
|
|
60
|
+
}));
|
|
61
|
+
},
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
// Utilities
|
|
65
|
+
function generateTabId(): TabId {
|
|
66
|
+
// Our tab ID for internal session management
|
|
67
|
+
return `tab_${generateUUID()}` as TabId;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function truncateTitle(title: string, maxLength = 20): string {
|
|
71
|
+
if (title.length <= maxLength) {
|
|
72
|
+
return title;
|
|
73
|
+
}
|
|
74
|
+
return `${title.slice(0, maxLength - 3)}...`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function addSession(
|
|
78
|
+
state: AgentSessionState,
|
|
79
|
+
session: {
|
|
80
|
+
agentId: ExternalAgentId;
|
|
81
|
+
firstMessage?: string;
|
|
82
|
+
},
|
|
83
|
+
): AgentSessionState {
|
|
84
|
+
const sessionSupport = getAgentSessionSupport(session.agentId);
|
|
85
|
+
|
|
86
|
+
const now = Date.now();
|
|
87
|
+
const title = session.firstMessage
|
|
88
|
+
? truncateTitle(session.firstMessage.trim())
|
|
89
|
+
: `New ${session.agentId} session`;
|
|
90
|
+
const tabId = generateTabId();
|
|
91
|
+
|
|
92
|
+
if (sessionSupport === "single") {
|
|
93
|
+
// For single session agents, replace any existing session for this agent
|
|
94
|
+
const existingSessions = state.sessions.filter(
|
|
95
|
+
(s) => s.agentId === session.agentId,
|
|
96
|
+
);
|
|
97
|
+
const otherSessions = state.sessions.filter(
|
|
98
|
+
(s) => s.agentId !== session.agentId,
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
if (existingSessions.length > 0) {
|
|
102
|
+
// Replace the existing session (overwrite it)
|
|
103
|
+
const existingSession = existingSessions[0];
|
|
104
|
+
const updatedSession: AgentSession = {
|
|
105
|
+
agentId: session.agentId,
|
|
106
|
+
title,
|
|
107
|
+
createdAt: now,
|
|
108
|
+
lastUsedAt: now,
|
|
109
|
+
tabId: existingSession.tabId, // Keep the same ID to maintain tab reference
|
|
110
|
+
externalAgentSessionId: null, // Clear the external session ID
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
...state,
|
|
115
|
+
sessions: [...otherSessions.slice(0, MAX_SESSIONS - 1), updatedSession],
|
|
116
|
+
activeTabId: updatedSession.tabId,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// For multiple session agents or when no existing session exists
|
|
122
|
+
return {
|
|
123
|
+
...state,
|
|
124
|
+
sessions: [
|
|
125
|
+
...state.sessions.slice(0, MAX_SESSIONS - 1),
|
|
126
|
+
{
|
|
127
|
+
agentId: session.agentId,
|
|
128
|
+
tabId,
|
|
129
|
+
title,
|
|
130
|
+
createdAt: now,
|
|
131
|
+
lastUsedAt: now,
|
|
132
|
+
externalAgentSessionId: null,
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
activeTabId: tabId,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function removeSession(
|
|
140
|
+
state: AgentSessionState,
|
|
141
|
+
sessionId: TabId,
|
|
142
|
+
): AgentSessionState {
|
|
143
|
+
const filteredSessions = state.sessions.filter((s) => s.tabId !== sessionId);
|
|
144
|
+
const newActiveTabId =
|
|
145
|
+
state.activeTabId === sessionId
|
|
146
|
+
? filteredSessions.length > 0
|
|
147
|
+
? filteredSessions[filteredSessions.length - 1].tabId
|
|
148
|
+
: null
|
|
149
|
+
: state.activeTabId;
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
sessions: filteredSessions,
|
|
153
|
+
activeTabId: newActiveTabId,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function updateSessionTitle(
|
|
158
|
+
state: AgentSessionState,
|
|
159
|
+
title: string,
|
|
160
|
+
): AgentSessionState {
|
|
161
|
+
const selectedTab = state.activeTabId;
|
|
162
|
+
if (!selectedTab) {
|
|
163
|
+
return state;
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
...state,
|
|
167
|
+
sessions: state.sessions.map((session) =>
|
|
168
|
+
session.tabId === selectedTab
|
|
169
|
+
? { ...session, title: truncateTitle(title) }
|
|
170
|
+
: session,
|
|
171
|
+
),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function updateSessionLastUsed(
|
|
176
|
+
state: AgentSessionState,
|
|
177
|
+
sessionId: TabId,
|
|
178
|
+
): AgentSessionState {
|
|
179
|
+
return {
|
|
180
|
+
...state,
|
|
181
|
+
sessions: state.sessions.map((session) =>
|
|
182
|
+
session.tabId === sessionId
|
|
183
|
+
? { ...session, lastUsedAt: Date.now() }
|
|
184
|
+
: session,
|
|
185
|
+
),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Update the sessionId for the current;y selected tab
|
|
191
|
+
*/
|
|
192
|
+
export function updateSessionExternalAgentSessionId(
|
|
193
|
+
state: AgentSessionState,
|
|
194
|
+
externalAgentSessionId: ExternalAgentSessionId,
|
|
195
|
+
): AgentSessionState {
|
|
196
|
+
const selectedTab = state.activeTabId;
|
|
197
|
+
if (!selectedTab) {
|
|
198
|
+
return state;
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
...state,
|
|
202
|
+
sessions: state.sessions.map((session) =>
|
|
203
|
+
session.tabId === selectedTab
|
|
204
|
+
? { ...session, externalAgentSessionId, lastUsedAt: Date.now() }
|
|
205
|
+
: session,
|
|
206
|
+
),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function getSessionsByAgent(
|
|
211
|
+
sessions: AgentSession[],
|
|
212
|
+
agentId: ExternalAgentId,
|
|
213
|
+
): AgentSession[] {
|
|
214
|
+
return sessions
|
|
215
|
+
.filter((session) => session.agentId === agentId)
|
|
216
|
+
.sort((a, b) => b.lastUsedAt - a.lastUsedAt);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function getAllAgentIds(): ExternalAgentId[] {
|
|
220
|
+
return ["claude", "gemini"];
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function getAgentDisplayName(agentId: ExternalAgentId): string {
|
|
224
|
+
return capitalize(agentId);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function getAgentWebSocketUrl(agentId: ExternalAgentId): string {
|
|
228
|
+
return AGENT_CONFIG[agentId].webSocketUrl;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
interface AgentConfig {
|
|
232
|
+
port: number;
|
|
233
|
+
command: string;
|
|
234
|
+
webSocketUrl: string;
|
|
235
|
+
sessionSupport: SessionSupportType;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const AGENT_CONFIG: Record<ExternalAgentId, AgentConfig> = {
|
|
239
|
+
claude: {
|
|
240
|
+
port: 3017,
|
|
241
|
+
command: "npx @zed-industries/claude-code-acp",
|
|
242
|
+
webSocketUrl: "ws://localhost:3017/message",
|
|
243
|
+
sessionSupport: "single",
|
|
244
|
+
},
|
|
245
|
+
gemini: {
|
|
246
|
+
port: 3019,
|
|
247
|
+
command: "npx @google/gemini-cli --experimental-acp",
|
|
248
|
+
webSocketUrl: "ws://localhost:3019/message",
|
|
249
|
+
sessionSupport: "single",
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
export function getAgentSessionSupport(
|
|
254
|
+
agentId: ExternalAgentId,
|
|
255
|
+
): SessionSupportType {
|
|
256
|
+
return AGENT_CONFIG[agentId].sessionSupport;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export function getAgentConnectionCommand(agentId: ExternalAgentId): string {
|
|
260
|
+
const port = AGENT_CONFIG[agentId].port;
|
|
261
|
+
const command = AGENT_CONFIG[agentId].command;
|
|
262
|
+
return `npx supergateway --stdio\\\n "${command}" \\\n --outputTransport ws --port ${port} `;
|
|
263
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { groupNotifications } from "use-acp";
|
|
5
|
+
import {
|
|
6
|
+
ConnectionChangeBlock,
|
|
7
|
+
ErrorBlock,
|
|
8
|
+
SessionNotificationsBlock,
|
|
9
|
+
} from "./blocks";
|
|
10
|
+
|
|
11
|
+
type NotificationEvent = Awaited<
|
|
12
|
+
ReturnType<typeof groupNotifications>
|
|
13
|
+
>[number][number];
|
|
14
|
+
|
|
15
|
+
interface AgentThreadProps {
|
|
16
|
+
notifications: NotificationEvent[];
|
|
17
|
+
isConnected: boolean;
|
|
18
|
+
onRetryConnection?: () => void;
|
|
19
|
+
onRetryLastAction?: () => void;
|
|
20
|
+
onDismissError?: (errorId: string) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const AgentThread = ({
|
|
24
|
+
notifications,
|
|
25
|
+
isConnected,
|
|
26
|
+
onRetryConnection,
|
|
27
|
+
onRetryLastAction,
|
|
28
|
+
onDismissError,
|
|
29
|
+
}: AgentThreadProps) => {
|
|
30
|
+
let combinedNotifications = groupNotifications(notifications);
|
|
31
|
+
|
|
32
|
+
// Filter out all connection changes unless it is the last one
|
|
33
|
+
combinedNotifications = combinedNotifications.filter((group, index) => {
|
|
34
|
+
const isLast = index === combinedNotifications.length - 1;
|
|
35
|
+
if (isLast) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
if (isConnectionChangeGroup(group)) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
return true;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const renderNotification = (group: NotificationEvent[]) => {
|
|
45
|
+
if (group.length === 0) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (isErrorGroup(group)) {
|
|
50
|
+
const lastError = group[group.length - 1];
|
|
51
|
+
return (
|
|
52
|
+
<ErrorBlock
|
|
53
|
+
key={lastError.id}
|
|
54
|
+
data={lastError.data}
|
|
55
|
+
onRetry={onRetryLastAction}
|
|
56
|
+
onDismiss={
|
|
57
|
+
onDismissError ? () => onDismissError(lastError.id) : undefined
|
|
58
|
+
}
|
|
59
|
+
/>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
if (isConnectionChangeGroup(group)) {
|
|
63
|
+
const lastConnectionChange = group[group.length - 1];
|
|
64
|
+
return (
|
|
65
|
+
<ConnectionChangeBlock
|
|
66
|
+
key={lastConnectionChange.id}
|
|
67
|
+
data={lastConnectionChange.data}
|
|
68
|
+
isConnected={isConnected}
|
|
69
|
+
onRetry={onRetryConnection}
|
|
70
|
+
timestamp={lastConnectionChange.timestamp}
|
|
71
|
+
/>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
if (isSessionNotificationGroup(group)) {
|
|
75
|
+
const startTimestamp = group[0].timestamp;
|
|
76
|
+
const endTimestamp = group[group.length - 1].timestamp;
|
|
77
|
+
const data = group.map((item) => item.data.update);
|
|
78
|
+
return (
|
|
79
|
+
<SessionNotificationsBlock
|
|
80
|
+
data={data}
|
|
81
|
+
startTimestamp={startTimestamp}
|
|
82
|
+
endTimestamp={endTimestamp}
|
|
83
|
+
/>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
return "Unknown notification type";
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<div className="flex flex-col gap-4 px-2 pb-10">
|
|
91
|
+
{combinedNotifications.map((notification) => (
|
|
92
|
+
<React.Fragment key={notification[0].id}>
|
|
93
|
+
{renderNotification(notification)}
|
|
94
|
+
</React.Fragment>
|
|
95
|
+
))}
|
|
96
|
+
</div>
|
|
97
|
+
);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
function isErrorGroup(
|
|
101
|
+
group: NotificationEvent[],
|
|
102
|
+
): group is Array<Extract<NotificationEvent, { type: "error" }>> {
|
|
103
|
+
// We only check the first since we know the group is the same type
|
|
104
|
+
return group[0].type === "error";
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function isConnectionChangeGroup(
|
|
108
|
+
group: NotificationEvent[],
|
|
109
|
+
): group is Array<Extract<NotificationEvent, { type: "connection_change" }>> {
|
|
110
|
+
// We only check the first since we know the group is the same type
|
|
111
|
+
return group[0].type === "connection_change";
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function isSessionNotificationGroup(
|
|
115
|
+
group: NotificationEvent[],
|
|
116
|
+
): group is Array<
|
|
117
|
+
Extract<NotificationEvent, { type: "session_notification" }>
|
|
118
|
+
> {
|
|
119
|
+
// We only check the first since we know the group is the same type
|
|
120
|
+
return group[0].type === "session_notification";
|
|
121
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
|
+
import type { ContentBlock } from "@zed-industries/agent-client-protocol";
|
|
3
|
+
import type { groupNotifications, useAcpClient } from "use-acp";
|
|
4
|
+
|
|
5
|
+
export type NotificationEvent = Awaited<
|
|
6
|
+
ReturnType<typeof groupNotifications>
|
|
7
|
+
>[number][number];
|
|
8
|
+
|
|
9
|
+
export type AgentConnectionState = ReturnType<
|
|
10
|
+
typeof useAcpClient
|
|
11
|
+
>["connectionState"];
|
|
12
|
+
|
|
13
|
+
export type AgentPendingPermission = ReturnType<
|
|
14
|
+
typeof useAcpClient
|
|
15
|
+
>["pendingPermission"];
|
|
16
|
+
|
|
17
|
+
// Notification events
|
|
18
|
+
|
|
19
|
+
export type ErrorNotificationEvent = Extract<
|
|
20
|
+
NotificationEvent,
|
|
21
|
+
{ type: "error" }
|
|
22
|
+
>;
|
|
23
|
+
export type ConnectionChangeNotificationEvent = Extract<
|
|
24
|
+
NotificationEvent,
|
|
25
|
+
{ type: "connection_change" }
|
|
26
|
+
>;
|
|
27
|
+
export type SessionNotificationEvent = Extract<
|
|
28
|
+
NotificationEvent,
|
|
29
|
+
{ type: "session_notification" }
|
|
30
|
+
>;
|
|
31
|
+
|
|
32
|
+
// Session notification events
|
|
33
|
+
|
|
34
|
+
export type SessionNotificationEventData =
|
|
35
|
+
SessionNotificationEvent["data"]["update"];
|
|
36
|
+
export type SessionNotificationEventType =
|
|
37
|
+
SessionNotificationEventData["sessionUpdate"];
|
|
38
|
+
|
|
39
|
+
export type NotificationDataOf<T extends SessionNotificationEventType> =
|
|
40
|
+
Extract<SessionNotificationEventData, { sessionUpdate: T }>;
|
|
41
|
+
|
|
42
|
+
// Session notification event data
|
|
43
|
+
|
|
44
|
+
export type UserNotificationEvent = NotificationDataOf<"user_message_chunk">;
|
|
45
|
+
export type AgentNotificationEvent = NotificationDataOf<"agent_message_chunk">;
|
|
46
|
+
export type AgentThoughtNotificationEvent =
|
|
47
|
+
NotificationDataOf<"agent_thought_chunk">;
|
|
48
|
+
export type ToolCallNotificationEvent = NotificationDataOf<"tool_call">;
|
|
49
|
+
export type ToolCallUpdateNotificationEvent =
|
|
50
|
+
NotificationDataOf<"tool_call_update">;
|
|
51
|
+
export type PlanNotificationEvent = NotificationDataOf<"plan">;
|
|
52
|
+
|
|
53
|
+
export type ContentBlockType = ContentBlock["type"];
|
|
54
|
+
export type ContentBlockOf<T extends ContentBlockType> = Extract<
|
|
55
|
+
ContentBlock,
|
|
56
|
+
{ type: T }
|
|
57
|
+
>;
|
|
58
|
+
|
|
59
|
+
// Agent session support types
|
|
60
|
+
export type SessionSupportType = "single" | "multiple";
|
|
61
|
+
export type ExternalAgentSessionId = NonNullable<
|
|
62
|
+
ReturnType<typeof useAcpClient>["activeSessionId"]
|
|
63
|
+
>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
|
+
import type { NotificationDataOf, SessionNotificationEventData } from "./types";
|
|
3
|
+
|
|
4
|
+
export function isToolCalls(
|
|
5
|
+
group: SessionNotificationEventData[],
|
|
6
|
+
): group is Array<NotificationDataOf<"tool_call" | "tool_call_update">> {
|
|
7
|
+
// We only check the first since we know the group is the same type
|
|
8
|
+
const first = group[0];
|
|
9
|
+
return (
|
|
10
|
+
first.sessionUpdate === "tool_call" ||
|
|
11
|
+
first.sessionUpdate === "tool_call_update"
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function isAgentThoughts(
|
|
16
|
+
group: SessionNotificationEventData[],
|
|
17
|
+
): group is Array<NotificationDataOf<"agent_thought_chunk">> {
|
|
18
|
+
// We only check the first since we know the group is the same type
|
|
19
|
+
const first = group[0];
|
|
20
|
+
return first.sessionUpdate === "agent_thought_chunk";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function isUserMessages(
|
|
24
|
+
group: SessionNotificationEventData[],
|
|
25
|
+
): group is Array<NotificationDataOf<"user_message_chunk">> {
|
|
26
|
+
// We only check the first since we know the group is the same type
|
|
27
|
+
const first = group[0];
|
|
28
|
+
return first.sessionUpdate === "user_message_chunk";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function isAgentMessages(
|
|
32
|
+
group: SessionNotificationEventData[],
|
|
33
|
+
): group is Array<NotificationDataOf<"agent_message_chunk">> {
|
|
34
|
+
// We only check the first since we know the group is the same type
|
|
35
|
+
const first = group[0];
|
|
36
|
+
return first.sessionUpdate === "agent_message_chunk";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function isPlans(
|
|
40
|
+
group: SessionNotificationEventData[],
|
|
41
|
+
): group is Array<NotificationDataOf<"plan">> {
|
|
42
|
+
// We only check the first since we know the group is the same type
|
|
43
|
+
const first = group[0];
|
|
44
|
+
return first.sessionUpdate === "plan";
|
|
45
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import type { FileUIPart } from "ai";
|
|
4
|
+
import { FileIcon, FileTextIcon, ImageIcon, XIcon } from "lucide-react";
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
import { cn } from "@/utils/cn";
|
|
7
|
+
|
|
8
|
+
export const AttachmentRenderer = ({
|
|
9
|
+
attachment,
|
|
10
|
+
}: {
|
|
11
|
+
attachment: FileUIPart;
|
|
12
|
+
}) => {
|
|
13
|
+
if (attachment.mediaType?.startsWith("image/")) {
|
|
14
|
+
return (
|
|
15
|
+
<img
|
|
16
|
+
src={attachment.url}
|
|
17
|
+
alt={attachment.filename}
|
|
18
|
+
className="max-h-[100px] max-w-[100px] object-contain mb-1.5"
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div className="flex flex-row gap-1 items-center text-xs">
|
|
25
|
+
<FileIcon className="h-3 w-3 mt-0.5" />
|
|
26
|
+
{attachment.filename}
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const FileAttachmentPill = ({
|
|
32
|
+
file,
|
|
33
|
+
className,
|
|
34
|
+
onRemove,
|
|
35
|
+
}: {
|
|
36
|
+
file: File;
|
|
37
|
+
className?: string;
|
|
38
|
+
onRemove: () => void;
|
|
39
|
+
}) => {
|
|
40
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<div
|
|
44
|
+
className={cn(
|
|
45
|
+
"py-1 px-1.5 bg-muted rounded-md cursor-pointer flex flex-row gap-1 items-center text-xs",
|
|
46
|
+
className,
|
|
47
|
+
)}
|
|
48
|
+
onMouseEnter={() => setIsHovered(true)}
|
|
49
|
+
onMouseLeave={() => setIsHovered(false)}
|
|
50
|
+
>
|
|
51
|
+
{isHovered ? (
|
|
52
|
+
<XIcon className="h-3 w-3 mt-0.5" onClick={onRemove} />
|
|
53
|
+
) : (
|
|
54
|
+
renderFileIcon(file)
|
|
55
|
+
)}
|
|
56
|
+
{file.name}
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
function renderFileIcon(file: File): React.ReactNode {
|
|
62
|
+
const classNames = "h-3 w-3 mt-0.5";
|
|
63
|
+
|
|
64
|
+
if (file.type.startsWith("image/")) {
|
|
65
|
+
return <ImageIcon className={classNames} />;
|
|
66
|
+
} else if (file.type.startsWith("text/")) {
|
|
67
|
+
return <FileTextIcon className={classNames} />;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return <FileIcon className={classNames} />;
|
|
71
|
+
}
|