@blade-hq/agent-react 2612.0.0-beta.31 → 2612.0.0-beta.33

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.
@@ -0,0 +1,577 @@
1
+ import {
2
+ BrowserConnectionDialog,
3
+ Check,
4
+ LoaderCircle,
5
+ Pencil,
6
+ Plug,
7
+ PluginConnectorList,
8
+ RefreshCw,
9
+ Search,
10
+ SessionPluginConfigForm,
11
+ SessionPluginIcon,
12
+ Trash2,
13
+ Unplug,
14
+ catalogToConnectorItems,
15
+ filterConnectorItems,
16
+ useSessionPluginActivation
17
+ } from "./chunk-6PSSFMTQ.js";
18
+
19
+ // src/components/connector-panel-types.ts
20
+ function selectionFromItem(item) {
21
+ return {
22
+ name: item.name,
23
+ displayName: item.display_name ?? null,
24
+ icon: item.icon,
25
+ iconSource: item.iconSource
26
+ };
27
+ }
28
+ var TRIGGER_ICON_LIMIT = 3;
29
+ function triggerSummary(items) {
30
+ const visible = items.slice(0, TRIGGER_ICON_LIMIT);
31
+ return {
32
+ visible,
33
+ overflow: items.length - visible.length,
34
+ total: items.length
35
+ };
36
+ }
37
+
38
+ // src/components/AccountConnectors.tsx
39
+ import { isLocalConnectorName } from "@blade-hq/agent-client/internal";
40
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
41
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
42
+ var actionBaseClass = "inline-flex h-8 shrink-0 items-center justify-center gap-1.5 rounded-lg border border-[hsl(var(--border))] bg-[hsl(var(--background))] text-xs font-medium text-[hsl(var(--foreground))] transition-colors hover:bg-[hsl(var(--accent))] focus-visible:outline-2 focus-visible:outline-offset-2 disabled:pointer-events-none disabled:opacity-50";
43
+ var actionClass = `${actionBaseClass} px-3`;
44
+ var iconActionClass = `${actionBaseClass} w-8 p-0`;
45
+ async function failureMessage(error) {
46
+ const response = error?.response;
47
+ if (response) {
48
+ try {
49
+ const body = await response.json();
50
+ if (typeof body.detail === "string") return body.detail;
51
+ if (typeof body.detail?.message === "string") return body.detail.message;
52
+ } catch {
53
+ }
54
+ }
55
+ return "\u64CD\u4F5C\u672A\u5B8C\u6210\uFF0C\u8BF7\u91CD\u8BD5";
56
+ }
57
+ function ConnectionDialog({ client, connector, onConnected, onClose }) {
58
+ const [view, setView] = useState(null);
59
+ const [failure, setFailure] = useState(null);
60
+ const [submitting, setSubmitting] = useState(false);
61
+ const [connected, setConnected] = useState(false);
62
+ const dialog = useRef(null);
63
+ const connectionRequest = useRef(null);
64
+ useEffect(() => () => connectionRequest.current?.abort(), []);
65
+ useEffect(() => {
66
+ const element = dialog.current;
67
+ element?.showModal();
68
+ return () => element?.close();
69
+ }, []);
70
+ useEffect(() => {
71
+ const abort = new AbortController();
72
+ client.connectors.read(connector.name, { signal: abort.signal }).then((value) => {
73
+ if (!abort.signal.aborted) setView(value);
74
+ }).catch(async (error) => {
75
+ const message = await failureMessage(error);
76
+ if (!abort.signal.aborted) setFailure(message);
77
+ });
78
+ return () => abort.abort();
79
+ }, [client, connector.name]);
80
+ return /* @__PURE__ */ jsx(
81
+ "dialog",
82
+ {
83
+ ref: dialog,
84
+ "aria-label": `\u8FDE\u63A5 ${connector.display_name || connector.name}`,
85
+ className: "fixed inset-0 m-0 h-full max-h-none w-full max-w-none items-center justify-center border-0 bg-black/40 p-0 open:flex",
86
+ onCancel: (event) => {
87
+ event.preventDefault();
88
+ if (!submitting) onClose();
89
+ },
90
+ children: /* @__PURE__ */ jsxs("div", { className: "max-h-[80vh] w-[min(448px,calc(100%_-_32px))] overflow-y-auto rounded-lg border border-[hsl(var(--border))] bg-[hsl(var(--popover))] p-4 text-[hsl(var(--popover-foreground))] shadow-lg", children: [
91
+ /* @__PURE__ */ jsxs("div", { className: "mb-3 text-sm font-medium", children: [
92
+ "\u8FDE\u63A5 ",
93
+ connector.display_name || connector.name
94
+ ] }),
95
+ /* @__PURE__ */ jsx("p", { className: "mb-3 text-xs text-[hsl(var(--muted-foreground))]", children: view?.config.state === "not_required" ? "\u65E0\u9700\u586B\u5199\u8D26\u53F7\uFF0C\u9A8C\u8BC1\u8FDE\u63A5\u540E\u5373\u53EF\u4F7F\u7528\u3002" : "\u586B\u5199\u4F60\u7684\u8D26\u53F7\u4FE1\u606F\uFF0C\u9A8C\u8BC1\u901A\u8FC7\u540E\u53EF\u5728\u5BF9\u8BDD\u4E2D\u4F7F\u7528\u3002" }),
96
+ !view ? /* @__PURE__ */ jsx("div", { className: "text-xs", children: failure || "\u6B63\u5728\u83B7\u53D6\u8BA4\u8BC1\u8868\u5355\u2026" }) : !connected ? /* @__PURE__ */ jsx(
97
+ SessionPluginConfigForm,
98
+ {
99
+ config: view.config,
100
+ submitting,
101
+ errors: [],
102
+ failure,
103
+ submitLabel: "\u8FDE\u63A5\u5E76\u9A8C\u8BC1",
104
+ submittingLabel: "\u6B63\u5728\u8FDE\u63A5\u2026",
105
+ onCancel: onClose,
106
+ onSubmit: async (draft) => {
107
+ setSubmitting(true);
108
+ setFailure(null);
109
+ const request = new AbortController();
110
+ connectionRequest.current = request;
111
+ try {
112
+ const connected2 = await client.connectors.connect(connector.name, {
113
+ expected_revision: view.config.revision,
114
+ set: draft.set,
115
+ remove: draft.remove
116
+ }, { signal: request.signal });
117
+ if (request.signal.aborted) return;
118
+ setView(connected2);
119
+ setConnected(true);
120
+ try {
121
+ await onConnected();
122
+ } catch {
123
+ if (!request.signal.aborted) setFailure("\u5DF2\u8FDE\u63A5\uFF0C\u4F46\u5217\u8868\u6216\u5BF9\u8BDD\u72B6\u6001\u672A\u80FD\u5237\u65B0\u3002\u8BF7\u8FD4\u56DE\u540E\u91CD\u8BD5\u5237\u65B0\u6216\u542F\u7528\u3002");
124
+ return;
125
+ }
126
+ if (!request.signal.aborted) onClose();
127
+ } catch (error) {
128
+ if (request.signal.aborted) return;
129
+ setFailure(await failureMessage(error));
130
+ try {
131
+ setView(await client.connectors.read(connector.name));
132
+ } catch {
133
+ }
134
+ } finally {
135
+ if (!request.signal.aborted) setSubmitting(false);
136
+ }
137
+ }
138
+ }
139
+ ) : null,
140
+ connected ? /* @__PURE__ */ jsx("output", { className: "block text-sm", children: failure || "\u8D26\u53F7\u5DF2\u8FDE\u63A5\u3002" }) : null,
141
+ !view || connected ? /* @__PURE__ */ jsx("button", { type: "button", className: `${actionClass} mt-3`, onClick: onClose, children: "\u5173\u95ED" }) : null
142
+ ] })
143
+ }
144
+ );
145
+ }
146
+ function AccountConnectors({ client, query = "", sessionId = null, pending, onUse, onSelectedChange, disabledReason, catalog = false, manage = false, onEdit }) {
147
+ const [items, setItems] = useState([]);
148
+ const [editing, setEditing] = useState(null);
149
+ const [failure, setFailure] = useState(null);
150
+ const [loading, setLoading] = useState(true);
151
+ const deleteDialog = useRef(null);
152
+ const [deleting, setDeleting] = useState(null);
153
+ const [busy, setBusy] = useState(false);
154
+ useEffect(() => {
155
+ if (!deleting) return;
156
+ const element = deleteDialog.current;
157
+ element?.showModal();
158
+ return () => element?.close();
159
+ }, [deleting]);
160
+ const lifetime = useRef(null);
161
+ const load = useCallback((signal) => sessionId ? client.connectors.listForSession(sessionId, { signal }) : client.connectors.list({ signal }), [client, sessionId]);
162
+ const reload = useCallback(async () => {
163
+ const scope = lifetime.current;
164
+ const result = await load(scope?.signal);
165
+ if (scope === lifetime.current && !scope?.signal.aborted) {
166
+ setItems(result.connectors);
167
+ setFailure(null);
168
+ }
169
+ }, [load]);
170
+ useEffect(() => {
171
+ const abort = new AbortController();
172
+ lifetime.current = abort;
173
+ setItems([]);
174
+ setEditing(null);
175
+ setLoading(true);
176
+ setFailure(null);
177
+ load(abort.signal).then((value) => {
178
+ if (!abort.signal.aborted) setItems(value.connectors);
179
+ }).catch(async (error) => {
180
+ const message = await failureMessage(error);
181
+ if (!abort.signal.aborted) setFailure(message);
182
+ }).finally(() => {
183
+ if (!abort.signal.aborted) setLoading(false);
184
+ });
185
+ return () => abort.abort();
186
+ }, [load]);
187
+ const visible = useMemo(() => {
188
+ const result = items.map((item) => ({ ...item, active: sessionId ? item.active : Boolean(pending?.has(item.name)) }));
189
+ if (!sessionId) {
190
+ for (const name of pending ?? []) if (isLocalConnectorName(name) && !result.some((item) => item.name === name)) result.push({ name, display_name: name.split(".").slice(2).join("."), description: "\u8FDE\u63A5\u5668\u5DF2\u79FB\u9664\u6216\u6682\u4E0D\u53EF\u7528\uFF0C\u53EF\u53D6\u6D88\u9009\u62E9\u540E\u7EE7\u7EED\u3002", source: name.startsWith("connector.builtin.") ? "builtin" : "personal", operations: 0, available: false, active: true, connection: { status: "disconnected", revision: 0, account_label: "", verified_at: "" } });
191
+ }
192
+ return result;
193
+ }, [items, pending, sessionId]);
194
+ const selected = useRef(onSelectedChange);
195
+ selected.current = onSelectedChange;
196
+ useEffect(() => {
197
+ selected.current?.(visible.filter((item) => item.active));
198
+ }, [visible]);
199
+ const matching = visible.filter((item) => `${item.display_name} ${item.description}`.toLowerCase().includes(query.trim().toLowerCase()));
200
+ return /* @__PURE__ */ jsxs("div", { className: catalog ? "grid gap-4 sm:grid-cols-2 xl:grid-cols-3" : void 0, children: [
201
+ !catalog ? /* @__PURE__ */ jsx("div", { className: "px-3 pt-1 pb-0.5 text-[10px] text-[hsl(var(--muted-foreground))]", children: "\u8FDE\u63A5\u5668" }) : null,
202
+ loading ? /* @__PURE__ */ jsx("p", { className: "px-3 py-2 text-xs text-[hsl(var(--muted-foreground))]", children: "\u6B63\u5728\u8BFB\u53D6\u8FDE\u63A5\u5668\u2026" }) : null,
203
+ !loading && !failure && matching.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-3 py-2 text-xs text-[hsl(var(--muted-foreground))]", children: query.trim() ? "\u6CA1\u6709\u5339\u914D\u7684\u8FDE\u63A5\u5668" : "\u6682\u65E0\u8FDE\u63A5\u5668" }) : null,
204
+ matching.map((item) => {
205
+ const connected = item.connection.status === "connected";
206
+ const label = item.display_name || item.name;
207
+ const ConnectIcon = connected ? RefreshCw : Plug;
208
+ const connectionClass = `${actionClass} ${!connected ? "border-transparent bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))] hover:bg-[hsl(var(--primary)/0.9)]" : ""}`;
209
+ const connectAction = /* @__PURE__ */ jsxs("button", { type: "button", disabled: busy || item.available === false, className: connectionClass, onClick: () => {
210
+ setFailure(null);
211
+ setEditing(item);
212
+ }, children: [
213
+ /* @__PURE__ */ jsx(ConnectIcon, { size: 14, "aria-hidden": "true" }),
214
+ connected ? "\u91CD\u65B0\u8FDE\u63A5" : "\u8FDE\u63A5"
215
+ ] });
216
+ const useAction = onUse && (item.active || connected && item.operations > 0) ? /* @__PURE__ */ jsxs("button", { type: "button", disabled: busy || Boolean(disabledReason), title: disabledReason ?? void 0, className: actionClass, onClick: async () => {
217
+ const scope = lifetime.current;
218
+ setBusy(true);
219
+ setFailure(null);
220
+ try {
221
+ await onUse(item.name, label, !item.active);
222
+ if (sessionId) await reload();
223
+ } catch (error) {
224
+ const message = await failureMessage(error);
225
+ if (scope === lifetime.current && !scope?.signal.aborted) setFailure(message);
226
+ } finally {
227
+ if (scope === lifetime.current && !scope?.signal.aborted) setBusy(false);
228
+ }
229
+ }, children: [
230
+ item.active ? /* @__PURE__ */ jsx(Check, { size: 14, "aria-hidden": "true" }) : null,
231
+ item.active ? "\u5DF2\u542F\u7528" : "\u4F7F\u7528"
232
+ ] }) : null;
233
+ return /* @__PURE__ */ jsxs("div", { className: catalog ? "flex flex-col rounded-xl border border-[hsl(var(--border))] bg-[hsl(var(--card))] p-5" : "px-3 py-1.5", children: [
234
+ /* @__PURE__ */ jsxs("div", { className: `flex items-center gap-2 ${catalog ? "text-sm" : "text-[11px]"}`, children: [
235
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate font-medium", title: label, children: label }),
236
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 rounded-md bg-[hsl(var(--muted))] px-1.5 py-0.5 text-[10px] text-[hsl(var(--muted-foreground))]", children: item.source === "builtin" ? "\u5185\u7F6E" : "\u6211\u521B\u5EFA\u7684" }),
237
+ !catalog ? /* @__PURE__ */ jsxs(Fragment, { children: [
238
+ connectAction,
239
+ useAction
240
+ ] }) : null
241
+ ] }),
242
+ catalog ? /* @__PURE__ */ jsx("p", { className: "mt-2 flex-1 text-xs leading-5 text-[hsl(var(--muted-foreground))]", children: item.description }) : null,
243
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 flex items-center gap-1.5 text-[11px] text-[hsl(var(--muted-foreground))]", children: [
244
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: `size-1.5 shrink-0 rounded-full ${connected ? "bg-emerald-500" : "bg-[hsl(var(--muted-foreground)/0.4)]"}` }),
245
+ connected ? `\u5DF2\u8FDE\u63A5${item.connection.account_label ? ` \xB7 ${item.connection.account_label}` : ""}${item.operations === 0 ? "\uFF0C\u5C1A\u672A\u914D\u7F6E\u4E1A\u52A1\u64CD\u4F5C" : ""}` : item.connection.status === "auth_required" ? "\u9700\u8981\u91CD\u65B0\u8BA4\u8BC1" : "\u672A\u8FDE\u63A5"
246
+ ] }),
247
+ catalog ? /* @__PURE__ */ jsxs("div", { className: "mt-4 flex items-center gap-2 border-t border-[hsl(var(--border))] pt-4", children: [
248
+ connectAction,
249
+ useAction,
250
+ manage ? /* @__PURE__ */ jsxs("div", { className: "ml-auto flex items-center gap-1.5", children: [
251
+ /* @__PURE__ */ jsx("button", { type: "button", disabled: busy, "aria-label": "\u65AD\u5F00", title: "\u65AD\u5F00\u8FDE\u63A5", className: iconActionClass, onClick: async () => {
252
+ setBusy(true);
253
+ try {
254
+ await client.connectors.disconnect(item.name);
255
+ await reload();
256
+ } catch (error) {
257
+ setFailure(await failureMessage(error));
258
+ } finally {
259
+ setBusy(false);
260
+ }
261
+ }, children: /* @__PURE__ */ jsx(Unplug, { size: 15, "aria-hidden": "true" }) }),
262
+ item.source === "personal" ? /* @__PURE__ */ jsxs(Fragment, { children: [
263
+ /* @__PURE__ */ jsx("button", { type: "button", disabled: busy, "aria-label": "\u7F16\u8F91", title: "\u7F16\u8F91\u8FDE\u63A5\u5668", className: iconActionClass, onClick: () => onEdit?.(item), children: /* @__PURE__ */ jsx(Pencil, { size: 15, "aria-hidden": "true" }) }),
264
+ /* @__PURE__ */ jsx("button", { type: "button", disabled: busy, "aria-label": "\u5220\u9664", title: "\u5220\u9664\u8FDE\u63A5\u5668", className: `${iconActionClass} text-[hsl(var(--destructive))] hover:border-[hsl(var(--destructive)/0.3)] hover:bg-[hsl(var(--destructive)/0.08)]`, onClick: () => setDeleting(item), children: /* @__PURE__ */ jsx(Trash2, { size: 15, "aria-hidden": "true" }) })
265
+ ] }) : null
266
+ ] }) : null
267
+ ] }) : null
268
+ ] }, item.name);
269
+ }),
270
+ deleting ? /* @__PURE__ */ jsxs("dialog", { ref: deleteDialog, onCancel: (event) => {
271
+ event.preventDefault();
272
+ if (!busy) setDeleting(null);
273
+ }, "aria-label": "\u5220\u9664\u8FDE\u63A5\u5668", className: "fixed inset-0 z-50 m-auto w-[min(420px,calc(100%_-_32px))] rounded-xl border border-[hsl(var(--border))] bg-[hsl(var(--popover))] p-5 text-[hsl(var(--popover-foreground))] shadow-xl", children: [
274
+ /* @__PURE__ */ jsxs("p", { className: "text-sm", children: [
275
+ "\u5220\u9664\u300C",
276
+ deleting.display_name || deleting.name,
277
+ "\u300D\u53CA\u5176\u8FDE\u63A5\u4FE1\u606F\uFF1F\u9700\u8981\u4F7F\u7528\u65F6\u53EF\u4EE5\u91CD\u65B0\u521B\u5EFA\u3002"
278
+ ] }),
279
+ /* @__PURE__ */ jsxs("div", { className: "mt-4 flex justify-end gap-3", children: [
280
+ /* @__PURE__ */ jsx("button", { type: "button", disabled: busy, className: actionClass, onClick: () => setDeleting(null), children: "\u53D6\u6D88" }),
281
+ /* @__PURE__ */ jsx("button", { type: "button", disabled: busy, className: `${actionClass} border-transparent bg-[hsl(var(--destructive))] text-white hover:bg-[hsl(var(--destructive)/0.9)]`, onClick: async () => {
282
+ setBusy(true);
283
+ try {
284
+ await client.connectors.delete(deleting.name);
285
+ setDeleting(null);
286
+ await reload();
287
+ } catch (error) {
288
+ setFailure(await failureMessage(error));
289
+ setDeleting(null);
290
+ } finally {
291
+ setBusy(false);
292
+ }
293
+ }, children: busy ? "\u6B63\u5728\u5220\u9664\u2026" : "\u786E\u8BA4\u5220\u9664" })
294
+ ] })
295
+ ] }) : null,
296
+ failure ? /* @__PURE__ */ jsxs("div", { role: "alert", className: "px-3 py-1 text-xs text-[hsl(var(--destructive))]", children: [
297
+ failure,
298
+ /* @__PURE__ */ jsx("button", { type: "button", className: `${actionClass} ml-2`, onClick: () => {
299
+ void reload().catch(() => setFailure("\u8BFB\u53D6\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5"));
300
+ }, children: "\u91CD\u8BD5" })
301
+ ] }) : null,
302
+ editing?.authorization?.type === "browser" ? /* @__PURE__ */ jsx(BrowserConnectionDialog, { client, name: editing.name, label: editing.display_name || editing.name, onClose: () => {
303
+ setEditing(null);
304
+ void reload().catch(() => setFailure("\u8BFB\u53D6\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5"));
305
+ } }, editing.name) : editing ? /* @__PURE__ */ jsx(ConnectionDialog, { client, connector: editing, onClose: () => setEditing(null), onConnected: async () => {
306
+ const scope = lifetime.current;
307
+ await reload();
308
+ if (scope !== lifetime.current || scope?.signal.aborted) return;
309
+ if (onUse && editing.operations > 0 && !disabledReason) {
310
+ await onUse(editing.name, editing.display_name || editing.name, true);
311
+ if (sessionId) await reload();
312
+ }
313
+ } }, editing.name) : null
314
+ ] });
315
+ }
316
+
317
+ // src/components/ConnectorPanel.tsx
318
+ import { isLocalConnectorName as isLocalConnectorName2 } from "@blade-hq/agent-client/internal";
319
+ import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState as useState2 } from "react";
320
+ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
321
+ function PanelDivider() {
322
+ return /* @__PURE__ */ jsx2("div", { className: "my-1 border-[hsl(var(--border))] border-t" });
323
+ }
324
+ function PanelTitle({ children }) {
325
+ return /* @__PURE__ */ jsx2("div", { className: "px-3 pt-1 pb-0.5 text-[10px] text-[hsl(var(--muted-foreground))]", children });
326
+ }
327
+ function ConnectorPanel({
328
+ client,
329
+ sessionId,
330
+ pendingPlugins,
331
+ onPendingPluginsChange,
332
+ pendingDisabledReason,
333
+ onPluginsChanged,
334
+ onSelectedChange,
335
+ renderComputerSection,
336
+ renderExternalAccounts,
337
+ renderFooterActions,
338
+ className
339
+ }) {
340
+ const [query, setQuery] = useState2("");
341
+ const [selectedConnectors, setSelectedConnectors] = useState2([]);
342
+ const [pendingCatalog, setPendingCatalog] = useState2(null);
343
+ const [pendingLoading, setPendingLoading] = useState2(false);
344
+ const [pendingError, setPendingError] = useState2(false);
345
+ const activation = useSessionPluginActivation({
346
+ client,
347
+ sessionId: sessionId ?? "",
348
+ onChange: onPluginsChanged
349
+ });
350
+ const { reload, items: sessionItems } = activation;
351
+ useEffect2(() => {
352
+ if (sessionId) reload();
353
+ }, [reload, sessionId]);
354
+ const loadPendingCatalog = useCallback2(async () => {
355
+ setPendingLoading(true);
356
+ setPendingError(false);
357
+ try {
358
+ const { plugins } = await client.plugins.catalog();
359
+ setPendingCatalog(plugins);
360
+ } catch {
361
+ setPendingError(true);
362
+ } finally {
363
+ setPendingLoading(false);
364
+ }
365
+ }, [client]);
366
+ useEffect2(() => {
367
+ if (sessionId) return;
368
+ void loadPendingCatalog();
369
+ }, [loadPendingCatalog, sessionId]);
370
+ const selected = useMemo2(
371
+ () => new Set((pendingPlugins ?? []).map((item) => item.name)),
372
+ [pendingPlugins]
373
+ );
374
+ const items = useMemo2(() => {
375
+ if (sessionId) return sessionItems;
376
+ return catalogToConnectorItems(pendingCatalog ?? []).map((item) => ({
377
+ ...item,
378
+ active: selected.has(item.name)
379
+ }));
380
+ }, [pendingCatalog, selected, sessionId, sessionItems]);
381
+ const visibleItems = useMemo2(
382
+ () => sessionId ? filterConnectorItems(items, query) : filterConnectorItems(items, query).map((item) => ({ ...item, config: void 0 })),
383
+ [items, query, sessionId]
384
+ );
385
+ const togglePending = useCallback2((item, next) => {
386
+ const kept = (pendingPlugins ?? []).filter((entry) => entry.name !== item.name);
387
+ onPendingPluginsChange?.(next ? [...kept, selectionFromItem(item)] : kept);
388
+ }, [onPendingPluginsChange, pendingPlugins]);
389
+ const toggle = useCallback2((item, next) => {
390
+ if (sessionId) activation.toggle(item, next);
391
+ else togglePending(item, next);
392
+ }, [activation, sessionId, togglePending]);
393
+ const selectedItems = useMemo2(
394
+ () => [
395
+ ...items.filter((item) => item.active && !isLocalConnectorName2(item.name)),
396
+ ...selectedConnectors.map((item) => ({
397
+ name: item.name,
398
+ display_name: item.display_name,
399
+ icon: null,
400
+ iconSource: "account",
401
+ installed: true,
402
+ active: true,
403
+ status: "valid",
404
+ projected: false,
405
+ connector: true
406
+ }))
407
+ ],
408
+ [items, selectedConnectors]
409
+ );
410
+ const onSelectedChangeRef = useRef2(onSelectedChange);
411
+ onSelectedChangeRef.current = onSelectedChange;
412
+ const latestSelected = useRef2(selectedItems);
413
+ latestSelected.current = selectedItems;
414
+ const reportSelected = useCallback2(() => {
415
+ onSelectedChangeRef.current?.(latestSelected.current);
416
+ }, []);
417
+ const selectedNames = selectedItems.map((item) => item.name).join("\0");
418
+ useEffect2(reportSelected, [reportSelected, selectedNames]);
419
+ const failed = sessionId ? activation.listError : pendingError;
420
+ return /* @__PURE__ */ jsxs2("div", { className, children: [
421
+ /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2 border-[hsl(var(--border))] border-b px-3 py-2", children: [
422
+ /* @__PURE__ */ jsx2(Search, { size: 12, className: "shrink-0 text-[hsl(var(--muted-foreground))]", "aria-hidden": "true" }),
423
+ /* @__PURE__ */ jsx2(
424
+ "input",
425
+ {
426
+ value: query,
427
+ onChange: (event) => setQuery(event.target.value),
428
+ placeholder: "\u641C\u7D22\u8FDE\u63A5\u5668",
429
+ "aria-label": "\u641C\u7D22\u8FDE\u63A5\u5668",
430
+ className: "min-w-0 flex-1 bg-transparent text-[11px] outline-none placeholder:text-[hsl(var(--muted-foreground))]"
431
+ }
432
+ )
433
+ ] }),
434
+ renderComputerSection ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
435
+ /* @__PURE__ */ jsx2(PanelTitle, { children: "\u7535\u8111" }),
436
+ renderComputerSection(query)
437
+ ] }) : null,
438
+ /* @__PURE__ */ jsx2(PanelDivider, {}),
439
+ /* @__PURE__ */ jsx2(
440
+ AccountConnectors,
441
+ {
442
+ client,
443
+ query,
444
+ sessionId,
445
+ pending: selected,
446
+ onSelectedChange: setSelectedConnectors,
447
+ disabledReason: pendingDisabledReason,
448
+ onUse: async (name, label, enabled) => {
449
+ if (sessionId) {
450
+ const result = await client.sessions.setSessionPluginActivation(sessionId, name, enabled);
451
+ onPluginsChanged?.(result.plugin);
452
+ } else {
453
+ const kept = (pendingPlugins ?? []).filter((item) => item.name !== name);
454
+ onPendingPluginsChange?.(enabled ? [...kept, { name, displayName: label, icon: null, iconSource: "account" }] : kept);
455
+ }
456
+ }
457
+ }
458
+ ),
459
+ /* @__PURE__ */ jsx2(PanelDivider, {}),
460
+ /* @__PURE__ */ jsx2(PanelTitle, { children: "\u63D2\u4EF6" }),
461
+ !sessionId ? /* @__PURE__ */ jsx2("div", { className: "px-3 pb-0.5 text-[10px] text-[hsl(var(--muted-foreground))]", children: "\u5148\u9009\u4E2D\uFF0C\u53D1\u9001\u8FD9\u6761\u6D88\u606F\u65F6\u4F1A\u4E00\u5E76\u542F\u7528" }) : null,
462
+ (sessionId ? activation.loading : pendingLoading) && visibleItems.length === 0 ? /* @__PURE__ */ jsxs2("div", { className: "px-3 py-1.5 text-[11px] text-[hsl(var(--muted-foreground))]", children: [
463
+ /* @__PURE__ */ jsx2(LoaderCircle, { size: 12, className: "mr-1 inline animate-spin" }),
464
+ "\u6B63\u5728\u83B7\u53D6\u63D2\u4EF6\u2026"
465
+ ] }) : null,
466
+ failed ? /* @__PURE__ */ jsx2(
467
+ "button",
468
+ {
469
+ type: "button",
470
+ onClick: () => sessionId ? reload() : void loadPendingCatalog(),
471
+ className: "px-3 py-1.5 text-left text-[11px] text-[hsl(var(--muted-foreground))] underline",
472
+ children: "\u63D2\u4EF6\u5217\u8868\u6682\u4E0D\u53EF\u7528\uFF0C\u70B9\u51FB\u91CD\u8BD5"
473
+ }
474
+ ) : null,
475
+ /* @__PURE__ */ jsx2(
476
+ PluginConnectorList,
477
+ {
478
+ client,
479
+ sessionId,
480
+ items: visibleItems.filter((item) => !isLocalConnectorName2(item.name)),
481
+ inFlight: activation.inFlight,
482
+ busy: sessionId ? activation.busy : pendingLoading,
483
+ mutationError: sessionId ? activation.mutationError : false,
484
+ onToggle: toggle,
485
+ onRetry: activation.retry,
486
+ onEditConfig: activation.editConfig,
487
+ configuring: sessionId ? activation.configuring : null,
488
+ onConfigSaved: activation.onConfigSaved,
489
+ onConfigClose: activation.onConfigClose,
490
+ disabledReason: sessionId ? null : pendingDisabledReason,
491
+ unavailableText: failed ? null : query.trim() ? "\u6CA1\u6709\u5339\u914D\u7684\u63D2\u4EF6" : null,
492
+ emptyText: sessionId ? "\u5C1A\u672A\u5B89\u88C5\u63D2\u4EF6" : "\u5C1A\u672A\u5B89\u88C5\u63D2\u4EF6\uFF0C\u53BB\u5E94\u7528\u5E02\u573A\u6DFB\u52A0\u4E00\u4E2A",
493
+ draft: !sessionId
494
+ }
495
+ ),
496
+ renderExternalAccounts ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
497
+ /* @__PURE__ */ jsx2(PanelDivider, {}),
498
+ /* @__PURE__ */ jsx2(PanelTitle, { children: "\u5916\u90E8\u8D26\u53F7" }),
499
+ renderExternalAccounts(query)
500
+ ] }) : null,
501
+ renderFooterActions ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
502
+ /* @__PURE__ */ jsx2(PanelDivider, {}),
503
+ renderFooterActions()
504
+ ] }) : null
505
+ ] });
506
+ }
507
+
508
+ // src/components/PluginTriggerStack.tsx
509
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
510
+ var AVATAR_SIZE = 20;
511
+ function hueOf(name) {
512
+ let hash = 0;
513
+ for (const char of name) hash = (hash * 31 + char.codePointAt(0)) % 1000003;
514
+ return hash % 360;
515
+ }
516
+ var AVATAR_PALETTE = [
517
+ "bg-violet-100 text-violet-800 dark:bg-violet-500/25 dark:text-violet-200",
518
+ "bg-sky-100 text-sky-800 dark:bg-sky-500/25 dark:text-sky-200",
519
+ "bg-emerald-100 text-emerald-800 dark:bg-emerald-500/25 dark:text-emerald-200",
520
+ "bg-amber-100 text-amber-800 dark:bg-amber-500/25 dark:text-amber-200",
521
+ "bg-rose-100 text-rose-800 dark:bg-rose-500/25 dark:text-rose-200",
522
+ "bg-indigo-100 text-indigo-800 dark:bg-indigo-500/25 dark:text-indigo-200",
523
+ "bg-teal-100 text-teal-800 dark:bg-teal-500/25 dark:text-teal-200",
524
+ "bg-fuchsia-100 text-fuchsia-800 dark:bg-fuchsia-500/25 dark:text-fuchsia-200"
525
+ ];
526
+ function PluginTriggerStack({ client, sessionId, summary, className }) {
527
+ if (summary.total === 0) return null;
528
+ const ring = "ring-2 ring-[hsl(var(--background))]";
529
+ return /* @__PURE__ */ jsxs3("span", { className: `flex items-center -space-x-1 ${className ?? ""}`, children: [
530
+ summary.visible.map((item) => {
531
+ const label = item.displayName?.trim() || item.name;
532
+ const tone = AVATAR_PALETTE[hueOf(item.name) % AVATAR_PALETTE.length];
533
+ return item.icon ? /* @__PURE__ */ jsx3(
534
+ SessionPluginIcon,
535
+ {
536
+ client,
537
+ sessionId: item.iconSource === "account" ? null : sessionId,
538
+ plugin: item,
539
+ size: AVATAR_SIZE,
540
+ shape: "circle",
541
+ className: ring
542
+ },
543
+ item.name
544
+ ) : /* @__PURE__ */ jsx3(
545
+ "span",
546
+ {
547
+ title: label,
548
+ className: `flex shrink-0 items-center justify-center rounded-full font-semibold text-[11px] ${tone} ${ring}`,
549
+ style: { width: AVATAR_SIZE, height: AVATAR_SIZE },
550
+ children: Array.from(label.trim())[0]?.toUpperCase() ?? "?"
551
+ },
552
+ item.name
553
+ );
554
+ }),
555
+ summary.overflow > 0 ? /* @__PURE__ */ jsxs3(
556
+ "span",
557
+ {
558
+ className: `flex shrink-0 items-center justify-center rounded-full bg-[hsl(var(--muted))] font-medium text-[hsl(var(--muted-foreground))] text-[10px] ${ring}`,
559
+ style: { width: AVATAR_SIZE, height: AVATAR_SIZE },
560
+ children: [
561
+ "+",
562
+ summary.overflow
563
+ ]
564
+ }
565
+ ) : null
566
+ ] });
567
+ }
568
+
569
+ export {
570
+ selectionFromItem,
571
+ TRIGGER_ICON_LIMIT,
572
+ triggerSummary,
573
+ AccountConnectors,
574
+ ConnectorPanel,
575
+ PluginTriggerStack
576
+ };
577
+ //# sourceMappingURL=chunk-MHXY4KAY.js.map