@eventcatalog/core 5.0.0-beta.5 → 5.0.0-beta.6
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/analytics/analytics.cjs +1 -1
- package/dist/analytics/analytics.js +2 -2
- package/dist/analytics/log-build.cjs +1 -1
- package/dist/analytics/log-build.js +4 -4
- package/dist/{chunk-43AEF3JE.js → chunk-56CD2NH6.js} +1 -1
- package/dist/{chunk-YYMBYOQR.js → chunk-DE3QHVJP.js} +1 -1
- package/dist/{chunk-XQXMSQA5.js → chunk-L2IRFOE7.js} +1 -1
- package/dist/{chunk-SHHO3JJI.js → chunk-QBP3BBSV.js} +1 -1
- package/dist/{chunk-EBDTVZDW.js → chunk-XMPFHLK4.js} +4 -4
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +1 -1
- package/dist/eventcatalog.js +11 -11
- package/dist/federation/federate.js +4 -4
- package/dist/generate.cjs +1 -1
- package/dist/generate.js +3 -3
- package/dist/runtime-dependencies/@hocuspocus__provider.mjs +3 -0
- package/dist/runtime-dependencies/@hocuspocus__server.mjs +3 -0
- package/dist/runtime-dependencies/@mcp-b__webmcp-local-relay.mjs +3 -0
- package/dist/runtime-dependencies/@mcp-b__webmcp-polyfill.mjs +3 -0
- package/dist/runtime-dependencies/manifest.json +8 -0
- package/dist/runtime-dependencies/react-hotkeys-hook.mjs +3 -0
- package/dist/runtime-dependencies/y-protocols.mjs +3 -0
- package/dist/runtime-dependencies/y-protocols__awareness.mjs +3 -0
- package/dist/runtime-dependencies/yjs.mjs +3 -0
- package/dist/utils/cli-logger.cjs +1 -1
- package/dist/utils/cli-logger.js +2 -2
- package/eventcatalog/astro.config.mjs +2 -0
- package/eventcatalog/integrations/studio-server.mjs +17 -0
- package/eventcatalog/src/features/integrations/eventcatalog-features.ts +20 -1
- package/eventcatalog/src/features/mcp/apps/canvas/shared.ts +39 -0
- package/eventcatalog/src/features/mcp/apps/canvas/styles.css +22 -0
- package/eventcatalog/src/features/mcp/apps/canvas/view.tsx +269 -0
- package/eventcatalog/src/features/mcp/apps/generated/canvas.html +209 -0
- package/eventcatalog/src/features/mcp/apps/generated/viewer.html +2 -2
- package/eventcatalog/src/features/mcp/apps/shared/app-view.tsx +2 -1
- package/eventcatalog/src/features/mcp/mcp-apps.ts +6 -3
- package/eventcatalog/src/features/mcp/mcp-server.ts +17 -4
- package/eventcatalog/src/features/studio/agent-choreography.ts +378 -0
- package/eventcatalog/src/features/studio/canvas-actions.ts +287 -0
- package/eventcatalog/src/features/studio/canvas-doc.ts +280 -0
- package/eventcatalog/src/features/studio/catalog-resources.ts +152 -0
- package/eventcatalog/src/features/studio/catalog.ts +171 -0
- package/eventcatalog/src/features/studio/components/CanvasControls.tsx +153 -0
- package/eventcatalog/src/features/studio/components/CanvasHeader.tsx +332 -0
- package/eventcatalog/src/features/studio/components/Comments.tsx +455 -0
- package/eventcatalog/src/features/studio/components/ConnectAgentDialog.tsx +217 -0
- package/eventcatalog/src/features/studio/components/ContainerChooser.tsx +145 -0
- package/eventcatalog/src/features/studio/components/JoinForm.tsx +58 -0
- package/eventcatalog/src/features/studio/components/LeftPanel.tsx +218 -0
- package/eventcatalog/src/features/studio/components/Presence.tsx +284 -0
- package/eventcatalog/src/features/studio/components/PropertiesPanel.tsx +75 -0
- package/eventcatalog/src/features/studio/components/StudioDesigner.tsx +760 -0
- package/eventcatalog/src/features/studio/components/canvas-nodes.tsx +79 -0
- package/eventcatalog/src/features/studio/components/icons.ts +48 -0
- package/eventcatalog/src/features/studio/components/status.ts +22 -0
- package/eventcatalog/src/features/studio/edges.ts +54 -0
- package/eventcatalog/src/features/studio/flow-state.ts +92 -0
- package/eventcatalog/src/features/studio/grouping.ts +133 -0
- package/eventcatalog/src/features/studio/hooks/presence-sender.ts +46 -0
- package/eventcatalog/src/features/studio/hooks/presence-store.ts +115 -0
- package/eventcatalog/src/features/studio/hooks/use-canvas-webmcp.ts +337 -0
- package/eventcatalog/src/features/studio/hooks/use-comments.ts +47 -0
- package/eventcatalog/src/features/studio/hooks/use-follow-camera.ts +99 -0
- package/eventcatalog/src/features/studio/hooks/use-studio-flow.ts +423 -0
- package/eventcatalog/src/features/studio/layout.ts +39 -0
- package/eventcatalog/src/features/studio/levels.ts +216 -0
- package/eventcatalog/src/features/studio/node-types.ts +173 -0
- package/eventcatalog/src/features/studio/pages/[id].astro +38 -0
- package/eventcatalog/src/features/studio/pages/index.astro +8 -0
- package/eventcatalog/src/features/studio/pages/webmcp-relay.ts +27 -0
- package/eventcatalog/src/features/studio/placement.ts +197 -0
- package/eventcatalog/src/features/studio/server/canvas-mcp.ts +475 -0
- package/eventcatalog/src/features/studio/server/runtime.ts +215 -0
- package/eventcatalog/src/features/studio/server/tool-icons.ts +30 -0
- package/eventcatalog/src/features/studio/studio.css +89 -0
- package/eventcatalog/src/features/studio/tool-sync.ts +75 -0
- package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +9 -2
- package/eventcatalog/src/utils/feature.ts +4 -0
- package/package.json +9 -2
- package/eventcatalog/public/studio-bg.png +0 -0
- package/eventcatalog/src/components/Studio/StudioPageModal.tsx +0 -150
- package/eventcatalog/src/pages/studio.astro +0 -316
- package/dist/{chunk-ZEY2XUJN.js → chunk-AYNZXUB5.js} +7 -7
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import { memo, useEffect, useRef, useState, type ReactNode } from 'react';
|
|
2
|
+
import { Bot, Check, ChevronDown, Link2, Pencil, Plus } from 'lucide-react';
|
|
3
|
+
import { usePeople, type Peer, type PresenceStore } from '../hooks/presence-store';
|
|
4
|
+
import type { WebMcpStatus } from '../hooks/use-canvas-webmcp';
|
|
5
|
+
import type { Status, Transport } from '../hooks/use-studio-flow';
|
|
6
|
+
import { CONNECTION_DOT, STATUS } from './status';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The canvas's header, like Figma or Miro: two small floating bars. On the left the canvas (its title, and a
|
|
10
|
+
* menu), on the right who's here, connecting an agent and sharing.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const barClass =
|
|
14
|
+
'pointer-events-auto flex h-10 items-center rounded-xl border shadow-md bg-[rgb(var(--ec-card-bg))] border-[rgb(var(--ec-page-border))]';
|
|
15
|
+
const menuClass =
|
|
16
|
+
'absolute top-full z-50 mt-2 min-w-52 rounded-xl border p-1 shadow-xl bg-[rgb(var(--ec-card-bg))] border-[rgb(var(--ec-page-border))] text-[rgb(var(--ec-page-text))]';
|
|
17
|
+
const menuItemClass =
|
|
18
|
+
'flex w-full items-center gap-2 rounded-lg px-2.5 py-1.5 text-left text-xs hover:bg-[rgb(var(--ec-page-border)/0.5)]';
|
|
19
|
+
const MAX_AVATARS = 4;
|
|
20
|
+
|
|
21
|
+
/** Closes a popover on outside clicks and Escape */
|
|
22
|
+
function usePopover() {
|
|
23
|
+
const [open, setOpen] = useState(false);
|
|
24
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (!open) return;
|
|
27
|
+
const onPointerDown = (event: PointerEvent) => {
|
|
28
|
+
if (!ref.current?.contains(event.target as globalThis.Node)) setOpen(false);
|
|
29
|
+
};
|
|
30
|
+
const onKeyDown = (event: KeyboardEvent) => event.key === 'Escape' && setOpen(false);
|
|
31
|
+
window.addEventListener('pointerdown', onPointerDown);
|
|
32
|
+
window.addEventListener('keydown', onKeyDown);
|
|
33
|
+
return () => {
|
|
34
|
+
window.removeEventListener('pointerdown', onPointerDown);
|
|
35
|
+
window.removeEventListener('keydown', onKeyDown);
|
|
36
|
+
};
|
|
37
|
+
}, [open]);
|
|
38
|
+
return { open, setOpen, ref };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function Avatar({ peer, size = 28, children }: { peer: Peer; size?: number; children?: ReactNode }) {
|
|
42
|
+
return (
|
|
43
|
+
<span
|
|
44
|
+
className="flex items-center justify-center rounded-full text-[11px] font-semibold text-white ring-2 ring-[rgb(var(--ec-card-bg))]"
|
|
45
|
+
style={{ backgroundColor: peer.color, width: size, height: size }}
|
|
46
|
+
>
|
|
47
|
+
{children ?? (peer.agent ? <Bot size={14} /> : peer.name.slice(0, 2).toUpperCase())}
|
|
48
|
+
</span>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function CanvasMenu({
|
|
53
|
+
title,
|
|
54
|
+
status,
|
|
55
|
+
transport,
|
|
56
|
+
onRetitle,
|
|
57
|
+
shareUrl,
|
|
58
|
+
newCanvasUrl,
|
|
59
|
+
}: {
|
|
60
|
+
title?: string;
|
|
61
|
+
status: Status;
|
|
62
|
+
transport: Transport;
|
|
63
|
+
onRetitle: (title: string) => void;
|
|
64
|
+
shareUrl?: string;
|
|
65
|
+
newCanvasUrl?: string;
|
|
66
|
+
}) {
|
|
67
|
+
const menu = usePopover();
|
|
68
|
+
const titleInput = useRef<HTMLInputElement>(null);
|
|
69
|
+
const [draft, setDraft] = useState<string | null>(null);
|
|
70
|
+
const statusLabel = {
|
|
71
|
+
connected: transport === 'tools' ? 'Connected through the chat' : 'Connected',
|
|
72
|
+
connecting: 'Connecting…',
|
|
73
|
+
disconnected: 'Offline, reconnecting…',
|
|
74
|
+
}[status];
|
|
75
|
+
const statusColor = CONNECTION_DOT[status];
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<div ref={menu.ref} className="relative">
|
|
79
|
+
<div className={`${barClass} gap-1 pl-3 pr-1`}>
|
|
80
|
+
<span title={statusLabel} className={`h-2 w-2 shrink-0 rounded-full ${statusColor}`} />
|
|
81
|
+
<input
|
|
82
|
+
ref={titleInput}
|
|
83
|
+
aria-label="Canvas title"
|
|
84
|
+
value={draft ?? title ?? ''}
|
|
85
|
+
placeholder="Untitled canvas"
|
|
86
|
+
onChange={(e) => setDraft(e.target.value)}
|
|
87
|
+
onBlur={() => {
|
|
88
|
+
if (draft !== null && draft.trim() !== (title ?? '')) onRetitle(draft.trim());
|
|
89
|
+
setDraft(null);
|
|
90
|
+
}}
|
|
91
|
+
onKeyDown={(e) => e.key === 'Enter' && e.currentTarget.blur()}
|
|
92
|
+
size={Math.max(12, (draft ?? title ?? 'Untitled canvas').length)}
|
|
93
|
+
className="max-w-64 truncate rounded-md bg-transparent px-1.5 py-1 text-sm font-semibold placeholder:font-semibold placeholder:text-[rgb(var(--ec-page-text-muted))] hover:bg-[rgb(var(--ec-page-border)/0.4)] focus:bg-[rgb(var(--ec-input-bg))] focus:outline-none"
|
|
94
|
+
/>
|
|
95
|
+
<button
|
|
96
|
+
aria-label="Canvas menu"
|
|
97
|
+
onClick={() => menu.setOpen((open) => !open)}
|
|
98
|
+
className="rounded-md p-1.5 text-[rgb(var(--ec-icon-color))] hover:bg-[rgb(var(--ec-page-border)/0.5)]"
|
|
99
|
+
>
|
|
100
|
+
<ChevronDown size={14} />
|
|
101
|
+
</button>
|
|
102
|
+
</div>
|
|
103
|
+
{menu.open && (
|
|
104
|
+
<div className={`${menuClass} left-0`}>
|
|
105
|
+
<button
|
|
106
|
+
className={menuItemClass}
|
|
107
|
+
onClick={() => {
|
|
108
|
+
menu.setOpen(false);
|
|
109
|
+
titleInput.current?.focus();
|
|
110
|
+
titleInput.current?.select();
|
|
111
|
+
}}
|
|
112
|
+
>
|
|
113
|
+
<Pencil size={14} /> Rename canvas
|
|
114
|
+
</button>
|
|
115
|
+
<button
|
|
116
|
+
className={menuItemClass}
|
|
117
|
+
onClick={() => {
|
|
118
|
+
void navigator.clipboard.writeText(shareUrl ?? window.location.href);
|
|
119
|
+
menu.setOpen(false);
|
|
120
|
+
}}
|
|
121
|
+
>
|
|
122
|
+
<Link2 size={14} /> Copy link
|
|
123
|
+
</button>
|
|
124
|
+
{newCanvasUrl && (
|
|
125
|
+
<a className={menuItemClass} href={newCanvasUrl}>
|
|
126
|
+
<Plus size={14} /> New canvas
|
|
127
|
+
</a>
|
|
128
|
+
)}
|
|
129
|
+
<div className="my-1 h-px bg-[rgb(var(--ec-page-border))]" />
|
|
130
|
+
<p className="flex items-center gap-2 px-2.5 py-1.5 text-[11px] text-[rgb(var(--ec-page-text-muted))]">
|
|
131
|
+
<span className={`h-1.5 w-1.5 rounded-full ${statusColor}`} />
|
|
132
|
+
{statusLabel}
|
|
133
|
+
</p>
|
|
134
|
+
</div>
|
|
135
|
+
)}
|
|
136
|
+
</div>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** You: change the name others see you by */
|
|
141
|
+
function YouAvatar({ peer, onRename }: { peer: Peer; onRename: (name: string) => void }) {
|
|
142
|
+
const popover = usePopover();
|
|
143
|
+
const [draft, setDraft] = useState(peer.name);
|
|
144
|
+
const commit = () => {
|
|
145
|
+
const trimmed = draft.trim();
|
|
146
|
+
if (trimmed && trimmed !== peer.name) onRename(trimmed);
|
|
147
|
+
popover.setOpen(false);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<div ref={popover.ref} className="relative">
|
|
152
|
+
<button
|
|
153
|
+
title={`${peer.name} (you)`}
|
|
154
|
+
onClick={() => {
|
|
155
|
+
setDraft(peer.name);
|
|
156
|
+
popover.setOpen((open) => !open);
|
|
157
|
+
}}
|
|
158
|
+
className="block rounded-full transition-transform hover:scale-110"
|
|
159
|
+
>
|
|
160
|
+
<Avatar peer={peer} />
|
|
161
|
+
</button>
|
|
162
|
+
{popover.open && (
|
|
163
|
+
<div className={`${menuClass} right-0 w-60 space-y-2 p-3`}>
|
|
164
|
+
<label className="block space-y-1.5 text-[11px] font-medium text-[rgb(var(--ec-page-text-muted))]">
|
|
165
|
+
Your name on this canvas
|
|
166
|
+
<input
|
|
167
|
+
autoFocus
|
|
168
|
+
value={draft}
|
|
169
|
+
onChange={(e) => setDraft(e.target.value)}
|
|
170
|
+
onKeyDown={(e) => e.key === 'Enter' && commit()}
|
|
171
|
+
className="w-full rounded-md border px-2 py-1.5 text-xs bg-[rgb(var(--ec-input-bg))] border-[rgb(var(--ec-input-border))] text-[rgb(var(--ec-input-text))]"
|
|
172
|
+
/>
|
|
173
|
+
</label>
|
|
174
|
+
<button
|
|
175
|
+
onClick={commit}
|
|
176
|
+
className="w-full rounded-md px-2 py-1.5 text-xs font-medium bg-[rgb(var(--ec-button-bg))] text-[rgb(var(--ec-button-text))]"
|
|
177
|
+
>
|
|
178
|
+
Save
|
|
179
|
+
</button>
|
|
180
|
+
</div>
|
|
181
|
+
)}
|
|
182
|
+
</div>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Who's here: click someone to jump to them; everyone past the first few is in a list */
|
|
187
|
+
function People({
|
|
188
|
+
peers,
|
|
189
|
+
clientId,
|
|
190
|
+
onJumpTo,
|
|
191
|
+
onRename,
|
|
192
|
+
}: {
|
|
193
|
+
peers: Peer[];
|
|
194
|
+
clientId: number | null;
|
|
195
|
+
onJumpTo: (peer: Peer) => void;
|
|
196
|
+
onRename: (name: string) => void;
|
|
197
|
+
}) {
|
|
198
|
+
const overflow = usePopover();
|
|
199
|
+
const you = peers.find((peer) => peer.clientId === clientId);
|
|
200
|
+
const others = peers.filter((peer) => peer.clientId !== clientId);
|
|
201
|
+
const shown = others.slice(0, MAX_AVATARS);
|
|
202
|
+
const hidden = others.slice(MAX_AVATARS);
|
|
203
|
+
const describe = (peer: Peer) => `${peer.name}${peer.agent ? ` (AI agent)${peer.activity ? `: ${peer.activity}` : ''}` : ''}`;
|
|
204
|
+
|
|
205
|
+
return (
|
|
206
|
+
<div className="flex items-center gap-1.5">
|
|
207
|
+
<div className="flex -space-x-1.5">
|
|
208
|
+
{shown.map((peer) => (
|
|
209
|
+
<button
|
|
210
|
+
key={peer.clientId}
|
|
211
|
+
title={`${describe(peer)}. Click to jump to them`}
|
|
212
|
+
onClick={() => onJumpTo(peer)}
|
|
213
|
+
className="block rounded-full transition-transform hover:z-10 hover:scale-110"
|
|
214
|
+
>
|
|
215
|
+
<Avatar peer={peer} />
|
|
216
|
+
</button>
|
|
217
|
+
))}
|
|
218
|
+
{hidden.length > 0 && (
|
|
219
|
+
<div ref={overflow.ref} className="relative">
|
|
220
|
+
<button
|
|
221
|
+
title={`${hidden.length} more`}
|
|
222
|
+
onClick={() => overflow.setOpen((open) => !open)}
|
|
223
|
+
className="flex h-7 w-7 items-center justify-center rounded-full text-[11px] font-semibold ring-2 ring-[rgb(var(--ec-card-bg))] bg-[rgb(var(--ec-page-border))] text-[rgb(var(--ec-page-text))]"
|
|
224
|
+
>
|
|
225
|
+
+{hidden.length}
|
|
226
|
+
</button>
|
|
227
|
+
{overflow.open && (
|
|
228
|
+
<div className={`${menuClass} right-0`}>
|
|
229
|
+
{hidden.map((peer) => (
|
|
230
|
+
<button
|
|
231
|
+
key={peer.clientId}
|
|
232
|
+
className={menuItemClass}
|
|
233
|
+
onClick={() => {
|
|
234
|
+
onJumpTo(peer);
|
|
235
|
+
overflow.setOpen(false);
|
|
236
|
+
}}
|
|
237
|
+
>
|
|
238
|
+
<Avatar peer={peer} size={20} />
|
|
239
|
+
{describe(peer)}
|
|
240
|
+
</button>
|
|
241
|
+
))}
|
|
242
|
+
</div>
|
|
243
|
+
)}
|
|
244
|
+
</div>
|
|
245
|
+
)}
|
|
246
|
+
</div>
|
|
247
|
+
{you && <YouAvatar peer={you} onRename={onRename} />}
|
|
248
|
+
</div>
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export default memo(function CanvasHeader({
|
|
253
|
+
title,
|
|
254
|
+
onRetitle,
|
|
255
|
+
status,
|
|
256
|
+
transport,
|
|
257
|
+
presence,
|
|
258
|
+
clientId,
|
|
259
|
+
onJumpTo,
|
|
260
|
+
onRename,
|
|
261
|
+
onConnectAgent,
|
|
262
|
+
webMcp,
|
|
263
|
+
shareUrl,
|
|
264
|
+
newCanvasUrl,
|
|
265
|
+
}: {
|
|
266
|
+
title?: string;
|
|
267
|
+
onRetitle: (title: string) => void;
|
|
268
|
+
status: Status;
|
|
269
|
+
transport: Transport;
|
|
270
|
+
presence: PresenceStore | null;
|
|
271
|
+
clientId: number | null;
|
|
272
|
+
onJumpTo: (peer: Peer) => void;
|
|
273
|
+
onRename: (name: string) => void;
|
|
274
|
+
onConnectAgent: () => void;
|
|
275
|
+
webMcp: WebMcpStatus;
|
|
276
|
+
shareUrl?: string;
|
|
277
|
+
newCanvasUrl?: string;
|
|
278
|
+
}) {
|
|
279
|
+
const [copied, setCopied] = useState(false);
|
|
280
|
+
const peers = usePeople(presence);
|
|
281
|
+
const agentsHere = peers.some((peer) => peer.agent);
|
|
282
|
+
|
|
283
|
+
const share = async () => {
|
|
284
|
+
await navigator.clipboard.writeText(shareUrl ?? window.location.href);
|
|
285
|
+
setCopied(true);
|
|
286
|
+
setTimeout(() => setCopied(false), 1500);
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
return (
|
|
290
|
+
<div className="pointer-events-none absolute left-3 right-3 top-3 z-10 flex items-start justify-between gap-3">
|
|
291
|
+
<CanvasMenu
|
|
292
|
+
title={title}
|
|
293
|
+
status={status}
|
|
294
|
+
transport={transport}
|
|
295
|
+
onRetitle={onRetitle}
|
|
296
|
+
shareUrl={shareUrl}
|
|
297
|
+
newCanvasUrl={newCanvasUrl}
|
|
298
|
+
/>
|
|
299
|
+
|
|
300
|
+
<div className={`${barClass} gap-2 px-1.5`}>
|
|
301
|
+
<div className="pl-1">
|
|
302
|
+
<People peers={peers} clientId={clientId} onJumpTo={onJumpTo} onRename={onRename} />
|
|
303
|
+
</div>
|
|
304
|
+
<div className="h-5 w-px bg-[rgb(var(--ec-page-border))]" />
|
|
305
|
+
<button
|
|
306
|
+
onClick={onConnectAgent}
|
|
307
|
+
title={
|
|
308
|
+
webMcp.state === 'on'
|
|
309
|
+
? `Connect an AI agent to this canvas. WebMCP is on in this tab${webMcp.lastCall ? ` (last: ${webMcp.lastCall})` : ''}`
|
|
310
|
+
: 'Connect an AI agent to this canvas'
|
|
311
|
+
}
|
|
312
|
+
className="relative flex h-7 items-center gap-1.5 rounded-lg px-2.5 text-xs font-medium hover:bg-[rgb(var(--ec-page-border)/0.5)]"
|
|
313
|
+
>
|
|
314
|
+
<Bot size={15} />
|
|
315
|
+
Connect agent
|
|
316
|
+
{(webMcp.state === 'on' || agentsHere) && (
|
|
317
|
+
<span
|
|
318
|
+
className={`absolute left-5 top-1 h-1.5 w-1.5 rounded-full ring-2 ring-[rgb(var(--ec-card-bg))] ${STATUS.success.dot}`}
|
|
319
|
+
/>
|
|
320
|
+
)}
|
|
321
|
+
</button>
|
|
322
|
+
<button
|
|
323
|
+
onClick={share}
|
|
324
|
+
className="flex h-7 items-center gap-1.5 rounded-lg px-3 text-xs font-semibold bg-[rgb(var(--ec-button-bg))] text-[rgb(var(--ec-button-text))] hover:bg-[rgb(var(--ec-button-bg-hover))]"
|
|
325
|
+
>
|
|
326
|
+
{copied ? <Check size={14} /> : null}
|
|
327
|
+
{copied ? 'Link copied' : 'Share'}
|
|
328
|
+
</button>
|
|
329
|
+
</div>
|
|
330
|
+
</div>
|
|
331
|
+
);
|
|
332
|
+
});
|