@blade-hq/agent-react 2612.0.0-beta.77 → 2612.0.0-beta.78
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/README.md +12 -2
- package/dist/SessionPluginConfigForm-7LJ5SUNY.js +9 -0
- package/dist/SessionPluginConfigForm-7LJ5SUNY.js.map +1 -0
- package/dist/chat/LiteMessageList.d.ts +4 -0
- package/dist/{chunk-IVGTMWRV.js → chunk-66JXSDLB.js} +284 -289
- package/dist/chunk-66JXSDLB.js.map +1 -0
- package/dist/{chunk-VBHHFIII.js → chunk-C3LBNHVM.js} +12 -10
- package/dist/{chunk-VBHHFIII.js.map → chunk-C3LBNHVM.js.map} +1 -1
- package/dist/chunk-F4MWGSKX.js +1308 -0
- package/dist/chunk-F4MWGSKX.js.map +1 -0
- package/dist/{chunk-ZIECHNZA.js → chunk-FBGCDXRK.js} +110 -115
- package/dist/chunk-FBGCDXRK.js.map +1 -0
- package/dist/{chunk-26NE55SJ.js → chunk-FDB3J5QF.js} +8 -6
- package/dist/{chunk-26NE55SJ.js.map → chunk-FDB3J5QF.js.map} +1 -1
- package/dist/{chunk-WO2VCQ5N.js → chunk-FH5HA3GW.js} +551 -455
- package/dist/chunk-FH5HA3GW.js.map +1 -0
- package/dist/chunk-KHJEIHMJ.js +973 -0
- package/dist/chunk-KHJEIHMJ.js.map +1 -0
- package/dist/{chunk-XBEN2ASY.js → chunk-MWILWSPG.js} +4262 -3186
- package/dist/chunk-MWILWSPG.js.map +1 -0
- package/dist/{chunk-EQOKPZI2.js → chunk-NRYLAO3X.js} +87 -154
- package/dist/chunk-NRYLAO3X.js.map +1 -0
- package/dist/{chunk-F2IJZX2Z.js → chunk-SK43YNRX.js} +151 -1442
- package/dist/chunk-SK43YNRX.js.map +1 -0
- package/dist/components/BrowserConnectionDialog.d.ts +3 -2
- package/dist/components/ConfigConnection.d.ts +16 -0
- package/dist/components/ConnectorDetail.d.ts +46 -0
- package/dist/components/ConnectorRulesPanel.d.ts +2 -1
- package/dist/components/ConnectorRuntimeDiagnostics.d.ts +2 -2
- package/dist/components/DeviceConnection.d.ts +3 -1
- package/dist/components/FeishuPermissionStatus.d.ts +3 -1
- package/dist/components/ManagedConnection.d.ts +5 -2
- package/dist/components/PackEventNotice.d.ts +3 -0
- package/dist/components/SessionPluginConfigForm.d.ts +2 -1
- package/dist/connectors.d.ts +3 -0
- package/dist/connectors.js +7 -2
- package/dist/embed/entry.d.ts +4 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +13 -7
- package/dist/internal.js +16 -9
- package/dist/internal.js.map +1 -1
- package/dist/questions.js +8 -6
- package/dist/questions.js.map +1 -1
- package/dist/rich-input.js +16 -13
- package/dist/rich-input.js.map +1 -1
- package/dist/style.full.css +1 -1
- package/dist/tool-details.js +5 -4
- package/dist/tool-details.js.map +1 -1
- package/package.json +2 -2
- package/public-api.md +41 -3
- package/dist/chunk-EQOKPZI2.js.map +0 -1
- package/dist/chunk-F2IJZX2Z.js.map +0 -1
- package/dist/chunk-IVGTMWRV.js.map +0 -1
- package/dist/chunk-WO2VCQ5N.js.map +0 -1
- package/dist/chunk-XBEN2ASY.js.map +0 -1
- package/dist/chunk-ZIECHNZA.js.map +0 -1
|
@@ -0,0 +1,1308 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useBladeClient,
|
|
3
|
+
useSessionHandle,
|
|
4
|
+
useSessionState
|
|
5
|
+
} from "./chunk-NRYLAO3X.js";
|
|
6
|
+
import {
|
|
7
|
+
SessionPluginConfigForm
|
|
8
|
+
} from "./chunk-SK43YNRX.js";
|
|
9
|
+
import {
|
|
10
|
+
LoaderCircle,
|
|
11
|
+
Puzzle
|
|
12
|
+
} from "./chunk-KHJEIHMJ.js";
|
|
13
|
+
|
|
14
|
+
// src/chat/runtime/input-history-storage.ts
|
|
15
|
+
var generation = 0;
|
|
16
|
+
function getInputHistoryGeneration() {
|
|
17
|
+
return generation;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// src/chat/runtime/input-cache-reset.ts
|
|
21
|
+
var resetters = /* @__PURE__ */ new WeakMap();
|
|
22
|
+
function onInputCachesReset(client, reset) {
|
|
23
|
+
let set = resetters.get(client);
|
|
24
|
+
if (!set) {
|
|
25
|
+
set = /* @__PURE__ */ new Set();
|
|
26
|
+
resetters.set(client, set);
|
|
27
|
+
}
|
|
28
|
+
set.add(reset);
|
|
29
|
+
return () => {
|
|
30
|
+
set.delete(reset);
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// src/rich-input/lib/plugin-suggestions.ts
|
|
35
|
+
var accountPreparation = /* @__PURE__ */ new WeakMap();
|
|
36
|
+
var registeredClients = /* @__PURE__ */ new WeakSet();
|
|
37
|
+
function pluginRefsForSend(refs, turns = []) {
|
|
38
|
+
const latest = [...turns].reverse().find((turn) => turn.role === "user");
|
|
39
|
+
return refs.length || latest?.refs?.length ? refs : void 0;
|
|
40
|
+
}
|
|
41
|
+
function prepareAccount(client) {
|
|
42
|
+
if (!registeredClients.has(client)) {
|
|
43
|
+
registeredClients.add(client);
|
|
44
|
+
onInputCachesReset(client, () => accountPreparation.delete(client));
|
|
45
|
+
}
|
|
46
|
+
const current = accountPreparation.get(client);
|
|
47
|
+
if (current && current.token === client.token) return current.ready;
|
|
48
|
+
const ready = client.plugins.catalog().catch((error) => {
|
|
49
|
+
accountPreparation.delete(client);
|
|
50
|
+
throw error;
|
|
51
|
+
});
|
|
52
|
+
accountPreparation.set(client, { token: client.token, ready });
|
|
53
|
+
return ready;
|
|
54
|
+
}
|
|
55
|
+
async function getPluginSuggestionItems(client, session, query, pendingPlugins = []) {
|
|
56
|
+
if (!session && pendingPlugins.length === 0) return [];
|
|
57
|
+
const path = session ? `/api/sessions/${encodeURIComponent(session)}/plugin-tools/mentions` : "/api/plugin-mentions/mentions";
|
|
58
|
+
try {
|
|
59
|
+
if (!session) await prepareAccount(client);
|
|
60
|
+
const data = await client.json("POST", path, { arguments: { query, cursor: "" }, ...!session ? { plugin_selection: pendingPlugins } : {} });
|
|
61
|
+
return [...data.items.map((item) => ({ ...item, kind: "plugin", name: item.label })), ...data.errors.map((message) => ({ kind: "pluginError", message }))];
|
|
62
|
+
} catch {
|
|
63
|
+
return [{ kind: "pluginError", message: "\u63D2\u4EF6\u6761\u76EE\u6682\u65F6\u65E0\u6CD5\u52A0\u8F7D\uFF0C\u8BF7\u91CD\u65B0\u8F93\u5165 @ \u91CD\u8BD5" }];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/chat/runtime/input-services.tsx
|
|
68
|
+
import { createContext, useContext } from "react";
|
|
69
|
+
import { jsx } from "react/jsx-runtime";
|
|
70
|
+
function parseModelReasoningLevels(value) {
|
|
71
|
+
if (!value) return {};
|
|
72
|
+
try {
|
|
73
|
+
const parsed = JSON.parse(value);
|
|
74
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
75
|
+
return Object.fromEntries(
|
|
76
|
+
Object.entries(parsed).filter(
|
|
77
|
+
(entry) => Boolean(entry[0]) && typeof entry[1] === "string" && Boolean(entry[1])
|
|
78
|
+
)
|
|
79
|
+
);
|
|
80
|
+
} catch {
|
|
81
|
+
return {};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
var RichInputServicesContext = createContext({});
|
|
85
|
+
function RichInputServicesProvider({ value, children }) {
|
|
86
|
+
return /* @__PURE__ */ jsx(RichInputServicesContext.Provider, { value, children });
|
|
87
|
+
}
|
|
88
|
+
function useRichInputServices() {
|
|
89
|
+
return useContext(RichInputServicesContext);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/rich-input/lib/client-query.ts
|
|
93
|
+
import { useCallback, useEffect, useSyncExternalStore } from "react";
|
|
94
|
+
var EMPTY = { data: void 0, error: null, isLoading: false, isFetching: false };
|
|
95
|
+
var caches = /* @__PURE__ */ new WeakMap();
|
|
96
|
+
function entryFor(client, key) {
|
|
97
|
+
let cache = caches.get(client);
|
|
98
|
+
if (!cache) {
|
|
99
|
+
const created = /* @__PURE__ */ new Map();
|
|
100
|
+
cache = created;
|
|
101
|
+
caches.set(client, created);
|
|
102
|
+
onInputCachesReset(client, () => {
|
|
103
|
+
for (const [key2, entry2] of created) {
|
|
104
|
+
entry2.request = null;
|
|
105
|
+
entry2.fetchedAt = 0;
|
|
106
|
+
update(entry2, EMPTY);
|
|
107
|
+
const load = loadersFor(client).get(key2);
|
|
108
|
+
if (load && entry2.listeners.size > 0) void fetchClientQuery(client, key2, load, { force: true }).catch(() => {
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
let entry = cache.get(key);
|
|
114
|
+
if (!entry) {
|
|
115
|
+
entry = { snapshot: EMPTY, fetchedAt: 0, request: null, listeners: /* @__PURE__ */ new Set() };
|
|
116
|
+
cache.set(key, entry);
|
|
117
|
+
}
|
|
118
|
+
return entry;
|
|
119
|
+
}
|
|
120
|
+
function update(entry, patch) {
|
|
121
|
+
entry.snapshot = { ...entry.snapshot, ...patch };
|
|
122
|
+
for (const listener of [...entry.listeners]) listener();
|
|
123
|
+
}
|
|
124
|
+
function fetchClientQuery(client, key, load, { staleMs = 3e4, force = false } = {}) {
|
|
125
|
+
const entry = entryFor(client, key);
|
|
126
|
+
if (entry.request) return entry.request;
|
|
127
|
+
if (!force && entry.snapshot.data !== void 0 && Date.now() - entry.fetchedAt < staleMs) {
|
|
128
|
+
return Promise.resolve(entry.snapshot.data);
|
|
129
|
+
}
|
|
130
|
+
const request = Promise.resolve().then(() => load(client));
|
|
131
|
+
entry.request = request;
|
|
132
|
+
update(entry, { isFetching: true, isLoading: entry.snapshot.data === void 0 });
|
|
133
|
+
request.then(
|
|
134
|
+
(data) => {
|
|
135
|
+
if (entry.request !== request) return;
|
|
136
|
+
entry.request = null;
|
|
137
|
+
entry.fetchedAt = Date.now();
|
|
138
|
+
update(entry, { data, error: null, isFetching: false, isLoading: false });
|
|
139
|
+
},
|
|
140
|
+
(error) => {
|
|
141
|
+
if (entry.request !== request) return;
|
|
142
|
+
entry.request = null;
|
|
143
|
+
update(entry, { error, isFetching: false, isLoading: false });
|
|
144
|
+
}
|
|
145
|
+
);
|
|
146
|
+
return request;
|
|
147
|
+
}
|
|
148
|
+
function setClientQueryData(client, key, updater) {
|
|
149
|
+
const entry = entryFor(client, key);
|
|
150
|
+
if (entry.snapshot.data === void 0) return;
|
|
151
|
+
update(entry, { data: updater(entry.snapshot.data) });
|
|
152
|
+
}
|
|
153
|
+
function useClientQuery(key, load, { staleMs = 3e4 } = {}) {
|
|
154
|
+
const client = useBladeClient();
|
|
155
|
+
const subscribe = useCallback(
|
|
156
|
+
(listener) => {
|
|
157
|
+
if (!key) return () => {
|
|
158
|
+
};
|
|
159
|
+
const entry = entryFor(client, key);
|
|
160
|
+
entry.listeners.add(listener);
|
|
161
|
+
return () => {
|
|
162
|
+
entry.listeners.delete(listener);
|
|
163
|
+
};
|
|
164
|
+
},
|
|
165
|
+
[client, key]
|
|
166
|
+
);
|
|
167
|
+
const snapshot = useSyncExternalStore(
|
|
168
|
+
subscribe,
|
|
169
|
+
() => key ? entryFor(client, key).snapshot : EMPTY,
|
|
170
|
+
() => key ? entryFor(client, key).snapshot : EMPTY
|
|
171
|
+
);
|
|
172
|
+
if (key) loadersFor(client).set(key, load);
|
|
173
|
+
useEffect(() => {
|
|
174
|
+
if (!key) return;
|
|
175
|
+
const registered = loadersFor(client).get(key);
|
|
176
|
+
if (registered) void fetchClientQuery(client, key, registered, { staleMs }).catch(() => {
|
|
177
|
+
});
|
|
178
|
+
}, [client, key, staleMs]);
|
|
179
|
+
const refetch = useCallback(async () => {
|
|
180
|
+
if (!key) return void 0;
|
|
181
|
+
const registered = loadersFor(client).get(key);
|
|
182
|
+
if (!registered) return void 0;
|
|
183
|
+
return fetchClientQuery(client, key, registered, { force: true }).catch(() => void 0);
|
|
184
|
+
}, [client, key]);
|
|
185
|
+
return { ...snapshot, refetch };
|
|
186
|
+
}
|
|
187
|
+
var loaderRegistry = /* @__PURE__ */ new WeakMap();
|
|
188
|
+
function loadersFor(client) {
|
|
189
|
+
let loaders = loaderRegistry.get(client);
|
|
190
|
+
if (!loaders) {
|
|
191
|
+
loaders = /* @__PURE__ */ new Map();
|
|
192
|
+
loaderRegistry.set(client, loaders);
|
|
193
|
+
}
|
|
194
|
+
return loaders;
|
|
195
|
+
}
|
|
196
|
+
function refreshClientQuery(client, key) {
|
|
197
|
+
const entry = caches.get(client)?.get(key);
|
|
198
|
+
if (!entry) return;
|
|
199
|
+
entry.fetchedAt = 0;
|
|
200
|
+
const load = loadersFor(client).get(key);
|
|
201
|
+
if (load && entry.listeners.size > 0) void fetchClientQuery(client, key, load, { force: true }).catch(() => {
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// src/rich-input/lib/session-data.ts
|
|
206
|
+
import { useEffect as useEffect2, useRef } from "react";
|
|
207
|
+
var sessionPath = (sessionId, rest) => `/api/sessions/${encodeURIComponent(sessionId)}/${rest}`;
|
|
208
|
+
function useRefreshWhenRunEnds(sessionId, keys) {
|
|
209
|
+
const client = useBladeClient();
|
|
210
|
+
const streaming = useSessionState(sessionId, (state) => state.isStreaming);
|
|
211
|
+
const wasStreaming = useRef(streaming);
|
|
212
|
+
useEffect2(() => {
|
|
213
|
+
if (wasStreaming.current && !streaming) {
|
|
214
|
+
for (const key of keys) refreshClientQuery(client, key);
|
|
215
|
+
}
|
|
216
|
+
wasStreaming.current = streaming;
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
function useClientSessionSkills(sessionId) {
|
|
220
|
+
const key = sessionId ? `session-skills:${sessionId}` : null;
|
|
221
|
+
const query = useClientQuery(
|
|
222
|
+
key,
|
|
223
|
+
(client) => client.json("GET", sessionPath(sessionId ?? "", "skills"))
|
|
224
|
+
);
|
|
225
|
+
useRefreshWhenRunEnds(sessionId, key ? [key] : []);
|
|
226
|
+
return { data: query.data, loading: query.isLoading, error: query.error };
|
|
227
|
+
}
|
|
228
|
+
function useClientSkillStats(sessionId) {
|
|
229
|
+
const key = sessionId ? `skill-stats:${sessionId}` : null;
|
|
230
|
+
const query = useClientQuery(key, (client) => client.json("GET", sessionPath(sessionId ?? "", "skill-stats")), {
|
|
231
|
+
staleMs: 3e5
|
|
232
|
+
});
|
|
233
|
+
useRefreshWhenRunEnds(sessionId, key ? [key] : []);
|
|
234
|
+
return { data: query.data, loading: query.isLoading, error: query.error };
|
|
235
|
+
}
|
|
236
|
+
function useClientContextStats(sessionId) {
|
|
237
|
+
const key = sessionId ? `context-stats:${sessionId}` : null;
|
|
238
|
+
const query = useClientQuery(
|
|
239
|
+
key,
|
|
240
|
+
(client) => client.json("GET", sessionPath(sessionId ?? "", "context-stats"))
|
|
241
|
+
);
|
|
242
|
+
useRefreshWhenRunEnds(sessionId, key ? [key] : []);
|
|
243
|
+
return { data: query.data, loading: query.isLoading, error: query.error };
|
|
244
|
+
}
|
|
245
|
+
function useClientBackgroundTasks(sessionId) {
|
|
246
|
+
const client = useBladeClient();
|
|
247
|
+
const key = sessionId ? `background-tasks:${sessionId}` : null;
|
|
248
|
+
const query = useClientQuery(key, (c) => c.sessions.listBackgroundTasks(sessionId ?? ""));
|
|
249
|
+
const session = useSessionHandle(sessionId);
|
|
250
|
+
useEffect2(() => {
|
|
251
|
+
if (!session || !key) return;
|
|
252
|
+
return session.on("backgroundTask", () => refreshClientQuery(client, key));
|
|
253
|
+
}, [client, key, session]);
|
|
254
|
+
return { data: query.data, loading: query.isLoading, error: query.error };
|
|
255
|
+
}
|
|
256
|
+
function refreshClientSessionSkills(client, sessionId) {
|
|
257
|
+
refreshClientQuery(client, `session-skills:${sessionId}`);
|
|
258
|
+
refreshClientQuery(client, `skill-stats:${sessionId}`);
|
|
259
|
+
}
|
|
260
|
+
function useClientSessionModel(sessionId) {
|
|
261
|
+
const key = sessionId ? `session-detail:${sessionId}` : null;
|
|
262
|
+
const query = useClientQuery(key, (client) => client.sessions.getSession(sessionId ?? ""));
|
|
263
|
+
useRefreshWhenRunEnds(sessionId, key ? [key] : []);
|
|
264
|
+
return query.data?.model ?? null;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// src/rich-input/model/model-data.ts
|
|
268
|
+
var EMPTY_MODELS = [];
|
|
269
|
+
var EMPTY_IDS = [];
|
|
270
|
+
var CATALOG_KEY = "model-catalog";
|
|
271
|
+
function useClientModelCatalog() {
|
|
272
|
+
const { data, isLoading, isFetching, refetch } = useClientQuery(
|
|
273
|
+
CATALOG_KEY,
|
|
274
|
+
(client) => client.json("GET", "/api/config/models"),
|
|
275
|
+
{ staleMs: 0 }
|
|
276
|
+
);
|
|
277
|
+
return {
|
|
278
|
+
models: data?.models ?? EMPTY_MODELS,
|
|
279
|
+
defaultModel: data?.default ?? "",
|
|
280
|
+
thinkingAvailable: data?.thinkingAvailable ?? false,
|
|
281
|
+
unavailableProviderIds: data?.unavailableProviderIds ?? EMPTY_IDS,
|
|
282
|
+
unavailableModelIds: data?.unavailableModelIds ?? EMPTY_IDS,
|
|
283
|
+
isLoading,
|
|
284
|
+
isFetching,
|
|
285
|
+
refresh: async () => {
|
|
286
|
+
await refetch();
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
var PREFERRED_MODEL_KEY = "preferred_model";
|
|
291
|
+
var MODEL_REASONING_LEVELS_KEY = "llm.model_thinking_preferences";
|
|
292
|
+
var PREFERENCES_KEY = "model-preferences";
|
|
293
|
+
function loadPreferences(client) {
|
|
294
|
+
const query = new URLSearchParams();
|
|
295
|
+
query.append("key", PREFERRED_MODEL_KEY);
|
|
296
|
+
query.append("key", MODEL_REASONING_LEVELS_KEY);
|
|
297
|
+
return client.json("GET", `/api/user/preferences?${query.toString()}`).then((response) => response.values ?? {});
|
|
298
|
+
}
|
|
299
|
+
async function writePreference(client, key, value) {
|
|
300
|
+
const previous = await fetchClientQuery(client, PREFERENCES_KEY, loadPreferences, { staleMs: 6e4 }).catch(
|
|
301
|
+
() => ({})
|
|
302
|
+
);
|
|
303
|
+
setClientQueryData(client, PREFERENCES_KEY, (current) => ({ ...current, [key]: value }));
|
|
304
|
+
try {
|
|
305
|
+
await client.json("PUT", `/api/user/preferences/${encodeURIComponent(key)}`, { value });
|
|
306
|
+
} catch {
|
|
307
|
+
setClientQueryData(client, PREFERENCES_KEY, (current) => ({ ...current, [key]: previous[key] ?? null }));
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
var EMPTY_LEVELS = {};
|
|
311
|
+
function useClientModelPreferences() {
|
|
312
|
+
const client = useBladeClient();
|
|
313
|
+
const { data } = useClientQuery(PREFERENCES_KEY, loadPreferences, { staleMs: 6e4 });
|
|
314
|
+
const rawLevels = data?.[MODEL_REASONING_LEVELS_KEY] ?? null;
|
|
315
|
+
const reasoningLevels = rawLevels ? parseModelReasoningLevels(rawLevels) : EMPTY_LEVELS;
|
|
316
|
+
return {
|
|
317
|
+
preferredModel: data?.[PREFERRED_MODEL_KEY] ?? null,
|
|
318
|
+
setPreferredModel: (modelId) => void writePreference(client, PREFERRED_MODEL_KEY, modelId),
|
|
319
|
+
reasoningLevels,
|
|
320
|
+
setReasoningLevel: (modelId, level) => {
|
|
321
|
+
if (!modelId) return;
|
|
322
|
+
void writePreference(
|
|
323
|
+
client,
|
|
324
|
+
MODEL_REASONING_LEVELS_KEY,
|
|
325
|
+
JSON.stringify({ ...reasoningLevels, [modelId]: level })
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// src/rich-input/services.tsx
|
|
332
|
+
import { useMemo, useRef as useRef2 } from "react";
|
|
333
|
+
function useModelCatalog() {
|
|
334
|
+
const use = useRichInputServices().useModelCatalog ?? useClientModelCatalog;
|
|
335
|
+
return use();
|
|
336
|
+
}
|
|
337
|
+
function useModelPreferences() {
|
|
338
|
+
const use = useRichInputServices().useModelPreferences ?? useClientModelPreferences;
|
|
339
|
+
return use();
|
|
340
|
+
}
|
|
341
|
+
function useSessionConnection(sessionId) {
|
|
342
|
+
const connection = useSessionState(sessionId, (state) => state.connection);
|
|
343
|
+
const status = sessionId ? connection : "connected";
|
|
344
|
+
const everConnected = useRef2(false);
|
|
345
|
+
if (status === "connected") everConnected.current = true;
|
|
346
|
+
return { status, reconnectAttempt: 0, hasEverConnected: everConnected.current };
|
|
347
|
+
}
|
|
348
|
+
function useComposerConnectionInfo(sessionId) {
|
|
349
|
+
const use = useRichInputServices().useConnection ?? useSessionConnection;
|
|
350
|
+
return use(sessionId);
|
|
351
|
+
}
|
|
352
|
+
function useComposerConnection(sessionId) {
|
|
353
|
+
return useComposerConnectionInfo(sessionId).status;
|
|
354
|
+
}
|
|
355
|
+
function useSessionSkills(sessionId) {
|
|
356
|
+
const use = useRichInputServices().useSessionSkills ?? useClientSessionSkills;
|
|
357
|
+
return use(sessionId);
|
|
358
|
+
}
|
|
359
|
+
function useSkillStats(sessionId) {
|
|
360
|
+
const use = useRichInputServices().useSkillStats ?? useClientSkillStats;
|
|
361
|
+
return use(sessionId);
|
|
362
|
+
}
|
|
363
|
+
function useContextStats(sessionId) {
|
|
364
|
+
const use = useRichInputServices().useContextStats ?? useClientContextStats;
|
|
365
|
+
return use(sessionId);
|
|
366
|
+
}
|
|
367
|
+
function useBackgroundTaskList(sessionId) {
|
|
368
|
+
const use = useRichInputServices().useBackgroundTasks ?? useClientBackgroundTasks;
|
|
369
|
+
return use(sessionId);
|
|
370
|
+
}
|
|
371
|
+
function useClientSessionDataInvalidation() {
|
|
372
|
+
const client = useBladeClient();
|
|
373
|
+
return useMemo(
|
|
374
|
+
() => ({
|
|
375
|
+
filesUploaded: (_sessionId) => {
|
|
376
|
+
},
|
|
377
|
+
pluginsChanged: (sessionId) => refreshClientSessionSkills(client, sessionId)
|
|
378
|
+
}),
|
|
379
|
+
[client]
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
function useSessionDataInvalidation() {
|
|
383
|
+
const use = useRichInputServices().useSessionDataInvalidation ?? useClientSessionDataInvalidation;
|
|
384
|
+
return use();
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// src/components/SessionPluginIcon.tsx
|
|
388
|
+
import { useEffect as useEffect3, useState } from "react";
|
|
389
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
390
|
+
function sessionPluginLabel(plugin) {
|
|
391
|
+
const display = typeof plugin.display_name === "string" ? plugin.display_name.trim() : "";
|
|
392
|
+
return display || plugin.name;
|
|
393
|
+
}
|
|
394
|
+
var clientCaches = /* @__PURE__ */ new WeakMap();
|
|
395
|
+
var BLOB_CACHE_LIMIT = 64;
|
|
396
|
+
function iconBlob(client, sessionId, name, token) {
|
|
397
|
+
let blobCache = clientCaches.get(client);
|
|
398
|
+
if (!blobCache) {
|
|
399
|
+
blobCache = /* @__PURE__ */ new Map();
|
|
400
|
+
clientCaches.set(client, blobCache);
|
|
401
|
+
}
|
|
402
|
+
const key = sessionId ? `${sessionId}:${name}:${token}` : `account:${name}:${token}`;
|
|
403
|
+
const cached = blobCache.get(key);
|
|
404
|
+
if (cached) return cached;
|
|
405
|
+
const pending = (sessionId ? client.sessions.fetchSessionPluginIcon(sessionId, name) : client.plugins.fetchIcon(name)).catch(() => {
|
|
406
|
+
blobCache?.delete(key);
|
|
407
|
+
return null;
|
|
408
|
+
});
|
|
409
|
+
if (blobCache.size >= BLOB_CACHE_LIMIT) blobCache.clear();
|
|
410
|
+
blobCache.set(key, pending);
|
|
411
|
+
return pending;
|
|
412
|
+
}
|
|
413
|
+
function SessionPluginIcon({ client, sessionId, plugin, size = 20, shape = "rounded", className }) {
|
|
414
|
+
const [url, setUrl] = useState(null);
|
|
415
|
+
const token = plugin.icon;
|
|
416
|
+
const inlineIcon = token && /^data:image\/(?:png|jpeg|gif|webp);base64,/.test(token) ? token : null;
|
|
417
|
+
useEffect3(() => {
|
|
418
|
+
setUrl(null);
|
|
419
|
+
if (!token || inlineIcon) return;
|
|
420
|
+
let alive = true;
|
|
421
|
+
let objectUrl = null;
|
|
422
|
+
void iconBlob(client, sessionId, plugin.name, token).then((blob) => {
|
|
423
|
+
if (!alive || !blob) return;
|
|
424
|
+
objectUrl = URL.createObjectURL(blob);
|
|
425
|
+
setUrl(objectUrl);
|
|
426
|
+
});
|
|
427
|
+
return () => {
|
|
428
|
+
alive = false;
|
|
429
|
+
if (objectUrl) URL.revokeObjectURL(objectUrl);
|
|
430
|
+
};
|
|
431
|
+
}, [client, sessionId, plugin.name, token, inlineIcon]);
|
|
432
|
+
const frame = `flex shrink-0 items-center justify-center overflow-hidden ${shape === "circle" ? "rounded-full" : "rounded"} ${className ?? ""}`;
|
|
433
|
+
const frameStyle = { width: size, height: size };
|
|
434
|
+
const source = url === "" ? null : inlineIcon ?? url;
|
|
435
|
+
if (!source) {
|
|
436
|
+
return /* @__PURE__ */ jsx2("span", { className: `${frame} bg-[hsl(var(--muted))] text-[hsl(var(--muted-foreground))]`, style: frameStyle, "data-plugin-icon": "placeholder", children: /* @__PURE__ */ jsx2(Puzzle, { "aria-hidden": true, size: Math.round(size * 0.7) }) });
|
|
437
|
+
}
|
|
438
|
+
return /* @__PURE__ */ jsx2("span", { className: frame, style: frameStyle, "data-plugin-icon": "image", children: /* @__PURE__ */ jsx2("img", { src: source, alt: "", "aria-hidden": true, className: "h-full w-full object-cover", onError: () => setUrl("") }) });
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// src/components/SessionPluginConfigDialog.tsx
|
|
442
|
+
import { useEffect as useEffect4, useRef as useRef3, useState as useState2 } from "react";
|
|
443
|
+
import { Fragment, jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
444
|
+
async function readFailure(error) {
|
|
445
|
+
const response = error?.response;
|
|
446
|
+
if (!response || typeof response.json !== "function") {
|
|
447
|
+
return { message: "\u914D\u7F6E\u672A\u4FDD\u5B58\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5" };
|
|
448
|
+
}
|
|
449
|
+
try {
|
|
450
|
+
const body = await response.json();
|
|
451
|
+
const detail = body?.detail;
|
|
452
|
+
if (typeof detail === "string") return { message: detail };
|
|
453
|
+
if (detail && typeof detail === "object") {
|
|
454
|
+
return {
|
|
455
|
+
message: detail.message ?? "\u914D\u7F6E\u672A\u4FDD\u5B58\uFF0C\u8BF7\u68C0\u67E5\u586B\u5199\u5185\u5BB9",
|
|
456
|
+
errors: Array.isArray(detail.errors) ? detail.errors : void 0,
|
|
457
|
+
code: detail.code
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
} catch {
|
|
461
|
+
}
|
|
462
|
+
return { message: "\u914D\u7F6E\u672A\u4FDD\u5B58\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5" };
|
|
463
|
+
}
|
|
464
|
+
function SessionPluginConfigDialog({
|
|
465
|
+
client,
|
|
466
|
+
sessionId,
|
|
467
|
+
plugin,
|
|
468
|
+
...props
|
|
469
|
+
}) {
|
|
470
|
+
return /* @__PURE__ */ jsx3(
|
|
471
|
+
ConfigDialogState,
|
|
472
|
+
{
|
|
473
|
+
client,
|
|
474
|
+
sessionId,
|
|
475
|
+
plugin,
|
|
476
|
+
...props
|
|
477
|
+
},
|
|
478
|
+
`${sessionId}:${plugin.name}`
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
function ConfigDialogState({
|
|
482
|
+
client,
|
|
483
|
+
sessionId,
|
|
484
|
+
plugin,
|
|
485
|
+
onSaved,
|
|
486
|
+
onClose
|
|
487
|
+
}) {
|
|
488
|
+
const [host, setHost] = useState2(null);
|
|
489
|
+
const [restarting, setRestarting] = useState2(false);
|
|
490
|
+
const [scope, setScope] = useState2("personal");
|
|
491
|
+
const [config, setConfig] = useState2(null);
|
|
492
|
+
const pendingRevision = scope === "global" && host?.state === "ready" && config?.revision != null && host.revision !== config.revision;
|
|
493
|
+
const waitingForHost = host?.state === "starting" || host?.state === "updating" || pendingRevision;
|
|
494
|
+
useEffect4(() => {
|
|
495
|
+
if (!waitingForHost) return;
|
|
496
|
+
const abort = new AbortController();
|
|
497
|
+
let timer;
|
|
498
|
+
const refresh = async () => {
|
|
499
|
+
try {
|
|
500
|
+
const next = await client.sessions.getSessionPluginConfig(sessionId, plugin.name, { signal: abort.signal }, scope);
|
|
501
|
+
if (!abort.signal.aborted) setHost(next.host ?? null);
|
|
502
|
+
} catch {
|
|
503
|
+
}
|
|
504
|
+
if (!abort.signal.aborted) timer = setTimeout(refresh, 2e3);
|
|
505
|
+
};
|
|
506
|
+
timer = setTimeout(refresh, 2e3);
|
|
507
|
+
return () => {
|
|
508
|
+
abort.abort();
|
|
509
|
+
clearTimeout(timer);
|
|
510
|
+
};
|
|
511
|
+
}, [client, sessionId, plugin.name, scope, waitingForHost]);
|
|
512
|
+
const [canConfigureGlobal, setCanConfigureGlobal] = useState2(false);
|
|
513
|
+
const [loading, setLoading] = useState2(true);
|
|
514
|
+
const [loadFailure, setLoadFailure] = useState2(null);
|
|
515
|
+
const [saving, setSaving] = useState2(false);
|
|
516
|
+
const [failure, setFailure] = useState2(null);
|
|
517
|
+
const [fieldErrors, setFieldErrors] = useState2([]);
|
|
518
|
+
const [formVersion, setFormVersion] = useState2(0);
|
|
519
|
+
const alive = useRef3(true);
|
|
520
|
+
const generation2 = useRef3(0);
|
|
521
|
+
const dialog = useRef3(null);
|
|
522
|
+
useEffect4(() => {
|
|
523
|
+
const element = dialog.current;
|
|
524
|
+
if (!element || element.open) return;
|
|
525
|
+
if (typeof element.showModal === "function") element.showModal();
|
|
526
|
+
else element.setAttribute("open", "");
|
|
527
|
+
}, []);
|
|
528
|
+
const load = async () => {
|
|
529
|
+
const current = ++generation2.current;
|
|
530
|
+
setLoading(true);
|
|
531
|
+
setLoadFailure(null);
|
|
532
|
+
try {
|
|
533
|
+
const { config: state, can_configure_global, host: hostState } = await client.sessions.getSessionPluginConfig(sessionId, plugin.name, void 0, scope);
|
|
534
|
+
if (!alive.current || generation2.current !== current) return;
|
|
535
|
+
setCanConfigureGlobal(can_configure_global ?? false);
|
|
536
|
+
setHost(hostState ?? null);
|
|
537
|
+
setConfig(state);
|
|
538
|
+
setFormVersion((value) => value + 1);
|
|
539
|
+
} catch (error) {
|
|
540
|
+
if (!alive.current || generation2.current !== current) return;
|
|
541
|
+
const detail = await readFailure(error);
|
|
542
|
+
if (!alive.current || generation2.current !== current) return;
|
|
543
|
+
setLoadFailure(detail.message ?? "\u914D\u7F6E\u6682\u4E0D\u53EF\u7528");
|
|
544
|
+
} finally {
|
|
545
|
+
if (alive.current && generation2.current === current) setLoading(false);
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
useEffect4(() => {
|
|
549
|
+
alive.current = true;
|
|
550
|
+
setConfig(null);
|
|
551
|
+
setFailure(null);
|
|
552
|
+
setFieldErrors([]);
|
|
553
|
+
void load();
|
|
554
|
+
return () => {
|
|
555
|
+
alive.current = false;
|
|
556
|
+
generation2.current++;
|
|
557
|
+
};
|
|
558
|
+
}, [client, sessionId, plugin.name, scope]);
|
|
559
|
+
const save = async (draft) => {
|
|
560
|
+
if (!config) return;
|
|
561
|
+
const current = generation2.current;
|
|
562
|
+
setSaving(true);
|
|
563
|
+
setFailure(null);
|
|
564
|
+
setFieldErrors([]);
|
|
565
|
+
try {
|
|
566
|
+
const { config: state } = await client.sessions.updateSessionPluginConfig(
|
|
567
|
+
sessionId,
|
|
568
|
+
plugin.name,
|
|
569
|
+
{
|
|
570
|
+
expected_revision: config.revision,
|
|
571
|
+
set: draft.set,
|
|
572
|
+
remove: draft.remove
|
|
573
|
+
},
|
|
574
|
+
void 0,
|
|
575
|
+
scope
|
|
576
|
+
);
|
|
577
|
+
if (!alive.current || generation2.current !== current) return;
|
|
578
|
+
setConfig(state);
|
|
579
|
+
setFormVersion((value) => value + 1);
|
|
580
|
+
if (scope === "personal") onSaved?.(state);
|
|
581
|
+
} catch (error) {
|
|
582
|
+
if (!alive.current || generation2.current !== current) return;
|
|
583
|
+
const detail = await readFailure(error);
|
|
584
|
+
if (!alive.current || generation2.current !== current) return;
|
|
585
|
+
setFailure(detail.message ?? "\u914D\u7F6E\u672A\u4FDD\u5B58\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5");
|
|
586
|
+
setFieldErrors(detail.errors ?? []);
|
|
587
|
+
if (detail.code === "plugin_config_conflict") await load();
|
|
588
|
+
} finally {
|
|
589
|
+
if (alive.current) setSaving(false);
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
593
|
+
/* @__PURE__ */ jsx3("style", { children: ".blade-plugin-dialog::backdrop { background: rgb(0 0 0 / 40%); }" }),
|
|
594
|
+
/* @__PURE__ */ jsxs(
|
|
595
|
+
"dialog",
|
|
596
|
+
{
|
|
597
|
+
ref: dialog,
|
|
598
|
+
"aria-modal": "true",
|
|
599
|
+
"aria-label": `${sessionPluginLabel(plugin)} \u914D\u7F6E`,
|
|
600
|
+
onCancel: (event) => {
|
|
601
|
+
event.preventDefault();
|
|
602
|
+
onClose();
|
|
603
|
+
},
|
|
604
|
+
onKeyDown: (event) => {
|
|
605
|
+
if (event.key !== "Escape") return;
|
|
606
|
+
event.preventDefault();
|
|
607
|
+
event.stopPropagation();
|
|
608
|
+
onClose();
|
|
609
|
+
},
|
|
610
|
+
style: {
|
|
611
|
+
position: "fixed",
|
|
612
|
+
inset: 0,
|
|
613
|
+
margin: "auto",
|
|
614
|
+
boxSizing: "border-box",
|
|
615
|
+
width: "min(448px, calc(100% - 32px))"
|
|
616
|
+
},
|
|
617
|
+
className: "blade-plugin-dialog max-h-[80vh] overflow-y-auto rounded-lg border border-[hsl(var(--border))] bg-[hsl(var(--popover))] p-4 text-[hsl(var(--popover-foreground))] shadow-lg",
|
|
618
|
+
children: [
|
|
619
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-3 flex items-center gap-2", children: [
|
|
620
|
+
/* @__PURE__ */ jsx3(SessionPluginIcon, { client, sessionId, plugin }),
|
|
621
|
+
/* @__PURE__ */ jsx3(
|
|
622
|
+
"span",
|
|
623
|
+
{
|
|
624
|
+
className: "min-w-0 flex-1 truncate text-sm font-medium",
|
|
625
|
+
title: sessionPluginLabel(plugin),
|
|
626
|
+
children: sessionPluginLabel(plugin)
|
|
627
|
+
}
|
|
628
|
+
),
|
|
629
|
+
/* @__PURE__ */ jsx3(
|
|
630
|
+
"button",
|
|
631
|
+
{
|
|
632
|
+
type: "button",
|
|
633
|
+
"aria-label": "\u5173\u95ED",
|
|
634
|
+
className: "shrink-0 text-xs underline",
|
|
635
|
+
onClick: onClose,
|
|
636
|
+
children: "\u5173\u95ED"
|
|
637
|
+
}
|
|
638
|
+
)
|
|
639
|
+
] }),
|
|
640
|
+
canConfigureGlobal ? /* @__PURE__ */ jsxs("div", { className: "mb-3 flex gap-2 text-xs", children: [
|
|
641
|
+
/* @__PURE__ */ jsx3("button", { type: "button", disabled: saving, "aria-pressed": scope === "personal", onClick: () => setScope("personal"), children: "\u6211\u7684\u767B\u5F55" }),
|
|
642
|
+
/* @__PURE__ */ jsx3("button", { type: "button", disabled: saving, "aria-pressed": scope === "global", onClick: () => setScope("global"), children: "\u7EC4\u7EC7\u5171\u4EAB\u914D\u7F6E" })
|
|
643
|
+
] }) : null,
|
|
644
|
+
host ? /* @__PURE__ */ jsxs("div", { className: "mb-3 space-y-1 text-xs", children: [
|
|
645
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
646
|
+
"\u670D\u52A1\uFF1A",
|
|
647
|
+
pendingRevision ? "\u6B63\u5728\u66F4\u65B0" : host.reason,
|
|
648
|
+
" \xB7 \u5DF2\u6062\u590D ",
|
|
649
|
+
host.restarts,
|
|
650
|
+
" \u6B21"
|
|
651
|
+
] }),
|
|
652
|
+
canConfigureGlobal ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
653
|
+
/* @__PURE__ */ jsxs("p", { className: "break-all text-[hsl(var(--muted-foreground))]", children: [
|
|
654
|
+
"\u5305\u6458\u8981\uFF1A",
|
|
655
|
+
host.digest,
|
|
656
|
+
" \xB7 \u914D\u7F6E\uFF1A",
|
|
657
|
+
host.revision
|
|
658
|
+
] }),
|
|
659
|
+
/* @__PURE__ */ jsx3("button", { type: "button", disabled: restarting || saving, onClick: () => {
|
|
660
|
+
setRestarting(true);
|
|
661
|
+
void client.sessions.restartSessionPluginHost(sessionId, plugin.name).then(() => load()).catch(() => setFailure("\u670D\u52A1\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5")).finally(() => {
|
|
662
|
+
if (alive.current) setRestarting(false);
|
|
663
|
+
});
|
|
664
|
+
}, children: restarting ? "\u6B63\u5728\u91CD\u542F\u2026" : "\u91CD\u542F\u670D\u52A1" }),
|
|
665
|
+
host.recent?.length ? /* @__PURE__ */ jsxs("details", { children: [
|
|
666
|
+
/* @__PURE__ */ jsx3("summary", { children: "\u6700\u8FD1\u6D3B\u52A8" }),
|
|
667
|
+
host.recent.map((line, index) => /* @__PURE__ */ jsx3("p", { children: line }, `${index}-${line}`))
|
|
668
|
+
] }) : null
|
|
669
|
+
] }) : null
|
|
670
|
+
] }) : null,
|
|
671
|
+
scope === "personal" && config?.auth_status === "expired" ? /* @__PURE__ */ jsx3("p", { className: "mb-2 text-xs", children: "\u767B\u5F55\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u586B\u5199" }) : null,
|
|
672
|
+
scope === "personal" && config?.global_ready === false ? /* @__PURE__ */ jsx3("p", { className: "mb-2 text-xs", children: "\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u586B\u5199\u5171\u4EAB\u914D\u7F6E" }) : null,
|
|
673
|
+
scope === "global" ? /* @__PURE__ */ jsx3("p", { className: "mb-2 text-xs", children: "\u5171\u4EAB\u5BC6\u94A5\u7528\u4E8E\u63D2\u4EF6\u670D\u52A1\u3002\u547D\u4EE4\u7C7B\u63D2\u4EF6\u7684\u6C99\u76D2\u4EE3\u7801\u53EF\u4EE5\u8BFB\u53D6\u5B83\uFF1B\u9700\u4FDD\u5BC6\u65F6\u8BF7\u4F7F\u7528\u6258\u7BA1\u670D\u52A1\u3002" }) : null,
|
|
674
|
+
loading ? /* @__PURE__ */ jsx3("p", { className: "text-xs text-[hsl(var(--muted-foreground))]", children: "\u6B63\u5728\u8BFB\u53D6\u914D\u7F6E\u2026" }) : null,
|
|
675
|
+
loadFailure ? /* @__PURE__ */ jsxs("div", { className: "space-y-2 text-xs", children: [
|
|
676
|
+
/* @__PURE__ */ jsx3("p", { children: loadFailure }),
|
|
677
|
+
/* @__PURE__ */ jsx3(
|
|
678
|
+
"button",
|
|
679
|
+
{
|
|
680
|
+
type: "button",
|
|
681
|
+
className: "rounded-full border border-[hsl(var(--border))] px-2.5 py-1",
|
|
682
|
+
onClick: () => void load(),
|
|
683
|
+
children: "\u91CD\u8BD5"
|
|
684
|
+
}
|
|
685
|
+
)
|
|
686
|
+
] }) : null,
|
|
687
|
+
config && !loading ? /* @__PURE__ */ jsx3(
|
|
688
|
+
SessionPluginConfigForm,
|
|
689
|
+
{
|
|
690
|
+
config,
|
|
691
|
+
submitting: saving,
|
|
692
|
+
errors: fieldErrors,
|
|
693
|
+
failure,
|
|
694
|
+
onSubmit: (draft) => void save(draft),
|
|
695
|
+
onCancel: onClose
|
|
696
|
+
},
|
|
697
|
+
formVersion
|
|
698
|
+
) : null
|
|
699
|
+
]
|
|
700
|
+
}
|
|
701
|
+
)
|
|
702
|
+
] });
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// src/components/PluginConnectorList.tsx
|
|
706
|
+
import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
707
|
+
function PluginSwitch({
|
|
708
|
+
label,
|
|
709
|
+
checked,
|
|
710
|
+
pending,
|
|
711
|
+
disabled,
|
|
712
|
+
draft = false,
|
|
713
|
+
onToggle
|
|
714
|
+
}) {
|
|
715
|
+
return /* @__PURE__ */ jsx4(
|
|
716
|
+
"button",
|
|
717
|
+
{
|
|
718
|
+
type: "button",
|
|
719
|
+
role: "switch",
|
|
720
|
+
"aria-checked": checked,
|
|
721
|
+
"aria-label": draft ? `${label} \u5F85\u9009` : `${label} \u542F\u7528`,
|
|
722
|
+
disabled,
|
|
723
|
+
onClick: () => onToggle(!checked),
|
|
724
|
+
className: `relative inline-flex h-[18px] w-8 shrink-0 cursor-pointer items-center rounded-full border transition-colors disabled:cursor-not-allowed disabled:opacity-50 ${checked ? "border-transparent bg-[hsl(213_94%_55%)]" : "border-[hsl(var(--border))] bg-[hsl(var(--muted))]"}`,
|
|
725
|
+
children: /* @__PURE__ */ jsx4(
|
|
726
|
+
"span",
|
|
727
|
+
{
|
|
728
|
+
"aria-hidden": "true",
|
|
729
|
+
className: `pointer-events-none flex h-3.5 w-3.5 items-center justify-center rounded-full bg-white shadow transition-transform ${checked ? "translate-x-[16px]" : "translate-x-[2px]"}`,
|
|
730
|
+
children: pending ? /* @__PURE__ */ jsx4(LoaderCircle, { size: 9, className: "animate-spin text-[hsl(var(--muted-foreground))]" }) : null
|
|
731
|
+
}
|
|
732
|
+
)
|
|
733
|
+
}
|
|
734
|
+
);
|
|
735
|
+
}
|
|
736
|
+
function PluginConnectorList({
|
|
737
|
+
client,
|
|
738
|
+
sessionId,
|
|
739
|
+
items,
|
|
740
|
+
inFlight,
|
|
741
|
+
busy,
|
|
742
|
+
mutationError,
|
|
743
|
+
onToggle,
|
|
744
|
+
onRetry,
|
|
745
|
+
onEditConfig,
|
|
746
|
+
configuring,
|
|
747
|
+
onConfigSaved,
|
|
748
|
+
onConfigClose,
|
|
749
|
+
disabledReason,
|
|
750
|
+
unavailableText,
|
|
751
|
+
emptyText,
|
|
752
|
+
draft = false
|
|
753
|
+
}) {
|
|
754
|
+
const configuringItem = configuring ? items.find((item) => item.name === configuring.name) ?? null : null;
|
|
755
|
+
return /* @__PURE__ */ jsxs2("div", { className: "py-0.5", children: [
|
|
756
|
+
disabledReason ? /* @__PURE__ */ jsx4("div", { className: "px-3 py-1.5 text-[11px] text-[hsl(var(--muted-foreground))]", children: disabledReason }) : null,
|
|
757
|
+
items.length === 0 ? /* @__PURE__ */ jsx4("div", { className: "px-3 py-1.5 text-[11px] text-[hsl(var(--muted-foreground))]", children: unavailableText ?? emptyText }) : null,
|
|
758
|
+
items.map((item) => {
|
|
759
|
+
const label = sessionPluginLabel(item);
|
|
760
|
+
const actionable = !disabledReason && item.installed !== false || item.active;
|
|
761
|
+
const pending = Boolean(inFlight[item.name]);
|
|
762
|
+
const checked = item.active;
|
|
763
|
+
return /* @__PURE__ */ jsxs2("div", { className: "group px-3 py-1", children: [
|
|
764
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2", children: [
|
|
765
|
+
/* @__PURE__ */ jsx4(
|
|
766
|
+
SessionPluginIcon,
|
|
767
|
+
{
|
|
768
|
+
client,
|
|
769
|
+
sessionId: item.iconSource === "account" ? null : sessionId,
|
|
770
|
+
plugin: item
|
|
771
|
+
}
|
|
772
|
+
),
|
|
773
|
+
/* @__PURE__ */ jsx4("span", { className: "min-w-0 flex-1 truncate text-[11px]", title: label, children: label }),
|
|
774
|
+
item.auth_status === "expired" || item.auth_status === "login_required" || item.auth_status === "unavailable" ? /* @__PURE__ */ jsx4("button", { type: "button", disabled: !sessionId || busy, onClick: () => onEditConfig(item), className: "shrink-0 text-[10px] underline", children: item.auth_status === "expired" ? "\u767B\u5F55\u5DF2\u8FC7\u671F" : item.auth_status === "unavailable" ? "\u6682\u65F6\u4E0D\u53EF\u7528" : "\u672A\u767B\u5F55" }) : null,
|
|
775
|
+
!draft && item.config === "configured" ? /* @__PURE__ */ jsx4(
|
|
776
|
+
"button",
|
|
777
|
+
{
|
|
778
|
+
type: "button",
|
|
779
|
+
title: "\u66F4\u65B0\u8BE5\u63D2\u4EF6\u7684\u914D\u7F6E",
|
|
780
|
+
disabled: busy || !sessionId,
|
|
781
|
+
onClick: () => onEditConfig(item),
|
|
782
|
+
className: "shrink-0 text-[10px] text-[hsl(var(--muted-foreground))] underline opacity-0 [@media(hover:none)]:opacity-100 transition-opacity focus-visible:opacity-100 group-hover:opacity-100 disabled:cursor-not-allowed disabled:opacity-50",
|
|
783
|
+
children: "\u66F4\u65B0\u914D\u7F6E"
|
|
784
|
+
}
|
|
785
|
+
) : null,
|
|
786
|
+
!draft && (item.config === "required" || item.config === "error") ? /* @__PURE__ */ jsx4(
|
|
787
|
+
"button",
|
|
788
|
+
{
|
|
789
|
+
type: "button",
|
|
790
|
+
title: "\u8BE5\u63D2\u4EF6\u9700\u8981\u586B\u5199\u914D\u7F6E",
|
|
791
|
+
disabled: busy || !sessionId,
|
|
792
|
+
onClick: () => onEditConfig(item),
|
|
793
|
+
className: "shrink-0 text-[10px] text-[hsl(var(--muted-foreground))] underline disabled:cursor-not-allowed disabled:opacity-50",
|
|
794
|
+
children: "\u586B\u5199\u914D\u7F6E"
|
|
795
|
+
}
|
|
796
|
+
) : null,
|
|
797
|
+
/* @__PURE__ */ jsx4(
|
|
798
|
+
PluginSwitch,
|
|
799
|
+
{
|
|
800
|
+
label,
|
|
801
|
+
checked,
|
|
802
|
+
pending,
|
|
803
|
+
draft,
|
|
804
|
+
disabled: !actionable,
|
|
805
|
+
onToggle: (next) => onToggle(item, next)
|
|
806
|
+
}
|
|
807
|
+
)
|
|
808
|
+
] }),
|
|
809
|
+
!draft && item.active && item.status !== "valid" && item.status !== "required" ? /* @__PURE__ */ jsxs2("div", { className: "mt-0.5 flex items-center gap-2 pl-7 text-[10px] text-[hsl(var(--muted-foreground))]", children: [
|
|
810
|
+
/* @__PURE__ */ jsx4("span", { children: item.reason || "\u63D2\u4EF6\u6682\u4E0D\u53EF\u7528" }),
|
|
811
|
+
/* @__PURE__ */ jsx4(
|
|
812
|
+
"button",
|
|
813
|
+
{
|
|
814
|
+
type: "button",
|
|
815
|
+
disabled: busy,
|
|
816
|
+
onClick: () => onRetry(item),
|
|
817
|
+
className: "underline disabled:cursor-not-allowed disabled:opacity-50",
|
|
818
|
+
children: "\u91CD\u8BD5"
|
|
819
|
+
}
|
|
820
|
+
)
|
|
821
|
+
] }) : null
|
|
822
|
+
] }, item.name);
|
|
823
|
+
}),
|
|
824
|
+
mutationError ? /* @__PURE__ */ jsx4("div", { className: "px-3 pt-1 text-[10px] text-[hsl(var(--destructive))]", children: "\u64CD\u4F5C\u672A\u5B8C\u6210\uFF0C\u8BF7\u91CD\u8BD5" }) : null,
|
|
825
|
+
configuring && configuringItem && sessionId ? /* @__PURE__ */ jsx4(
|
|
826
|
+
SessionPluginConfigDialog,
|
|
827
|
+
{
|
|
828
|
+
client,
|
|
829
|
+
sessionId,
|
|
830
|
+
plugin: configuringItem,
|
|
831
|
+
onSaved: onConfigSaved,
|
|
832
|
+
onClose: onConfigClose
|
|
833
|
+
}
|
|
834
|
+
) : null
|
|
835
|
+
] });
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// src/components/plugin-connector.ts
|
|
839
|
+
function catalogToConnectorItems(catalog) {
|
|
840
|
+
return catalog.map((entry) => ({
|
|
841
|
+
name: entry.name,
|
|
842
|
+
display_name: entry.display_name,
|
|
843
|
+
icon: entry.icon,
|
|
844
|
+
iconSource: "account",
|
|
845
|
+
installed: entry.installed,
|
|
846
|
+
connector: entry.connector,
|
|
847
|
+
auth_status: entry.auth_status,
|
|
848
|
+
active: false,
|
|
849
|
+
status: "not_prepared",
|
|
850
|
+
projected: false
|
|
851
|
+
}));
|
|
852
|
+
}
|
|
853
|
+
function mergeConnectorItems(sessionPlugins, catalog) {
|
|
854
|
+
const byName = new Map((catalog ?? []).map((entry) => [entry.name, entry]));
|
|
855
|
+
return sessionPlugins.map((plugin) => {
|
|
856
|
+
const entry = byName.get(plugin.name);
|
|
857
|
+
if (!entry) {
|
|
858
|
+
return {
|
|
859
|
+
name: plugin.name,
|
|
860
|
+
display_name: plugin.display_name ?? null,
|
|
861
|
+
icon: plugin.icon ?? null,
|
|
862
|
+
iconSource: "session",
|
|
863
|
+
installed: plugin.installed,
|
|
864
|
+
active: plugin.active,
|
|
865
|
+
status: plugin.status,
|
|
866
|
+
reason: plugin.reason,
|
|
867
|
+
config: plugin.config,
|
|
868
|
+
projected: plugin.projected
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
return {
|
|
872
|
+
name: plugin.name,
|
|
873
|
+
display_name: entry.display_name ?? plugin.display_name ?? null,
|
|
874
|
+
icon: entry.icon,
|
|
875
|
+
iconSource: "account",
|
|
876
|
+
installed: entry.installed,
|
|
877
|
+
connector: entry.connector,
|
|
878
|
+
auth_status: entry.auth_status,
|
|
879
|
+
active: plugin.active,
|
|
880
|
+
status: plugin.status,
|
|
881
|
+
reason: plugin.reason,
|
|
882
|
+
config: plugin.config,
|
|
883
|
+
projected: plugin.projected
|
|
884
|
+
};
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
function filterConnectorItems(items, query) {
|
|
888
|
+
const needle = query.trim().toLowerCase();
|
|
889
|
+
if (!needle) return items;
|
|
890
|
+
return items.filter(
|
|
891
|
+
(item) => `${item.display_name ?? ""} ${item.name}`.toLowerCase().includes(needle)
|
|
892
|
+
);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// src/components/use-session-plugin-activation.ts
|
|
896
|
+
import { isLocalConnectorName } from "@blade-hq/agent-client/internal";
|
|
897
|
+
import { useCallback as useCallback2, useEffect as useEffect5, useMemo as useMemo2, useRef as useRef4, useState as useState3 } from "react";
|
|
898
|
+
function useSessionPluginActivation({
|
|
899
|
+
client,
|
|
900
|
+
sessionId,
|
|
901
|
+
onChange
|
|
902
|
+
}) {
|
|
903
|
+
const [plugins, setPlugins] = useState3([]);
|
|
904
|
+
const [catalog, setCatalog] = useState3(null);
|
|
905
|
+
const [loading, setLoading] = useState3(false);
|
|
906
|
+
const [listError, setListError] = useState3(false);
|
|
907
|
+
const [mutationError, setMutationError] = useState3(false);
|
|
908
|
+
const [inFlight, setInFlight] = useState3({});
|
|
909
|
+
const [configuring, setConfiguring] = useState3(null);
|
|
910
|
+
const alive = useRef4(true);
|
|
911
|
+
const generation2 = useRef4(0);
|
|
912
|
+
const mutationGenerations = useRef4(/* @__PURE__ */ new Map());
|
|
913
|
+
const desiredActivations = useRef4(/* @__PURE__ */ new Map());
|
|
914
|
+
const pendingMutations = useRef4(/* @__PURE__ */ new Map());
|
|
915
|
+
const submittedActivations = useRef4(/* @__PURE__ */ new Set());
|
|
916
|
+
const serverActive = useRef4(/* @__PURE__ */ new Set());
|
|
917
|
+
const lifetime = useRef4(0);
|
|
918
|
+
const request = useRef4(null);
|
|
919
|
+
const [desiredVersion, setDesiredVersion] = useState3(0);
|
|
920
|
+
const setDesired = useCallback2((name, value) => {
|
|
921
|
+
if (value === null) {
|
|
922
|
+
if (!desiredActivations.current.delete(name)) return;
|
|
923
|
+
} else {
|
|
924
|
+
if (desiredActivations.current.get(name) === value) return;
|
|
925
|
+
desiredActivations.current.set(name, value);
|
|
926
|
+
}
|
|
927
|
+
setDesiredVersion((version) => version + 1);
|
|
928
|
+
}, []);
|
|
929
|
+
const items = useMemo2(() => {
|
|
930
|
+
void desiredVersion;
|
|
931
|
+
const merged = mergeConnectorItems(plugins, catalog);
|
|
932
|
+
return merged.map((item) => {
|
|
933
|
+
if (configuring?.name === item.name && configuring.pendingActivation) {
|
|
934
|
+
return { ...item, active: false };
|
|
935
|
+
}
|
|
936
|
+
const desired = desiredActivations.current.get(item.name);
|
|
937
|
+
return desired === void 0 ? item : { ...item, active: desired };
|
|
938
|
+
});
|
|
939
|
+
}, [catalog, configuring, desiredVersion, plugins]);
|
|
940
|
+
const markInFlight = useCallback2((name, on) => {
|
|
941
|
+
setInFlight((current) => {
|
|
942
|
+
if (on) return current[name] ? current : { ...current, [name]: true };
|
|
943
|
+
if (!current[name]) return current;
|
|
944
|
+
const { [name]: removed, ...rest } = current;
|
|
945
|
+
void removed;
|
|
946
|
+
return rest;
|
|
947
|
+
});
|
|
948
|
+
}, []);
|
|
949
|
+
useEffect5(() => {
|
|
950
|
+
alive.current = true;
|
|
951
|
+
setPlugins([]);
|
|
952
|
+
setCatalog(null);
|
|
953
|
+
setLoading(false);
|
|
954
|
+
setListError(false);
|
|
955
|
+
setMutationError(false);
|
|
956
|
+
setInFlight({});
|
|
957
|
+
setConfiguring(null);
|
|
958
|
+
desiredActivations.current.clear();
|
|
959
|
+
mutationGenerations.current.clear();
|
|
960
|
+
pendingMutations.current.clear();
|
|
961
|
+
submittedActivations.current.clear();
|
|
962
|
+
return () => {
|
|
963
|
+
alive.current = false;
|
|
964
|
+
lifetime.current++;
|
|
965
|
+
generation2.current++;
|
|
966
|
+
request.current?.abort();
|
|
967
|
+
};
|
|
968
|
+
}, [client]);
|
|
969
|
+
const load = useCallback2(async () => {
|
|
970
|
+
if (!alive.current) return;
|
|
971
|
+
request.current?.abort();
|
|
972
|
+
const controller = new AbortController();
|
|
973
|
+
request.current = controller;
|
|
974
|
+
const current = ++generation2.current;
|
|
975
|
+
setLoading(true);
|
|
976
|
+
setListError(false);
|
|
977
|
+
try {
|
|
978
|
+
const result = await client.sessions.listSessionPlugins(sessionId, { signal: controller.signal });
|
|
979
|
+
const withConfig = await Promise.all(
|
|
980
|
+
result.plugins.map(async (item) => {
|
|
981
|
+
if (isLocalConnectorName(item.name) || item.config !== void 0 && item.config !== "unchecked") return item;
|
|
982
|
+
try {
|
|
983
|
+
const { config } = await client.sessions.getSessionPluginConfig(sessionId, item.name, {
|
|
984
|
+
signal: controller.signal
|
|
985
|
+
});
|
|
986
|
+
return { ...item, config: config.state };
|
|
987
|
+
} catch {
|
|
988
|
+
return item;
|
|
989
|
+
}
|
|
990
|
+
})
|
|
991
|
+
);
|
|
992
|
+
if (alive.current && current === generation2.current) {
|
|
993
|
+
serverActive.current = new Set(withConfig.filter((item) => item.active).map((item) => item.name));
|
|
994
|
+
setPlugins(withConfig.map((item) => {
|
|
995
|
+
const desired = desiredActivations.current.get(item.name);
|
|
996
|
+
return desired === void 0 ? item : { ...item, active: desired };
|
|
997
|
+
}));
|
|
998
|
+
}
|
|
999
|
+
} catch {
|
|
1000
|
+
if (alive.current && current === generation2.current && !controller.signal.aborted) setListError(true);
|
|
1001
|
+
} finally {
|
|
1002
|
+
if (alive.current && current === generation2.current) setLoading(false);
|
|
1003
|
+
}
|
|
1004
|
+
}, [client, sessionId]);
|
|
1005
|
+
const loadCatalog = useCallback2(async () => {
|
|
1006
|
+
try {
|
|
1007
|
+
const { plugins: entries } = await client.plugins.catalog();
|
|
1008
|
+
if (alive.current) setCatalog(entries);
|
|
1009
|
+
} catch {
|
|
1010
|
+
}
|
|
1011
|
+
}, [client]);
|
|
1012
|
+
const reload = useCallback2(() => {
|
|
1013
|
+
void load();
|
|
1014
|
+
void loadCatalog();
|
|
1015
|
+
}, [load, loadCatalog]);
|
|
1016
|
+
const abandon = useCallback2((name) => {
|
|
1017
|
+
setDesired(name, null);
|
|
1018
|
+
pendingMutations.current.delete(name);
|
|
1019
|
+
mutationGenerations.current.set(name, (mutationGenerations.current.get(name) ?? 0) + 1);
|
|
1020
|
+
markInFlight(name, false);
|
|
1021
|
+
void load();
|
|
1022
|
+
}, [load, markInFlight, setDesired]);
|
|
1023
|
+
const finish = useCallback2((name, mutation) => {
|
|
1024
|
+
pendingMutations.current.delete(name);
|
|
1025
|
+
setDesired(name, null);
|
|
1026
|
+
if (mutation === mutationGenerations.current.get(name)) markInFlight(name, false);
|
|
1027
|
+
}, [markInFlight, setDesired]);
|
|
1028
|
+
const commitActivation = useCallback2(
|
|
1029
|
+
async (name, active, mutation, startedAt) => {
|
|
1030
|
+
submittedActivations.current.add(name);
|
|
1031
|
+
try {
|
|
1032
|
+
const { plugin } = await client.sessions.setSessionPluginActivation(sessionId, name, active);
|
|
1033
|
+
if (!alive.current || startedAt !== lifetime.current) return;
|
|
1034
|
+
if (mutation !== mutationGenerations.current.get(name)) {
|
|
1035
|
+
const desired = desiredActivations.current.get(name);
|
|
1036
|
+
if (desired !== void 0 && desired !== plugin.active) {
|
|
1037
|
+
const next = (mutationGenerations.current.get(name) ?? 0) + 1;
|
|
1038
|
+
mutationGenerations.current.set(name, next);
|
|
1039
|
+
pendingMutations.current.set(name, next);
|
|
1040
|
+
markInFlight(name, true);
|
|
1041
|
+
await commitActivation(name, desired, next, startedAt);
|
|
1042
|
+
}
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
if (active) serverActive.current.add(name);
|
|
1046
|
+
else serverActive.current.delete(name);
|
|
1047
|
+
finish(name, mutation);
|
|
1048
|
+
setPlugins((current) => current.map((item) => item.name === plugin.name ? { ...item, ...plugin } : item));
|
|
1049
|
+
onChange?.(plugin);
|
|
1050
|
+
if (plugin.active && plugin.status === "required") {
|
|
1051
|
+
setConfiguring({ name: plugin.name, pendingActivation: true });
|
|
1052
|
+
}
|
|
1053
|
+
await load();
|
|
1054
|
+
} catch {
|
|
1055
|
+
if (alive.current && startedAt === lifetime.current && mutation === mutationGenerations.current.get(name)) {
|
|
1056
|
+
pendingMutations.current.delete(name);
|
|
1057
|
+
submittedActivations.current.delete(name);
|
|
1058
|
+
setDesired(name, null);
|
|
1059
|
+
markInFlight(name, false);
|
|
1060
|
+
setMutationError(true);
|
|
1061
|
+
void load();
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
},
|
|
1065
|
+
[client, finish, load, markInFlight, onChange, sessionId, setDesired]
|
|
1066
|
+
);
|
|
1067
|
+
const enable = useCallback2(async (name) => {
|
|
1068
|
+
if (!alive.current) return;
|
|
1069
|
+
const startedAt = lifetime.current;
|
|
1070
|
+
const mutation = (mutationGenerations.current.get(name) ?? 0) + 1;
|
|
1071
|
+
mutationGenerations.current.set(name, mutation);
|
|
1072
|
+
pendingMutations.current.set(name, mutation);
|
|
1073
|
+
setDesired(name, true);
|
|
1074
|
+
setMutationError(false);
|
|
1075
|
+
markInFlight(name, true);
|
|
1076
|
+
request.current?.abort();
|
|
1077
|
+
generation2.current++;
|
|
1078
|
+
setLoading(false);
|
|
1079
|
+
let state;
|
|
1080
|
+
try {
|
|
1081
|
+
const result = await client.sessions.prepareSessionPluginConfig(sessionId, name);
|
|
1082
|
+
if (!alive.current || startedAt !== lifetime.current || mutation !== mutationGenerations.current.get(name)) return;
|
|
1083
|
+
state = result.config;
|
|
1084
|
+
} catch {
|
|
1085
|
+
if (!alive.current || startedAt !== lifetime.current || mutation !== mutationGenerations.current.get(name)) return;
|
|
1086
|
+
abandon(name);
|
|
1087
|
+
setMutationError(true);
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
if (state.state === "required") {
|
|
1091
|
+
pendingMutations.current.delete(name);
|
|
1092
|
+
markInFlight(name, false);
|
|
1093
|
+
setConfiguring({ name, pendingActivation: true });
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
if (state.state !== "configured" && state.state !== "not_required") {
|
|
1097
|
+
abandon(name);
|
|
1098
|
+
setMutationError(true);
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
setPlugins((current) => current.map((item) => item.name === name ? { ...item, config: state.state } : item));
|
|
1102
|
+
await commitActivation(name, true, mutation, startedAt);
|
|
1103
|
+
}, [abandon, client, commitActivation, markInFlight, sessionId, setDesired]);
|
|
1104
|
+
const disable = useCallback2((name) => {
|
|
1105
|
+
const startedAt = lifetime.current;
|
|
1106
|
+
request.current?.abort();
|
|
1107
|
+
generation2.current++;
|
|
1108
|
+
setLoading(false);
|
|
1109
|
+
if (!submittedActivations.current.has(name) && !serverActive.current.has(name)) {
|
|
1110
|
+
abandon(name);
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1113
|
+
const mutation = (mutationGenerations.current.get(name) ?? 0) + 1;
|
|
1114
|
+
mutationGenerations.current.set(name, mutation);
|
|
1115
|
+
pendingMutations.current.set(name, mutation);
|
|
1116
|
+
setDesired(name, false);
|
|
1117
|
+
setMutationError(false);
|
|
1118
|
+
markInFlight(name, true);
|
|
1119
|
+
void commitActivation(name, false, mutation, startedAt);
|
|
1120
|
+
}, [abandon, commitActivation, markInFlight, setDesired]);
|
|
1121
|
+
const toggle = useCallback2((item, next) => {
|
|
1122
|
+
if (next) void enable(item.name);
|
|
1123
|
+
else disable(item.name);
|
|
1124
|
+
}, [disable, enable]);
|
|
1125
|
+
const retry = useCallback2((item) => {
|
|
1126
|
+
void enable(item.name);
|
|
1127
|
+
}, [enable]);
|
|
1128
|
+
const editConfig = useCallback2((item) => {
|
|
1129
|
+
setConfiguring({ name: item.name, pendingActivation: false });
|
|
1130
|
+
}, []);
|
|
1131
|
+
const onConfigSaved = useCallback2((state) => {
|
|
1132
|
+
const target = configuring;
|
|
1133
|
+
if (!target) return;
|
|
1134
|
+
setConfiguring(null);
|
|
1135
|
+
void loadCatalog();
|
|
1136
|
+
setPlugins((current) => current.map((item) => item.name === target.name ? { ...item, config: state.state } : item));
|
|
1137
|
+
if (!target.pendingActivation || state.state !== "configured") {
|
|
1138
|
+
void load();
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
const startedAt = lifetime.current;
|
|
1142
|
+
const mutation = (mutationGenerations.current.get(target.name) ?? 0) + 1;
|
|
1143
|
+
mutationGenerations.current.set(target.name, mutation);
|
|
1144
|
+
pendingMutations.current.set(target.name, mutation);
|
|
1145
|
+
setDesired(target.name, true);
|
|
1146
|
+
markInFlight(target.name, true);
|
|
1147
|
+
void commitActivation(target.name, true, mutation, startedAt);
|
|
1148
|
+
}, [commitActivation, configuring, load, loadCatalog, markInFlight, setDesired]);
|
|
1149
|
+
const onConfigClose = useCallback2(() => {
|
|
1150
|
+
const target = configuring;
|
|
1151
|
+
if (!target) return;
|
|
1152
|
+
setConfiguring(null);
|
|
1153
|
+
if (target.pendingActivation) abandon(target.name);
|
|
1154
|
+
else void load();
|
|
1155
|
+
}, [abandon, configuring, load]);
|
|
1156
|
+
return {
|
|
1157
|
+
items,
|
|
1158
|
+
loading,
|
|
1159
|
+
listError,
|
|
1160
|
+
mutationError,
|
|
1161
|
+
inFlight,
|
|
1162
|
+
busy: Object.keys(inFlight).length > 0,
|
|
1163
|
+
configuring,
|
|
1164
|
+
reload,
|
|
1165
|
+
toggle,
|
|
1166
|
+
retry,
|
|
1167
|
+
editConfig,
|
|
1168
|
+
onConfigSaved,
|
|
1169
|
+
onConfigClose
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
// src/lib/message-shortcuts.ts
|
|
1174
|
+
function usesCommandKey() {
|
|
1175
|
+
if (typeof navigator === "undefined") return false;
|
|
1176
|
+
return /Mac|iPhone|iPad|iPod/i.test(navigator.platform || navigator.userAgent);
|
|
1177
|
+
}
|
|
1178
|
+
function queueShortcutLabel() {
|
|
1179
|
+
return usesCommandKey() ? "\u2318 Enter" : "Ctrl Enter";
|
|
1180
|
+
}
|
|
1181
|
+
function isQueueShortcut(event) {
|
|
1182
|
+
return usesCommandKey() ? event.metaKey : event.ctrlKey;
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
// src/chat/runtime/actions.ts
|
|
1186
|
+
function buildPendingAskUserAnswer(state, message) {
|
|
1187
|
+
if (state.status !== "waiting_for_input") return void 0;
|
|
1188
|
+
const text = extractMessageText(message);
|
|
1189
|
+
if (!text) return void 0;
|
|
1190
|
+
for (let turnIndex = state.turns.length - 1; turnIndex >= 0; turnIndex -= 1) {
|
|
1191
|
+
const turn = state.turns[turnIndex];
|
|
1192
|
+
if (turn.loop_id === "root" || turn.role !== "assistant") continue;
|
|
1193
|
+
for (let toolIndex = turn.tool_calls.length - 1; toolIndex >= 0; toolIndex -= 1) {
|
|
1194
|
+
const toolCall = turn.tool_calls[toolIndex];
|
|
1195
|
+
if (toolCall.status !== "awaiting_answer") continue;
|
|
1196
|
+
if (!toolCall.tool_name.includes("AskUserQuestion")) continue;
|
|
1197
|
+
return { tool_call_id: toolCall.id, selections: {}, custom: { "0": text } };
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
return void 0;
|
|
1201
|
+
}
|
|
1202
|
+
function extractMessageText(message) {
|
|
1203
|
+
if (typeof message === "string") return message.trim();
|
|
1204
|
+
return message.filter((part) => part.type === "text").map((part) => part.text.trim()).filter(Boolean).join("\n").trim();
|
|
1205
|
+
}
|
|
1206
|
+
function sendToSession(session, message, mode, askUserAnswer, extras = {}) {
|
|
1207
|
+
return session.send(message, {
|
|
1208
|
+
mode,
|
|
1209
|
+
askUserAnswer: askUserAnswer ?? buildPendingAskUserAnswer(session.getState(), message),
|
|
1210
|
+
model: extras.model,
|
|
1211
|
+
thinkingOverride: extras.thinkingOverride,
|
|
1212
|
+
reasoningLevel: extras.reasoningLevel,
|
|
1213
|
+
refs: extras.refs,
|
|
1214
|
+
contextAttachments: extras.contextAttachments,
|
|
1215
|
+
topicAssetRefs: extras.topicAssetRefs,
|
|
1216
|
+
whatif: extras.whatif,
|
|
1217
|
+
replayDecision: extras.replayDecision,
|
|
1218
|
+
queueWhenRunning: extras.queueWhenRunning
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
// src/chat/chat-theme.tsx
|
|
1223
|
+
var NEUTRAL = {
|
|
1224
|
+
text: "text-[hsl(var(--muted-foreground))]",
|
|
1225
|
+
border: "border-l-[hsl(var(--swf-border-strong))]",
|
|
1226
|
+
surface: "bg-[hsl(var(--muted))]"
|
|
1227
|
+
};
|
|
1228
|
+
var TONE_CLASSES = {
|
|
1229
|
+
running: NEUTRAL,
|
|
1230
|
+
reasoning: NEUTRAL,
|
|
1231
|
+
success: {
|
|
1232
|
+
text: "text-[hsl(var(--swf-success))]",
|
|
1233
|
+
border: "border-l-[hsl(var(--swf-success))]",
|
|
1234
|
+
surface: "bg-[hsl(var(--swf-success-soft))]"
|
|
1235
|
+
},
|
|
1236
|
+
warning: {
|
|
1237
|
+
text: "text-[hsl(var(--swf-warning))]",
|
|
1238
|
+
border: "border-l-[hsl(var(--swf-warning))]",
|
|
1239
|
+
surface: "bg-[hsl(var(--swf-warning-soft))]"
|
|
1240
|
+
},
|
|
1241
|
+
danger: {
|
|
1242
|
+
text: "text-[hsl(var(--swf-danger))]",
|
|
1243
|
+
border: "border-l-[hsl(var(--swf-danger))]",
|
|
1244
|
+
surface: "bg-[hsl(var(--swf-danger-soft))]"
|
|
1245
|
+
}
|
|
1246
|
+
};
|
|
1247
|
+
function chatTone(tone) {
|
|
1248
|
+
return TONE_CLASSES[tone];
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
// src/lib/use-effect-event.ts
|
|
1252
|
+
import * as React from "react";
|
|
1253
|
+
function useEffectEventShim(callback) {
|
|
1254
|
+
const ref = React.useRef(callback);
|
|
1255
|
+
React.useLayoutEffect(() => {
|
|
1256
|
+
ref.current = callback;
|
|
1257
|
+
});
|
|
1258
|
+
return React.useCallback(((...args) => ref.current(...args)), []);
|
|
1259
|
+
}
|
|
1260
|
+
var builtin = React.useEffectEvent;
|
|
1261
|
+
var useEffectEvent2 = builtin ?? useEffectEventShim;
|
|
1262
|
+
|
|
1263
|
+
export {
|
|
1264
|
+
getInputHistoryGeneration,
|
|
1265
|
+
onInputCachesReset,
|
|
1266
|
+
pluginRefsForSend,
|
|
1267
|
+
getPluginSuggestionItems,
|
|
1268
|
+
queueShortcutLabel,
|
|
1269
|
+
isQueueShortcut,
|
|
1270
|
+
sendToSession,
|
|
1271
|
+
chatTone,
|
|
1272
|
+
RichInputServicesProvider,
|
|
1273
|
+
useRichInputServices,
|
|
1274
|
+
fetchClientQuery,
|
|
1275
|
+
setClientQueryData,
|
|
1276
|
+
useClientQuery,
|
|
1277
|
+
refreshClientQuery,
|
|
1278
|
+
useRefreshWhenRunEnds,
|
|
1279
|
+
useClientSessionSkills,
|
|
1280
|
+
useClientSkillStats,
|
|
1281
|
+
useClientContextStats,
|
|
1282
|
+
useClientBackgroundTasks,
|
|
1283
|
+
refreshClientSessionSkills,
|
|
1284
|
+
useClientSessionModel,
|
|
1285
|
+
useClientModelCatalog,
|
|
1286
|
+
PREFERRED_MODEL_KEY,
|
|
1287
|
+
MODEL_REASONING_LEVELS_KEY,
|
|
1288
|
+
useClientModelPreferences,
|
|
1289
|
+
useModelCatalog,
|
|
1290
|
+
useModelPreferences,
|
|
1291
|
+
useComposerConnectionInfo,
|
|
1292
|
+
useComposerConnection,
|
|
1293
|
+
useSessionSkills,
|
|
1294
|
+
useSkillStats,
|
|
1295
|
+
useContextStats,
|
|
1296
|
+
useBackgroundTaskList,
|
|
1297
|
+
useSessionDataInvalidation,
|
|
1298
|
+
useEffectEvent2 as useEffectEvent,
|
|
1299
|
+
sessionPluginLabel,
|
|
1300
|
+
SessionPluginIcon,
|
|
1301
|
+
SessionPluginConfigDialog,
|
|
1302
|
+
PluginConnectorList,
|
|
1303
|
+
catalogToConnectorItems,
|
|
1304
|
+
mergeConnectorItems,
|
|
1305
|
+
filterConnectorItems,
|
|
1306
|
+
useSessionPluginActivation
|
|
1307
|
+
};
|
|
1308
|
+
//# sourceMappingURL=chunk-F4MWGSKX.js.map
|