@blade-hq/agent-react 2608.0.5 → 2608.0.7-beta.0
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 +155 -3
- package/dist/components/AgentChat.d.ts +21 -0
- package/dist/components/AssistantTurnBlock.d.ts +1 -1
- package/dist/components/ChatSurface.d.ts +68 -0
- package/dist/components/ChatView.d.ts +20 -39
- package/dist/components/ConnectionBanner.d.ts +1 -1
- package/dist/components/LlmAdvancedSettings.d.ts +40 -0
- package/dist/components/LlmChat.d.ts +29 -0
- package/dist/components/MarkdownContent.d.ts +3 -6
- package/dist/components/MessageList.d.ts +10 -3
- package/dist/components/PostChatFollowupBlock.d.ts +59 -0
- package/dist/components/ReplayBar.d.ts +13 -0
- package/dist/components/ReplayMismatchPrompt.d.ts +8 -0
- package/dist/embed/entry.d.ts +5 -0
- package/dist/hooks/use-llm-chat.d.ts +57 -0
- package/dist/hooks/use-replay.d.ts +50 -0
- package/dist/index.d.ts +14 -1
- package/dist/index.js +1707 -505
- package/dist/index.js.map +1 -1
- package/dist/style.css +14 -0
- package/dist/style.full.css +15 -1
- package/package.json +2 -2
- package/public-api.md +424 -4
- package/dist/components/FileCard.d.ts +0 -16
- package/dist/lib/media-tags.d.ts +0 -24
package/dist/index.js
CHANGED
|
@@ -99,8 +99,453 @@ function useAgentSession(sessionId, options = {}) {
|
|
|
99
99
|
return { session, state, error };
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
// src/
|
|
103
|
-
import {
|
|
102
|
+
// src/hooks/use-replay.ts
|
|
103
|
+
import { DEFAULT_REPLAY_SPEED } from "@blade-hq/agent-client";
|
|
104
|
+
import { useCallback, useEffect as useEffect2, useState as useState2 } from "react";
|
|
105
|
+
var replayCreating = /* @__PURE__ */ new Set();
|
|
106
|
+
function useReplay(session) {
|
|
107
|
+
const client = useBladeClient();
|
|
108
|
+
const sessionId = session?.sessionId ?? null;
|
|
109
|
+
const [replay, setReplay] = useState2(null);
|
|
110
|
+
const [viewerRole, setViewerRole] = useState2(null);
|
|
111
|
+
const [sourceRunning, setSourceRunning] = useState2(false);
|
|
112
|
+
const [mismatch, setMismatch] = useState2(null);
|
|
113
|
+
const [preview, setPreview] = useState2({ status: "loading" });
|
|
114
|
+
const [isStarting, setIsStarting] = useState2(false);
|
|
115
|
+
const [error, setError] = useState2(null);
|
|
116
|
+
const [draftSpeed, setDraftSpeed] = useState2(DEFAULT_REPLAY_SPEED);
|
|
117
|
+
useEffect2(() => {
|
|
118
|
+
if (!session) {
|
|
119
|
+
setReplay(null);
|
|
120
|
+
setViewerRole(null);
|
|
121
|
+
setSourceRunning(false);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const pull = () => {
|
|
125
|
+
const state = session.getState();
|
|
126
|
+
setReplay(state.replay);
|
|
127
|
+
setViewerRole(state.viewerRole);
|
|
128
|
+
setSourceRunning(state.isStreaming || state.status === "running");
|
|
129
|
+
};
|
|
130
|
+
pull();
|
|
131
|
+
return session.subscribe(pull);
|
|
132
|
+
}, [session]);
|
|
133
|
+
const [lastSessionId, setLastSessionId] = useState2(sessionId);
|
|
134
|
+
if (sessionId !== lastSessionId) {
|
|
135
|
+
setLastSessionId(sessionId);
|
|
136
|
+
setMismatch(null);
|
|
137
|
+
setError(null);
|
|
138
|
+
setDraftSpeed(DEFAULT_REPLAY_SPEED);
|
|
139
|
+
}
|
|
140
|
+
useEffect2(() => {
|
|
141
|
+
setPreview({ status: "loading" });
|
|
142
|
+
if (!sessionId) return;
|
|
143
|
+
let alive = true;
|
|
144
|
+
client.sessions.getReplayPreview(sessionId).then((result) => {
|
|
145
|
+
if (!alive) return;
|
|
146
|
+
setPreview({
|
|
147
|
+
status: "ready",
|
|
148
|
+
supported: result.supported,
|
|
149
|
+
reason: result.reason ?? null
|
|
150
|
+
});
|
|
151
|
+
}).catch(() => {
|
|
152
|
+
if (alive) setPreview({ status: "ready", supported: true, reason: null });
|
|
153
|
+
});
|
|
154
|
+
return () => {
|
|
155
|
+
alive = false;
|
|
156
|
+
};
|
|
157
|
+
}, [client, sessionId, sourceRunning]);
|
|
158
|
+
useEffect2(() => {
|
|
159
|
+
if (!session) return;
|
|
160
|
+
return session.on("replayMismatch", ({ actualMessage, expectedMessage, respond }) => {
|
|
161
|
+
setMismatch({
|
|
162
|
+
actualMessage: actualMessage ?? "",
|
|
163
|
+
expectedMessage: expectedMessage ?? "",
|
|
164
|
+
resolve: (decision) => {
|
|
165
|
+
setMismatch(null);
|
|
166
|
+
respond(decision);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
}, [session]);
|
|
171
|
+
const isReplay = replay?.isReplay ?? false;
|
|
172
|
+
const setSpeed = useCallback(
|
|
173
|
+
async (next) => {
|
|
174
|
+
setDraftSpeed(next);
|
|
175
|
+
if (!session) return;
|
|
176
|
+
setError(null);
|
|
177
|
+
try {
|
|
178
|
+
await session.setReplaySpeed(next);
|
|
179
|
+
} catch (err) {
|
|
180
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
[session]
|
|
184
|
+
);
|
|
185
|
+
const exitToAutonomous = useCallback(async () => {
|
|
186
|
+
if (!session) return;
|
|
187
|
+
setError(null);
|
|
188
|
+
const pending = mismatch;
|
|
189
|
+
if (pending) {
|
|
190
|
+
pending.resolve("continue_replay");
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
try {
|
|
194
|
+
await session.exitReplay();
|
|
195
|
+
} catch (err) {
|
|
196
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
197
|
+
}
|
|
198
|
+
}, [session, mismatch]);
|
|
199
|
+
const startReplay = useCallback(
|
|
200
|
+
async (speed) => {
|
|
201
|
+
if (!sessionId) throw new Error("\u5F53\u524D\u6CA1\u6709\u53EF\u56DE\u653E\u7684\u4F1A\u8BDD");
|
|
202
|
+
if (replayCreating.has(sessionId)) throw new Error("\u6B63\u5728\u521B\u5EFA\u56DE\u653E\u4F1A\u8BDD\uFF0C\u8BF7\u7A0D\u5019");
|
|
203
|
+
replayCreating.add(sessionId);
|
|
204
|
+
setIsStarting(true);
|
|
205
|
+
setError(null);
|
|
206
|
+
try {
|
|
207
|
+
const result = await client.sessions.startReplaySession(sessionId, speed ?? draftSpeed);
|
|
208
|
+
return result.session_id;
|
|
209
|
+
} catch (err) {
|
|
210
|
+
const failure = err instanceof Error ? err : new Error(String(err));
|
|
211
|
+
setError(failure);
|
|
212
|
+
throw failure;
|
|
213
|
+
} finally {
|
|
214
|
+
replayCreating.delete(sessionId);
|
|
215
|
+
setIsStarting(false);
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
[client, sessionId, draftSpeed]
|
|
219
|
+
);
|
|
220
|
+
return {
|
|
221
|
+
isReplay,
|
|
222
|
+
speed: isReplay ? replay?.speed ?? 1 : draftSpeed,
|
|
223
|
+
setSpeed,
|
|
224
|
+
exitToAutonomous,
|
|
225
|
+
mismatch,
|
|
226
|
+
canControl: viewerRole !== "viewer",
|
|
227
|
+
canReplay: Boolean(sessionId) && preview.status === "ready" && preview.supported && viewerRole !== "viewer",
|
|
228
|
+
unsupportedReason: preview.status === "ready" && !preview.supported ? preview.reason : null,
|
|
229
|
+
startReplay,
|
|
230
|
+
isStarting,
|
|
231
|
+
error
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// src/hooks/use-llm-chat.ts
|
|
236
|
+
import { useCallback as useCallback2, useMemo as useMemo2, useRef as useRef2, useState as useState3 } from "react";
|
|
237
|
+
var DEFAULT_HISTORY_TURNS = 12;
|
|
238
|
+
var DEFAULT_MAX_TOOL_ROUNDS = 4;
|
|
239
|
+
var syntheticIdSeq = 0;
|
|
240
|
+
function useLlmChat(options) {
|
|
241
|
+
const [history, setHistory] = useState3([]);
|
|
242
|
+
const [streamingText, setStreamingText] = useState3(null);
|
|
243
|
+
const [streamingCalls, setStreamingCalls] = useState3([]);
|
|
244
|
+
const [failedToolIds, setFailedToolIds] = useState3([]);
|
|
245
|
+
const [error, setError] = useState3(null);
|
|
246
|
+
const [isStreaming, setIsStreaming] = useState3(false);
|
|
247
|
+
const abortRef = useRef2(null);
|
|
248
|
+
const generationRef = useRef2(0);
|
|
249
|
+
const historyRef = useRef2([]);
|
|
250
|
+
const optionsRef = useRef2(options);
|
|
251
|
+
optionsRef.current = options;
|
|
252
|
+
historyRef.current = history;
|
|
253
|
+
const stop = useCallback2(() => {
|
|
254
|
+
abortRef.current?.abort();
|
|
255
|
+
abortRef.current = null;
|
|
256
|
+
}, []);
|
|
257
|
+
const reset = useCallback2(() => {
|
|
258
|
+
generationRef.current += 1;
|
|
259
|
+
stop();
|
|
260
|
+
setHistory([]);
|
|
261
|
+
setStreamingText(null);
|
|
262
|
+
setStreamingCalls([]);
|
|
263
|
+
setFailedToolIds([]);
|
|
264
|
+
setError(null);
|
|
265
|
+
setIsStreaming(false);
|
|
266
|
+
}, [stop]);
|
|
267
|
+
const send = useCallback2(async (text) => {
|
|
268
|
+
const content = text.trim();
|
|
269
|
+
if (!content || abortRef.current) return false;
|
|
270
|
+
const opts = optionsRef.current;
|
|
271
|
+
const maxRounds = opts.maxToolRounds ?? DEFAULT_MAX_TOOL_ROUNDS;
|
|
272
|
+
const commit = (message) => {
|
|
273
|
+
historyRef.current = [...historyRef.current, message];
|
|
274
|
+
setHistory(historyRef.current);
|
|
275
|
+
};
|
|
276
|
+
setError(null);
|
|
277
|
+
setIsStreaming(true);
|
|
278
|
+
setStreamingText("");
|
|
279
|
+
setStreamingCalls([]);
|
|
280
|
+
commit({ role: "user", content });
|
|
281
|
+
let partial = "";
|
|
282
|
+
const controller = new AbortController();
|
|
283
|
+
abortRef.current = controller;
|
|
284
|
+
const generation = generationRef.current;
|
|
285
|
+
try {
|
|
286
|
+
for (let round = 0; ; round += 1) {
|
|
287
|
+
const result = await streamOnce(opts, historyRef.current, controller.signal, {
|
|
288
|
+
onText: (chunk) => {
|
|
289
|
+
partial += chunk;
|
|
290
|
+
setStreamingText((prev) => `${prev ?? ""}${chunk}`);
|
|
291
|
+
},
|
|
292
|
+
onToolCalls: (calls) => setStreamingCalls(calls)
|
|
293
|
+
});
|
|
294
|
+
const assistant = {
|
|
295
|
+
role: "assistant",
|
|
296
|
+
content: result.content,
|
|
297
|
+
...result.toolCalls.length ? { tool_calls: result.toolCalls } : {}
|
|
298
|
+
};
|
|
299
|
+
setStreamingText(null);
|
|
300
|
+
setStreamingCalls([]);
|
|
301
|
+
commit(assistant);
|
|
302
|
+
if (!result.toolCalls.length) return true;
|
|
303
|
+
const bail = (reason) => {
|
|
304
|
+
for (const call of result.toolCalls) {
|
|
305
|
+
commit({ role: "tool", tool_call_id: call.id, content: JSON.stringify({ error: reason }) });
|
|
306
|
+
}
|
|
307
|
+
setFailedToolIds((prev) => [...prev, ...result.toolCalls.map((call) => call.id)]);
|
|
308
|
+
};
|
|
309
|
+
if (!opts.onToolCall) {
|
|
310
|
+
bail("\u8FD9\u4E2A\u5E94\u7528\u6CA1\u6709\u63D0\u4F9B\u5DE5\u5177\u6267\u884C\u5165\u53E3");
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
313
|
+
if (round >= maxRounds) {
|
|
314
|
+
bail(`\u5DE5\u5177\u8C03\u7528\u5DF2\u8FBE\u4E0A\u9650 ${maxRounds} \u8F6E\uFF0C\u6CA1\u6709\u6267\u884C`);
|
|
315
|
+
setError(`\u5DE5\u5177\u8C03\u7528\u8D85\u8FC7 ${maxRounds} \u8F6E\u4ECD\u672A\u7ED9\u51FA\u7ED3\u8BBA\uFF0C\u5DF2\u505C\u4E0B\u3002`);
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
for (const call of result.toolCalls) {
|
|
319
|
+
let output;
|
|
320
|
+
try {
|
|
321
|
+
const value = await opts.onToolCall({
|
|
322
|
+
id: call.id,
|
|
323
|
+
name: call.function.name,
|
|
324
|
+
arguments: call.function.arguments
|
|
325
|
+
});
|
|
326
|
+
output = typeof value === "string" ? value : JSON.stringify(value ?? null);
|
|
327
|
+
} catch (err) {
|
|
328
|
+
output = JSON.stringify({ error: err instanceof Error ? err.message : String(err) });
|
|
329
|
+
setFailedToolIds((prev) => [...prev, call.id]);
|
|
330
|
+
}
|
|
331
|
+
commit({ role: "tool", tool_call_id: call.id, content: output });
|
|
332
|
+
}
|
|
333
|
+
setStreamingText("");
|
|
334
|
+
}
|
|
335
|
+
} catch (err) {
|
|
336
|
+
setStreamingText(null);
|
|
337
|
+
setStreamingCalls([]);
|
|
338
|
+
if (controller.signal.aborted) {
|
|
339
|
+
if (generation !== generationRef.current) return false;
|
|
340
|
+
commit({ role: "assistant", content: partial ? `${partial}\uFF08\u5DF2\u505C\u6B62\uFF09` : "\uFF08\u5DF2\u505C\u6B62\uFF09" });
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
if (partial && generation === generationRef.current) {
|
|
344
|
+
commit({ role: "assistant", content: partial });
|
|
345
|
+
}
|
|
346
|
+
setError(err instanceof Error && err.message ? err.message : "\u5BF9\u8BDD\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5");
|
|
347
|
+
return false;
|
|
348
|
+
} finally {
|
|
349
|
+
if (abortRef.current === controller) abortRef.current = null;
|
|
350
|
+
setIsStreaming(false);
|
|
351
|
+
}
|
|
352
|
+
}, []);
|
|
353
|
+
const messages = useMemo2(
|
|
354
|
+
() => toChatMessages(history, streamingText, streamingCalls, failedToolIds),
|
|
355
|
+
[history, streamingText, streamingCalls, failedToolIds]
|
|
356
|
+
);
|
|
357
|
+
return { messages, isStreaming, error, send, stop, reset };
|
|
358
|
+
}
|
|
359
|
+
async function streamOnce(options, history, signal, handlers) {
|
|
360
|
+
const {
|
|
361
|
+
baseURL,
|
|
362
|
+
model,
|
|
363
|
+
apiKey,
|
|
364
|
+
headers,
|
|
365
|
+
system,
|
|
366
|
+
historyTurns = DEFAULT_HISTORY_TURNS,
|
|
367
|
+
temperature,
|
|
368
|
+
extraBody,
|
|
369
|
+
tools,
|
|
370
|
+
fetchImpl
|
|
371
|
+
} = options;
|
|
372
|
+
const trimmed = trimHistory(history, historyTurns);
|
|
373
|
+
const payload = {
|
|
374
|
+
model,
|
|
375
|
+
stream: true,
|
|
376
|
+
messages: system ? [{ role: "system", content: system }, ...trimmed] : trimmed,
|
|
377
|
+
...tools?.length ? { tools } : {},
|
|
378
|
+
...temperature === void 0 ? {} : { temperature },
|
|
379
|
+
...extraBody
|
|
380
|
+
};
|
|
381
|
+
const doFetch = fetchImpl ?? globalThis.fetch;
|
|
382
|
+
const url = `${baseURL.replace(/\/$/, "")}/chat/completions`;
|
|
383
|
+
let response;
|
|
384
|
+
try {
|
|
385
|
+
response = await doFetch(url, {
|
|
386
|
+
method: "POST",
|
|
387
|
+
headers: {
|
|
388
|
+
"Content-Type": "application/json",
|
|
389
|
+
...apiKey ? { Authorization: `Bearer ${apiKey}` } : {},
|
|
390
|
+
...headers
|
|
391
|
+
},
|
|
392
|
+
body: JSON.stringify(payload),
|
|
393
|
+
signal
|
|
394
|
+
});
|
|
395
|
+
} catch (err) {
|
|
396
|
+
if (signal.aborted) throw err;
|
|
397
|
+
throw new Error(`\u8FDE\u4E0D\u4E0A\u6A21\u578B\u670D\u52A1 ${url}\uFF1A${err instanceof Error ? err.message : String(err)}`);
|
|
398
|
+
}
|
|
399
|
+
if (!response.ok) {
|
|
400
|
+
throw new Error(await describeHttpError(response));
|
|
401
|
+
}
|
|
402
|
+
if (!response.body) {
|
|
403
|
+
throw new Error("\u6A21\u578B\u670D\u52A1\u6CA1\u6709\u8FD4\u56DE\u6D41\u5F0F\u54CD\u5E94");
|
|
404
|
+
}
|
|
405
|
+
let content = "";
|
|
406
|
+
const calls = [];
|
|
407
|
+
for await (const chunk of readSse(response.body, signal)) {
|
|
408
|
+
if (chunk.error) {
|
|
409
|
+
throw new Error(chunk.error);
|
|
410
|
+
}
|
|
411
|
+
if (chunk.text) {
|
|
412
|
+
content += chunk.text;
|
|
413
|
+
handlers.onText(chunk.text);
|
|
414
|
+
}
|
|
415
|
+
if (chunk.toolCallDeltas) {
|
|
416
|
+
for (const delta of chunk.toolCallDeltas) {
|
|
417
|
+
const index = delta.index ?? 0;
|
|
418
|
+
const existing = calls[index] ?? { id: "", type: "function", function: { name: "", arguments: "" } };
|
|
419
|
+
calls[index] = {
|
|
420
|
+
id: delta.id ?? existing.id,
|
|
421
|
+
type: "function",
|
|
422
|
+
function: {
|
|
423
|
+
name: delta.function?.name ?? existing.function.name,
|
|
424
|
+
arguments: `${existing.function.arguments}${delta.function?.arguments ?? ""}`
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
handlers.onToolCalls(calls.filter(Boolean).map((call) => ({ ...call })));
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
const normalized = calls.filter(Boolean).filter((call) => call.function.name).map((call) => {
|
|
432
|
+
if (call.id) return call;
|
|
433
|
+
syntheticIdSeq += 1;
|
|
434
|
+
return { ...call, id: `call_auto_${syntheticIdSeq}` };
|
|
435
|
+
});
|
|
436
|
+
return { content, toolCalls: normalized };
|
|
437
|
+
}
|
|
438
|
+
function trimHistory(history, historyTurns) {
|
|
439
|
+
const userIndexes = history.reduce((acc, msg, i) => {
|
|
440
|
+
if (msg.role === "user") acc.push(i);
|
|
441
|
+
return acc;
|
|
442
|
+
}, []);
|
|
443
|
+
if (userIndexes.length <= historyTurns) return history;
|
|
444
|
+
return history.slice(userIndexes[userIndexes.length - historyTurns]);
|
|
445
|
+
}
|
|
446
|
+
async function describeHttpError(response) {
|
|
447
|
+
const raw = await response.text().catch(() => "");
|
|
448
|
+
try {
|
|
449
|
+
const parsed = JSON.parse(raw);
|
|
450
|
+
const message = typeof parsed.error === "string" ? parsed.error : parsed.error?.message ?? (typeof parsed.detail === "string" ? parsed.detail : void 0);
|
|
451
|
+
if (message) return `\u6A21\u578B\u670D\u52A1\u8FD4\u56DE ${response.status}\uFF1A${message}`;
|
|
452
|
+
} catch {
|
|
453
|
+
}
|
|
454
|
+
const snippet = raw.trim().slice(0, 120);
|
|
455
|
+
return snippet ? `\u6A21\u578B\u670D\u52A1\u8FD4\u56DE ${response.status}\uFF1A${snippet}` : `\u6A21\u578B\u670D\u52A1\u8FD4\u56DE ${response.status}`;
|
|
456
|
+
}
|
|
457
|
+
async function* readSse(stream, signal) {
|
|
458
|
+
const reader = stream.getReader();
|
|
459
|
+
const decoder = new TextDecoder();
|
|
460
|
+
let buffer = "";
|
|
461
|
+
try {
|
|
462
|
+
while (!signal.aborted) {
|
|
463
|
+
const { done, value } = await reader.read();
|
|
464
|
+
if (done) {
|
|
465
|
+
const tail = parseSse(buffer);
|
|
466
|
+
if (tail && tail !== "done") yield tail;
|
|
467
|
+
break;
|
|
468
|
+
}
|
|
469
|
+
buffer = `${buffer}${decoder.decode(value, { stream: true })}`.replace(/\r\n/g, "\n");
|
|
470
|
+
let boundary = buffer.indexOf("\n\n");
|
|
471
|
+
while (boundary !== -1) {
|
|
472
|
+
const chunk = parseSse(buffer.slice(0, boundary));
|
|
473
|
+
buffer = buffer.slice(boundary + 2);
|
|
474
|
+
if (chunk === "done") return;
|
|
475
|
+
if (chunk) yield chunk;
|
|
476
|
+
boundary = buffer.indexOf("\n\n");
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
} finally {
|
|
480
|
+
reader.cancel().catch(() => {
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
function parseSse(raw) {
|
|
485
|
+
const dataLines = [];
|
|
486
|
+
for (const line of raw.split("\n")) {
|
|
487
|
+
if (line.startsWith("data:")) dataLines.push(line.slice(5).trim());
|
|
488
|
+
}
|
|
489
|
+
if (!dataLines.length) return null;
|
|
490
|
+
const data = dataLines.join("\n");
|
|
491
|
+
if (data === "[DONE]") return "done";
|
|
492
|
+
let parsed;
|
|
493
|
+
try {
|
|
494
|
+
parsed = JSON.parse(data);
|
|
495
|
+
} catch {
|
|
496
|
+
return null;
|
|
497
|
+
}
|
|
498
|
+
if (parsed.error) {
|
|
499
|
+
const message = typeof parsed.error === "string" ? parsed.error : parsed.error.message;
|
|
500
|
+
return { error: message || "\u6A21\u578B\u670D\u52A1\u8FD4\u56DE\u4E86\u9519\u8BEF" };
|
|
501
|
+
}
|
|
502
|
+
const delta = parsed.choices?.[0]?.delta;
|
|
503
|
+
if (!delta) return null;
|
|
504
|
+
return { text: delta.content, toolCallDeltas: delta.tool_calls };
|
|
505
|
+
}
|
|
506
|
+
function toChatMessages(history, streamingText, streamingCalls, failedToolIds) {
|
|
507
|
+
const results = /* @__PURE__ */ new Map();
|
|
508
|
+
for (const msg of history) {
|
|
509
|
+
if (msg.role === "tool") results.set(msg.tool_call_id, msg.content);
|
|
510
|
+
}
|
|
511
|
+
const messages = [];
|
|
512
|
+
for (const msg of history) {
|
|
513
|
+
if (msg.role === "tool") continue;
|
|
514
|
+
if (msg.role === "user") {
|
|
515
|
+
messages.push({ role: "user", content: msg.content, status: "completed" });
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
518
|
+
messages.push({
|
|
519
|
+
role: "assistant",
|
|
520
|
+
content: msg.content,
|
|
521
|
+
status: "completed",
|
|
522
|
+
...msg.tool_calls?.length ? { tool_calls: msg.tool_calls.map((call) => toToolCallInfo(call, results, failedToolIds)) } : {}
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
if (streamingText !== null) {
|
|
526
|
+
messages.push({
|
|
527
|
+
role: "assistant",
|
|
528
|
+
content: streamingText,
|
|
529
|
+
status: "streaming",
|
|
530
|
+
...streamingCalls.length ? { tool_calls: streamingCalls.map((call) => toToolCallInfo(call, results, failedToolIds)) } : {}
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
return messages;
|
|
534
|
+
}
|
|
535
|
+
function toToolCallInfo(call, results, failedToolIds) {
|
|
536
|
+
const result = results.get(call.id);
|
|
537
|
+
const failed = failedToolIds.includes(call.id);
|
|
538
|
+
return {
|
|
539
|
+
id: call.id,
|
|
540
|
+
name: call.function.name,
|
|
541
|
+
arguments: call.function.arguments,
|
|
542
|
+
...result === void 0 ? {} : { result },
|
|
543
|
+
status: result === void 0 ? "pending" : failed ? "error" : "done"
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// src/components/AgentChat.tsx
|
|
548
|
+
import { BladeApiError, latestPostChatFollowup } from "@blade-hq/agent-client";
|
|
104
549
|
|
|
105
550
|
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/createLucideIcon.js
|
|
106
551
|
import { forwardRef as forwardRef2, createElement as createElement2 } from "react";
|
|
@@ -173,11 +618,16 @@ var createLucideIcon = (iconName, iconNode) => {
|
|
|
173
618
|
return Component2;
|
|
174
619
|
};
|
|
175
620
|
|
|
176
|
-
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/
|
|
177
|
-
var
|
|
178
|
-
["
|
|
179
|
-
["path", { d: "
|
|
180
|
-
|
|
621
|
+
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/arrow-right.js
|
|
622
|
+
var ArrowRight = createLucideIcon("ArrowRight", [
|
|
623
|
+
["path", { d: "M5 12h14", key: "1ays0h" }],
|
|
624
|
+
["path", { d: "m12 5 7 7-7 7", key: "xquz4c" }]
|
|
625
|
+
]);
|
|
626
|
+
|
|
627
|
+
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/arrow-up-right.js
|
|
628
|
+
var ArrowUpRight = createLucideIcon("ArrowUpRight", [
|
|
629
|
+
["path", { d: "M7 7h10v10", key: "1tivn9" }],
|
|
630
|
+
["path", { d: "M7 17 17 7", key: "1vkiza" }]
|
|
181
631
|
]);
|
|
182
632
|
|
|
183
633
|
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/arrow-up.js
|
|
@@ -247,31 +697,6 @@ var Copy = createLucideIcon("Copy", [
|
|
|
247
697
|
["path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2", key: "zix9uf" }]
|
|
248
698
|
]);
|
|
249
699
|
|
|
250
|
-
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/download.js
|
|
251
|
-
var Download = createLucideIcon("Download", [
|
|
252
|
-
["path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4", key: "ih7n3h" }],
|
|
253
|
-
["polyline", { points: "7 10 12 15 17 10", key: "2ggqvy" }],
|
|
254
|
-
["line", { x1: "12", x2: "12", y1: "15", y2: "3", key: "1vk2je" }]
|
|
255
|
-
]);
|
|
256
|
-
|
|
257
|
-
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/file-code-2.js
|
|
258
|
-
var FileCode2 = createLucideIcon("FileCode2", [
|
|
259
|
-
["path", { d: "M4 22h14a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v4", key: "1pf5j1" }],
|
|
260
|
-
["path", { d: "M14 2v4a2 2 0 0 0 2 2h4", key: "tnqrlb" }],
|
|
261
|
-
["path", { d: "m5 12-3 3 3 3", key: "oke12k" }],
|
|
262
|
-
["path", { d: "m9 18 3-3-3-3", key: "112psh" }]
|
|
263
|
-
]);
|
|
264
|
-
|
|
265
|
-
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/file-spreadsheet.js
|
|
266
|
-
var FileSpreadsheet = createLucideIcon("FileSpreadsheet", [
|
|
267
|
-
["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z", key: "1rqfz7" }],
|
|
268
|
-
["path", { d: "M14 2v4a2 2 0 0 0 2 2h4", key: "tnqrlb" }],
|
|
269
|
-
["path", { d: "M8 13h2", key: "yr2amv" }],
|
|
270
|
-
["path", { d: "M14 13h2", key: "un5t4a" }],
|
|
271
|
-
["path", { d: "M8 17h2", key: "2yhykz" }],
|
|
272
|
-
["path", { d: "M14 17h2", key: "10kma7" }]
|
|
273
|
-
]);
|
|
274
|
-
|
|
275
700
|
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/file-text.js
|
|
276
701
|
var FileText = createLucideIcon("FileText", [
|
|
277
702
|
["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z", key: "1rqfz7" }],
|
|
@@ -281,29 +706,11 @@ var FileText = createLucideIcon("FileText", [
|
|
|
281
706
|
["path", { d: "M16 17H8", key: "z1uh3a" }]
|
|
282
707
|
]);
|
|
283
708
|
|
|
284
|
-
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/
|
|
285
|
-
var
|
|
286
|
-
["
|
|
287
|
-
["path", { d: "
|
|
288
|
-
]
|
|
289
|
-
|
|
290
|
-
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/film.js
|
|
291
|
-
var Film = createLucideIcon("Film", [
|
|
292
|
-
["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", key: "afitv7" }],
|
|
293
|
-
["path", { d: "M7 3v18", key: "bbkbws" }],
|
|
294
|
-
["path", { d: "M3 7.5h4", key: "zfgn84" }],
|
|
295
|
-
["path", { d: "M3 12h18", key: "1i2n21" }],
|
|
296
|
-
["path", { d: "M3 16.5h4", key: "1230mu" }],
|
|
297
|
-
["path", { d: "M17 3v18", key: "in4fa5" }],
|
|
298
|
-
["path", { d: "M17 7.5h4", key: "myr1c1" }],
|
|
299
|
-
["path", { d: "M17 16.5h4", key: "go4c1d" }]
|
|
300
|
-
]);
|
|
301
|
-
|
|
302
|
-
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/image.js
|
|
303
|
-
var Image = createLucideIcon("Image", [
|
|
304
|
-
["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2", key: "1m3agn" }],
|
|
305
|
-
["circle", { cx: "9", cy: "9", r: "2", key: "af1f0g" }],
|
|
306
|
-
["path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21", key: "1xmnt7" }]
|
|
709
|
+
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/globe.js
|
|
710
|
+
var Globe = createLucideIcon("Globe", [
|
|
711
|
+
["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
|
|
712
|
+
["path", { d: "M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20", key: "13o1zl" }],
|
|
713
|
+
["path", { d: "M2 12h20", key: "9i4pu4" }]
|
|
307
714
|
]);
|
|
308
715
|
|
|
309
716
|
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/layers.js
|
|
@@ -369,11 +776,32 @@ var MessageSquare = createLucideIcon("MessageSquare", [
|
|
|
369
776
|
["path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z", key: "1lielz" }]
|
|
370
777
|
]);
|
|
371
778
|
|
|
372
|
-
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/
|
|
373
|
-
var
|
|
374
|
-
["
|
|
375
|
-
|
|
376
|
-
|
|
779
|
+
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/play.js
|
|
780
|
+
var Play = createLucideIcon("Play", [
|
|
781
|
+
["polygon", { points: "6 3 20 12 6 21 6 3", key: "1oa8hb" }]
|
|
782
|
+
]);
|
|
783
|
+
|
|
784
|
+
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/settings-2.js
|
|
785
|
+
var Settings2 = createLucideIcon("Settings2", [
|
|
786
|
+
["path", { d: "M20 7h-9", key: "3s1dr2" }],
|
|
787
|
+
["path", { d: "M14 17H5", key: "gfn3mx" }],
|
|
788
|
+
["circle", { cx: "17", cy: "17", r: "3", key: "18b49y" }],
|
|
789
|
+
["circle", { cx: "7", cy: "7", r: "3", key: "dfmy0x" }]
|
|
790
|
+
]);
|
|
791
|
+
|
|
792
|
+
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/sparkles.js
|
|
793
|
+
var Sparkles = createLucideIcon("Sparkles", [
|
|
794
|
+
[
|
|
795
|
+
"path",
|
|
796
|
+
{
|
|
797
|
+
d: "M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z",
|
|
798
|
+
key: "4pj2yx"
|
|
799
|
+
}
|
|
800
|
+
],
|
|
801
|
+
["path", { d: "M20 3v4", key: "1olli1" }],
|
|
802
|
+
["path", { d: "M22 5h-4", key: "1gvqau" }],
|
|
803
|
+
["path", { d: "M4 17v2", key: "vumght" }],
|
|
804
|
+
["path", { d: "M5 18H3", key: "zchphs" }]
|
|
377
805
|
]);
|
|
378
806
|
|
|
379
807
|
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/square.js
|
|
@@ -400,8 +828,8 @@ var X = createLucideIcon("X", [
|
|
|
400
828
|
["path", { d: "m6 6 12 12", key: "d8bk6v" }]
|
|
401
829
|
]);
|
|
402
830
|
|
|
403
|
-
// src/components/
|
|
404
|
-
import { useEffect as
|
|
831
|
+
// src/components/AgentChat.tsx
|
|
832
|
+
import { useCallback as useCallback7, useEffect as useEffect9, useMemo as useMemo8, useState as useState13 } from "react";
|
|
405
833
|
|
|
406
834
|
// src/lib/utils.ts
|
|
407
835
|
function cn(...inputs) {
|
|
@@ -416,8 +844,120 @@ async function copyToClipboard(text) {
|
|
|
416
844
|
}
|
|
417
845
|
}
|
|
418
846
|
|
|
419
|
-
// src/components/
|
|
847
|
+
// src/components/ReplayBar.tsx
|
|
420
848
|
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
849
|
+
var SPEED_OPTIONS = [1, 2, 5];
|
|
850
|
+
function ReplayBar({
|
|
851
|
+
isReplay,
|
|
852
|
+
speed,
|
|
853
|
+
onSpeedChange,
|
|
854
|
+
onExit,
|
|
855
|
+
canControl = true,
|
|
856
|
+
className
|
|
857
|
+
}) {
|
|
858
|
+
if (!isReplay) return null;
|
|
859
|
+
return /* @__PURE__ */ jsxs(
|
|
860
|
+
"div",
|
|
861
|
+
{
|
|
862
|
+
className: cn(
|
|
863
|
+
"flex flex-wrap items-center gap-2 border-b border-[hsl(var(--border))] bg-[hsl(var(--muted))]/40 px-4 py-2 text-xs",
|
|
864
|
+
className
|
|
865
|
+
),
|
|
866
|
+
children: [
|
|
867
|
+
/* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5 font-medium text-[hsl(var(--foreground))]", children: [
|
|
868
|
+
/* @__PURE__ */ jsx2(Play, { size: 13 }),
|
|
869
|
+
"\u56DE\u653E\u6A21\u5F0F"
|
|
870
|
+
] }),
|
|
871
|
+
/* @__PURE__ */ jsx2("span", { className: "text-[hsl(var(--muted-foreground))]", children: "\u6B63\u5728\u91CD\u73B0\u4E4B\u524D\u7684\u5BF9\u8BDD" }),
|
|
872
|
+
/* @__PURE__ */ jsxs("div", { className: "ml-auto flex items-center gap-2", children: [
|
|
873
|
+
/* @__PURE__ */ jsx2("div", { className: "flex items-center gap-0.5 rounded-md border border-[hsl(var(--border))] bg-[hsl(var(--card))] p-0.5", children: SPEED_OPTIONS.map((option) => /* @__PURE__ */ jsxs(
|
|
874
|
+
"button",
|
|
875
|
+
{
|
|
876
|
+
type: "button",
|
|
877
|
+
onClick: () => onSpeedChange(option),
|
|
878
|
+
"aria-pressed": speed === option,
|
|
879
|
+
disabled: !canControl,
|
|
880
|
+
className: cn(
|
|
881
|
+
"h-6 rounded px-2 font-medium transition-colors",
|
|
882
|
+
speed === option ? "bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))]" : "text-[hsl(var(--muted-foreground))] hover:bg-[hsl(var(--accent))] hover:text-[hsl(var(--foreground))]",
|
|
883
|
+
!canControl && "cursor-not-allowed opacity-50"
|
|
884
|
+
),
|
|
885
|
+
children: [
|
|
886
|
+
option,
|
|
887
|
+
"x"
|
|
888
|
+
]
|
|
889
|
+
},
|
|
890
|
+
option
|
|
891
|
+
)) }),
|
|
892
|
+
/* @__PURE__ */ jsx2(
|
|
893
|
+
"button",
|
|
894
|
+
{
|
|
895
|
+
type: "button",
|
|
896
|
+
onClick: onExit,
|
|
897
|
+
disabled: !canControl,
|
|
898
|
+
className: cn(
|
|
899
|
+
"h-7 rounded-md border border-[hsl(var(--border))] px-2.5 text-[hsl(var(--muted-foreground))] transition-colors hover:bg-[hsl(var(--accent))] hover:text-[hsl(var(--foreground))]",
|
|
900
|
+
!canControl && "cursor-not-allowed opacity-50 hover:bg-transparent"
|
|
901
|
+
),
|
|
902
|
+
children: "\u9000\u51FA\u56DE\u653E"
|
|
903
|
+
}
|
|
904
|
+
)
|
|
905
|
+
] })
|
|
906
|
+
]
|
|
907
|
+
}
|
|
908
|
+
);
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
// src/components/ReplayMismatchPrompt.tsx
|
|
912
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
913
|
+
function ReplayMismatchPrompt({ mismatch, className }) {
|
|
914
|
+
if (!mismatch) return null;
|
|
915
|
+
return /* @__PURE__ */ jsxs2(
|
|
916
|
+
"div",
|
|
917
|
+
{
|
|
918
|
+
className: cn(
|
|
919
|
+
"mx-auto my-3 max-w-3xl rounded-2xl border border-[hsl(var(--border))] bg-[hsl(var(--card))] px-4 py-3 text-sm",
|
|
920
|
+
className
|
|
921
|
+
),
|
|
922
|
+
children: [
|
|
923
|
+
/* @__PURE__ */ jsx3("div", { className: "font-medium text-[hsl(var(--foreground))]", children: "\u8FD9\u53E5\u8BDD\u548C\u4E4B\u524D\u5F55\u5236\u7684\u4E0D\u4E00\u6837" }),
|
|
924
|
+
/* @__PURE__ */ jsxs2("dl", { className: "mt-2 space-y-1 text-xs text-[hsl(var(--muted-foreground))]", children: [
|
|
925
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex gap-2", children: [
|
|
926
|
+
/* @__PURE__ */ jsx3("dt", { className: "shrink-0", children: "\u5F55\u5236\u7684\u662F" }),
|
|
927
|
+
/* @__PURE__ */ jsx3("dd", { className: "min-w-0 break-words text-[hsl(var(--foreground))]", children: mismatch.expectedMessage || "\uFF08\u7A7A\uFF09" })
|
|
928
|
+
] }),
|
|
929
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex gap-2", children: [
|
|
930
|
+
/* @__PURE__ */ jsx3("dt", { className: "shrink-0", children: "\u4F60\u8F93\u5165\u7684" }),
|
|
931
|
+
/* @__PURE__ */ jsx3("dd", { className: "min-w-0 break-words text-[hsl(var(--foreground))]", children: mismatch.actualMessage || "\uFF08\u7A7A\uFF09" })
|
|
932
|
+
] })
|
|
933
|
+
] }),
|
|
934
|
+
/* @__PURE__ */ jsxs2("div", { className: "mt-3 flex flex-wrap gap-2", children: [
|
|
935
|
+
/* @__PURE__ */ jsx3(
|
|
936
|
+
"button",
|
|
937
|
+
{
|
|
938
|
+
type: "button",
|
|
939
|
+
onClick: () => mismatch.resolve("keep_replay"),
|
|
940
|
+
className: "h-8 rounded-md bg-[hsl(var(--primary))] px-3 text-xs font-medium text-[hsl(var(--primary-foreground))] transition-opacity hover:opacity-90",
|
|
941
|
+
children: "\u6309\u5F55\u5236\u5185\u5BB9\u7EE7\u7EED"
|
|
942
|
+
}
|
|
943
|
+
),
|
|
944
|
+
/* @__PURE__ */ jsx3(
|
|
945
|
+
"button",
|
|
946
|
+
{
|
|
947
|
+
type: "button",
|
|
948
|
+
onClick: () => mismatch.resolve("continue_replay"),
|
|
949
|
+
className: "h-8 rounded-md border border-[hsl(var(--border))] px-3 text-xs text-[hsl(var(--muted-foreground))] transition-colors hover:bg-[hsl(var(--accent))] hover:text-[hsl(var(--foreground))]",
|
|
950
|
+
children: "\u4ECE\u8FD9\u91CC\u5F00\u59CB\u771F\u7684\u8FD0\u884C"
|
|
951
|
+
}
|
|
952
|
+
)
|
|
953
|
+
] })
|
|
954
|
+
]
|
|
955
|
+
}
|
|
956
|
+
);
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// src/components/ChatInput.tsx
|
|
960
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
421
961
|
function ChatInput({
|
|
422
962
|
value,
|
|
423
963
|
onValueChange,
|
|
@@ -442,8 +982,8 @@ function ChatInput({
|
|
|
442
982
|
void handleSend();
|
|
443
983
|
}
|
|
444
984
|
};
|
|
445
|
-
return /* @__PURE__ */
|
|
446
|
-
/* @__PURE__ */
|
|
985
|
+
return /* @__PURE__ */ jsx4("div", { className: cn("blade-chat-input border-t border-[hsl(var(--border))] py-3", className), children: /* @__PURE__ */ jsxs3("div", { className: "blade-chat-input-inner mx-auto flex max-w-[748px] items-end gap-2 rounded-2xl border border-[hsl(var(--border))] bg-[hsl(var(--card))] px-3 py-2", children: [
|
|
986
|
+
/* @__PURE__ */ jsx4(
|
|
447
987
|
"textarea",
|
|
448
988
|
{
|
|
449
989
|
value,
|
|
@@ -460,7 +1000,7 @@ function ChatInput({
|
|
|
460
1000
|
className: "blade-chat-textarea max-h-48 min-h-[28px] flex-1 resize-none bg-transparent py-1 text-sm leading-6 text-[hsl(var(--foreground))] outline-none placeholder:text-[hsl(var(--muted-foreground)/0.6)]"
|
|
461
1001
|
}
|
|
462
1002
|
),
|
|
463
|
-
isStreaming ? /* @__PURE__ */
|
|
1003
|
+
isStreaming ? /* @__PURE__ */ jsx4(
|
|
464
1004
|
"button",
|
|
465
1005
|
{
|
|
466
1006
|
type: "button",
|
|
@@ -469,9 +1009,9 @@ function ChatInput({
|
|
|
469
1009
|
"aria-label": isStopping ? "\u6B63\u5728\u505C\u6B62" : "\u505C\u6B62\u56DE\u590D",
|
|
470
1010
|
title: isStopping ? "\u6B63\u5728\u505C\u6B62" : "\u505C\u6B62\u56DE\u590D",
|
|
471
1011
|
className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[hsl(var(--muted))] text-[hsl(var(--foreground))] transition-opacity hover:opacity-90 disabled:opacity-60",
|
|
472
|
-
children: isStopping ? /* @__PURE__ */
|
|
1012
|
+
children: isStopping ? /* @__PURE__ */ jsx4(LoaderCircle, { size: 14, className: "animate-spin" }) : /* @__PURE__ */ jsx4(Square, { size: 12, fill: "currentColor" })
|
|
473
1013
|
}
|
|
474
|
-
) : /* @__PURE__ */
|
|
1014
|
+
) : /* @__PURE__ */ jsx4(
|
|
475
1015
|
"button",
|
|
476
1016
|
{
|
|
477
1017
|
type: "button",
|
|
@@ -480,31 +1020,81 @@ function ChatInput({
|
|
|
480
1020
|
"aria-label": "\u53D1\u9001\u6D88\u606F",
|
|
481
1021
|
title: "\u53D1\u9001\u6D88\u606F",
|
|
482
1022
|
className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))] transition-opacity hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-40",
|
|
483
|
-
children: /* @__PURE__ */
|
|
1023
|
+
children: /* @__PURE__ */ jsx4(ArrowUp, { size: 15 })
|
|
484
1024
|
}
|
|
485
1025
|
)
|
|
486
1026
|
] }) });
|
|
487
1027
|
}
|
|
488
1028
|
|
|
489
1029
|
// src/components/ConnectionBanner.tsx
|
|
490
|
-
import {
|
|
1030
|
+
import { useEffect as useEffect3, useRef as useRef3, useState as useState4 } from "react";
|
|
1031
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1032
|
+
var CONNECTION_NOTICE_DELAY_MS = 3e3;
|
|
1033
|
+
var CONNECTION_ERROR_DELAY_MS = 15e3;
|
|
1034
|
+
function useConnectionNoticePhase(connected) {
|
|
1035
|
+
const [phase, setPhase] = useState4("hidden");
|
|
1036
|
+
const connectedRef = useRef3(connected);
|
|
1037
|
+
const timersRef = useRef3([]);
|
|
1038
|
+
connectedRef.current = connected;
|
|
1039
|
+
useEffect3(() => {
|
|
1040
|
+
const clearTimers = () => {
|
|
1041
|
+
for (const timer of timersRef.current) clearTimeout(timer);
|
|
1042
|
+
timersRef.current = [];
|
|
1043
|
+
};
|
|
1044
|
+
const startGracePeriod = () => {
|
|
1045
|
+
clearTimers();
|
|
1046
|
+
setPhase("hidden");
|
|
1047
|
+
timersRef.current = [
|
|
1048
|
+
setTimeout(() => setPhase("recovering"), CONNECTION_NOTICE_DELAY_MS),
|
|
1049
|
+
setTimeout(() => setPhase("failed"), CONNECTION_ERROR_DELAY_MS)
|
|
1050
|
+
];
|
|
1051
|
+
};
|
|
1052
|
+
if (connected) {
|
|
1053
|
+
clearTimers();
|
|
1054
|
+
setPhase("hidden");
|
|
1055
|
+
} else {
|
|
1056
|
+
startGracePeriod();
|
|
1057
|
+
}
|
|
1058
|
+
const handleForeground = () => {
|
|
1059
|
+
if (!connectedRef.current) startGracePeriod();
|
|
1060
|
+
};
|
|
1061
|
+
const handleVisibilityChange = () => {
|
|
1062
|
+
if (document.visibilityState === "visible") handleForeground();
|
|
1063
|
+
};
|
|
1064
|
+
window.addEventListener("blade:app-active", handleForeground);
|
|
1065
|
+
window.addEventListener("focus", handleForeground);
|
|
1066
|
+
window.addEventListener("pageshow", handleForeground);
|
|
1067
|
+
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
1068
|
+
return () => {
|
|
1069
|
+
clearTimers();
|
|
1070
|
+
window.removeEventListener("blade:app-active", handleForeground);
|
|
1071
|
+
window.removeEventListener("focus", handleForeground);
|
|
1072
|
+
window.removeEventListener("pageshow", handleForeground);
|
|
1073
|
+
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
1074
|
+
};
|
|
1075
|
+
}, [connected]);
|
|
1076
|
+
return phase;
|
|
1077
|
+
}
|
|
491
1078
|
function ConnectionBanner({ connection, className }) {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
const
|
|
496
|
-
|
|
1079
|
+
const hasConnectedRef = useRef3(connection === "connected" || connection === "reconnecting");
|
|
1080
|
+
if (connection === "connected") hasConnectedRef.current = true;
|
|
1081
|
+
const connected = connection === "connected";
|
|
1082
|
+
const phase = useConnectionNoticePhase(connected);
|
|
1083
|
+
if (connected || phase === "hidden") return null;
|
|
1084
|
+
const recovering = phase === "recovering";
|
|
1085
|
+
const firstConnection = !hasConnectedRef.current;
|
|
1086
|
+
return /* @__PURE__ */ jsx5("div", { className: cn("blade-chat-banner bg-[hsl(var(--background))] px-5 pt-3", className), children: /* @__PURE__ */ jsxs4(
|
|
497
1087
|
"div",
|
|
498
1088
|
{
|
|
499
1089
|
className: cn(
|
|
500
1090
|
"mx-auto flex max-w-3xl items-start gap-3 rounded-2xl border px-4 py-3",
|
|
501
|
-
|
|
1091
|
+
recovering ? "border-amber-500/25 bg-amber-500/10 text-amber-100" : "border-rose-500/25 bg-rose-500/10 text-rose-100"
|
|
502
1092
|
),
|
|
503
1093
|
children: [
|
|
504
|
-
/* @__PURE__ */
|
|
505
|
-
/* @__PURE__ */
|
|
506
|
-
/* @__PURE__ */
|
|
507
|
-
/* @__PURE__ */
|
|
1094
|
+
/* @__PURE__ */ jsx5("span", { className: "mt-0.5 shrink-0", children: recovering ? /* @__PURE__ */ jsx5(LoaderCircle, { size: 14, className: "animate-spin" }) : /* @__PURE__ */ jsx5(TriangleAlert, { size: 14 }) }),
|
|
1095
|
+
/* @__PURE__ */ jsxs4("div", { className: "min-w-0", children: [
|
|
1096
|
+
/* @__PURE__ */ jsx5("div", { className: "text-sm font-medium", children: recovering ? firstConnection ? "\u6B63\u5728\u8FDE\u63A5\u2026" : "\u6B63\u5728\u6062\u590D\u8FDE\u63A5\u2026" : "\u6682\u65F6\u65E0\u6CD5\u8FDE\u63A5" }),
|
|
1097
|
+
/* @__PURE__ */ jsx5("div", { className: "text-xs opacity-80", children: recovering ? "\u6062\u590D\u540E\u4F1A\u81EA\u52A8\u540C\u6B65\u6700\u65B0\u6D88\u606F\uFF0C\u8BF7\u7A0D\u5019" : "\u8BF7\u68C0\u67E5\u7F51\u7EDC\u6216\u670D\u52A1\u72B6\u6001\uFF0C\u7CFB\u7EDF\u4F1A\u7EE7\u7EED\u81EA\u52A8\u91CD\u8BD5" })
|
|
508
1098
|
] })
|
|
509
1099
|
]
|
|
510
1100
|
}
|
|
@@ -513,10 +1103,10 @@ function ConnectionBanner({ connection, className }) {
|
|
|
513
1103
|
|
|
514
1104
|
// src/components/MessageList.tsx
|
|
515
1105
|
import { isHiddenInternalMessage } from "@blade-hq/agent-client";
|
|
516
|
-
import { useCallback as
|
|
1106
|
+
import { useCallback as useCallback6, useEffect as useEffect8, useMemo as useMemo7, useRef as useRef8, useState as useState12 } from "react";
|
|
517
1107
|
|
|
518
1108
|
// ../../node_modules/.pnpm/use-stick-to-bottom@1.1.3_react@19.2.4/node_modules/use-stick-to-bottom/dist/useStickToBottom.js
|
|
519
|
-
import { useCallback, useMemo as
|
|
1109
|
+
import { useCallback as useCallback3, useMemo as useMemo3, useRef as useRef4, useState as useState5 } from "react";
|
|
520
1110
|
var DEFAULT_SPRING_ANIMATION = {
|
|
521
1111
|
/**
|
|
522
1112
|
* A value from 0 to 1, on how much to damp the animation.
|
|
@@ -553,12 +1143,12 @@ globalThis.document?.addEventListener("click", () => {
|
|
|
553
1143
|
mouseDown = false;
|
|
554
1144
|
});
|
|
555
1145
|
var useStickToBottom = (options = {}) => {
|
|
556
|
-
const [escapedFromLock, updateEscapedFromLock] =
|
|
557
|
-
const [isAtBottom, updateIsAtBottom] =
|
|
558
|
-
const [isNearBottom, setIsNearBottom] =
|
|
559
|
-
const optionsRef =
|
|
1146
|
+
const [escapedFromLock, updateEscapedFromLock] = useState5(false);
|
|
1147
|
+
const [isAtBottom, updateIsAtBottom] = useState5(options.initial !== false);
|
|
1148
|
+
const [isNearBottom, setIsNearBottom] = useState5(false);
|
|
1149
|
+
const optionsRef = useRef4(null);
|
|
560
1150
|
optionsRef.current = options;
|
|
561
|
-
const isSelecting =
|
|
1151
|
+
const isSelecting = useCallback3(() => {
|
|
562
1152
|
if (!mouseDown) {
|
|
563
1153
|
return false;
|
|
564
1154
|
}
|
|
@@ -569,15 +1159,15 @@ var useStickToBottom = (options = {}) => {
|
|
|
569
1159
|
const range = selection.getRangeAt(0);
|
|
570
1160
|
return range.commonAncestorContainer.contains(scrollRef.current) || scrollRef.current?.contains(range.commonAncestorContainer);
|
|
571
1161
|
}, []);
|
|
572
|
-
const setIsAtBottom =
|
|
1162
|
+
const setIsAtBottom = useCallback3((isAtBottom2) => {
|
|
573
1163
|
state.isAtBottom = isAtBottom2;
|
|
574
1164
|
updateIsAtBottom(isAtBottom2);
|
|
575
1165
|
}, []);
|
|
576
|
-
const setEscapedFromLock =
|
|
1166
|
+
const setEscapedFromLock = useCallback3((escapedFromLock2) => {
|
|
577
1167
|
state.escapedFromLock = escapedFromLock2;
|
|
578
1168
|
updateEscapedFromLock(escapedFromLock2);
|
|
579
1169
|
}, []);
|
|
580
|
-
const state =
|
|
1170
|
+
const state = useMemo3(() => {
|
|
581
1171
|
let lastCalculation;
|
|
582
1172
|
return {
|
|
583
1173
|
escapedFromLock,
|
|
@@ -630,7 +1220,7 @@ var useStickToBottom = (options = {}) => {
|
|
|
630
1220
|
}
|
|
631
1221
|
};
|
|
632
1222
|
}, []);
|
|
633
|
-
const scrollToBottom =
|
|
1223
|
+
const scrollToBottom = useCallback3((scrollOptions = {}) => {
|
|
634
1224
|
if (typeof scrollOptions === "string") {
|
|
635
1225
|
scrollOptions = { animation: scrollOptions };
|
|
636
1226
|
}
|
|
@@ -715,11 +1305,11 @@ var useStickToBottom = (options = {}) => {
|
|
|
715
1305
|
}
|
|
716
1306
|
return next();
|
|
717
1307
|
}, [setIsAtBottom, isSelecting, state]);
|
|
718
|
-
const stopScroll =
|
|
1308
|
+
const stopScroll = useCallback3(() => {
|
|
719
1309
|
setEscapedFromLock(true);
|
|
720
1310
|
setIsAtBottom(false);
|
|
721
1311
|
}, [setEscapedFromLock, setIsAtBottom]);
|
|
722
|
-
const handleScroll =
|
|
1312
|
+
const handleScroll = useCallback3(({ target }) => {
|
|
723
1313
|
if (target !== scrollRef.current) {
|
|
724
1314
|
return;
|
|
725
1315
|
}
|
|
@@ -758,7 +1348,7 @@ var useStickToBottom = (options = {}) => {
|
|
|
758
1348
|
}
|
|
759
1349
|
}, 1);
|
|
760
1350
|
}, [setEscapedFromLock, setIsAtBottom, isSelecting, state]);
|
|
761
|
-
const handleWheel =
|
|
1351
|
+
const handleWheel = useCallback3(({ target, deltaY }) => {
|
|
762
1352
|
let element = target;
|
|
763
1353
|
while (!["scroll", "auto"].includes(getComputedStyle(element).overflow)) {
|
|
764
1354
|
if (!element.parentElement) {
|
|
@@ -828,7 +1418,7 @@ var useStickToBottom = (options = {}) => {
|
|
|
828
1418
|
};
|
|
829
1419
|
};
|
|
830
1420
|
function useRefCallback(callback, deps) {
|
|
831
|
-
const result =
|
|
1421
|
+
const result = useCallback3((ref) => {
|
|
832
1422
|
result.current = ref;
|
|
833
1423
|
return callback(ref);
|
|
834
1424
|
}, deps);
|
|
@@ -860,11 +1450,11 @@ function mergeAnimations(...animations) {
|
|
|
860
1450
|
|
|
861
1451
|
// ../../node_modules/.pnpm/use-stick-to-bottom@1.1.3_react@19.2.4/node_modules/use-stick-to-bottom/dist/StickToBottom.js
|
|
862
1452
|
import * as React from "react";
|
|
863
|
-
import { createContext as createContext2, useContext as useContext2, useEffect as
|
|
1453
|
+
import { createContext as createContext2, useContext as useContext2, useEffect as useEffect4, useImperativeHandle, useLayoutEffect, useMemo as useMemo4, useRef as useRef5 } from "react";
|
|
864
1454
|
var StickToBottomContext = createContext2(null);
|
|
865
|
-
var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect :
|
|
1455
|
+
var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect4;
|
|
866
1456
|
function StickToBottom({ instance, children, resize, initial, mass, damping, stiffness, targetScrollTop: currentTargetScrollTop, contextRef, ...props }) {
|
|
867
|
-
const customTargetScrollTop =
|
|
1457
|
+
const customTargetScrollTop = useRef5(null);
|
|
868
1458
|
const targetScrollTop = React.useCallback((target, elements) => {
|
|
869
1459
|
const get = context?.targetScrollTop ?? currentTargetScrollTop;
|
|
870
1460
|
return get?.(target, elements) ?? target;
|
|
@@ -878,7 +1468,7 @@ function StickToBottom({ instance, children, resize, initial, mass, damping, sti
|
|
|
878
1468
|
targetScrollTop
|
|
879
1469
|
});
|
|
880
1470
|
const { scrollRef, contentRef, scrollToBottom, stopScroll, isAtBottom, escapedFromLock, state } = instance ?? defaultInstance;
|
|
881
|
-
const context =
|
|
1471
|
+
const context = useMemo4(() => ({
|
|
882
1472
|
scrollToBottom,
|
|
883
1473
|
stopScroll,
|
|
884
1474
|
scrollRef,
|
|
@@ -941,10 +1531,10 @@ function useStickToBottomContext() {
|
|
|
941
1531
|
|
|
942
1532
|
// src/components/AssistantTurnBlock.tsx
|
|
943
1533
|
import { getTextContent, normalizeMessageContent } from "@blade-hq/agent-client";
|
|
944
|
-
import { useState as
|
|
1534
|
+
import { useState as useState10 } from "react";
|
|
945
1535
|
|
|
946
1536
|
// src/components/AgentLoopBlock.tsx
|
|
947
|
-
import { useState as
|
|
1537
|
+
import { useState as useState6 } from "react";
|
|
948
1538
|
|
|
949
1539
|
// src/components/display-utils.ts
|
|
950
1540
|
var TOOL_NAME_ALIASES = {
|
|
@@ -1066,7 +1656,7 @@ function formatToolResult(result) {
|
|
|
1066
1656
|
}
|
|
1067
1657
|
|
|
1068
1658
|
// src/components/AgentLoopBlock.tsx
|
|
1069
|
-
import { jsx as
|
|
1659
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1070
1660
|
function parseAgentDescription(argumentsJson) {
|
|
1071
1661
|
try {
|
|
1072
1662
|
const parsed = JSON.parse(argumentsJson);
|
|
@@ -1076,12 +1666,12 @@ function parseAgentDescription(argumentsJson) {
|
|
|
1076
1666
|
}
|
|
1077
1667
|
}
|
|
1078
1668
|
function AgentLoopBlock({ toolCall }) {
|
|
1079
|
-
const [expanded, setExpanded] =
|
|
1669
|
+
const [expanded, setExpanded] = useState6(false);
|
|
1080
1670
|
const description = parseAgentDescription(toolCall.arguments);
|
|
1081
1671
|
const running = toolCall.status === "pending" || toolCall.status === "awaiting_answer";
|
|
1082
1672
|
const failed = toolCall.status === "error" || toolCall.status === "cancelled";
|
|
1083
|
-
return /* @__PURE__ */
|
|
1084
|
-
/* @__PURE__ */
|
|
1673
|
+
return /* @__PURE__ */ jsxs5("div", { className: "blade-chat-agent-loop ml-4 text-xs", children: [
|
|
1674
|
+
/* @__PURE__ */ jsxs5(
|
|
1085
1675
|
"div",
|
|
1086
1676
|
{
|
|
1087
1677
|
className: cn(
|
|
@@ -1089,7 +1679,7 @@ function AgentLoopBlock({ toolCall }) {
|
|
|
1089
1679
|
failed ? "border-l-[hsl(var(--muted-foreground)/0.5)]" : running ? "border-l-blue-500" : "border-l-[hsl(var(--primary))]"
|
|
1090
1680
|
),
|
|
1091
1681
|
children: [
|
|
1092
|
-
/* @__PURE__ */
|
|
1682
|
+
/* @__PURE__ */ jsxs5(
|
|
1093
1683
|
"button",
|
|
1094
1684
|
{
|
|
1095
1685
|
type: "button",
|
|
@@ -1097,7 +1687,7 @@ function AgentLoopBlock({ toolCall }) {
|
|
|
1097
1687
|
className: "flex min-w-0 flex-1 items-center gap-2 text-left transition-colors hover:bg-white/3 focus-visible:ring-1 focus-visible:ring-[hsl(var(--ring))] focus:outline-none",
|
|
1098
1688
|
"aria-expanded": expanded,
|
|
1099
1689
|
children: [
|
|
1100
|
-
/* @__PURE__ */
|
|
1690
|
+
/* @__PURE__ */ jsx6(
|
|
1101
1691
|
ChevronRight,
|
|
1102
1692
|
{
|
|
1103
1693
|
size: 11,
|
|
@@ -1107,8 +1697,8 @@ function AgentLoopBlock({ toolCall }) {
|
|
|
1107
1697
|
)
|
|
1108
1698
|
}
|
|
1109
1699
|
),
|
|
1110
|
-
/* @__PURE__ */
|
|
1111
|
-
/* @__PURE__ */
|
|
1700
|
+
/* @__PURE__ */ jsx6(Bot, { size: 12, className: "shrink-0 text-[hsl(var(--muted-foreground))]" }),
|
|
1701
|
+
/* @__PURE__ */ jsxs5(
|
|
1112
1702
|
"span",
|
|
1113
1703
|
{
|
|
1114
1704
|
className: cn(
|
|
@@ -1116,171 +1706,43 @@ function AgentLoopBlock({ toolCall }) {
|
|
|
1116
1706
|
failed ? "text-[hsl(var(--muted-foreground))]" : running ? "text-blue-300" : "text-[hsl(var(--primary))]"
|
|
1117
1707
|
),
|
|
1118
1708
|
children: [
|
|
1119
|
-
running ? /* @__PURE__ */
|
|
1120
|
-
/* @__PURE__ */
|
|
1709
|
+
running ? /* @__PURE__ */ jsx6(LoaderCircle, { size: 11, className: "animate-spin" }) : failed ? /* @__PURE__ */ jsx6(X, { size: 11 }) : /* @__PURE__ */ jsx6(Check, { size: 11 }),
|
|
1710
|
+
/* @__PURE__ */ jsx6("span", { children: running ? "\u6267\u884C\u4E2D" : failed ? "\u5DF2\u7EC8\u6B62" : "\u5B8C\u6210" })
|
|
1121
1711
|
]
|
|
1122
1712
|
}
|
|
1123
1713
|
),
|
|
1124
|
-
/* @__PURE__ */
|
|
1714
|
+
/* @__PURE__ */ jsxs5("span", { className: "min-w-0 flex-1 truncate font-medium text-[hsl(var(--foreground))]", children: [
|
|
1125
1715
|
"\u5B50\u667A\u80FD\u4F53\uFF1A",
|
|
1126
1716
|
description
|
|
1127
1717
|
] })
|
|
1128
1718
|
]
|
|
1129
1719
|
}
|
|
1130
1720
|
),
|
|
1131
|
-
typeof toolCall.duration_ms === "number" && toolCall.duration_ms > 0 && /* @__PURE__ */
|
|
1721
|
+
typeof toolCall.duration_ms === "number" && toolCall.duration_ms > 0 && /* @__PURE__ */ jsx6("span", { className: "shrink-0 font-mono text-[10px] text-[hsl(var(--muted-foreground))]", children: formatToolDuration(toolCall.duration_ms) })
|
|
1132
1722
|
]
|
|
1133
1723
|
}
|
|
1134
1724
|
),
|
|
1135
|
-
expanded && toolCall.result != null && /* @__PURE__ */
|
|
1136
|
-
/* @__PURE__ */
|
|
1137
|
-
/* @__PURE__ */
|
|
1725
|
+
expanded && toolCall.result != null && /* @__PURE__ */ jsxs5("div", { className: "ml-4 mt-1 rounded-xl bg-[hsl(var(--card))] px-3 py-3", children: [
|
|
1726
|
+
/* @__PURE__ */ jsx6("div", { className: "mb-1 text-[10px] uppercase tracking-wider text-[hsl(var(--muted-foreground))]", children: "\u7ED3\u679C" }),
|
|
1727
|
+
/* @__PURE__ */ jsx6("pre", { className: "max-h-[400px] overflow-auto whitespace-pre-wrap rounded-md bg-[hsl(var(--muted))] p-2 font-mono text-[11px] text-[hsl(var(--foreground))]", children: formatToolResult(toolCall.result) })
|
|
1138
1728
|
] })
|
|
1139
1729
|
] });
|
|
1140
1730
|
}
|
|
1141
1731
|
|
|
1142
1732
|
// src/components/MarkdownContent.tsx
|
|
1143
1733
|
import {
|
|
1144
|
-
useEffect as
|
|
1145
|
-
useMemo as
|
|
1146
|
-
useRef as
|
|
1147
|
-
useState as
|
|
1734
|
+
useEffect as useEffect5,
|
|
1735
|
+
useMemo as useMemo5,
|
|
1736
|
+
useRef as useRef6,
|
|
1737
|
+
useState as useState7
|
|
1148
1738
|
} from "react";
|
|
1149
|
-
|
|
1150
|
-
// src/lib/media-tags.ts
|
|
1151
|
-
var FILE_CARD_TAG = "blade-file-card";
|
|
1152
|
-
var MEDIA_LINE_RE = /^[ \t]*(?:[-*+][ \t]+|\d+\.[ \t]+)?(?<wrap>[`"'*_]*)MEDIA:[ \t]*(?<path>.+?)\k<wrap>[ \t]*[。,、;;,]?[ \t]*\r?$/gm;
|
|
1153
|
-
var MEDIA_LINE_START_RE = /^[ \t]*(?:[-*+][ \t]+|\d+\.[ \t]+)?[`"'*_]*MEDIA:/;
|
|
1154
|
-
function basename(path) {
|
|
1155
|
-
return path.split(/[/\\]/).filter(Boolean).pop() || path;
|
|
1156
|
-
}
|
|
1157
|
-
function unwrapMediaPath(path) {
|
|
1158
|
-
if (!path.startsWith("<")) return path;
|
|
1159
|
-
const closingBracket = path.indexOf(">");
|
|
1160
|
-
if (closingBracket < 0) return null;
|
|
1161
|
-
return `${path.slice(1, closingBracket)}${path.slice(closingBracket + 1)}`;
|
|
1162
|
-
}
|
|
1163
|
-
function isSafeMediaPath(path) {
|
|
1164
|
-
if (!path) return false;
|
|
1165
|
-
return !path.split(/[/\\]/).includes("..");
|
|
1166
|
-
}
|
|
1167
|
-
function escapeAttribute(value) {
|
|
1168
|
-
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
1169
|
-
}
|
|
1170
|
-
function foldMediaLines(text) {
|
|
1171
|
-
return text.replace(MEDIA_LINE_RE, (match, _wrap, rawPath) => {
|
|
1172
|
-
const path = unwrapMediaPath(rawPath);
|
|
1173
|
-
if (path === null) return match;
|
|
1174
|
-
const name = basename(path);
|
|
1175
|
-
if (!isSafeMediaPath(path)) {
|
|
1176
|
-
return `\`${name}\``;
|
|
1177
|
-
}
|
|
1178
|
-
return `<${FILE_CARD_TAG} data-path="${escapeAttribute(path)}" data-name="${escapeAttribute(name)}">${escapeAttribute(name)}</${FILE_CARD_TAG}>`;
|
|
1179
|
-
});
|
|
1180
|
-
}
|
|
1181
|
-
function collapseMediaTags(text, options) {
|
|
1182
|
-
if (!text.includes("MEDIA:")) return text;
|
|
1183
|
-
if (!options?.streaming) return foldMediaLines(text);
|
|
1184
|
-
const newlineIndex = text.lastIndexOf("\n");
|
|
1185
|
-
const head = newlineIndex < 0 ? "" : text.slice(0, newlineIndex + 1);
|
|
1186
|
-
const lastLine = newlineIndex < 0 ? text : text.slice(newlineIndex + 1);
|
|
1187
|
-
if (MEDIA_LINE_START_RE.test(lastLine)) {
|
|
1188
|
-
return foldMediaLines(head);
|
|
1189
|
-
}
|
|
1190
|
-
return foldMediaLines(head) + lastLine;
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
// src/components/FileCard.tsx
|
|
1194
|
-
import { useState as useState4 } from "react";
|
|
1195
|
-
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1196
|
-
var IMAGE_EXTS = /* @__PURE__ */ new Set(["png", "jpg", "jpeg", "gif", "svg", "webp", "ico", "bmp"]);
|
|
1197
|
-
var SHEET_EXTS = /* @__PURE__ */ new Set(["xlsx", "xls", "xlsm", "xlsb", "csv"]);
|
|
1198
|
-
var TEXT_EXTS = /* @__PURE__ */ new Set(["txt", "md", "markdown", "pdf", "doc", "docx", "rtf"]);
|
|
1199
|
-
var CODE_EXTS = /* @__PURE__ */ new Set(["js", "jsx", "ts", "tsx", "py", "go", "json", "yaml", "yml", "html", "css"]);
|
|
1200
|
-
var VIDEO_EXTS = /* @__PURE__ */ new Set(["mp4", "webm", "mov", "mkv", "avi"]);
|
|
1201
|
-
var AUDIO_EXTS = /* @__PURE__ */ new Set(["mp3", "wav", "ogg", "m4a", "flac"]);
|
|
1202
|
-
var ARCHIVE_EXTS = /* @__PURE__ */ new Set(["zip", "tar", "gz", "tgz", "rar", "7z"]);
|
|
1203
|
-
function getFileIcon(name) {
|
|
1204
|
-
const ext = name.split(".").pop()?.toLowerCase() ?? "";
|
|
1205
|
-
if (IMAGE_EXTS.has(ext)) return Image;
|
|
1206
|
-
if (SHEET_EXTS.has(ext)) return FileSpreadsheet;
|
|
1207
|
-
if (TEXT_EXTS.has(ext)) return FileText;
|
|
1208
|
-
if (CODE_EXTS.has(ext)) return FileCode2;
|
|
1209
|
-
if (VIDEO_EXTS.has(ext)) return Film;
|
|
1210
|
-
if (AUDIO_EXTS.has(ext)) return Music;
|
|
1211
|
-
if (ARCHIVE_EXTS.has(ext)) return Archive;
|
|
1212
|
-
return File;
|
|
1213
|
-
}
|
|
1214
|
-
function stringValue(value) {
|
|
1215
|
-
return typeof value === "string" ? value : void 0;
|
|
1216
|
-
}
|
|
1217
|
-
function extractChildrenText(children) {
|
|
1218
|
-
if (typeof children === "string") return children;
|
|
1219
|
-
if (Array.isArray(children)) return children.map(extractChildrenText).join("");
|
|
1220
|
-
if (children && typeof children === "object" && "props" in children) {
|
|
1221
|
-
return extractChildrenText(children.props?.children);
|
|
1222
|
-
}
|
|
1223
|
-
return "";
|
|
1224
|
-
}
|
|
1225
|
-
function FileCard({
|
|
1226
|
-
node,
|
|
1227
|
-
"data-path": pathAttribute,
|
|
1228
|
-
"data-name": nameAttribute,
|
|
1229
|
-
dataPath,
|
|
1230
|
-
dataName,
|
|
1231
|
-
sessionId,
|
|
1232
|
-
children,
|
|
1233
|
-
className,
|
|
1234
|
-
...props
|
|
1235
|
-
}) {
|
|
1236
|
-
const client = useBladeClient();
|
|
1237
|
-
const [downloading, setDownloading] = useState4(false);
|
|
1238
|
-
const [failed, setFailed] = useState4(false);
|
|
1239
|
-
const nodeProperties = node && typeof node === "object" && "properties" in node ? node.properties : void 0;
|
|
1240
|
-
const path = pathAttribute ?? dataPath ?? stringValue(nodeProperties?.["data-path"]) ?? stringValue(nodeProperties?.dataPath) ?? "";
|
|
1241
|
-
const name = nameAttribute ?? dataName ?? stringValue(nodeProperties?.["data-name"]) ?? stringValue(nodeProperties?.dataName) ?? extractChildrenText(children) ?? "";
|
|
1242
|
-
const disabled = !sessionId || !isSafeMediaPath(path);
|
|
1243
|
-
const Icon2 = getFileIcon(name);
|
|
1244
|
-
const handleDownload = async () => {
|
|
1245
|
-
if (disabled || downloading || !sessionId) return;
|
|
1246
|
-
setDownloading(true);
|
|
1247
|
-
setFailed(false);
|
|
1248
|
-
try {
|
|
1249
|
-
await client.sessions.downloadFile(sessionId, path, name);
|
|
1250
|
-
} catch {
|
|
1251
|
-
setFailed(true);
|
|
1252
|
-
} finally {
|
|
1253
|
-
setDownloading(false);
|
|
1254
|
-
}
|
|
1255
|
-
};
|
|
1256
|
-
return /* @__PURE__ */ jsxs4("span", { ...props, className: cn("blade-chat-file-card", className), children: [
|
|
1257
|
-
/* @__PURE__ */ jsxs4(
|
|
1258
|
-
"button",
|
|
1259
|
-
{
|
|
1260
|
-
type: "button",
|
|
1261
|
-
onClick: handleDownload,
|
|
1262
|
-
disabled: disabled || downloading,
|
|
1263
|
-
"aria-label": `\u4E0B\u8F7D\u6587\u4EF6\uFF1A${name}`,
|
|
1264
|
-
title: failed ? "\u4E0B\u8F7D\u5931\u8D25\uFF0C\u70B9\u51FB\u91CD\u8BD5" : `\u4E0B\u8F7D ${name}`,
|
|
1265
|
-
children: [
|
|
1266
|
-
/* @__PURE__ */ jsx5("span", { className: "blade-chat-file-card-icon", children: downloading ? /* @__PURE__ */ jsx5(LoaderCircle, { size: 15, className: "animate-spin" }) : /* @__PURE__ */ jsx5(Icon2, { size: 15 }) }),
|
|
1267
|
-
/* @__PURE__ */ jsx5("span", { className: "blade-chat-file-card-name", children: name }),
|
|
1268
|
-
/* @__PURE__ */ jsx5(Download, { size: 13, className: "blade-chat-file-card-action" })
|
|
1269
|
-
]
|
|
1270
|
-
}
|
|
1271
|
-
),
|
|
1272
|
-
failed && /* @__PURE__ */ jsx5("span", { className: "blade-chat-file-card-error", children: "\u4E0B\u8F7D\u5931\u8D25" })
|
|
1273
|
-
] });
|
|
1274
|
-
}
|
|
1275
|
-
|
|
1276
|
-
// src/components/MarkdownContent.tsx
|
|
1277
|
-
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1739
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1278
1740
|
var SYSTEM_REMINDER_RE = /<system-reminder>[\s\S]*?<\/system-reminder>/gi;
|
|
1279
1741
|
function CodeBlockPre({ children, node: _node, ...props }) {
|
|
1280
|
-
const preRef =
|
|
1281
|
-
const [copied, setCopied] =
|
|
1282
|
-
const [language, setLanguage] =
|
|
1283
|
-
|
|
1742
|
+
const preRef = useRef6(null);
|
|
1743
|
+
const [copied, setCopied] = useState7(false);
|
|
1744
|
+
const [language, setLanguage] = useState7("");
|
|
1745
|
+
useEffect5(() => {
|
|
1284
1746
|
const codeEl = preRef.current?.querySelector("code");
|
|
1285
1747
|
setLanguage(codeEl?.className.match(/language-(\S+)/)?.[1] ?? "");
|
|
1286
1748
|
}, []);
|
|
@@ -1291,10 +1753,10 @@ function CodeBlockPre({ children, node: _node, ...props }) {
|
|
|
1291
1753
|
setTimeout(() => setCopied(false), 2e3);
|
|
1292
1754
|
}
|
|
1293
1755
|
};
|
|
1294
|
-
return /* @__PURE__ */
|
|
1295
|
-
/* @__PURE__ */
|
|
1296
|
-
/* @__PURE__ */
|
|
1297
|
-
/* @__PURE__ */
|
|
1756
|
+
return /* @__PURE__ */ jsxs6("div", { className: "blade-chat-codeblock not-prose my-3 overflow-hidden rounded-xl border border-[hsl(var(--border))]", children: [
|
|
1757
|
+
/* @__PURE__ */ jsxs6("div", { className: "blade-chat-codeblock-header flex h-[34px] items-center justify-between border-b border-[hsl(var(--border))] bg-[hsl(var(--muted))/0.5] pl-3.5 pr-1.5", children: [
|
|
1758
|
+
/* @__PURE__ */ jsx7("span", { className: "font-mono text-[12px] text-[hsl(var(--muted-foreground))]", children: language || "code" }),
|
|
1759
|
+
/* @__PURE__ */ jsxs6(
|
|
1298
1760
|
"button",
|
|
1299
1761
|
{
|
|
1300
1762
|
type: "button",
|
|
@@ -1304,13 +1766,13 @@ function CodeBlockPre({ children, node: _node, ...props }) {
|
|
|
1304
1766
|
copied ? "text-[hsl(var(--primary))]" : "text-[hsl(var(--muted-foreground))] hover:bg-[hsl(var(--accent))] hover:text-[hsl(var(--foreground))]"
|
|
1305
1767
|
),
|
|
1306
1768
|
children: [
|
|
1307
|
-
copied ? /* @__PURE__ */
|
|
1308
|
-
/* @__PURE__ */
|
|
1769
|
+
copied ? /* @__PURE__ */ jsx7(Check, { size: 12 }) : /* @__PURE__ */ jsx7(Copy, { size: 12 }),
|
|
1770
|
+
/* @__PURE__ */ jsx7("span", { children: copied ? "\u5DF2\u590D\u5236" : "\u590D\u5236" })
|
|
1309
1771
|
]
|
|
1310
1772
|
}
|
|
1311
1773
|
)
|
|
1312
1774
|
] }),
|
|
1313
|
-
/* @__PURE__ */
|
|
1775
|
+
/* @__PURE__ */ jsx7(
|
|
1314
1776
|
"pre",
|
|
1315
1777
|
{
|
|
1316
1778
|
ref: preRef,
|
|
@@ -1322,36 +1784,22 @@ function CodeBlockPre({ children, node: _node, ...props }) {
|
|
|
1322
1784
|
] });
|
|
1323
1785
|
}
|
|
1324
1786
|
function ExternalAnchor({ node: _node, children, ...props }) {
|
|
1325
|
-
return /* @__PURE__ */
|
|
1787
|
+
return /* @__PURE__ */ jsx7("a", { ...props, target: "_blank", rel: "noopener noreferrer", children });
|
|
1326
1788
|
}
|
|
1327
1789
|
var MARKDOWN_COMPONENTS = {
|
|
1328
1790
|
pre: CodeBlockPre,
|
|
1329
1791
|
a: ExternalAnchor
|
|
1330
1792
|
};
|
|
1331
|
-
var CUSTOM_ALLOWED_TAGS = {
|
|
1332
|
-
[FILE_CARD_TAG]: ["dataPath", "dataName", "data-path", "data-name"]
|
|
1333
|
-
};
|
|
1334
1793
|
function MarkdownContent({ children, className, mode, sessionId }) {
|
|
1335
|
-
const
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
return collapseMediaTags(withoutReminders, { streaming });
|
|
1340
|
-
}, [children, sessionId, streaming]);
|
|
1341
|
-
const components = useMemo4(
|
|
1342
|
-
() => ({
|
|
1343
|
-
...MARKDOWN_COMPONENTS,
|
|
1344
|
-
[FILE_CARD_TAG]: (fileCardProps) => /* @__PURE__ */ jsx6(FileCard, { ...fileCardProps, sessionId })
|
|
1345
|
-
}),
|
|
1346
|
-
[sessionId]
|
|
1347
|
-
);
|
|
1348
|
-
return /* @__PURE__ */ jsx6(
|
|
1794
|
+
const resolvedChildren = useMemo5(() => {
|
|
1795
|
+
return children.replace(SYSTEM_REMINDER_RE, "");
|
|
1796
|
+
}, [children]);
|
|
1797
|
+
return /* @__PURE__ */ jsx7(
|
|
1349
1798
|
_r,
|
|
1350
1799
|
{
|
|
1351
1800
|
className: cn("blade-chat-markdown break-words", className),
|
|
1352
1801
|
mode: mode ?? "static",
|
|
1353
|
-
components,
|
|
1354
|
-
allowedTags: CUSTOM_ALLOWED_TAGS,
|
|
1802
|
+
components: MARKDOWN_COMPONENTS,
|
|
1355
1803
|
children: resolvedChildren
|
|
1356
1804
|
},
|
|
1357
1805
|
sessionId ?? "no-session"
|
|
@@ -1359,17 +1807,17 @@ function MarkdownContent({ children, className, mode, sessionId }) {
|
|
|
1359
1807
|
}
|
|
1360
1808
|
|
|
1361
1809
|
// src/components/Shimmer.tsx
|
|
1362
|
-
import { jsx as
|
|
1810
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
1363
1811
|
function Shimmer({ children = "\u6B63\u5728\u601D\u8003...", className }) {
|
|
1364
|
-
return /* @__PURE__ */
|
|
1812
|
+
return /* @__PURE__ */ jsx8("span", { className: cn("blade-shimmer-text text-sm font-medium", className), children });
|
|
1365
1813
|
}
|
|
1366
1814
|
|
|
1367
1815
|
// src/components/ToolCallBlock.tsx
|
|
1368
|
-
import { useState as
|
|
1816
|
+
import { useState as useState9 } from "react";
|
|
1369
1817
|
|
|
1370
1818
|
// src/components/AskUserQuestionBlock.tsx
|
|
1371
|
-
import { useEffect as
|
|
1372
|
-
import { jsx as
|
|
1819
|
+
import { useEffect as useEffect6, useMemo as useMemo6, useState as useState8 } from "react";
|
|
1820
|
+
import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1373
1821
|
function AskUserQuestionBlock({
|
|
1374
1822
|
data,
|
|
1375
1823
|
answered,
|
|
@@ -1378,16 +1826,16 @@ function AskUserQuestionBlock({
|
|
|
1378
1826
|
answerData,
|
|
1379
1827
|
onAnswer
|
|
1380
1828
|
}) {
|
|
1381
|
-
const [selections, setSelections] =
|
|
1382
|
-
const [customTexts, setCustomTexts] =
|
|
1383
|
-
const [usingCustom, setUsingCustom] =
|
|
1384
|
-
const [submitted, setSubmitted] =
|
|
1385
|
-
|
|
1829
|
+
const [selections, setSelections] = useState8(/* @__PURE__ */ new Map());
|
|
1830
|
+
const [customTexts, setCustomTexts] = useState8(/* @__PURE__ */ new Map());
|
|
1831
|
+
const [usingCustom, setUsingCustom] = useState8(/* @__PURE__ */ new Set());
|
|
1832
|
+
const [submitted, setSubmitted] = useState8(false);
|
|
1833
|
+
useEffect6(() => {
|
|
1386
1834
|
if (sessionStatus === "failed" || sessionStatus === "interrupted") {
|
|
1387
1835
|
setSubmitted(false);
|
|
1388
1836
|
}
|
|
1389
1837
|
}, [sessionStatus]);
|
|
1390
|
-
const displayAnswerState =
|
|
1838
|
+
const displayAnswerState = useMemo6(() => {
|
|
1391
1839
|
if (!(answered && answerData)) {
|
|
1392
1840
|
return { selections, customTexts, usingCustom };
|
|
1393
1841
|
}
|
|
@@ -1472,7 +1920,7 @@ ${parts.join("\n")}`;
|
|
|
1472
1920
|
setSubmitted(true);
|
|
1473
1921
|
onAnswer(text, toolCallId, nextAnswerData);
|
|
1474
1922
|
};
|
|
1475
|
-
return /* @__PURE__ */
|
|
1923
|
+
return /* @__PURE__ */ jsxs7(
|
|
1476
1924
|
"div",
|
|
1477
1925
|
{
|
|
1478
1926
|
className: cn(
|
|
@@ -1480,12 +1928,12 @@ ${parts.join("\n")}`;
|
|
|
1480
1928
|
answered ? "max-w-2xl space-y-3 p-3 text-xs text-[hsl(var(--muted-foreground))] opacity-80" : "max-w-lg space-y-5 p-4 text-sm"
|
|
1481
1929
|
),
|
|
1482
1930
|
children: [
|
|
1483
|
-
data.source_loop?.description && /* @__PURE__ */
|
|
1931
|
+
data.source_loop?.description && /* @__PURE__ */ jsxs7("div", { className: "rounded-lg bg-[hsl(var(--muted)/0.35)] px-3 py-2 text-xs text-[hsl(var(--muted-foreground))]", children: [
|
|
1484
1932
|
"\u5B50\u667A\u80FD\u4F53\u300C",
|
|
1485
1933
|
data.source_loop.description,
|
|
1486
1934
|
"\u300D\u5728\u7B49\u5F85\u4F60\u7684\u56DE\u7B54"
|
|
1487
1935
|
] }),
|
|
1488
|
-
data.questions.map((q, qIdx) => /* @__PURE__ */
|
|
1936
|
+
data.questions.map((q, qIdx) => /* @__PURE__ */ jsx9(
|
|
1489
1937
|
QuestionCard,
|
|
1490
1938
|
{
|
|
1491
1939
|
question: q,
|
|
@@ -1500,7 +1948,7 @@ ${parts.join("\n")}`;
|
|
|
1500
1948
|
},
|
|
1501
1949
|
q.question
|
|
1502
1950
|
)),
|
|
1503
|
-
!answered && !submitted && onAnswer && /* @__PURE__ */
|
|
1951
|
+
!answered && !submitted && onAnswer && /* @__PURE__ */ jsx9(
|
|
1504
1952
|
"button",
|
|
1505
1953
|
{
|
|
1506
1954
|
type: "button",
|
|
@@ -1510,14 +1958,14 @@ ${parts.join("\n")}`;
|
|
|
1510
1958
|
children: allAnswered ? "\u786E\u8BA4" : "\u8BF7\u5148\u9009\u62E9\u4E00\u4E2A\u9009\u9879"
|
|
1511
1959
|
}
|
|
1512
1960
|
),
|
|
1513
|
-
submitted && !answered && /* @__PURE__ */
|
|
1961
|
+
submitted && !answered && /* @__PURE__ */ jsxs7(
|
|
1514
1962
|
"button",
|
|
1515
1963
|
{
|
|
1516
1964
|
type: "button",
|
|
1517
1965
|
disabled: true,
|
|
1518
1966
|
className: "flex w-full items-center justify-center gap-2 rounded-lg bg-[hsl(var(--primary))] px-4 py-2 text-xs font-semibold text-[hsl(var(--primary-foreground))] opacity-80",
|
|
1519
1967
|
children: [
|
|
1520
|
-
/* @__PURE__ */
|
|
1968
|
+
/* @__PURE__ */ jsx9(LoaderCircle, { size: 14, className: "animate-spin" }),
|
|
1521
1969
|
"\u786E\u8BA4\u4E2D"
|
|
1522
1970
|
]
|
|
1523
1971
|
}
|
|
@@ -1538,30 +1986,30 @@ function QuestionCard({
|
|
|
1538
1986
|
onCustomChange
|
|
1539
1987
|
}) {
|
|
1540
1988
|
const multi = question.multiSelect ?? false;
|
|
1541
|
-
return /* @__PURE__ */
|
|
1542
|
-
/* @__PURE__ */
|
|
1543
|
-
/* @__PURE__ */
|
|
1989
|
+
return /* @__PURE__ */ jsxs7("div", { children: [
|
|
1990
|
+
/* @__PURE__ */ jsxs7("div", { className: cn("flex items-start gap-2", answered ? "mb-2" : "mb-3"), children: [
|
|
1991
|
+
/* @__PURE__ */ jsx9(
|
|
1544
1992
|
MessageSquareMore,
|
|
1545
1993
|
{
|
|
1546
1994
|
size: answered ? 12 : 13,
|
|
1547
1995
|
className: "mt-0.5 shrink-0 text-[hsl(var(--primary))]"
|
|
1548
1996
|
}
|
|
1549
1997
|
),
|
|
1550
|
-
/* @__PURE__ */
|
|
1998
|
+
/* @__PURE__ */ jsx9(
|
|
1551
1999
|
"div",
|
|
1552
2000
|
{
|
|
1553
2001
|
className: cn(
|
|
1554
2002
|
"min-w-0 flex-1 font-medium text-[hsl(var(--foreground))]",
|
|
1555
2003
|
answered ? "text-xs" : "text-sm"
|
|
1556
2004
|
),
|
|
1557
|
-
children: /* @__PURE__ */
|
|
2005
|
+
children: /* @__PURE__ */ jsx9(MarkdownContent, { className: "blade-chat-prose", children: question.question })
|
|
1558
2006
|
}
|
|
1559
2007
|
)
|
|
1560
2008
|
] }),
|
|
1561
|
-
/* @__PURE__ */
|
|
2009
|
+
/* @__PURE__ */ jsxs7("div", { className: cn("flex flex-col pl-5", answered ? "gap-1" : "gap-1.5"), children: [
|
|
1562
2010
|
question.options.map((opt, optIdx) => {
|
|
1563
2011
|
const isSel = selected.has(optIdx);
|
|
1564
|
-
return /* @__PURE__ */
|
|
2012
|
+
return /* @__PURE__ */ jsxs7(
|
|
1565
2013
|
"button",
|
|
1566
2014
|
{
|
|
1567
2015
|
type: "button",
|
|
@@ -1575,14 +2023,14 @@ function QuestionCard({
|
|
|
1575
2023
|
answered && "cursor-default opacity-70"
|
|
1576
2024
|
),
|
|
1577
2025
|
children: [
|
|
1578
|
-
multi && /* @__PURE__ */
|
|
2026
|
+
multi && /* @__PURE__ */ jsx9(
|
|
1579
2027
|
"div",
|
|
1580
2028
|
{
|
|
1581
2029
|
className: cn(
|
|
1582
2030
|
"mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded border transition-colors",
|
|
1583
2031
|
isSel && !answered ? "border-[hsl(var(--primary-foreground)/0.6)] bg-[hsl(var(--primary-foreground)/0.2)]" : isSel ? "border-[hsl(var(--primary)/0.45)] bg-[hsl(var(--primary)/0.12)]" : "border-[hsl(var(--border))]"
|
|
1584
2032
|
),
|
|
1585
|
-
children: isSel && /* @__PURE__ */
|
|
2033
|
+
children: isSel && /* @__PURE__ */ jsx9(
|
|
1586
2034
|
Check,
|
|
1587
2035
|
{
|
|
1588
2036
|
size: 9,
|
|
@@ -1591,9 +2039,9 @@ function QuestionCard({
|
|
|
1591
2039
|
)
|
|
1592
2040
|
}
|
|
1593
2041
|
),
|
|
1594
|
-
/* @__PURE__ */
|
|
1595
|
-
/* @__PURE__ */
|
|
1596
|
-
opt.description && /* @__PURE__ */
|
|
2042
|
+
/* @__PURE__ */ jsxs7("div", { className: "min-w-0", children: [
|
|
2043
|
+
/* @__PURE__ */ jsx9("div", { className: cn("font-medium", answered ? "text-xs" : "text-[13px]"), children: opt.label }),
|
|
2044
|
+
opt.description && /* @__PURE__ */ jsx9(
|
|
1597
2045
|
"div",
|
|
1598
2046
|
{
|
|
1599
2047
|
className: cn(
|
|
@@ -1610,7 +2058,7 @@ function QuestionCard({
|
|
|
1610
2058
|
opt.label
|
|
1611
2059
|
);
|
|
1612
2060
|
}),
|
|
1613
|
-
answered && !isCustom ? null : /* @__PURE__ */
|
|
2061
|
+
answered && !isCustom ? null : /* @__PURE__ */ jsxs7(
|
|
1614
2062
|
"div",
|
|
1615
2063
|
{
|
|
1616
2064
|
className: cn(
|
|
@@ -1620,8 +2068,8 @@ function QuestionCard({
|
|
|
1620
2068
|
answered && "cursor-default opacity-70"
|
|
1621
2069
|
),
|
|
1622
2070
|
children: [
|
|
1623
|
-
/* @__PURE__ */
|
|
1624
|
-
/* @__PURE__ */
|
|
2071
|
+
/* @__PURE__ */ jsx9("span", { className: "shrink-0 text-xs text-[hsl(var(--muted-foreground))]", children: "\u5176\u4ED6\uFF1A" }),
|
|
2072
|
+
/* @__PURE__ */ jsx9(
|
|
1625
2073
|
"input",
|
|
1626
2074
|
{
|
|
1627
2075
|
type: "text",
|
|
@@ -1689,7 +2137,7 @@ function normalizeOptionItem(value) {
|
|
|
1689
2137
|
}
|
|
1690
2138
|
|
|
1691
2139
|
// src/components/ToolCallBlock.tsx
|
|
1692
|
-
import { Fragment, jsx as
|
|
2140
|
+
import { Fragment, jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1693
2141
|
function resolveAskQuestionState({
|
|
1694
2142
|
toolStatus,
|
|
1695
2143
|
hasAnswerData,
|
|
@@ -1709,12 +2157,12 @@ function ToolCallBlock({
|
|
|
1709
2157
|
sessionStatus,
|
|
1710
2158
|
renderer
|
|
1711
2159
|
}) {
|
|
1712
|
-
const [expanded, setExpanded] =
|
|
2160
|
+
const [expanded, setExpanded] = useState9(false);
|
|
1713
2161
|
const normalizedName = formatToolName(toolCall.name);
|
|
1714
2162
|
if (renderer) {
|
|
1715
2163
|
const custom = renderer(toolCall);
|
|
1716
2164
|
if (custom !== null && custom !== void 0) {
|
|
1717
|
-
return /* @__PURE__ */
|
|
2165
|
+
return /* @__PURE__ */ jsx10(Fragment, { children: custom });
|
|
1718
2166
|
}
|
|
1719
2167
|
}
|
|
1720
2168
|
if (normalizedName === "AskUserQuestion") {
|
|
@@ -1726,7 +2174,7 @@ function ToolCallBlock({
|
|
|
1726
2174
|
});
|
|
1727
2175
|
const canAnswer = questionState.awaitingAnswer && Boolean(onAnswer);
|
|
1728
2176
|
if (askData) {
|
|
1729
|
-
return /* @__PURE__ */
|
|
2177
|
+
return /* @__PURE__ */ jsx10(
|
|
1730
2178
|
AskUserQuestionBlock,
|
|
1731
2179
|
{
|
|
1732
2180
|
data: askData,
|
|
@@ -1739,24 +2187,24 @@ function ToolCallBlock({
|
|
|
1739
2187
|
);
|
|
1740
2188
|
}
|
|
1741
2189
|
if (toolCall.status === "pending") {
|
|
1742
|
-
return /* @__PURE__ */
|
|
1743
|
-
/* @__PURE__ */
|
|
1744
|
-
/* @__PURE__ */
|
|
2190
|
+
return /* @__PURE__ */ jsxs8("div", { className: "ml-4 flex max-w-lg items-center gap-2 rounded-xl border border-[hsl(var(--border))] bg-[hsl(var(--card))] p-4 text-sm text-[hsl(var(--muted-foreground))]", children: [
|
|
2191
|
+
/* @__PURE__ */ jsx10(LoaderCircle, { size: 14, className: "animate-spin" }),
|
|
2192
|
+
/* @__PURE__ */ jsx10("span", { children: "\u6B63\u5728\u51C6\u5907\u95EE\u9898\u2026" })
|
|
1745
2193
|
] });
|
|
1746
2194
|
}
|
|
1747
|
-
return /* @__PURE__ */
|
|
1748
|
-
/* @__PURE__ */
|
|
1749
|
-
/* @__PURE__ */
|
|
2195
|
+
return /* @__PURE__ */ jsxs8("div", { className: "ml-4 max-w-lg rounded-xl border border-amber-500/35 bg-amber-500/10 p-4 text-sm text-[hsl(var(--foreground))]", children: [
|
|
2196
|
+
/* @__PURE__ */ jsx10("div", { className: "font-semibold", children: "\u9009\u62E9\u9898\u5185\u5BB9\u6682\u65F6\u65E0\u6CD5\u663E\u793A" }),
|
|
2197
|
+
/* @__PURE__ */ jsx10("div", { className: "mt-1 text-xs leading-5 text-[hsl(var(--muted-foreground))]", children: "\u6536\u5230\u7684\u4EA4\u4E92\u6570\u636E\u4E0D\u5B8C\u6574\u3002\u8BF7\u8BA9\u667A\u80FD\u4F53\u91CD\u65B0\u63D0\u95EE\u3002" })
|
|
1750
2198
|
] });
|
|
1751
2199
|
}
|
|
1752
2200
|
const tone = getToolTone(toolCall.status);
|
|
1753
2201
|
const displayName = getToolDisplayLabel(toolCall);
|
|
1754
2202
|
const toneClass = tone === "red" ? "border-l-[hsl(var(--muted-foreground)/0.5)]" : tone === "amber" ? "border-l-amber-400" : tone === "blue" ? "border-l-blue-500" : "border-l-[hsl(var(--primary))]";
|
|
1755
|
-
const statusIcon = toolCall.status === "pending" ? /* @__PURE__ */
|
|
2203
|
+
const statusIcon = toolCall.status === "pending" ? /* @__PURE__ */ jsx10(LoaderCircle, { size: 11, className: "animate-spin" }) : toolCall.status === "awaiting_answer" ? /* @__PURE__ */ jsx10(MessageSquareMore, { size: 11 }) : toolCall.status === "cancelled" || toolCall.status === "error" ? /* @__PURE__ */ jsx10(X, { size: 11 }) : /* @__PURE__ */ jsx10(Check, { size: 11 });
|
|
1756
2204
|
const statusTextClass = tone === "red" ? "text-[hsl(var(--muted-foreground))]" : tone === "amber" ? "text-amber-300" : tone === "blue" ? "text-blue-300" : "text-[hsl(var(--primary))]";
|
|
1757
|
-
return /* @__PURE__ */
|
|
1758
|
-
/* @__PURE__ */
|
|
1759
|
-
/* @__PURE__ */
|
|
2205
|
+
return /* @__PURE__ */ jsxs8("div", { className: "blade-chat-tool ml-4 text-xs", children: [
|
|
2206
|
+
/* @__PURE__ */ jsxs8("div", { className: cn("border-l-[3px] flex items-center gap-2 px-3 py-2", toneClass), children: [
|
|
2207
|
+
/* @__PURE__ */ jsxs8(
|
|
1760
2208
|
"button",
|
|
1761
2209
|
{
|
|
1762
2210
|
type: "button",
|
|
@@ -1764,7 +2212,7 @@ function ToolCallBlock({
|
|
|
1764
2212
|
className: "flex min-w-0 flex-1 items-center gap-2 text-left transition-colors hover:bg-white/3 focus-visible:ring-1 focus-visible:ring-[hsl(var(--ring))] focus:outline-none",
|
|
1765
2213
|
"aria-expanded": expanded,
|
|
1766
2214
|
children: [
|
|
1767
|
-
/* @__PURE__ */
|
|
2215
|
+
/* @__PURE__ */ jsx10(
|
|
1768
2216
|
ChevronRight,
|
|
1769
2217
|
{
|
|
1770
2218
|
size: 11,
|
|
@@ -1774,24 +2222,24 @@ function ToolCallBlock({
|
|
|
1774
2222
|
)
|
|
1775
2223
|
}
|
|
1776
2224
|
),
|
|
1777
|
-
/* @__PURE__ */
|
|
2225
|
+
/* @__PURE__ */ jsxs8("span", { className: cn("flex shrink-0 items-center gap-1 text-[10px]", statusTextClass), children: [
|
|
1778
2226
|
statusIcon,
|
|
1779
|
-
/* @__PURE__ */
|
|
2227
|
+
/* @__PURE__ */ jsx10("span", { children: getToolStatusLabel(toolCall.status) })
|
|
1780
2228
|
] }),
|
|
1781
|
-
/* @__PURE__ */
|
|
2229
|
+
/* @__PURE__ */ jsx10("span", { className: "min-w-0 flex-1 truncate font-medium text-[hsl(var(--foreground))]", children: displayName })
|
|
1782
2230
|
]
|
|
1783
2231
|
}
|
|
1784
2232
|
),
|
|
1785
|
-
typeof toolCall.duration_ms === "number" && toolCall.duration_ms > 0 && /* @__PURE__ */
|
|
2233
|
+
typeof toolCall.duration_ms === "number" && toolCall.duration_ms > 0 && /* @__PURE__ */ jsx10("span", { className: "shrink-0 font-mono text-[10px] text-[hsl(var(--muted-foreground))]", children: formatToolDuration(toolCall.duration_ms) })
|
|
1786
2234
|
] }),
|
|
1787
|
-
expanded && /* @__PURE__ */
|
|
1788
|
-
/* @__PURE__ */
|
|
1789
|
-
/* @__PURE__ */
|
|
1790
|
-
/* @__PURE__ */
|
|
1791
|
-
/* @__PURE__ */
|
|
1792
|
-
toolCall.result != null && /* @__PURE__ */
|
|
1793
|
-
/* @__PURE__ */
|
|
1794
|
-
/* @__PURE__ */
|
|
2235
|
+
expanded && /* @__PURE__ */ jsxs8("div", { className: "blade-chat-tool-detail ml-4 mt-1 rounded-xl bg-[hsl(var(--card))] px-3 py-3", children: [
|
|
2236
|
+
/* @__PURE__ */ jsx10("div", { className: "mb-1 text-[10px] uppercase tracking-wider text-[hsl(var(--muted-foreground))]", children: "\u5DE5\u5177" }),
|
|
2237
|
+
/* @__PURE__ */ jsx10("div", { className: "mb-3 font-mono text-[11px] text-[hsl(var(--foreground))]", children: normalizedName }),
|
|
2238
|
+
/* @__PURE__ */ jsx10("div", { className: "mb-1 text-[10px] uppercase tracking-wider text-[hsl(var(--muted-foreground))]", children: "\u53C2\u6570" }),
|
|
2239
|
+
/* @__PURE__ */ jsx10("pre", { className: "overflow-x-auto whitespace-pre-wrap rounded-md bg-[hsl(var(--muted))] p-2 font-mono text-[11px] text-[hsl(var(--foreground))]", children: formatToolArgs(toolCall.arguments) }),
|
|
2240
|
+
toolCall.result != null && /* @__PURE__ */ jsxs8(Fragment, { children: [
|
|
2241
|
+
/* @__PURE__ */ jsx10("div", { className: "mb-1 mt-3 text-[10px] uppercase tracking-wider text-[hsl(var(--muted-foreground))]", children: "\u7ED3\u679C" }),
|
|
2242
|
+
/* @__PURE__ */ jsx10("pre", { className: "max-h-[400px] overflow-auto whitespace-pre-wrap rounded-md bg-[hsl(var(--muted))] p-2 font-mono text-[11px] text-[hsl(var(--foreground))]", children: formatToolResult(toolCall.result) })
|
|
1795
2243
|
] })
|
|
1796
2244
|
] })
|
|
1797
2245
|
] });
|
|
@@ -1809,11 +2257,11 @@ function buildAskUserPayload(argumentsJson) {
|
|
|
1809
2257
|
}
|
|
1810
2258
|
|
|
1811
2259
|
// src/components/AssistantTurnBlock.tsx
|
|
1812
|
-
import { jsx as
|
|
2260
|
+
import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1813
2261
|
function ThinkingBlock({ reasoning, isStreaming }) {
|
|
1814
|
-
const [open, setOpen] =
|
|
1815
|
-
return /* @__PURE__ */
|
|
1816
|
-
/* @__PURE__ */
|
|
2262
|
+
const [open, setOpen] = useState10(false);
|
|
2263
|
+
return /* @__PURE__ */ jsxs9("div", { className: "blade-chat-thinking ml-4 text-sm", children: [
|
|
2264
|
+
/* @__PURE__ */ jsxs9(
|
|
1817
2265
|
"button",
|
|
1818
2266
|
{
|
|
1819
2267
|
type: "button",
|
|
@@ -1821,14 +2269,14 @@ function ThinkingBlock({ reasoning, isStreaming }) {
|
|
|
1821
2269
|
"aria-expanded": open,
|
|
1822
2270
|
className: "inline-flex items-center gap-1.5 text-[hsl(var(--muted-foreground))] transition-colors hover:text-[hsl(var(--foreground))]",
|
|
1823
2271
|
children: [
|
|
1824
|
-
/* @__PURE__ */
|
|
1825
|
-
isStreaming ? /* @__PURE__ */
|
|
1826
|
-
/* @__PURE__ */
|
|
2272
|
+
/* @__PURE__ */ jsx11(Brain, { size: 12, className: "shrink-0" }),
|
|
2273
|
+
isStreaming ? /* @__PURE__ */ jsx11(Shimmer, { className: "text-xs", children: "\u6B63\u5728\u601D\u8003" }) : /* @__PURE__ */ jsx11("span", { children: "\u601D\u8003\u8FC7\u7A0B" }),
|
|
2274
|
+
/* @__PURE__ */ jsxs9("span", { className: "text-[hsl(var(--muted-foreground))]/70", children: [
|
|
1827
2275
|
"\xB7 ",
|
|
1828
2276
|
new Intl.NumberFormat("zh-CN").format(reasoning.length),
|
|
1829
2277
|
" \u5B57"
|
|
1830
2278
|
] }),
|
|
1831
|
-
/* @__PURE__ */
|
|
2279
|
+
/* @__PURE__ */ jsx11(
|
|
1832
2280
|
ChevronDown,
|
|
1833
2281
|
{
|
|
1834
2282
|
size: 12,
|
|
@@ -1838,7 +2286,7 @@ function ThinkingBlock({ reasoning, isStreaming }) {
|
|
|
1838
2286
|
]
|
|
1839
2287
|
}
|
|
1840
2288
|
),
|
|
1841
|
-
open && /* @__PURE__ */
|
|
2289
|
+
open && /* @__PURE__ */ jsx11("div", { className: "mt-1.5 whitespace-pre-wrap border-l-2 border-[hsl(var(--border))] pl-3 text-[11px] leading-5 text-[hsl(var(--muted-foreground))]", children: reasoning })
|
|
1842
2290
|
] });
|
|
1843
2291
|
}
|
|
1844
2292
|
function getMessageText(message) {
|
|
@@ -1864,21 +2312,21 @@ function AssistantTurnBlock({
|
|
|
1864
2312
|
(message) => getMessageText(message) || message.reasoning || (message.tool_calls?.length ?? 0) > 0
|
|
1865
2313
|
);
|
|
1866
2314
|
const latestReasoningIndex = isStreaming ? findLatestReasoningMessageIndex(messages) : -1;
|
|
1867
|
-
return /* @__PURE__ */
|
|
1868
|
-
hasInterrupted && /* @__PURE__ */
|
|
2315
|
+
return /* @__PURE__ */ jsxs9("div", { className: "blade-chat-assistant-turn flex flex-col gap-3", children: [
|
|
2316
|
+
hasInterrupted && /* @__PURE__ */ jsx11("div", { className: "ml-4 w-fit rounded-full border border-amber-500/30 bg-amber-500/10 px-2.5 py-1 text-[10px] font-medium uppercase tracking-[0.12em] text-amber-300", children: "\u5DF2\u4E2D\u65AD" }),
|
|
1869
2317
|
messages.map((message, index) => {
|
|
1870
2318
|
const isLast = index === messages.length - 1;
|
|
1871
2319
|
const streamingThis = isStreaming && isLast;
|
|
1872
2320
|
const text = getMessageText(message);
|
|
1873
2321
|
const toolCalls = message.tool_calls ?? [];
|
|
1874
2322
|
const showReasoning = !!message.reasoning && (!isStreaming || index === latestReasoningIndex);
|
|
1875
|
-
return /* @__PURE__ */
|
|
2323
|
+
return /* @__PURE__ */ jsxs9(
|
|
1876
2324
|
"div",
|
|
1877
2325
|
{
|
|
1878
2326
|
className: "flex flex-col gap-3",
|
|
1879
2327
|
children: [
|
|
1880
|
-
showReasoning && message.reasoning && /* @__PURE__ */
|
|
1881
|
-
text && /* @__PURE__ */
|
|
2328
|
+
showReasoning && message.reasoning && /* @__PURE__ */ jsx11(ThinkingBlock, { reasoning: message.reasoning, isStreaming: streamingThis && !text }),
|
|
2329
|
+
text && /* @__PURE__ */ jsx11("div", { className: "blade-chat-assistant-text text-[15px] leading-8 text-[hsl(var(--foreground))]", children: /* @__PURE__ */ jsx11(
|
|
1882
2330
|
MarkdownContent,
|
|
1883
2331
|
{
|
|
1884
2332
|
mode: streamingThis ? "streaming" : "static",
|
|
@@ -1887,8 +2335,8 @@ function AssistantTurnBlock({
|
|
|
1887
2335
|
children: text
|
|
1888
2336
|
}
|
|
1889
2337
|
) }),
|
|
1890
|
-
toolCalls.length > 0 && /* @__PURE__ */
|
|
1891
|
-
(toolCall) => formatToolName(toolCall.name) === "Agent" ? /* @__PURE__ */
|
|
2338
|
+
toolCalls.length > 0 && /* @__PURE__ */ jsx11("div", { className: "flex flex-col gap-2", children: toolCalls.map(
|
|
2339
|
+
(toolCall) => formatToolName(toolCall.name) === "Agent" ? /* @__PURE__ */ jsx11(AgentLoopBlock, { toolCall }, toolCall.id) : /* @__PURE__ */ jsx11(
|
|
1892
2340
|
ToolCallBlock,
|
|
1893
2341
|
{
|
|
1894
2342
|
toolCall,
|
|
@@ -1906,13 +2354,13 @@ function AssistantTurnBlock({
|
|
|
1906
2354
|
message.entry_id ?? `${message.timestamp ?? "assistant"}-${index}`
|
|
1907
2355
|
);
|
|
1908
2356
|
}),
|
|
1909
|
-
isStreaming && !hasAnyContent && /* @__PURE__ */
|
|
2357
|
+
isStreaming && !hasAnyContent && /* @__PURE__ */ jsx11(Shimmer, { className: "ml-4", children: "\u6B63\u5728\u751F\u6210..." })
|
|
1910
2358
|
] });
|
|
1911
2359
|
}
|
|
1912
2360
|
|
|
1913
2361
|
// src/components/RenderErrorBoundary.tsx
|
|
1914
2362
|
import { Component } from "react";
|
|
1915
|
-
import { jsx as
|
|
2363
|
+
import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1916
2364
|
function getFirstComponentName(componentStack) {
|
|
1917
2365
|
const match = componentStack.match(/\n\s+at\s+([^\s(]+)/);
|
|
1918
2366
|
return match?.[1] ?? null;
|
|
@@ -1945,39 +2393,425 @@ var RenderErrorBoundary = class extends Component {
|
|
|
1945
2393
|
return children;
|
|
1946
2394
|
}
|
|
1947
2395
|
const componentName = getFirstComponentName(componentStack);
|
|
1948
|
-
return /* @__PURE__ */
|
|
1949
|
-
/* @__PURE__ */
|
|
1950
|
-
/* @__PURE__ */
|
|
1951
|
-
/* @__PURE__ */
|
|
2396
|
+
return /* @__PURE__ */ jsx12("div", { className: "blade-chat-render-error rounded-xl border border-amber-500/30 bg-amber-500/8 px-4 py-3 text-sm text-amber-100", children: /* @__PURE__ */ jsxs10("div", { className: "flex items-start gap-2", children: [
|
|
2397
|
+
/* @__PURE__ */ jsx12(TriangleAlert, { className: "mt-0.5 h-4 w-4 shrink-0 text-amber-300" }),
|
|
2398
|
+
/* @__PURE__ */ jsxs10("div", { className: "min-w-0 flex-1", children: [
|
|
2399
|
+
/* @__PURE__ */ jsxs10("div", { className: "font-medium", children: [
|
|
1952
2400
|
label,
|
|
1953
2401
|
"\u6E32\u67D3\u5931\u8D25"
|
|
1954
2402
|
] }),
|
|
1955
|
-
/* @__PURE__ */
|
|
2403
|
+
/* @__PURE__ */ jsxs10("div", { className: "mt-1 break-words text-xs leading-5 text-amber-100/75", children: [
|
|
1956
2404
|
componentName ? `\u7EC4\u4EF6\uFF1A${componentName}\u3002` : null,
|
|
1957
2405
|
error.message || "\u53D1\u751F\u4E86\u672A\u9884\u671F\u7684\u6E32\u67D3\u9519\u8BEF\u3002"
|
|
1958
2406
|
] }),
|
|
1959
|
-
details ? /* @__PURE__ */
|
|
2407
|
+
details ? /* @__PURE__ */ jsx12("div", { className: "mt-1 truncate text-xs text-amber-100/55", children: details }) : null
|
|
1960
2408
|
] })
|
|
1961
2409
|
] }) });
|
|
1962
2410
|
}
|
|
1963
2411
|
};
|
|
1964
2412
|
|
|
1965
|
-
// src/components/
|
|
1966
|
-
import {
|
|
1967
|
-
import { jsx as
|
|
1968
|
-
function
|
|
1969
|
-
|
|
2413
|
+
// src/components/PostChatFollowupBlock.tsx
|
|
2414
|
+
import { useCallback as useCallback5, useEffect as useEffect7, useRef as useRef7, useState as useState11 } from "react";
|
|
2415
|
+
import { Fragment as Fragment2, jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
2416
|
+
function emitInteraction(callback, event) {
|
|
2417
|
+
try {
|
|
2418
|
+
callback?.(event);
|
|
2419
|
+
} catch {
|
|
2420
|
+
}
|
|
1970
2421
|
}
|
|
1971
|
-
function
|
|
1972
|
-
return
|
|
2422
|
+
function basename(path) {
|
|
2423
|
+
return path.split(/[\\/]/).filter(Boolean).pop() || path;
|
|
1973
2424
|
}
|
|
1974
|
-
|
|
1975
|
-
|
|
2425
|
+
function ArtifactCard({
|
|
2426
|
+
artifact,
|
|
2427
|
+
sessionId,
|
|
2428
|
+
assistantEntryId,
|
|
2429
|
+
artifactIndex,
|
|
2430
|
+
onInteraction,
|
|
2431
|
+
onArtifactOpened
|
|
2432
|
+
}) {
|
|
2433
|
+
const client = useBladeClient();
|
|
2434
|
+
const [downloading, setDownloading] = useState11(false);
|
|
2435
|
+
const name = artifact.label || basename(artifact.target);
|
|
2436
|
+
if (artifact.kind === "link") {
|
|
2437
|
+
return /* @__PURE__ */ jsxs11(
|
|
2438
|
+
"a",
|
|
2439
|
+
{
|
|
2440
|
+
href: artifact.target,
|
|
2441
|
+
target: "_blank",
|
|
2442
|
+
rel: "noopener noreferrer",
|
|
2443
|
+
onClick: () => onArtifactOpened(artifactIndex, "link"),
|
|
2444
|
+
title: `${name}
|
|
2445
|
+
${artifact.target}`,
|
|
2446
|
+
className: "group relative flex min-w-0 items-center gap-1.5 rounded-md border border-[hsl(var(--border))] bg-[hsl(var(--card))] px-2 py-1.5 text-xs text-[hsl(var(--card-foreground))] hover:bg-[hsl(var(--accent))]",
|
|
2447
|
+
children: [
|
|
2448
|
+
/* @__PURE__ */ jsx13(Globe, { size: 15, className: "shrink-0 text-[hsl(var(--primary))]" }),
|
|
2449
|
+
/* @__PURE__ */ jsx13("span", { className: "min-w-0 flex-1 truncate font-medium", children: name }),
|
|
2450
|
+
/* @__PURE__ */ jsx13(ArrowUpRight, { size: 13, className: "absolute right-2 opacity-0 group-hover:opacity-100 group-focus-visible:opacity-100" })
|
|
2451
|
+
]
|
|
2452
|
+
}
|
|
2453
|
+
);
|
|
2454
|
+
}
|
|
2455
|
+
const fileName = basename(artifact.target);
|
|
2456
|
+
const downloadUrl = sessionId ? client.buildAuthedUrl(
|
|
2457
|
+
`/api/sessions/${encodeURIComponent(sessionId)}/files/${encodeURIComponent(artifact.target)}`
|
|
2458
|
+
) : void 0;
|
|
2459
|
+
const handleDownload = async (event) => {
|
|
2460
|
+
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
2461
|
+
event.preventDefault();
|
|
2462
|
+
if (!sessionId || downloading) return;
|
|
2463
|
+
setDownloading(true);
|
|
2464
|
+
emitInteraction(onInteraction, {
|
|
2465
|
+
type: "artifact_download_started",
|
|
2466
|
+
sessionId,
|
|
2467
|
+
assistantEntryId,
|
|
2468
|
+
artifactIndex,
|
|
2469
|
+
artifactKind: "file"
|
|
2470
|
+
});
|
|
2471
|
+
try {
|
|
2472
|
+
await client.sessions.downloadFile(sessionId, artifact.target, fileName);
|
|
2473
|
+
emitInteraction(onInteraction, {
|
|
2474
|
+
type: "artifact_download_succeeded",
|
|
2475
|
+
sessionId,
|
|
2476
|
+
assistantEntryId,
|
|
2477
|
+
artifactIndex,
|
|
2478
|
+
artifactKind: "file"
|
|
2479
|
+
});
|
|
2480
|
+
} catch {
|
|
2481
|
+
} finally {
|
|
2482
|
+
setDownloading(false);
|
|
2483
|
+
}
|
|
2484
|
+
};
|
|
2485
|
+
return /* @__PURE__ */ jsx13(
|
|
2486
|
+
"a",
|
|
2487
|
+
{
|
|
2488
|
+
href: downloadUrl,
|
|
2489
|
+
download: fileName,
|
|
2490
|
+
onClick: handleDownload,
|
|
2491
|
+
title: fileName,
|
|
2492
|
+
"aria-label": `\u4E0B\u8F7D\u6587\u4EF6\uFF1A${fileName}`,
|
|
2493
|
+
"aria-disabled": !sessionId || void 0,
|
|
2494
|
+
"aria-busy": downloading || void 0,
|
|
2495
|
+
className: "min-w-0 cursor-pointer break-all text-xs text-[hsl(var(--primary))] underline aria-disabled:cursor-not-allowed aria-disabled:opacity-60 aria-busy:cursor-wait",
|
|
2496
|
+
children: fileName
|
|
2497
|
+
}
|
|
2498
|
+
);
|
|
2499
|
+
}
|
|
2500
|
+
var FEEDBACK_REASONS = [
|
|
2501
|
+
{ value: "not_solved", label: "\u6CA1\u89E3\u51B3\u95EE\u9898" },
|
|
2502
|
+
{ value: "inaccurate", label: "\u5185\u5BB9\u4E0D\u51C6\u786E" },
|
|
2503
|
+
{ value: "incomplete", label: "\u5185\u5BB9\u4E0D\u5B8C\u6574" },
|
|
2504
|
+
{ value: "needs_major_changes", label: "\u9700\u8981\u5927\u91CF\u4FEE\u6539" },
|
|
2505
|
+
{ value: "too_slow", label: "\u592A\u6162" },
|
|
2506
|
+
{ value: "other", label: "\u5176\u4ED6" }
|
|
2507
|
+
];
|
|
2508
|
+
function feedbackReasonLabel(reason) {
|
|
2509
|
+
return FEEDBACK_REASONS.find((item) => item.value === reason)?.label ?? null;
|
|
2510
|
+
}
|
|
2511
|
+
function HistoricalResultFeedback({ feedback }) {
|
|
2512
|
+
const label = feedbackReasonLabel(feedback.reason);
|
|
2513
|
+
return /* @__PURE__ */ jsxs11(
|
|
2514
|
+
"section",
|
|
2515
|
+
{
|
|
2516
|
+
"aria-label": "\u5386\u53F2\u7ED3\u679C\u53CD\u9988",
|
|
2517
|
+
className: "mt-3 w-fit max-w-full rounded-lg border border-[hsl(var(--border))] bg-[hsl(var(--muted)/0.2)] px-3 py-2 text-xs text-[hsl(var(--muted-foreground))]",
|
|
2518
|
+
children: [
|
|
2519
|
+
/* @__PURE__ */ jsxs11("span", { children: [
|
|
2520
|
+
"\u4F60\u5BF9\u6B64\u8F6E\u7ED3\u679C\u7684\u8BC4\u4EF7\uFF1A",
|
|
2521
|
+
feedback.helpful ? "\u6709\u5E2E\u52A9" : "\u6CA1\u5E2E\u52A9"
|
|
2522
|
+
] }),
|
|
2523
|
+
label ? /* @__PURE__ */ jsxs11("span", { children: [
|
|
2524
|
+
" \xB7 ",
|
|
2525
|
+
label
|
|
2526
|
+
] }) : null
|
|
2527
|
+
]
|
|
2528
|
+
}
|
|
2529
|
+
);
|
|
2530
|
+
}
|
|
2531
|
+
function ResultFeedback({
|
|
2532
|
+
followup,
|
|
2533
|
+
sessionId,
|
|
2534
|
+
isViewer,
|
|
2535
|
+
onInteraction,
|
|
2536
|
+
savedFeedback,
|
|
2537
|
+
onFeedbackSaved
|
|
2538
|
+
}) {
|
|
2539
|
+
const client = useBladeClient();
|
|
2540
|
+
const [saved, setSaved] = useState11(savedFeedback ?? null);
|
|
2541
|
+
const [helpful, setHelpful] = useState11(savedFeedback?.helpful ?? null);
|
|
2542
|
+
const [reason, setReason] = useState11(savedFeedback?.reason ?? null);
|
|
2543
|
+
const [saving, setSaving] = useState11(false);
|
|
2544
|
+
const [saveError, setSaveError] = useState11(false);
|
|
2545
|
+
const reportedShown = useRef7(false);
|
|
2546
|
+
const latestChoice = useRef7(null);
|
|
2547
|
+
const eligible = followup.feedback_eligible === true && Boolean(sessionId) && !isViewer;
|
|
2548
|
+
useEffect7(() => {
|
|
2549
|
+
if (!eligible || reportedShown.current) return;
|
|
2550
|
+
reportedShown.current = true;
|
|
2551
|
+
emitInteraction(onInteraction, {
|
|
2552
|
+
type: "result_feedback_shown",
|
|
2553
|
+
sessionId,
|
|
2554
|
+
assistantEntryId: followup.assistant_entry_id
|
|
2555
|
+
});
|
|
2556
|
+
}, [eligible, followup.assistant_entry_id, onInteraction, sessionId]);
|
|
2557
|
+
useEffect7(() => {
|
|
2558
|
+
if (!savedFeedback || latestChoice.current) return;
|
|
2559
|
+
setSaved(savedFeedback);
|
|
2560
|
+
setHelpful(savedFeedback.helpful);
|
|
2561
|
+
setReason(savedFeedback.reason);
|
|
2562
|
+
}, [savedFeedback]);
|
|
2563
|
+
const submit = useCallback5(
|
|
2564
|
+
async (nextHelpful, nextReason) => {
|
|
2565
|
+
if (!sessionId) return;
|
|
2566
|
+
const choice = { helpful: nextHelpful, reason: nextReason };
|
|
2567
|
+
latestChoice.current = choice;
|
|
2568
|
+
setHelpful(nextHelpful);
|
|
2569
|
+
setReason(nextReason);
|
|
2570
|
+
setSaving(true);
|
|
2571
|
+
setSaveError(false);
|
|
2572
|
+
try {
|
|
2573
|
+
const result = await client.sessions.putResultFeedback(
|
|
2574
|
+
sessionId,
|
|
2575
|
+
followup.assistant_entry_id,
|
|
2576
|
+
choice
|
|
2577
|
+
);
|
|
2578
|
+
if (latestChoice.current !== choice) return;
|
|
2579
|
+
setSaved(result);
|
|
2580
|
+
onFeedbackSaved?.(result);
|
|
2581
|
+
emitInteraction(onInteraction, {
|
|
2582
|
+
type: "result_feedback_submitted",
|
|
2583
|
+
sessionId,
|
|
2584
|
+
assistantEntryId: followup.assistant_entry_id,
|
|
2585
|
+
helpful: result.helpful,
|
|
2586
|
+
reason: result.reason,
|
|
2587
|
+
updatedAt: result.updated_at
|
|
2588
|
+
});
|
|
2589
|
+
} catch {
|
|
2590
|
+
if (latestChoice.current === choice) setSaveError(true);
|
|
2591
|
+
} finally {
|
|
2592
|
+
if (latestChoice.current === choice) setSaving(false);
|
|
2593
|
+
}
|
|
2594
|
+
},
|
|
2595
|
+
[client, followup.assistant_entry_id, onFeedbackSaved, onInteraction, sessionId]
|
|
2596
|
+
);
|
|
2597
|
+
if (!eligible) return null;
|
|
2598
|
+
return /* @__PURE__ */ jsxs11(
|
|
2599
|
+
"section",
|
|
2600
|
+
{
|
|
2601
|
+
"aria-label": "\u7ED3\u679C\u53CD\u9988",
|
|
2602
|
+
className: "flex flex-col gap-2 border-t border-[hsl(var(--border))] pt-3",
|
|
2603
|
+
children: [
|
|
2604
|
+
/* @__PURE__ */ jsx13("div", { className: "text-xs font-medium text-[hsl(var(--muted-foreground))]", children: "\u76EE\u524D\u7684\u6574\u4F53\u7ED3\u679C\u6709\u5E2E\u52A9\u5417\uFF1F" }),
|
|
2605
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex flex-wrap gap-1.5", children: [
|
|
2606
|
+
/* @__PURE__ */ jsx13(
|
|
2607
|
+
"button",
|
|
2608
|
+
{
|
|
2609
|
+
type: "button",
|
|
2610
|
+
"aria-pressed": helpful === true,
|
|
2611
|
+
disabled: saving,
|
|
2612
|
+
onClick: () => void submit(true, null),
|
|
2613
|
+
className: "rounded-lg border border-[hsl(var(--border))] px-2.5 py-1.5 text-xs aria-pressed:bg-[hsl(var(--primary)/0.1)] disabled:opacity-60",
|
|
2614
|
+
children: "\u6709\u5E2E\u52A9"
|
|
2615
|
+
}
|
|
2616
|
+
),
|
|
2617
|
+
/* @__PURE__ */ jsx13(
|
|
2618
|
+
"button",
|
|
2619
|
+
{
|
|
2620
|
+
type: "button",
|
|
2621
|
+
"aria-pressed": helpful === false,
|
|
2622
|
+
disabled: saving,
|
|
2623
|
+
onClick: () => void submit(false, reason),
|
|
2624
|
+
className: "rounded-lg border border-[hsl(var(--border))] px-2.5 py-1.5 text-xs aria-pressed:bg-[hsl(var(--primary)/0.1)] disabled:opacity-60",
|
|
2625
|
+
children: "\u6CA1\u5E2E\u52A9"
|
|
2626
|
+
}
|
|
2627
|
+
)
|
|
2628
|
+
] }),
|
|
2629
|
+
helpful === false ? /* @__PURE__ */ jsx13("div", { className: "flex flex-wrap gap-1.5", "aria-label": "\u6CA1\u5E2E\u52A9\u7684\u4E3B\u8981\u539F\u56E0", children: FEEDBACK_REASONS.map((item) => /* @__PURE__ */ jsx13(
|
|
2630
|
+
"button",
|
|
2631
|
+
{
|
|
2632
|
+
type: "button",
|
|
2633
|
+
"aria-pressed": reason === item.value,
|
|
2634
|
+
disabled: saving,
|
|
2635
|
+
onClick: () => void submit(false, item.value),
|
|
2636
|
+
className: "rounded-full bg-[hsl(var(--muted)/0.62)] px-2.5 py-1 text-[11px] aria-pressed:bg-[hsl(var(--primary)/0.14)] disabled:opacity-60",
|
|
2637
|
+
children: item.label
|
|
2638
|
+
},
|
|
2639
|
+
item.value
|
|
2640
|
+
)) }) : null,
|
|
2641
|
+
saveError ? /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-2 text-xs text-[hsl(var(--destructive))]", children: [
|
|
2642
|
+
/* @__PURE__ */ jsx13("span", { children: "\u53CD\u9988\u6682\u672A\u4FDD\u5B58\uFF0C\u53EF\u91CD\u8BD5" }),
|
|
2643
|
+
/* @__PURE__ */ jsx13(
|
|
2644
|
+
"button",
|
|
2645
|
+
{
|
|
2646
|
+
type: "button",
|
|
2647
|
+
className: "underline",
|
|
2648
|
+
onClick: () => {
|
|
2649
|
+
const choice = latestChoice.current;
|
|
2650
|
+
if (choice) void submit(choice.helpful, choice.reason);
|
|
2651
|
+
},
|
|
2652
|
+
children: "\u91CD\u8BD5"
|
|
2653
|
+
}
|
|
2654
|
+
)
|
|
2655
|
+
] }) : saved ? /* @__PURE__ */ jsx13("div", { className: "text-[11px] text-[hsl(var(--muted-foreground))]", children: "\u5DF2\u4FDD\u5B58\uFF0C\u53EF\u968F\u65F6\u4FEE\u6539" }) : null
|
|
2656
|
+
]
|
|
2657
|
+
}
|
|
2658
|
+
);
|
|
2659
|
+
}
|
|
2660
|
+
function PostChatFollowupBlock({
|
|
2661
|
+
followup,
|
|
2662
|
+
sessionId,
|
|
2663
|
+
onSuggestion,
|
|
2664
|
+
isViewer = false,
|
|
2665
|
+
onInteraction,
|
|
2666
|
+
savedFeedback,
|
|
2667
|
+
onFeedbackSaved
|
|
2668
|
+
}) {
|
|
2669
|
+
const [expanded, setExpanded] = useState11(false);
|
|
2670
|
+
const adopted = useRef7(/* @__PURE__ */ new Set());
|
|
2671
|
+
const reportedSuggestions = useRef7(false);
|
|
2672
|
+
const reportedArtifacts = useRef7(/* @__PURE__ */ new Set());
|
|
2673
|
+
const openedArtifacts = useRef7(/* @__PURE__ */ new Set());
|
|
2674
|
+
const artifacts = followup.final_artifacts ?? [];
|
|
2675
|
+
const visibleArtifacts = expanded ? artifacts : artifacts.slice(0, 3);
|
|
2676
|
+
useEffect7(() => {
|
|
2677
|
+
if (!reportedSuggestions.current && followup.suggestions.length > 0) {
|
|
2678
|
+
reportedSuggestions.current = true;
|
|
2679
|
+
emitInteraction(onInteraction, {
|
|
2680
|
+
type: "suggestions_shown",
|
|
2681
|
+
sessionId,
|
|
2682
|
+
assistantEntryId: followup.assistant_entry_id,
|
|
2683
|
+
count: followup.suggestions.length
|
|
2684
|
+
});
|
|
2685
|
+
}
|
|
2686
|
+
for (let artifactIndex = 0; artifactIndex < visibleArtifacts.length; artifactIndex += 1) {
|
|
2687
|
+
if (reportedArtifacts.current.has(artifactIndex)) continue;
|
|
2688
|
+
const artifact = visibleArtifacts[artifactIndex];
|
|
2689
|
+
if (!artifact) continue;
|
|
2690
|
+
reportedArtifacts.current.add(artifactIndex);
|
|
2691
|
+
emitInteraction(onInteraction, {
|
|
2692
|
+
type: "artifact_shown",
|
|
2693
|
+
sessionId,
|
|
2694
|
+
assistantEntryId: followup.assistant_entry_id,
|
|
2695
|
+
artifactIndex,
|
|
2696
|
+
artifactKind: artifact.kind
|
|
2697
|
+
});
|
|
2698
|
+
}
|
|
2699
|
+
}, [
|
|
2700
|
+
followup.assistant_entry_id,
|
|
2701
|
+
followup.suggestions.length,
|
|
2702
|
+
onInteraction,
|
|
2703
|
+
sessionId,
|
|
2704
|
+
visibleArtifacts
|
|
2705
|
+
]);
|
|
2706
|
+
const reportArtifactOpened = useCallback5(
|
|
2707
|
+
(artifactIndex, artifactKind) => {
|
|
2708
|
+
if (openedArtifacts.current.has(artifactIndex)) return;
|
|
2709
|
+
openedArtifacts.current.add(artifactIndex);
|
|
2710
|
+
emitInteraction(onInteraction, {
|
|
2711
|
+
type: "artifact_opened",
|
|
2712
|
+
sessionId,
|
|
2713
|
+
assistantEntryId: followup.assistant_entry_id,
|
|
2714
|
+
artifactIndex,
|
|
2715
|
+
artifactKind
|
|
2716
|
+
});
|
|
2717
|
+
},
|
|
2718
|
+
[followup.assistant_entry_id, onInteraction, sessionId]
|
|
2719
|
+
);
|
|
2720
|
+
if (!followup.recaption && artifacts.length === 0 && followup.suggestions.length === 0 && !followup.feedback_eligible)
|
|
2721
|
+
return null;
|
|
2722
|
+
return /* @__PURE__ */ jsxs11("div", { className: "mt-3 flex w-fit max-w-full flex-col gap-3 rounded-xl border border-[hsl(var(--border))] bg-[hsl(var(--muted)/0.28)] p-3 sm:max-w-[680px]", children: [
|
|
2723
|
+
followup.recaption || artifacts.length > 0 ? /* @__PURE__ */ jsxs11("section", { "aria-label": "\u672C\u8F6E\u5C0F\u7ED3", className: "flex flex-col gap-2", children: [
|
|
2724
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-1.5 text-xs font-medium text-[hsl(var(--muted-foreground))]", children: [
|
|
2725
|
+
/* @__PURE__ */ jsx13(Sparkles, { size: 14 }),
|
|
2726
|
+
"\u672C\u8F6E\u5C0F\u7ED3"
|
|
2727
|
+
] }),
|
|
2728
|
+
followup.recaption ? /* @__PURE__ */ jsx13("p", { className: "text-[13px] leading-5", children: followup.recaption }) : null,
|
|
2729
|
+
artifacts.length > 0 ? /* @__PURE__ */ jsxs11(Fragment2, { children: [
|
|
2730
|
+
/* @__PURE__ */ jsx13("div", { className: "grid max-w-full grid-cols-3 gap-1.5", children: visibleArtifacts.map((artifact, artifactIndex) => /* @__PURE__ */ jsx13(
|
|
2731
|
+
ArtifactCard,
|
|
2732
|
+
{
|
|
2733
|
+
artifact,
|
|
2734
|
+
sessionId,
|
|
2735
|
+
assistantEntryId: followup.assistant_entry_id,
|
|
2736
|
+
artifactIndex,
|
|
2737
|
+
onInteraction,
|
|
2738
|
+
onArtifactOpened: reportArtifactOpened
|
|
2739
|
+
},
|
|
2740
|
+
`${artifact.kind}:${artifactIndex}`
|
|
2741
|
+
)) }),
|
|
2742
|
+
artifacts.length > 3 ? /* @__PURE__ */ jsxs11(
|
|
2743
|
+
"button",
|
|
2744
|
+
{
|
|
2745
|
+
type: "button",
|
|
2746
|
+
onClick: () => setExpanded((value) => !value),
|
|
2747
|
+
"aria-expanded": expanded,
|
|
2748
|
+
className: "flex w-fit items-center gap-0.5 text-[11px] text-[hsl(var(--muted-foreground))]",
|
|
2749
|
+
children: [
|
|
2750
|
+
expanded ? "\u6536\u8D77" : `\u5C55\u5F00 ${artifacts.length - 3} \u4E2A`,
|
|
2751
|
+
/* @__PURE__ */ jsx13(ChevronDown, { size: 13, className: expanded ? "rotate-180" : void 0 })
|
|
2752
|
+
]
|
|
2753
|
+
}
|
|
2754
|
+
) : null
|
|
2755
|
+
] }) : null
|
|
2756
|
+
] }) : null,
|
|
2757
|
+
followup.suggestions.length > 0 ? /* @__PURE__ */ jsxs11("section", { "aria-label": "\u4E0B\u4E00\u6B65\u5EFA\u8BAE", className: "flex flex-col gap-1.5", children: [
|
|
2758
|
+
/* @__PURE__ */ jsx13("div", { className: "text-xs font-medium text-[hsl(var(--muted-foreground))]", children: "\u4E0B\u4E00\u6B65\u53EF\u4EE5" }),
|
|
2759
|
+
followup.suggestions.map((suggestion, suggestionIndex) => /* @__PURE__ */ jsxs11(
|
|
2760
|
+
"button",
|
|
2761
|
+
{
|
|
2762
|
+
type: "button",
|
|
2763
|
+
disabled: isViewer,
|
|
2764
|
+
onClick: () => {
|
|
2765
|
+
if (!adopted.current.has(suggestionIndex)) {
|
|
2766
|
+
adopted.current.add(suggestionIndex);
|
|
2767
|
+
emitInteraction(onInteraction, {
|
|
2768
|
+
type: "suggestion_adopted",
|
|
2769
|
+
sessionId,
|
|
2770
|
+
assistantEntryId: followup.assistant_entry_id,
|
|
2771
|
+
suggestionIndex
|
|
2772
|
+
});
|
|
2773
|
+
}
|
|
2774
|
+
onSuggestion?.(suggestion);
|
|
2775
|
+
},
|
|
2776
|
+
className: "group flex items-center gap-2 rounded-xl bg-[hsl(var(--muted)/0.62)] px-3 py-2 text-left text-[13px] disabled:cursor-default disabled:opacity-60",
|
|
2777
|
+
children: [
|
|
2778
|
+
/* @__PURE__ */ jsx13("span", { children: suggestion }),
|
|
2779
|
+
/* @__PURE__ */ jsx13(ArrowRight, { size: 14, className: "ml-auto shrink-0" })
|
|
2780
|
+
]
|
|
2781
|
+
},
|
|
2782
|
+
suggestion
|
|
2783
|
+
))
|
|
2784
|
+
] }) : null,
|
|
2785
|
+
/* @__PURE__ */ jsx13(
|
|
2786
|
+
ResultFeedback,
|
|
2787
|
+
{
|
|
2788
|
+
followup,
|
|
2789
|
+
sessionId,
|
|
2790
|
+
isViewer,
|
|
2791
|
+
onInteraction,
|
|
2792
|
+
savedFeedback,
|
|
2793
|
+
onFeedbackSaved
|
|
2794
|
+
}
|
|
2795
|
+
)
|
|
2796
|
+
] });
|
|
2797
|
+
}
|
|
2798
|
+
|
|
2799
|
+
// src/components/UserMessageBubble.tsx
|
|
2800
|
+
import { getFileParts, getImageParts, getTextContent as getTextContent2 } from "@blade-hq/agent-client";
|
|
2801
|
+
import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
2802
|
+
function isUserMessage(message) {
|
|
2803
|
+
return message.role === "user";
|
|
2804
|
+
}
|
|
2805
|
+
function isErrorMessage(message) {
|
|
2806
|
+
return message.role === "error";
|
|
2807
|
+
}
|
|
2808
|
+
var isSending = (message) => message.status === "streaming";
|
|
2809
|
+
function UserMessageBubble({ message, className }) {
|
|
1976
2810
|
const text = getTextContent2(message.content).trim();
|
|
1977
2811
|
const fileParts = getFileParts(message.content);
|
|
1978
2812
|
const imageParts = getImageParts(message.content);
|
|
1979
|
-
return /* @__PURE__ */
|
|
1980
|
-
imageParts.length > 0 && /* @__PURE__ */
|
|
2813
|
+
return /* @__PURE__ */ jsx14("div", { className: cn("blade-chat-user-row flex justify-end", className), children: /* @__PURE__ */ jsxs12("div", { className: "blade-chat-user-col flex max-w-[72%] flex-col items-end gap-3", children: [
|
|
2814
|
+
imageParts.length > 0 && /* @__PURE__ */ jsx14("div", { className: "grid gap-2", children: imageParts.map((part) => /* @__PURE__ */ jsx14(
|
|
1981
2815
|
"img",
|
|
1982
2816
|
{
|
|
1983
2817
|
src: part.image_url.url,
|
|
@@ -1986,21 +2820,21 @@ function UserMessageBubble({ message, className }) {
|
|
|
1986
2820
|
},
|
|
1987
2821
|
part.image_url.url
|
|
1988
2822
|
)) }),
|
|
1989
|
-
fileParts.length > 0 && /* @__PURE__ */
|
|
2823
|
+
fileParts.length > 0 && /* @__PURE__ */ jsx14("div", { className: "flex flex-col items-end gap-1.5", children: fileParts.map((part) => /* @__PURE__ */ jsxs12(
|
|
1990
2824
|
"div",
|
|
1991
2825
|
{
|
|
1992
2826
|
className: "flex items-center gap-1.5 rounded-lg border border-[hsl(var(--user-msg-border))] bg-[hsl(var(--muted)/0.3)] px-2.5 py-1.5 text-xs text-[hsl(var(--muted-foreground))]",
|
|
1993
2827
|
children: [
|
|
1994
|
-
/* @__PURE__ */
|
|
1995
|
-
/* @__PURE__ */
|
|
2828
|
+
/* @__PURE__ */ jsx14(FileText, { size: 12, className: "shrink-0" }),
|
|
2829
|
+
/* @__PURE__ */ jsx14("span", { className: "max-w-56 truncate", title: part.name, children: part.name })
|
|
1996
2830
|
]
|
|
1997
2831
|
},
|
|
1998
2832
|
`${part.name}-${part.data.length}`
|
|
1999
2833
|
)) }),
|
|
2000
|
-
text && /* @__PURE__ */
|
|
2001
|
-
text && isSending(message) && /* @__PURE__ */
|
|
2002
|
-
/* @__PURE__ */
|
|
2003
|
-
/* @__PURE__ */
|
|
2834
|
+
text && /* @__PURE__ */ jsx14("div", { className: "blade-chat-user-bubble max-w-full rounded-[20px] rounded-br-[6px] border border-[hsl(var(--user-msg-border))] bg-[hsl(var(--user-msg-bg))] px-[18px] py-[13px] text-sm leading-[1.65] text-[hsl(var(--user-msg-fg))]", children: /* @__PURE__ */ jsx14(MarkdownContent, { className: "blade-chat-prose", children: text }) }),
|
|
2835
|
+
text && isSending(message) && /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-1 pr-1 text-[11px] font-medium text-[hsl(var(--muted-foreground))/0.85]", children: [
|
|
2836
|
+
/* @__PURE__ */ jsx14(LoaderCircle, { size: 11, className: "animate-spin", "aria-hidden": "true" }),
|
|
2837
|
+
/* @__PURE__ */ jsx14("span", { children: "\u53D1\u9001\u4E2D" })
|
|
2004
2838
|
] })
|
|
2005
2839
|
] }) });
|
|
2006
2840
|
}
|
|
@@ -2009,11 +2843,11 @@ function ErrorMessageBlock({
|
|
|
2009
2843
|
className
|
|
2010
2844
|
}) {
|
|
2011
2845
|
const text = getTextContent2(message.content);
|
|
2012
|
-
return /* @__PURE__ */
|
|
2846
|
+
return /* @__PURE__ */ jsx14("div", { className: cn("blade-chat-error-row flex justify-center", className), children: /* @__PURE__ */ jsx14("div", { className: "blade-chat-error-block max-w-[85%] border-l-[3px] border-[hsl(var(--border))] px-4 py-1 text-sm leading-7 text-[hsl(var(--muted-foreground))]", children: text }) });
|
|
2013
2847
|
}
|
|
2014
2848
|
|
|
2015
2849
|
// src/components/MessageList.tsx
|
|
2016
|
-
import { jsx as
|
|
2850
|
+
import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
2017
2851
|
function parseModeChange(message) {
|
|
2018
2852
|
if (message.kind !== "mode_change" || typeof message.content !== "string") {
|
|
2019
2853
|
return null;
|
|
@@ -2047,6 +2881,8 @@ function getMessageResetSignature(messages) {
|
|
|
2047
2881
|
}
|
|
2048
2882
|
function MessageList({
|
|
2049
2883
|
messages,
|
|
2884
|
+
postChatFollowup,
|
|
2885
|
+
onSuggestion,
|
|
2050
2886
|
isStreaming,
|
|
2051
2887
|
sessionStatus,
|
|
2052
2888
|
askAnswers,
|
|
@@ -2054,9 +2890,13 @@ function MessageList({
|
|
|
2054
2890
|
toolCallRenderer,
|
|
2055
2891
|
emptyState,
|
|
2056
2892
|
className,
|
|
2057
|
-
sessionId
|
|
2893
|
+
sessionId,
|
|
2894
|
+
isViewer = false,
|
|
2895
|
+
onFollowupInteraction,
|
|
2896
|
+
resultFeedbackByEntry = /* @__PURE__ */ new Map(),
|
|
2897
|
+
onResultFeedbackSaved
|
|
2058
2898
|
}) {
|
|
2059
|
-
const renderBlocks =
|
|
2899
|
+
const renderBlocks = useMemo7(() => {
|
|
2060
2900
|
const visible = messages.filter((message) => {
|
|
2061
2901
|
if ((message.loop_name ?? "root") !== "root") return false;
|
|
2062
2902
|
if (isHiddenInternalMessage(message)) return false;
|
|
@@ -2102,7 +2942,7 @@ function MessageList({
|
|
|
2102
2942
|
blocks.push({
|
|
2103
2943
|
type: "message",
|
|
2104
2944
|
message,
|
|
2105
|
-
key: message.entry_id ?? `${message.role}-${blocks.length}`
|
|
2945
|
+
key: message.render_id ?? message.entry_id ?? `${message.role}-${blocks.length}`
|
|
2106
2946
|
});
|
|
2107
2947
|
}
|
|
2108
2948
|
flushAssistant();
|
|
@@ -2129,63 +2969,86 @@ function MessageList({
|
|
|
2129
2969
|
}
|
|
2130
2970
|
return blocks;
|
|
2131
2971
|
}, [messages, isStreaming]);
|
|
2132
|
-
return /* @__PURE__ */
|
|
2133
|
-
/* @__PURE__ */
|
|
2134
|
-
renderBlocks.length === 0 ? emptyState ?? /* @__PURE__ */
|
|
2135
|
-
/* @__PURE__ */
|
|
2136
|
-
/* @__PURE__ */
|
|
2137
|
-
/* @__PURE__ */
|
|
2972
|
+
return /* @__PURE__ */ jsx15("div", { className: cn("blade-chat-messages relative min-h-0 flex-1", className), children: /* @__PURE__ */ jsxs13(StickToBottom, { className: "h-full overflow-y-hidden", initial: "instant", resize: "instant", children: [
|
|
2973
|
+
/* @__PURE__ */ jsx15(StickToBottom.Content, { className: "blade-chat-messages-scroll", children: /* @__PURE__ */ jsx15("div", { className: "blade-chat-messages-content mx-auto max-w-[748px]", children: /* @__PURE__ */ jsxs13("div", { className: "flex min-w-0 flex-col", children: [
|
|
2974
|
+
renderBlocks.length === 0 ? emptyState ?? /* @__PURE__ */ jsxs13("div", { className: "blade-chat-empty", children: [
|
|
2975
|
+
/* @__PURE__ */ jsx15(MessageSquare, { size: 40, strokeWidth: 1.5 }),
|
|
2976
|
+
/* @__PURE__ */ jsx15("span", { className: "text-base font-medium", children: "\u5F00\u59CB\u5BF9\u8BDD" }),
|
|
2977
|
+
/* @__PURE__ */ jsx15("span", { className: "text-sm opacity-60", children: "\u5728\u4E0B\u65B9\u8F93\u5165\u6D88\u606F\u5F00\u59CB\u804A\u5929" })
|
|
2138
2978
|
] }) : renderBlocks.map((block) => {
|
|
2139
2979
|
if (block.type === "message") {
|
|
2140
|
-
return /* @__PURE__ */
|
|
2980
|
+
return /* @__PURE__ */ jsx15("div", { "data-entry-id": block.message.entry_id, children: isUserMessage(block.message) ? /* @__PURE__ */ jsx15(UserMessageBubble, { message: block.message }) : isErrorMessage(block.message) ? /* @__PURE__ */ jsx15(ErrorMessageBlock, { message: block.message }) : null }, block.key);
|
|
2141
2981
|
}
|
|
2142
2982
|
if (block.type === "assistant_turn") {
|
|
2143
|
-
|
|
2983
|
+
const blockFeedback = block.messages.map(
|
|
2984
|
+
(message) => message.entry_id ? resultFeedbackByEntry.get(message.entry_id) : void 0
|
|
2985
|
+
).find((feedback) => feedback != null);
|
|
2986
|
+
const hasActiveFollowup = Boolean(
|
|
2987
|
+
postChatFollowup && block.messages.some(
|
|
2988
|
+
(message) => message.entry_id === postChatFollowup.assistant_entry_id
|
|
2989
|
+
)
|
|
2990
|
+
);
|
|
2991
|
+
return /* @__PURE__ */ jsx15("div", { "data-entry-id": block.messages[0]?.entry_id, children: /* @__PURE__ */ jsxs13(
|
|
2144
2992
|
RenderErrorBoundary,
|
|
2145
2993
|
{
|
|
2146
2994
|
label: "\u52A9\u624B\u6D88\u606F",
|
|
2147
2995
|
details: block.key,
|
|
2148
2996
|
resetKey: getMessageResetSignature(block.messages),
|
|
2149
|
-
children:
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2997
|
+
children: [
|
|
2998
|
+
/* @__PURE__ */ jsx15(
|
|
2999
|
+
AssistantTurnBlock,
|
|
3000
|
+
{
|
|
3001
|
+
messages: block.messages,
|
|
3002
|
+
isStreaming: block.isStreaming,
|
|
3003
|
+
askAnswers,
|
|
3004
|
+
onAnswer,
|
|
3005
|
+
sessionStatus,
|
|
3006
|
+
toolCallRenderer,
|
|
3007
|
+
sessionId
|
|
3008
|
+
}
|
|
3009
|
+
),
|
|
3010
|
+
blockFeedback && !hasActiveFollowup ? /* @__PURE__ */ jsx15(HistoricalResultFeedback, { feedback: blockFeedback }) : null,
|
|
3011
|
+
hasActiveFollowup && postChatFollowup ? /* @__PURE__ */ jsx15(
|
|
3012
|
+
PostChatFollowupBlock,
|
|
3013
|
+
{
|
|
3014
|
+
followup: postChatFollowup,
|
|
3015
|
+
sessionId,
|
|
3016
|
+
onSuggestion,
|
|
3017
|
+
isViewer,
|
|
3018
|
+
onInteraction: onFollowupInteraction,
|
|
3019
|
+
savedFeedback: blockFeedback,
|
|
3020
|
+
onFeedbackSaved: onResultFeedbackSaved
|
|
3021
|
+
}
|
|
3022
|
+
) : null
|
|
3023
|
+
]
|
|
2161
3024
|
}
|
|
2162
3025
|
) }, block.key);
|
|
2163
3026
|
}
|
|
2164
3027
|
if (block.type === "compaction") {
|
|
2165
|
-
return /* @__PURE__ */
|
|
3028
|
+
return /* @__PURE__ */ jsxs13(
|
|
2166
3029
|
"div",
|
|
2167
3030
|
{
|
|
2168
3031
|
className: "flex items-center gap-2 text-xs text-[hsl(var(--muted-foreground))]",
|
|
2169
3032
|
children: [
|
|
2170
|
-
/* @__PURE__ */
|
|
2171
|
-
/* @__PURE__ */
|
|
3033
|
+
/* @__PURE__ */ jsx15(Layers, { size: 12 }),
|
|
3034
|
+
/* @__PURE__ */ jsx15("span", { children: "\u4E0A\u4E0B\u6587\u5DF2\u538B\u7F29" })
|
|
2172
3035
|
]
|
|
2173
3036
|
},
|
|
2174
3037
|
block.key
|
|
2175
3038
|
);
|
|
2176
3039
|
}
|
|
2177
|
-
return /* @__PURE__ */
|
|
3040
|
+
return /* @__PURE__ */ jsx15(PlanningDivider, { kind: block.kind }, block.key);
|
|
2178
3041
|
}),
|
|
2179
|
-
sessionStatus === "interrupted" && !isStreaming ? /* @__PURE__ */
|
|
3042
|
+
sessionStatus === "interrupted" && !isStreaming ? /* @__PURE__ */ jsx15("div", { className: "flex", children: /* @__PURE__ */ jsx15("div", { className: "rounded-full border border-amber-500/30 bg-amber-500/10 px-2.5 py-1 text-[10px] font-medium uppercase tracking-[0.12em] text-amber-300", children: "\u5DF2\u4E2D\u65AD" }) }) : null
|
|
2180
3043
|
] }) }) }),
|
|
2181
|
-
/* @__PURE__ */
|
|
2182
|
-
/* @__PURE__ */
|
|
3044
|
+
/* @__PURE__ */ jsx15(AutoScrollOnUserSend, { userMessageCount: messages.filter((m) => isUserMessage(m)).length }),
|
|
3045
|
+
/* @__PURE__ */ jsx15(ScrollToBottomButton, {})
|
|
2183
3046
|
] }) });
|
|
2184
3047
|
}
|
|
2185
3048
|
function AutoScrollOnUserSend({ userMessageCount }) {
|
|
2186
3049
|
const { scrollToBottom } = useStickToBottomContext();
|
|
2187
|
-
const previousCountRef =
|
|
2188
|
-
|
|
3050
|
+
const previousCountRef = useRef8(userMessageCount);
|
|
3051
|
+
useEffect8(() => {
|
|
2189
3052
|
if (userMessageCount > previousCountRef.current) {
|
|
2190
3053
|
scrollToBottom("instant");
|
|
2191
3054
|
}
|
|
@@ -2195,9 +3058,9 @@ function AutoScrollOnUserSend({ userMessageCount }) {
|
|
|
2195
3058
|
}
|
|
2196
3059
|
function ScrollToBottomButton() {
|
|
2197
3060
|
const { isAtBottom, scrollToBottom } = useStickToBottomContext();
|
|
2198
|
-
const [visible, setVisible] =
|
|
2199
|
-
const hideTimerRef =
|
|
2200
|
-
|
|
3061
|
+
const [visible, setVisible] = useState12(false);
|
|
3062
|
+
const hideTimerRef = useRef8(null);
|
|
3063
|
+
useEffect8(() => {
|
|
2201
3064
|
if (isAtBottom) {
|
|
2202
3065
|
if (!hideTimerRef.current) {
|
|
2203
3066
|
hideTimerRef.current = setTimeout(() => {
|
|
@@ -2219,7 +3082,7 @@ function ScrollToBottomButton() {
|
|
|
2219
3082
|
}
|
|
2220
3083
|
};
|
|
2221
3084
|
}, [isAtBottom]);
|
|
2222
|
-
const handleClick =
|
|
3085
|
+
const handleClick = useCallback6(() => {
|
|
2223
3086
|
if (hideTimerRef.current) {
|
|
2224
3087
|
clearTimeout(hideTimerRef.current);
|
|
2225
3088
|
hideTimerRef.current = null;
|
|
@@ -2228,7 +3091,7 @@ function ScrollToBottomButton() {
|
|
|
2228
3091
|
scrollToBottom();
|
|
2229
3092
|
}, [scrollToBottom]);
|
|
2230
3093
|
if (!visible) return null;
|
|
2231
|
-
return /* @__PURE__ */
|
|
3094
|
+
return /* @__PURE__ */ jsxs13(
|
|
2232
3095
|
"button",
|
|
2233
3096
|
{
|
|
2234
3097
|
type: "button",
|
|
@@ -2236,34 +3099,120 @@ function ScrollToBottomButton() {
|
|
|
2236
3099
|
"aria-label": "\u6EDA\u52A8\u5230\u5E95\u90E8",
|
|
2237
3100
|
className: "blade-chat-scroll-bottom absolute bottom-4 right-4 flex items-center gap-1 rounded-full border border-[hsl(var(--border))] bg-[hsl(var(--card))] px-3 py-1.5 text-xs text-[hsl(var(--muted-foreground))] shadow-lg transition-colors hover:bg-[hsl(var(--accent))] hover:text-[hsl(var(--foreground))]",
|
|
2238
3101
|
children: [
|
|
2239
|
-
/* @__PURE__ */
|
|
2240
|
-
/* @__PURE__ */
|
|
3102
|
+
/* @__PURE__ */ jsx15(ChevronDown, { size: 14 }),
|
|
3103
|
+
/* @__PURE__ */ jsx15("span", { className: "blade-chat-scroll-bottom-label", children: "\u6EDA\u52A8\u5230\u5E95\u90E8" })
|
|
2241
3104
|
]
|
|
2242
3105
|
}
|
|
2243
3106
|
);
|
|
2244
3107
|
}
|
|
2245
3108
|
function PlanningDivider({ kind }) {
|
|
2246
|
-
return /* @__PURE__ */
|
|
2247
|
-
/* @__PURE__ */
|
|
2248
|
-
/* @__PURE__ */
|
|
2249
|
-
/* @__PURE__ */
|
|
2250
|
-
/* @__PURE__ */
|
|
3109
|
+
return /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-3 py-1", children: [
|
|
3110
|
+
/* @__PURE__ */ jsx15("div", { className: "h-px flex-1 bg-gradient-to-r from-transparent via-amber-400/40 to-transparent" }),
|
|
3111
|
+
/* @__PURE__ */ jsxs13("div", { className: "inline-flex items-center gap-1.5 rounded-full border border-amber-500/30 bg-amber-500/10 px-3 py-1 text-[11px] text-amber-300", children: [
|
|
3112
|
+
/* @__PURE__ */ jsx15(Lightbulb, { size: 12 }),
|
|
3113
|
+
/* @__PURE__ */ jsx15("span", { children: kind === "enter" ? "\u8FDB\u5165\u89C4\u5212\u6A21\u5F0F" : "\u89C4\u5212\u5B8C\u6210" })
|
|
2251
3114
|
] }),
|
|
2252
|
-
/* @__PURE__ */
|
|
3115
|
+
/* @__PURE__ */ jsx15("div", { className: "h-px flex-1 bg-gradient-to-r from-transparent via-amber-400/40 to-transparent" })
|
|
2253
3116
|
] });
|
|
2254
3117
|
}
|
|
2255
3118
|
|
|
2256
|
-
// src/components/
|
|
2257
|
-
import { jsx as
|
|
3119
|
+
// src/components/ChatSurface.tsx
|
|
3120
|
+
import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
2258
3121
|
function themeAttr(theme) {
|
|
2259
3122
|
return theme === "dark" ? "dark" : void 0;
|
|
2260
3123
|
}
|
|
3124
|
+
function ChatSurface({
|
|
3125
|
+
theme,
|
|
3126
|
+
classNames,
|
|
3127
|
+
renderers,
|
|
3128
|
+
slots,
|
|
3129
|
+
placeholder,
|
|
3130
|
+
connection,
|
|
3131
|
+
errorMessage,
|
|
3132
|
+
messages,
|
|
3133
|
+
postChatFollowup,
|
|
3134
|
+
isStreaming,
|
|
3135
|
+
isStopping,
|
|
3136
|
+
inputText,
|
|
3137
|
+
onInputChange,
|
|
3138
|
+
onSuggestion,
|
|
3139
|
+
onSend,
|
|
3140
|
+
onStop,
|
|
3141
|
+
sessionStatus,
|
|
3142
|
+
askAnswers,
|
|
3143
|
+
onAnswer,
|
|
3144
|
+
sessionId,
|
|
3145
|
+
isViewer,
|
|
3146
|
+
resultFeedbackByEntry,
|
|
3147
|
+
onResultFeedbackSaved,
|
|
3148
|
+
onFollowupInteraction,
|
|
3149
|
+
beforeInput,
|
|
3150
|
+
banner
|
|
3151
|
+
}) {
|
|
3152
|
+
return /* @__PURE__ */ jsxs14(
|
|
3153
|
+
"div",
|
|
3154
|
+
{
|
|
3155
|
+
"data-theme": themeAttr(theme),
|
|
3156
|
+
className: cn(
|
|
3157
|
+
"blade-chat flex min-h-0 flex-1 flex-col overflow-hidden",
|
|
3158
|
+
classNames?.root
|
|
3159
|
+
),
|
|
3160
|
+
children: [
|
|
3161
|
+
/* @__PURE__ */ jsx16(ConnectionBanner, { connection, className: classNames?.banner }),
|
|
3162
|
+
banner,
|
|
3163
|
+
errorMessage && /* @__PURE__ */ jsxs14("div", { className: "blade-chat-error-bar flex items-start gap-2 border-b px-4 py-3 text-sm", children: [
|
|
3164
|
+
/* @__PURE__ */ jsx16(CircleAlert, { size: 16, className: "mt-0.5 shrink-0" }),
|
|
3165
|
+
/* @__PURE__ */ jsx16("span", { children: errorMessage })
|
|
3166
|
+
] }),
|
|
3167
|
+
slots?.header,
|
|
3168
|
+
/* @__PURE__ */ jsx16(
|
|
3169
|
+
MessageList,
|
|
3170
|
+
{
|
|
3171
|
+
messages,
|
|
3172
|
+
postChatFollowup,
|
|
3173
|
+
onSuggestion,
|
|
3174
|
+
isStreaming,
|
|
3175
|
+
sessionStatus,
|
|
3176
|
+
askAnswers,
|
|
3177
|
+
onAnswer,
|
|
3178
|
+
toolCallRenderer: renderers?.toolCall,
|
|
3179
|
+
emptyState: slots?.emptyState,
|
|
3180
|
+
className: classNames?.messageList,
|
|
3181
|
+
sessionId,
|
|
3182
|
+
isViewer,
|
|
3183
|
+
resultFeedbackByEntry,
|
|
3184
|
+
onResultFeedbackSaved,
|
|
3185
|
+
onFollowupInteraction
|
|
3186
|
+
}
|
|
3187
|
+
),
|
|
3188
|
+
beforeInput,
|
|
3189
|
+
/* @__PURE__ */ jsx16(
|
|
3190
|
+
ChatInput,
|
|
3191
|
+
{
|
|
3192
|
+
value: inputText,
|
|
3193
|
+
onValueChange: onInputChange,
|
|
3194
|
+
onSend,
|
|
3195
|
+
onStop,
|
|
3196
|
+
isStreaming,
|
|
3197
|
+
isStopping,
|
|
3198
|
+
placeholder,
|
|
3199
|
+
className: classNames?.chatInput
|
|
3200
|
+
}
|
|
3201
|
+
),
|
|
3202
|
+
slots?.footer
|
|
3203
|
+
]
|
|
3204
|
+
}
|
|
3205
|
+
);
|
|
3206
|
+
}
|
|
3207
|
+
|
|
3208
|
+
// src/components/AgentChat.tsx
|
|
3209
|
+
import { Fragment as Fragment3, jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
2261
3210
|
function isUnauthorizedError(error) {
|
|
2262
3211
|
return error instanceof BladeApiError && error.status === 401;
|
|
2263
3212
|
}
|
|
2264
3213
|
function LoginCard({ client, onLoggedIn }) {
|
|
2265
|
-
const [loggingIn, setLoggingIn] =
|
|
2266
|
-
const [loginError, setLoginError] =
|
|
3214
|
+
const [loggingIn, setLoggingIn] = useState13(false);
|
|
3215
|
+
const [loginError, setLoginError] = useState13(null);
|
|
2267
3216
|
const handleLogin = async () => {
|
|
2268
3217
|
setLoggingIn(true);
|
|
2269
3218
|
setLoginError(null);
|
|
@@ -2276,11 +3225,11 @@ function LoginCard({ client, onLoggedIn }) {
|
|
|
2276
3225
|
setLoggingIn(false);
|
|
2277
3226
|
}
|
|
2278
3227
|
};
|
|
2279
|
-
return /* @__PURE__ */
|
|
2280
|
-
/* @__PURE__ */
|
|
2281
|
-
/* @__PURE__ */
|
|
2282
|
-
/* @__PURE__ */
|
|
2283
|
-
/* @__PURE__ */
|
|
3228
|
+
return /* @__PURE__ */ jsx17("div", { className: "blade-chat-login flex flex-1 items-center justify-center p-6", children: /* @__PURE__ */ jsxs15("div", { className: "flex w-full max-w-sm flex-col items-center gap-4 rounded-2xl border border-[hsl(var(--border))] bg-[hsl(var(--card))] px-6 py-8 text-center", children: [
|
|
3229
|
+
/* @__PURE__ */ jsx17(LockKeyhole, { size: 28, className: "text-[hsl(var(--muted-foreground))]" }),
|
|
3230
|
+
/* @__PURE__ */ jsx17("div", { className: "text-base font-medium text-[hsl(var(--foreground))]", children: "\u9700\u8981\u767B\u5F55\u540E\u4F7F\u7528" }),
|
|
3231
|
+
/* @__PURE__ */ jsx17("div", { className: "text-sm text-[hsl(var(--muted-foreground))]", children: "\u767B\u5F55\u540E\u5373\u53EF\u4E0E\u667A\u80FD\u4F53\u5BF9\u8BDD\uFF0C\u4F60\u7684\u4F1A\u8BDD\u5185\u5BB9\u4EC5\u81EA\u5DF1\u53EF\u89C1\u3002" }),
|
|
3232
|
+
/* @__PURE__ */ jsx17(
|
|
2284
3233
|
"button",
|
|
2285
3234
|
{
|
|
2286
3235
|
type: "button",
|
|
@@ -2290,20 +3239,20 @@ function LoginCard({ client, onLoggedIn }) {
|
|
|
2290
3239
|
children: loggingIn ? "\u767B\u5F55\u4E2D\u2026" : "\u767B\u5F55"
|
|
2291
3240
|
}
|
|
2292
3241
|
),
|
|
2293
|
-
loginError && /* @__PURE__ */
|
|
3242
|
+
loginError && /* @__PURE__ */ jsx17("div", { className: "text-xs text-[hsl(var(--destructive))]", children: loginError })
|
|
2294
3243
|
] }) });
|
|
2295
3244
|
}
|
|
2296
|
-
function
|
|
3245
|
+
function AgentChat(props) {
|
|
2297
3246
|
const client = useBladeClient();
|
|
2298
|
-
const [attempt, setAttempt] =
|
|
2299
|
-
const [needLogin, setNeedLogin] =
|
|
3247
|
+
const [attempt, setAttempt] = useState13(0);
|
|
3248
|
+
const [needLogin, setNeedLogin] = useState13(() => !client.hasToken());
|
|
2300
3249
|
if (needLogin) {
|
|
2301
|
-
return /* @__PURE__ */
|
|
3250
|
+
return /* @__PURE__ */ jsx17(
|
|
2302
3251
|
"div",
|
|
2303
3252
|
{
|
|
2304
3253
|
"data-theme": themeAttr(props.theme),
|
|
2305
3254
|
className: cn("blade-chat flex min-h-0 flex-1 flex-col", props.classNames?.root),
|
|
2306
|
-
children: /* @__PURE__ */
|
|
3255
|
+
children: /* @__PURE__ */ jsx17(
|
|
2307
3256
|
LoginCard,
|
|
2308
3257
|
{
|
|
2309
3258
|
client,
|
|
@@ -2316,7 +3265,7 @@ function ChatView(props) {
|
|
|
2316
3265
|
}
|
|
2317
3266
|
);
|
|
2318
3267
|
}
|
|
2319
|
-
return /* @__PURE__ */
|
|
3268
|
+
return /* @__PURE__ */ jsx17(ChatSessionView, { ...props, onUnauthorized: () => setNeedLogin(true) }, attempt);
|
|
2320
3269
|
}
|
|
2321
3270
|
function ChatSessionView({
|
|
2322
3271
|
sessionId,
|
|
@@ -2329,20 +3278,61 @@ function ChatSessionView({
|
|
|
2329
3278
|
slots,
|
|
2330
3279
|
placeholder,
|
|
2331
3280
|
theme,
|
|
3281
|
+
onFollowupInteraction,
|
|
2332
3282
|
onUnauthorized
|
|
2333
3283
|
}) {
|
|
3284
|
+
const client = useBladeClient();
|
|
2334
3285
|
const { session, state, error } = useAgentSession(sessionId, {
|
|
2335
3286
|
createOptions,
|
|
2336
3287
|
onSessionCreated
|
|
2337
3288
|
});
|
|
2338
|
-
const
|
|
2339
|
-
const [
|
|
2340
|
-
|
|
3289
|
+
const replay = useReplay(session);
|
|
3290
|
+
const [stopRequested, setStopRequested] = useState13(false);
|
|
3291
|
+
const [inputText, setInputText] = useState13("");
|
|
3292
|
+
const [resultFeedback, setResultFeedback] = useState13([]);
|
|
3293
|
+
const resolvedSessionId = session?.sessionId;
|
|
3294
|
+
const isViewer = state?.viewerRole === "viewer";
|
|
3295
|
+
useEffect9(() => {
|
|
3296
|
+
setResultFeedback([]);
|
|
3297
|
+
if (!resolvedSessionId || isViewer) return;
|
|
3298
|
+
let cancelled = false;
|
|
3299
|
+
void client.sessions.listResultFeedback(resolvedSessionId).then(
|
|
3300
|
+
(items) => {
|
|
3301
|
+
if (cancelled) return;
|
|
3302
|
+
setResultFeedback((current) => {
|
|
3303
|
+
const merged = new Map(items.map((item) => [item.assistant_entry_id, item]));
|
|
3304
|
+
for (const item of current) {
|
|
3305
|
+
const fetched = merged.get(item.assistant_entry_id);
|
|
3306
|
+
if (!fetched || fetched.updated_at < item.updated_at) {
|
|
3307
|
+
merged.set(item.assistant_entry_id, item);
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
return [...merged.values()];
|
|
3311
|
+
});
|
|
3312
|
+
},
|
|
3313
|
+
() => {
|
|
3314
|
+
}
|
|
3315
|
+
);
|
|
3316
|
+
return () => {
|
|
3317
|
+
cancelled = true;
|
|
3318
|
+
};
|
|
3319
|
+
}, [client, isViewer, resolvedSessionId]);
|
|
3320
|
+
const resultFeedbackByEntry = useMemo8(
|
|
3321
|
+
() => new Map(resultFeedback.map((item) => [item.assistant_entry_id, item])),
|
|
3322
|
+
[resultFeedback]
|
|
3323
|
+
);
|
|
3324
|
+
const handleResultFeedbackSaved = useCallback7((saved) => {
|
|
3325
|
+
setResultFeedback((current) => [
|
|
3326
|
+
...current.filter((item) => item.assistant_entry_id !== saved.assistant_entry_id),
|
|
3327
|
+
saved
|
|
3328
|
+
]);
|
|
3329
|
+
}, []);
|
|
3330
|
+
useEffect9(() => {
|
|
2341
3331
|
if (session) {
|
|
2342
3332
|
onSessionReady?.(session);
|
|
2343
3333
|
}
|
|
2344
3334
|
}, [session, onSessionReady]);
|
|
2345
|
-
|
|
3335
|
+
useEffect9(() => {
|
|
2346
3336
|
if (!session) return;
|
|
2347
3337
|
const offAttach = session.on("attachRequested", ({ label, content }) => {
|
|
2348
3338
|
setInputText((prev) => `${prev ? `${prev}
|
|
@@ -2358,12 +3348,12 @@ ${content}`);
|
|
|
2358
3348
|
offInsert();
|
|
2359
3349
|
};
|
|
2360
3350
|
}, [session]);
|
|
2361
|
-
|
|
3351
|
+
useEffect9(() => {
|
|
2362
3352
|
if (isUnauthorizedError(error)) {
|
|
2363
3353
|
onUnauthorized();
|
|
2364
3354
|
}
|
|
2365
3355
|
}, [error, onUnauthorized]);
|
|
2366
|
-
|
|
3356
|
+
useEffect9(() => {
|
|
2367
3357
|
if (!session || !commands) return;
|
|
2368
3358
|
const unsubscribes = Object.entries(commands).map(
|
|
2369
3359
|
([action, handler]) => session.onCommand(action, (payload) => handler(payload))
|
|
@@ -2375,7 +3365,7 @@ ${content}`);
|
|
|
2375
3365
|
const isStreaming = state?.isStreaming ?? false;
|
|
2376
3366
|
const isStopping = stopRequested && isStreaming;
|
|
2377
3367
|
const connectError = error && !isUnauthorizedError(error) ? error.message || "\u8FDE\u63A5\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5" : null;
|
|
2378
|
-
const errorMessage = connectError ?? state?.errorMessage ?? null;
|
|
3368
|
+
const errorMessage = connectError ?? state?.errorMessage ?? replay.error?.message ?? null;
|
|
2379
3369
|
const handleSend = (text) => {
|
|
2380
3370
|
setStopRequested(false);
|
|
2381
3371
|
return session?.send(text, { mode: state?.mode ?? void 0 }) ?? Promise.resolve(false);
|
|
@@ -2384,73 +3374,287 @@ ${content}`);
|
|
|
2384
3374
|
setStopRequested(true);
|
|
2385
3375
|
void session?.stop();
|
|
2386
3376
|
};
|
|
2387
|
-
return /* @__PURE__ */
|
|
2388
|
-
|
|
3377
|
+
return /* @__PURE__ */ jsx17(
|
|
3378
|
+
ChatSurface,
|
|
2389
3379
|
{
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
3380
|
+
theme,
|
|
3381
|
+
onFollowupInteraction,
|
|
3382
|
+
classNames,
|
|
3383
|
+
renderers,
|
|
3384
|
+
slots,
|
|
3385
|
+
placeholder,
|
|
3386
|
+
connection: state?.connection ?? "connecting",
|
|
3387
|
+
banner: /* @__PURE__ */ jsxs15(Fragment3, { children: [
|
|
3388
|
+
/* @__PURE__ */ jsx17(
|
|
3389
|
+
ReplayBar,
|
|
2398
3390
|
{
|
|
2399
|
-
|
|
2400
|
-
|
|
3391
|
+
isReplay: replay.isReplay,
|
|
3392
|
+
speed: replay.speed,
|
|
3393
|
+
canControl: replay.canControl,
|
|
3394
|
+
onSpeedChange: (next) => void replay.setSpeed(next),
|
|
3395
|
+
onExit: () => void replay.exitToAutonomous()
|
|
2401
3396
|
}
|
|
2402
3397
|
),
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
3398
|
+
/* @__PURE__ */ jsx17(ReplayMismatchPrompt, { mismatch: replay.mismatch })
|
|
3399
|
+
] }),
|
|
3400
|
+
errorMessage,
|
|
3401
|
+
messages: state?.messages ?? [],
|
|
3402
|
+
postChatFollowup: latestPostChatFollowup(state?.turns ?? []),
|
|
3403
|
+
resultFeedbackByEntry,
|
|
3404
|
+
onResultFeedbackSaved: handleResultFeedbackSaved,
|
|
3405
|
+
isStreaming,
|
|
3406
|
+
isStopping,
|
|
3407
|
+
inputText,
|
|
3408
|
+
onInputChange: setInputText,
|
|
3409
|
+
onSuggestion: setInputText,
|
|
3410
|
+
onSend: handleSend,
|
|
3411
|
+
onStop: handleStop,
|
|
3412
|
+
sessionStatus: state?.status ?? void 0,
|
|
3413
|
+
askAnswers: state?.askAnswers,
|
|
3414
|
+
onAnswer: (answer, toolCallId, answerData) => {
|
|
3415
|
+
void session?.send(answer, {
|
|
3416
|
+
mode: state?.mode ?? void 0,
|
|
3417
|
+
askUserAnswer: { tool_call_id: toolCallId, ...answerData }
|
|
3418
|
+
});
|
|
3419
|
+
},
|
|
3420
|
+
sessionId: resolvedSessionId,
|
|
3421
|
+
isViewer
|
|
3422
|
+
}
|
|
3423
|
+
);
|
|
3424
|
+
}
|
|
3425
|
+
|
|
3426
|
+
// src/components/LlmChat.tsx
|
|
3427
|
+
import { useEffect as useEffect10, useMemo as useMemo9, useState as useState15 } from "react";
|
|
3428
|
+
|
|
3429
|
+
// src/components/LlmAdvancedSettings.tsx
|
|
3430
|
+
import { useState as useState14 } from "react";
|
|
3431
|
+
import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
3432
|
+
var FIELDS = [
|
|
3433
|
+
{ id: "baseURL", label: "\u6A21\u578B\u670D\u52A1\u5730\u5740", placeholder: "http://\u5185\u7F51\u5730\u5740/v1" },
|
|
3434
|
+
{ id: "model", label: "\u6A21\u578B", placeholder: "\u6A21\u578B\u540D\u79F0" },
|
|
3435
|
+
{ id: "apiKey", label: "\u5BC6\u94A5", placeholder: "\u7559\u7A7A\u8868\u793A\u4E0D\u6539", secret: true }
|
|
3436
|
+
];
|
|
3437
|
+
function normalizeAdvanced(value) {
|
|
3438
|
+
if (!value) return null;
|
|
3439
|
+
const config = value === true ? {} : value;
|
|
3440
|
+
return {
|
|
3441
|
+
baseURL: config.baseURL ?? true,
|
|
3442
|
+
model: config.model ?? true,
|
|
3443
|
+
apiKey: config.apiKey ?? false,
|
|
3444
|
+
storage: config.storage ?? "local"
|
|
3445
|
+
};
|
|
3446
|
+
}
|
|
3447
|
+
function storageKeyFor(settings, baseURL) {
|
|
3448
|
+
const explicit = typeof settings === "object" ? settings.storageKey : void 0;
|
|
3449
|
+
return `blade-llm-override:${explicit ?? baseURL}`;
|
|
3450
|
+
}
|
|
3451
|
+
function readOverride(settings, baseURL) {
|
|
3452
|
+
const normalized = normalizeAdvanced(settings);
|
|
3453
|
+
if (!normalized || normalized.storage !== "local" || typeof localStorage === "undefined") return {};
|
|
3454
|
+
try {
|
|
3455
|
+
const raw = localStorage.getItem(storageKeyFor(settings, baseURL));
|
|
3456
|
+
if (!raw) return {};
|
|
3457
|
+
const stored = JSON.parse(raw);
|
|
3458
|
+
return Object.fromEntries(
|
|
3459
|
+
Object.entries(stored).filter(([key]) => normalized[key])
|
|
3460
|
+
);
|
|
3461
|
+
} catch {
|
|
3462
|
+
return {};
|
|
3463
|
+
}
|
|
3464
|
+
}
|
|
3465
|
+
function writeOverride(settings, baseURL, override) {
|
|
3466
|
+
const normalized = normalizeAdvanced(settings);
|
|
3467
|
+
if (!normalized || normalized.storage !== "local" || typeof localStorage === "undefined") return;
|
|
3468
|
+
try {
|
|
3469
|
+
const key = storageKeyFor(settings, baseURL);
|
|
3470
|
+
if (Object.keys(override).length === 0) localStorage.removeItem(key);
|
|
3471
|
+
else localStorage.setItem(key, JSON.stringify(override));
|
|
3472
|
+
} catch {
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3475
|
+
function LlmAdvancedSettingsBar({ settings, defaults, override, onChange }) {
|
|
3476
|
+
const normalized = normalizeAdvanced(settings);
|
|
3477
|
+
const [open, setOpen] = useState14(false);
|
|
3478
|
+
const [draft, setDraft] = useState14(override);
|
|
3479
|
+
if (!normalized) return null;
|
|
3480
|
+
const fields = FIELDS.filter((field) => normalized[field.id]);
|
|
3481
|
+
const dirty = Object.keys(override).length > 0;
|
|
3482
|
+
return /* @__PURE__ */ jsxs16("div", { className: "blade-chat-advanced border-t border-[hsl(var(--border))] px-4 py-2 text-xs", children: [
|
|
3483
|
+
/* @__PURE__ */ jsxs16(
|
|
3484
|
+
"button",
|
|
3485
|
+
{
|
|
3486
|
+
type: "button",
|
|
3487
|
+
onClick: () => {
|
|
3488
|
+
setDraft(override);
|
|
3489
|
+
setOpen((value) => !value);
|
|
3490
|
+
},
|
|
3491
|
+
className: "flex items-center gap-1.5 text-[hsl(var(--muted-foreground))] transition-colors hover:text-[hsl(var(--foreground))]",
|
|
3492
|
+
children: [
|
|
3493
|
+
/* @__PURE__ */ jsx18(Settings2, { size: 13 }),
|
|
3494
|
+
"\u9AD8\u7EA7\u8BBE\u7F6E",
|
|
3495
|
+
dirty && /* @__PURE__ */ jsx18("span", { className: "text-[hsl(var(--primary))]", children: "\uFF08\u5DF2\u81EA\u5B9A\u4E49\uFF09" })
|
|
3496
|
+
]
|
|
3497
|
+
}
|
|
3498
|
+
),
|
|
3499
|
+
open && /* @__PURE__ */ jsxs16("div", { className: "mt-2 flex flex-col gap-2", children: [
|
|
3500
|
+
fields.map((field) => /* @__PURE__ */ jsxs16("label", { className: "flex flex-col gap-1", children: [
|
|
3501
|
+
/* @__PURE__ */ jsx18("span", { className: "text-[hsl(var(--muted-foreground))]", children: field.label }),
|
|
3502
|
+
/* @__PURE__ */ jsx18(
|
|
3503
|
+
"input",
|
|
2410
3504
|
{
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
3505
|
+
type: field.secret ? "password" : "text",
|
|
3506
|
+
value: draft[field.id] ?? "",
|
|
3507
|
+
placeholder: field.id === "apiKey" ? field.placeholder : defaults[field.id] || field.placeholder,
|
|
3508
|
+
onChange: (event) => setDraft({ ...draft, [field.id]: event.target.value }),
|
|
3509
|
+
className: "rounded-md border border-[hsl(var(--border))] bg-[hsl(var(--background))] px-2 py-1 text-xs text-[hsl(var(--foreground))] outline-none"
|
|
3510
|
+
}
|
|
3511
|
+
)
|
|
3512
|
+
] }, field.id)),
|
|
3513
|
+
normalized.apiKey && /* @__PURE__ */ jsx18("p", { className: "text-[hsl(var(--muted-foreground))]", children: "\u5BC6\u94A5\u4F1A\u5B58\u5728\u8FD9\u53F0\u6D4F\u89C8\u5668\u91CC\u3002\u53EA\u5728\u4F60\u4FE1\u5F97\u8FC7\u8FD9\u53F0\u673A\u5668\u65F6\u586B\u3002" }),
|
|
3514
|
+
/* @__PURE__ */ jsxs16("div", { className: "flex gap-2", children: [
|
|
3515
|
+
/* @__PURE__ */ jsx18(
|
|
3516
|
+
"button",
|
|
3517
|
+
{
|
|
3518
|
+
type: "button",
|
|
3519
|
+
onClick: () => {
|
|
3520
|
+
const next = Object.fromEntries(
|
|
3521
|
+
Object.entries(draft).filter(([, value]) => String(value ?? "").trim() !== "")
|
|
3522
|
+
);
|
|
3523
|
+
onChange(next);
|
|
3524
|
+
setOpen(false);
|
|
2420
3525
|
},
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
className: classNames?.messageList,
|
|
2424
|
-
sessionId: session?.sessionId
|
|
3526
|
+
className: "rounded-md bg-[hsl(var(--primary))] px-2.5 py-1 font-medium text-[hsl(var(--primary-foreground))]",
|
|
3527
|
+
children: "\u4FDD\u5B58"
|
|
2425
3528
|
}
|
|
2426
3529
|
),
|
|
2427
|
-
/* @__PURE__ */
|
|
2428
|
-
|
|
3530
|
+
/* @__PURE__ */ jsx18(
|
|
3531
|
+
"button",
|
|
2429
3532
|
{
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
3533
|
+
type: "button",
|
|
3534
|
+
onClick: () => {
|
|
3535
|
+
setDraft({});
|
|
3536
|
+
onChange({});
|
|
3537
|
+
setOpen(false);
|
|
3538
|
+
},
|
|
3539
|
+
className: "rounded-md border border-[hsl(var(--border))] px-2.5 py-1 text-[hsl(var(--foreground))]",
|
|
3540
|
+
children: "\u6062\u590D\u9ED8\u8BA4"
|
|
2438
3541
|
}
|
|
2439
|
-
)
|
|
2440
|
-
|
|
2441
|
-
|
|
3542
|
+
)
|
|
3543
|
+
] })
|
|
3544
|
+
] })
|
|
3545
|
+
] });
|
|
3546
|
+
}
|
|
3547
|
+
|
|
3548
|
+
// src/components/LlmChat.tsx
|
|
3549
|
+
import { jsx as jsx19 } from "react/jsx-runtime";
|
|
3550
|
+
function LlmChat({
|
|
3551
|
+
classNames,
|
|
3552
|
+
renderers,
|
|
3553
|
+
slots,
|
|
3554
|
+
placeholder,
|
|
3555
|
+
theme,
|
|
3556
|
+
onReady,
|
|
3557
|
+
advanced,
|
|
3558
|
+
onOverrideChange,
|
|
3559
|
+
...options
|
|
3560
|
+
}) {
|
|
3561
|
+
const [override, setOverride] = useState15(() => readOverride(advanced, options.baseURL));
|
|
3562
|
+
const effective = { ...options, ...override };
|
|
3563
|
+
const { messages, isStreaming, error, send, stop, reset } = useLlmChat(effective);
|
|
3564
|
+
const [inputText, setInputText] = useState15("");
|
|
3565
|
+
const [stopRequested, setStopRequested] = useState15(false);
|
|
3566
|
+
const handle = useMemo9(
|
|
3567
|
+
() => ({
|
|
3568
|
+
insertText: (text) => setInputText((prev) => prev ? `${prev}
|
|
3569
|
+
${text}` : text),
|
|
3570
|
+
send,
|
|
3571
|
+
reset
|
|
3572
|
+
}),
|
|
3573
|
+
[send, reset]
|
|
3574
|
+
);
|
|
3575
|
+
useEffect10(() => {
|
|
3576
|
+
onReady?.(handle);
|
|
3577
|
+
}, [handle, onReady]);
|
|
3578
|
+
return /* @__PURE__ */ jsx19(
|
|
3579
|
+
ChatSurface,
|
|
3580
|
+
{
|
|
3581
|
+
theme,
|
|
3582
|
+
classNames,
|
|
3583
|
+
renderers,
|
|
3584
|
+
slots,
|
|
3585
|
+
placeholder,
|
|
3586
|
+
connection: "connected",
|
|
3587
|
+
errorMessage: error,
|
|
3588
|
+
messages,
|
|
3589
|
+
isStreaming,
|
|
3590
|
+
isStopping: stopRequested && isStreaming,
|
|
3591
|
+
inputText,
|
|
3592
|
+
onInputChange: setInputText,
|
|
3593
|
+
onSend: async (text) => {
|
|
3594
|
+
setStopRequested(false);
|
|
3595
|
+
if (!text.trim() || isStreaming) return false;
|
|
3596
|
+
void send(text);
|
|
3597
|
+
return true;
|
|
3598
|
+
},
|
|
3599
|
+
onStop: () => {
|
|
3600
|
+
setStopRequested(true);
|
|
3601
|
+
stop();
|
|
3602
|
+
},
|
|
3603
|
+
beforeInput: advanced ? /* @__PURE__ */ jsx19(
|
|
3604
|
+
LlmAdvancedSettingsBar,
|
|
3605
|
+
{
|
|
3606
|
+
settings: advanced,
|
|
3607
|
+
defaults: { baseURL: options.baseURL, model: options.model },
|
|
3608
|
+
override,
|
|
3609
|
+
onChange: (next) => {
|
|
3610
|
+
setOverride(next);
|
|
3611
|
+
writeOverride(advanced, options.baseURL, next);
|
|
3612
|
+
onOverrideChange?.(next);
|
|
3613
|
+
}
|
|
3614
|
+
}
|
|
3615
|
+
) : void 0
|
|
2442
3616
|
}
|
|
2443
3617
|
);
|
|
2444
3618
|
}
|
|
2445
3619
|
|
|
3620
|
+
// src/components/ChatView.tsx
|
|
3621
|
+
import { jsx as jsx20 } from "react/jsx-runtime";
|
|
3622
|
+
function ChatView(props) {
|
|
3623
|
+
const { mode, llm, onLlmReady, ...rest } = props;
|
|
3624
|
+
if (mode === "llm") {
|
|
3625
|
+
if (!llm) {
|
|
3626
|
+
throw new Error('ChatView: mode="llm" \u9700\u8981\u540C\u65F6\u4F20 llm={{ baseURL, model }}');
|
|
3627
|
+
}
|
|
3628
|
+
return /* @__PURE__ */ jsx20(
|
|
3629
|
+
LlmChat,
|
|
3630
|
+
{
|
|
3631
|
+
...llm,
|
|
3632
|
+
classNames: rest.classNames,
|
|
3633
|
+
renderers: rest.renderers,
|
|
3634
|
+
slots: rest.slots,
|
|
3635
|
+
placeholder: rest.placeholder,
|
|
3636
|
+
theme: rest.theme,
|
|
3637
|
+
onReady: onLlmReady
|
|
3638
|
+
}
|
|
3639
|
+
);
|
|
3640
|
+
}
|
|
3641
|
+
return /* @__PURE__ */ jsx20(AgentChat, { ...rest });
|
|
3642
|
+
}
|
|
3643
|
+
|
|
2446
3644
|
// src/index.ts
|
|
2447
3645
|
export * from "@blade-hq/agent-client";
|
|
2448
3646
|
export {
|
|
3647
|
+
AgentChat,
|
|
2449
3648
|
BladeProvider,
|
|
2450
3649
|
ChatView,
|
|
3650
|
+
LlmChat,
|
|
2451
3651
|
MarkdownContent,
|
|
3652
|
+
ReplayBar,
|
|
3653
|
+
ReplayMismatchPrompt,
|
|
2452
3654
|
useAgentSession,
|
|
2453
|
-
useBladeClient
|
|
3655
|
+
useBladeClient,
|
|
3656
|
+
useLlmChat,
|
|
3657
|
+
useReplay
|
|
2454
3658
|
};
|
|
2455
3659
|
/*! Bundled license information:
|
|
2456
3660
|
|
|
@@ -2458,7 +3662,8 @@ lucide-react/dist/esm/shared/src/utils.js:
|
|
|
2458
3662
|
lucide-react/dist/esm/defaultAttributes.js:
|
|
2459
3663
|
lucide-react/dist/esm/Icon.js:
|
|
2460
3664
|
lucide-react/dist/esm/createLucideIcon.js:
|
|
2461
|
-
lucide-react/dist/esm/icons/
|
|
3665
|
+
lucide-react/dist/esm/icons/arrow-right.js:
|
|
3666
|
+
lucide-react/dist/esm/icons/arrow-up-right.js:
|
|
2462
3667
|
lucide-react/dist/esm/icons/arrow-up.js:
|
|
2463
3668
|
lucide-react/dist/esm/icons/bot.js:
|
|
2464
3669
|
lucide-react/dist/esm/icons/brain.js:
|
|
@@ -2467,20 +3672,17 @@ lucide-react/dist/esm/icons/chevron-down.js:
|
|
|
2467
3672
|
lucide-react/dist/esm/icons/chevron-right.js:
|
|
2468
3673
|
lucide-react/dist/esm/icons/circle-alert.js:
|
|
2469
3674
|
lucide-react/dist/esm/icons/copy.js:
|
|
2470
|
-
lucide-react/dist/esm/icons/download.js:
|
|
2471
|
-
lucide-react/dist/esm/icons/file-code-2.js:
|
|
2472
|
-
lucide-react/dist/esm/icons/file-spreadsheet.js:
|
|
2473
3675
|
lucide-react/dist/esm/icons/file-text.js:
|
|
2474
|
-
lucide-react/dist/esm/icons/
|
|
2475
|
-
lucide-react/dist/esm/icons/film.js:
|
|
2476
|
-
lucide-react/dist/esm/icons/image.js:
|
|
3676
|
+
lucide-react/dist/esm/icons/globe.js:
|
|
2477
3677
|
lucide-react/dist/esm/icons/layers.js:
|
|
2478
3678
|
lucide-react/dist/esm/icons/lightbulb.js:
|
|
2479
3679
|
lucide-react/dist/esm/icons/loader-circle.js:
|
|
2480
3680
|
lucide-react/dist/esm/icons/lock-keyhole.js:
|
|
2481
3681
|
lucide-react/dist/esm/icons/message-square-more.js:
|
|
2482
3682
|
lucide-react/dist/esm/icons/message-square.js:
|
|
2483
|
-
lucide-react/dist/esm/icons/
|
|
3683
|
+
lucide-react/dist/esm/icons/play.js:
|
|
3684
|
+
lucide-react/dist/esm/icons/settings-2.js:
|
|
3685
|
+
lucide-react/dist/esm/icons/sparkles.js:
|
|
2484
3686
|
lucide-react/dist/esm/icons/square.js:
|
|
2485
3687
|
lucide-react/dist/esm/icons/triangle-alert.js:
|
|
2486
3688
|
lucide-react/dist/esm/icons/x.js:
|