@blade-hq/agent-react 2608.0.6 → 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/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 +1673 -434
- package/dist/index.js.map +1 -1
- package/dist/style.css +52 -11
- package/dist/style.full.css +53 -12
- 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,6 +618,18 @@ var createLucideIcon = (iconName, iconNode) => {
|
|
|
173
618
|
return Component2;
|
|
174
619
|
};
|
|
175
620
|
|
|
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" }]
|
|
631
|
+
]);
|
|
632
|
+
|
|
176
633
|
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/arrow-up.js
|
|
177
634
|
var ArrowUp = createLucideIcon("ArrowUp", [
|
|
178
635
|
["path", { d: "m5 12 7-7 7 7", key: "hav0vg" }],
|
|
@@ -249,6 +706,13 @@ var FileText = createLucideIcon("FileText", [
|
|
|
249
706
|
["path", { d: "M16 17H8", key: "z1uh3a" }]
|
|
250
707
|
]);
|
|
251
708
|
|
|
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" }]
|
|
714
|
+
]);
|
|
715
|
+
|
|
252
716
|
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/layers.js
|
|
253
717
|
var Layers = createLucideIcon("Layers", [
|
|
254
718
|
[
|
|
@@ -312,6 +776,34 @@ var MessageSquare = createLucideIcon("MessageSquare", [
|
|
|
312
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" }]
|
|
313
777
|
]);
|
|
314
778
|
|
|
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" }]
|
|
805
|
+
]);
|
|
806
|
+
|
|
315
807
|
// ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/square.js
|
|
316
808
|
var Square = createLucideIcon("Square", [
|
|
317
809
|
["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", key: "afitv7" }]
|
|
@@ -336,8 +828,8 @@ var X = createLucideIcon("X", [
|
|
|
336
828
|
["path", { d: "m6 6 12 12", key: "d8bk6v" }]
|
|
337
829
|
]);
|
|
338
830
|
|
|
339
|
-
// src/components/
|
|
340
|
-
import { useEffect as
|
|
831
|
+
// src/components/AgentChat.tsx
|
|
832
|
+
import { useCallback as useCallback7, useEffect as useEffect9, useMemo as useMemo8, useState as useState13 } from "react";
|
|
341
833
|
|
|
342
834
|
// src/lib/utils.ts
|
|
343
835
|
function cn(...inputs) {
|
|
@@ -352,8 +844,120 @@ async function copyToClipboard(text) {
|
|
|
352
844
|
}
|
|
353
845
|
}
|
|
354
846
|
|
|
355
|
-
// src/components/
|
|
847
|
+
// src/components/ReplayBar.tsx
|
|
356
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";
|
|
357
961
|
function ChatInput({
|
|
358
962
|
value,
|
|
359
963
|
onValueChange,
|
|
@@ -378,8 +982,8 @@ function ChatInput({
|
|
|
378
982
|
void handleSend();
|
|
379
983
|
}
|
|
380
984
|
};
|
|
381
|
-
return /* @__PURE__ */
|
|
382
|
-
/* @__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(
|
|
383
987
|
"textarea",
|
|
384
988
|
{
|
|
385
989
|
value,
|
|
@@ -396,7 +1000,7 @@ function ChatInput({
|
|
|
396
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)]"
|
|
397
1001
|
}
|
|
398
1002
|
),
|
|
399
|
-
isStreaming ? /* @__PURE__ */
|
|
1003
|
+
isStreaming ? /* @__PURE__ */ jsx4(
|
|
400
1004
|
"button",
|
|
401
1005
|
{
|
|
402
1006
|
type: "button",
|
|
@@ -405,9 +1009,9 @@ function ChatInput({
|
|
|
405
1009
|
"aria-label": isStopping ? "\u6B63\u5728\u505C\u6B62" : "\u505C\u6B62\u56DE\u590D",
|
|
406
1010
|
title: isStopping ? "\u6B63\u5728\u505C\u6B62" : "\u505C\u6B62\u56DE\u590D",
|
|
407
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",
|
|
408
|
-
children: isStopping ? /* @__PURE__ */
|
|
1012
|
+
children: isStopping ? /* @__PURE__ */ jsx4(LoaderCircle, { size: 14, className: "animate-spin" }) : /* @__PURE__ */ jsx4(Square, { size: 12, fill: "currentColor" })
|
|
409
1013
|
}
|
|
410
|
-
) : /* @__PURE__ */
|
|
1014
|
+
) : /* @__PURE__ */ jsx4(
|
|
411
1015
|
"button",
|
|
412
1016
|
{
|
|
413
1017
|
type: "button",
|
|
@@ -416,23 +1020,23 @@ function ChatInput({
|
|
|
416
1020
|
"aria-label": "\u53D1\u9001\u6D88\u606F",
|
|
417
1021
|
title: "\u53D1\u9001\u6D88\u606F",
|
|
418
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",
|
|
419
|
-
children: /* @__PURE__ */
|
|
1023
|
+
children: /* @__PURE__ */ jsx4(ArrowUp, { size: 15 })
|
|
420
1024
|
}
|
|
421
1025
|
)
|
|
422
1026
|
] }) });
|
|
423
1027
|
}
|
|
424
1028
|
|
|
425
1029
|
// src/components/ConnectionBanner.tsx
|
|
426
|
-
import { useEffect as
|
|
427
|
-
import { jsx as
|
|
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";
|
|
428
1032
|
var CONNECTION_NOTICE_DELAY_MS = 3e3;
|
|
429
1033
|
var CONNECTION_ERROR_DELAY_MS = 15e3;
|
|
430
1034
|
function useConnectionNoticePhase(connected) {
|
|
431
|
-
const [phase, setPhase] =
|
|
432
|
-
const connectedRef =
|
|
433
|
-
const timersRef =
|
|
1035
|
+
const [phase, setPhase] = useState4("hidden");
|
|
1036
|
+
const connectedRef = useRef3(connected);
|
|
1037
|
+
const timersRef = useRef3([]);
|
|
434
1038
|
connectedRef.current = connected;
|
|
435
|
-
|
|
1039
|
+
useEffect3(() => {
|
|
436
1040
|
const clearTimers = () => {
|
|
437
1041
|
for (const timer of timersRef.current) clearTimeout(timer);
|
|
438
1042
|
timersRef.current = [];
|
|
@@ -472,14 +1076,14 @@ function useConnectionNoticePhase(connected) {
|
|
|
472
1076
|
return phase;
|
|
473
1077
|
}
|
|
474
1078
|
function ConnectionBanner({ connection, className }) {
|
|
475
|
-
const hasConnectedRef =
|
|
1079
|
+
const hasConnectedRef = useRef3(connection === "connected" || connection === "reconnecting");
|
|
476
1080
|
if (connection === "connected") hasConnectedRef.current = true;
|
|
477
1081
|
const connected = connection === "connected";
|
|
478
1082
|
const phase = useConnectionNoticePhase(connected);
|
|
479
1083
|
if (connected || phase === "hidden") return null;
|
|
480
1084
|
const recovering = phase === "recovering";
|
|
481
1085
|
const firstConnection = !hasConnectedRef.current;
|
|
482
|
-
return /* @__PURE__ */
|
|
1086
|
+
return /* @__PURE__ */ jsx5("div", { className: cn("blade-chat-banner bg-[hsl(var(--background))] px-5 pt-3", className), children: /* @__PURE__ */ jsxs4(
|
|
483
1087
|
"div",
|
|
484
1088
|
{
|
|
485
1089
|
className: cn(
|
|
@@ -487,10 +1091,10 @@ function ConnectionBanner({ connection, className }) {
|
|
|
487
1091
|
recovering ? "border-amber-500/25 bg-amber-500/10 text-amber-100" : "border-rose-500/25 bg-rose-500/10 text-rose-100"
|
|
488
1092
|
),
|
|
489
1093
|
children: [
|
|
490
|
-
/* @__PURE__ */
|
|
491
|
-
/* @__PURE__ */
|
|
492
|
-
/* @__PURE__ */
|
|
493
|
-
/* @__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" })
|
|
494
1098
|
] })
|
|
495
1099
|
]
|
|
496
1100
|
}
|
|
@@ -499,10 +1103,10 @@ function ConnectionBanner({ connection, className }) {
|
|
|
499
1103
|
|
|
500
1104
|
// src/components/MessageList.tsx
|
|
501
1105
|
import { isHiddenInternalMessage } from "@blade-hq/agent-client";
|
|
502
|
-
import { useCallback as
|
|
1106
|
+
import { useCallback as useCallback6, useEffect as useEffect8, useMemo as useMemo7, useRef as useRef8, useState as useState12 } from "react";
|
|
503
1107
|
|
|
504
1108
|
// ../../node_modules/.pnpm/use-stick-to-bottom@1.1.3_react@19.2.4/node_modules/use-stick-to-bottom/dist/useStickToBottom.js
|
|
505
|
-
import { useCallback, useMemo as
|
|
1109
|
+
import { useCallback as useCallback3, useMemo as useMemo3, useRef as useRef4, useState as useState5 } from "react";
|
|
506
1110
|
var DEFAULT_SPRING_ANIMATION = {
|
|
507
1111
|
/**
|
|
508
1112
|
* A value from 0 to 1, on how much to damp the animation.
|
|
@@ -539,12 +1143,12 @@ globalThis.document?.addEventListener("click", () => {
|
|
|
539
1143
|
mouseDown = false;
|
|
540
1144
|
});
|
|
541
1145
|
var useStickToBottom = (options = {}) => {
|
|
542
|
-
const [escapedFromLock, updateEscapedFromLock] =
|
|
543
|
-
const [isAtBottom, updateIsAtBottom] =
|
|
544
|
-
const [isNearBottom, setIsNearBottom] =
|
|
545
|
-
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);
|
|
546
1150
|
optionsRef.current = options;
|
|
547
|
-
const isSelecting =
|
|
1151
|
+
const isSelecting = useCallback3(() => {
|
|
548
1152
|
if (!mouseDown) {
|
|
549
1153
|
return false;
|
|
550
1154
|
}
|
|
@@ -555,15 +1159,15 @@ var useStickToBottom = (options = {}) => {
|
|
|
555
1159
|
const range = selection.getRangeAt(0);
|
|
556
1160
|
return range.commonAncestorContainer.contains(scrollRef.current) || scrollRef.current?.contains(range.commonAncestorContainer);
|
|
557
1161
|
}, []);
|
|
558
|
-
const setIsAtBottom =
|
|
1162
|
+
const setIsAtBottom = useCallback3((isAtBottom2) => {
|
|
559
1163
|
state.isAtBottom = isAtBottom2;
|
|
560
1164
|
updateIsAtBottom(isAtBottom2);
|
|
561
1165
|
}, []);
|
|
562
|
-
const setEscapedFromLock =
|
|
1166
|
+
const setEscapedFromLock = useCallback3((escapedFromLock2) => {
|
|
563
1167
|
state.escapedFromLock = escapedFromLock2;
|
|
564
1168
|
updateEscapedFromLock(escapedFromLock2);
|
|
565
1169
|
}, []);
|
|
566
|
-
const state =
|
|
1170
|
+
const state = useMemo3(() => {
|
|
567
1171
|
let lastCalculation;
|
|
568
1172
|
return {
|
|
569
1173
|
escapedFromLock,
|
|
@@ -616,7 +1220,7 @@ var useStickToBottom = (options = {}) => {
|
|
|
616
1220
|
}
|
|
617
1221
|
};
|
|
618
1222
|
}, []);
|
|
619
|
-
const scrollToBottom =
|
|
1223
|
+
const scrollToBottom = useCallback3((scrollOptions = {}) => {
|
|
620
1224
|
if (typeof scrollOptions === "string") {
|
|
621
1225
|
scrollOptions = { animation: scrollOptions };
|
|
622
1226
|
}
|
|
@@ -701,11 +1305,11 @@ var useStickToBottom = (options = {}) => {
|
|
|
701
1305
|
}
|
|
702
1306
|
return next();
|
|
703
1307
|
}, [setIsAtBottom, isSelecting, state]);
|
|
704
|
-
const stopScroll =
|
|
1308
|
+
const stopScroll = useCallback3(() => {
|
|
705
1309
|
setEscapedFromLock(true);
|
|
706
1310
|
setIsAtBottom(false);
|
|
707
1311
|
}, [setEscapedFromLock, setIsAtBottom]);
|
|
708
|
-
const handleScroll =
|
|
1312
|
+
const handleScroll = useCallback3(({ target }) => {
|
|
709
1313
|
if (target !== scrollRef.current) {
|
|
710
1314
|
return;
|
|
711
1315
|
}
|
|
@@ -744,7 +1348,7 @@ var useStickToBottom = (options = {}) => {
|
|
|
744
1348
|
}
|
|
745
1349
|
}, 1);
|
|
746
1350
|
}, [setEscapedFromLock, setIsAtBottom, isSelecting, state]);
|
|
747
|
-
const handleWheel =
|
|
1351
|
+
const handleWheel = useCallback3(({ target, deltaY }) => {
|
|
748
1352
|
let element = target;
|
|
749
1353
|
while (!["scroll", "auto"].includes(getComputedStyle(element).overflow)) {
|
|
750
1354
|
if (!element.parentElement) {
|
|
@@ -814,7 +1418,7 @@ var useStickToBottom = (options = {}) => {
|
|
|
814
1418
|
};
|
|
815
1419
|
};
|
|
816
1420
|
function useRefCallback(callback, deps) {
|
|
817
|
-
const result =
|
|
1421
|
+
const result = useCallback3((ref) => {
|
|
818
1422
|
result.current = ref;
|
|
819
1423
|
return callback(ref);
|
|
820
1424
|
}, deps);
|
|
@@ -846,11 +1450,11 @@ function mergeAnimations(...animations) {
|
|
|
846
1450
|
|
|
847
1451
|
// ../../node_modules/.pnpm/use-stick-to-bottom@1.1.3_react@19.2.4/node_modules/use-stick-to-bottom/dist/StickToBottom.js
|
|
848
1452
|
import * as React from "react";
|
|
849
|
-
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";
|
|
850
1454
|
var StickToBottomContext = createContext2(null);
|
|
851
|
-
var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect :
|
|
1455
|
+
var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect4;
|
|
852
1456
|
function StickToBottom({ instance, children, resize, initial, mass, damping, stiffness, targetScrollTop: currentTargetScrollTop, contextRef, ...props }) {
|
|
853
|
-
const customTargetScrollTop =
|
|
1457
|
+
const customTargetScrollTop = useRef5(null);
|
|
854
1458
|
const targetScrollTop = React.useCallback((target, elements) => {
|
|
855
1459
|
const get = context?.targetScrollTop ?? currentTargetScrollTop;
|
|
856
1460
|
return get?.(target, elements) ?? target;
|
|
@@ -864,7 +1468,7 @@ function StickToBottom({ instance, children, resize, initial, mass, damping, sti
|
|
|
864
1468
|
targetScrollTop
|
|
865
1469
|
});
|
|
866
1470
|
const { scrollRef, contentRef, scrollToBottom, stopScroll, isAtBottom, escapedFromLock, state } = instance ?? defaultInstance;
|
|
867
|
-
const context =
|
|
1471
|
+
const context = useMemo4(() => ({
|
|
868
1472
|
scrollToBottom,
|
|
869
1473
|
stopScroll,
|
|
870
1474
|
scrollRef,
|
|
@@ -927,10 +1531,10 @@ function useStickToBottomContext() {
|
|
|
927
1531
|
|
|
928
1532
|
// src/components/AssistantTurnBlock.tsx
|
|
929
1533
|
import { getTextContent, normalizeMessageContent } from "@blade-hq/agent-client";
|
|
930
|
-
import { useState as
|
|
1534
|
+
import { useState as useState10 } from "react";
|
|
931
1535
|
|
|
932
1536
|
// src/components/AgentLoopBlock.tsx
|
|
933
|
-
import { useState as
|
|
1537
|
+
import { useState as useState6 } from "react";
|
|
934
1538
|
|
|
935
1539
|
// src/components/display-utils.ts
|
|
936
1540
|
var TOOL_NAME_ALIASES = {
|
|
@@ -1052,7 +1656,7 @@ function formatToolResult(result) {
|
|
|
1052
1656
|
}
|
|
1053
1657
|
|
|
1054
1658
|
// src/components/AgentLoopBlock.tsx
|
|
1055
|
-
import { jsx as
|
|
1659
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1056
1660
|
function parseAgentDescription(argumentsJson) {
|
|
1057
1661
|
try {
|
|
1058
1662
|
const parsed = JSON.parse(argumentsJson);
|
|
@@ -1062,12 +1666,12 @@ function parseAgentDescription(argumentsJson) {
|
|
|
1062
1666
|
}
|
|
1063
1667
|
}
|
|
1064
1668
|
function AgentLoopBlock({ toolCall }) {
|
|
1065
|
-
const [expanded, setExpanded] =
|
|
1669
|
+
const [expanded, setExpanded] = useState6(false);
|
|
1066
1670
|
const description = parseAgentDescription(toolCall.arguments);
|
|
1067
1671
|
const running = toolCall.status === "pending" || toolCall.status === "awaiting_answer";
|
|
1068
1672
|
const failed = toolCall.status === "error" || toolCall.status === "cancelled";
|
|
1069
|
-
return /* @__PURE__ */
|
|
1070
|
-
/* @__PURE__ */
|
|
1673
|
+
return /* @__PURE__ */ jsxs5("div", { className: "blade-chat-agent-loop ml-4 text-xs", children: [
|
|
1674
|
+
/* @__PURE__ */ jsxs5(
|
|
1071
1675
|
"div",
|
|
1072
1676
|
{
|
|
1073
1677
|
className: cn(
|
|
@@ -1075,7 +1679,7 @@ function AgentLoopBlock({ toolCall }) {
|
|
|
1075
1679
|
failed ? "border-l-[hsl(var(--muted-foreground)/0.5)]" : running ? "border-l-blue-500" : "border-l-[hsl(var(--primary))]"
|
|
1076
1680
|
),
|
|
1077
1681
|
children: [
|
|
1078
|
-
/* @__PURE__ */
|
|
1682
|
+
/* @__PURE__ */ jsxs5(
|
|
1079
1683
|
"button",
|
|
1080
1684
|
{
|
|
1081
1685
|
type: "button",
|
|
@@ -1083,7 +1687,7 @@ function AgentLoopBlock({ toolCall }) {
|
|
|
1083
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",
|
|
1084
1688
|
"aria-expanded": expanded,
|
|
1085
1689
|
children: [
|
|
1086
|
-
/* @__PURE__ */
|
|
1690
|
+
/* @__PURE__ */ jsx6(
|
|
1087
1691
|
ChevronRight,
|
|
1088
1692
|
{
|
|
1089
1693
|
size: 11,
|
|
@@ -1093,8 +1697,8 @@ function AgentLoopBlock({ toolCall }) {
|
|
|
1093
1697
|
)
|
|
1094
1698
|
}
|
|
1095
1699
|
),
|
|
1096
|
-
/* @__PURE__ */
|
|
1097
|
-
/* @__PURE__ */
|
|
1700
|
+
/* @__PURE__ */ jsx6(Bot, { size: 12, className: "shrink-0 text-[hsl(var(--muted-foreground))]" }),
|
|
1701
|
+
/* @__PURE__ */ jsxs5(
|
|
1098
1702
|
"span",
|
|
1099
1703
|
{
|
|
1100
1704
|
className: cn(
|
|
@@ -1102,156 +1706,43 @@ function AgentLoopBlock({ toolCall }) {
|
|
|
1102
1706
|
failed ? "text-[hsl(var(--muted-foreground))]" : running ? "text-blue-300" : "text-[hsl(var(--primary))]"
|
|
1103
1707
|
),
|
|
1104
1708
|
children: [
|
|
1105
|
-
running ? /* @__PURE__ */
|
|
1106
|
-
/* @__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" })
|
|
1107
1711
|
]
|
|
1108
1712
|
}
|
|
1109
1713
|
),
|
|
1110
|
-
/* @__PURE__ */
|
|
1714
|
+
/* @__PURE__ */ jsxs5("span", { className: "min-w-0 flex-1 truncate font-medium text-[hsl(var(--foreground))]", children: [
|
|
1111
1715
|
"\u5B50\u667A\u80FD\u4F53\uFF1A",
|
|
1112
1716
|
description
|
|
1113
1717
|
] })
|
|
1114
1718
|
]
|
|
1115
1719
|
}
|
|
1116
1720
|
),
|
|
1117
|
-
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) })
|
|
1118
1722
|
]
|
|
1119
1723
|
}
|
|
1120
1724
|
),
|
|
1121
|
-
expanded && toolCall.result != null && /* @__PURE__ */
|
|
1122
|
-
/* @__PURE__ */
|
|
1123
|
-
/* @__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) })
|
|
1124
1728
|
] })
|
|
1125
1729
|
] });
|
|
1126
1730
|
}
|
|
1127
1731
|
|
|
1128
1732
|
// src/components/MarkdownContent.tsx
|
|
1129
1733
|
import {
|
|
1130
|
-
useEffect as
|
|
1131
|
-
useMemo as
|
|
1132
|
-
useRef as
|
|
1133
|
-
useState as
|
|
1734
|
+
useEffect as useEffect5,
|
|
1735
|
+
useMemo as useMemo5,
|
|
1736
|
+
useRef as useRef6,
|
|
1737
|
+
useState as useState7
|
|
1134
1738
|
} from "react";
|
|
1135
|
-
|
|
1136
|
-
// src/lib/media-tags.ts
|
|
1137
|
-
var FILE_CARD_TAG = "blade-file-card";
|
|
1138
|
-
var MEDIA_LINE_RE = /^[ \t]*(?:[-*+][ \t]+|\d+\.[ \t]+)?(?<wrap>[`"'*_]*)MEDIA:[ \t]*(?<path>.+?)\k<wrap>[ \t]*[。,、;;,]?[ \t]*\r?$/gm;
|
|
1139
|
-
var MEDIA_LINE_START_RE = /^[ \t]*(?:[-*+][ \t]+|\d+\.[ \t]+)?[`"'*_]*MEDIA:/;
|
|
1140
|
-
function basename(path) {
|
|
1141
|
-
return path.split(/[/\\]/).filter(Boolean).pop() || path;
|
|
1142
|
-
}
|
|
1143
|
-
function unwrapMediaPath(path) {
|
|
1144
|
-
if (!path.startsWith("<")) return path;
|
|
1145
|
-
const closingBracket = path.indexOf(">");
|
|
1146
|
-
if (closingBracket < 0) return null;
|
|
1147
|
-
return `${path.slice(1, closingBracket)}${path.slice(closingBracket + 1)}`;
|
|
1148
|
-
}
|
|
1149
|
-
function isSafeMediaPath(path) {
|
|
1150
|
-
if (!path) return false;
|
|
1151
|
-
return !path.split(/[/\\]/).includes("..");
|
|
1152
|
-
}
|
|
1153
|
-
function escapeAttribute(value) {
|
|
1154
|
-
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
1155
|
-
}
|
|
1156
|
-
function foldMediaLines(text) {
|
|
1157
|
-
return text.replace(MEDIA_LINE_RE, (match, _wrap, rawPath) => {
|
|
1158
|
-
const path = unwrapMediaPath(rawPath);
|
|
1159
|
-
if (path === null) return match;
|
|
1160
|
-
const name = basename(path);
|
|
1161
|
-
if (!isSafeMediaPath(path)) {
|
|
1162
|
-
return `\`${name}\``;
|
|
1163
|
-
}
|
|
1164
|
-
return `<${FILE_CARD_TAG} data-path="${escapeAttribute(path)}" data-name="${escapeAttribute(name)}">${escapeAttribute(name)}</${FILE_CARD_TAG}>`;
|
|
1165
|
-
});
|
|
1166
|
-
}
|
|
1167
|
-
function collapseMediaTags(text, options) {
|
|
1168
|
-
if (!text.includes("MEDIA:")) return text;
|
|
1169
|
-
if (!options?.streaming) return foldMediaLines(text);
|
|
1170
|
-
const newlineIndex = text.lastIndexOf("\n");
|
|
1171
|
-
const head = newlineIndex < 0 ? "" : text.slice(0, newlineIndex + 1);
|
|
1172
|
-
const lastLine = newlineIndex < 0 ? text : text.slice(newlineIndex + 1);
|
|
1173
|
-
if (MEDIA_LINE_START_RE.test(lastLine)) {
|
|
1174
|
-
return foldMediaLines(head);
|
|
1175
|
-
}
|
|
1176
|
-
return foldMediaLines(head) + lastLine;
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
// src/components/FileCard.tsx
|
|
1180
|
-
import { useState as useState5 } from "react";
|
|
1181
|
-
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1182
|
-
function stringValue(value) {
|
|
1183
|
-
return typeof value === "string" ? value : void 0;
|
|
1184
|
-
}
|
|
1185
|
-
function extractChildrenText(children) {
|
|
1186
|
-
if (typeof children === "string") return children;
|
|
1187
|
-
if (Array.isArray(children)) return children.map(extractChildrenText).join("");
|
|
1188
|
-
if (children && typeof children === "object" && "props" in children) {
|
|
1189
|
-
return extractChildrenText(children.props?.children);
|
|
1190
|
-
}
|
|
1191
|
-
return "";
|
|
1192
|
-
}
|
|
1193
|
-
function FileCard({
|
|
1194
|
-
node,
|
|
1195
|
-
"data-path": pathAttribute,
|
|
1196
|
-
"data-name": nameAttribute,
|
|
1197
|
-
dataPath,
|
|
1198
|
-
dataName,
|
|
1199
|
-
sessionId,
|
|
1200
|
-
children,
|
|
1201
|
-
className,
|
|
1202
|
-
...props
|
|
1203
|
-
}) {
|
|
1204
|
-
const client = useBladeClient();
|
|
1205
|
-
const [downloading, setDownloading] = useState5(false);
|
|
1206
|
-
const [failed, setFailed] = useState5(false);
|
|
1207
|
-
const nodeProperties = node && typeof node === "object" && "properties" in node ? node.properties : void 0;
|
|
1208
|
-
const path = pathAttribute ?? dataPath ?? stringValue(nodeProperties?.["data-path"]) ?? stringValue(nodeProperties?.dataPath) ?? "";
|
|
1209
|
-
const name = nameAttribute ?? dataName ?? stringValue(nodeProperties?.["data-name"]) ?? stringValue(nodeProperties?.dataName) ?? extractChildrenText(children) ?? "";
|
|
1210
|
-
const disabled = !sessionId || !isSafeMediaPath(path);
|
|
1211
|
-
const downloadUrl = disabled ? void 0 : client.buildAuthedUrl(
|
|
1212
|
-
`/api/sessions/${encodeURIComponent(sessionId)}/files/${encodeURIComponent(path)}`
|
|
1213
|
-
);
|
|
1214
|
-
const handleDownload = async (event) => {
|
|
1215
|
-
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
1216
|
-
event.preventDefault();
|
|
1217
|
-
if (disabled || downloading || !sessionId) return;
|
|
1218
|
-
setDownloading(true);
|
|
1219
|
-
setFailed(false);
|
|
1220
|
-
try {
|
|
1221
|
-
await client.sessions.downloadFile(sessionId, path, name);
|
|
1222
|
-
} catch {
|
|
1223
|
-
setFailed(true);
|
|
1224
|
-
} finally {
|
|
1225
|
-
setDownloading(false);
|
|
1226
|
-
}
|
|
1227
|
-
};
|
|
1228
|
-
return /* @__PURE__ */ jsxs4("span", { ...props, className: cn("blade-chat-file-card", className), children: [
|
|
1229
|
-
/* @__PURE__ */ jsx5(
|
|
1230
|
-
"a",
|
|
1231
|
-
{
|
|
1232
|
-
href: downloadUrl,
|
|
1233
|
-
download: name,
|
|
1234
|
-
onClick: handleDownload,
|
|
1235
|
-
"aria-disabled": disabled || void 0,
|
|
1236
|
-
"aria-busy": downloading || void 0,
|
|
1237
|
-
"aria-label": `\u4E0B\u8F7D\u6587\u4EF6\uFF1A${name}`,
|
|
1238
|
-
title: failed ? "\u4E0B\u8F7D\u5931\u8D25\uFF0C\u70B9\u51FB\u91CD\u8BD5" : name,
|
|
1239
|
-
className: "blade-chat-file-link",
|
|
1240
|
-
children: name
|
|
1241
|
-
}
|
|
1242
|
-
),
|
|
1243
|
-
failed && /* @__PURE__ */ jsx5("span", { className: "blade-chat-file-card-error", children: "\u4E0B\u8F7D\u5931\u8D25" })
|
|
1244
|
-
] });
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
|
-
// src/components/MarkdownContent.tsx
|
|
1248
|
-
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1739
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1249
1740
|
var SYSTEM_REMINDER_RE = /<system-reminder>[\s\S]*?<\/system-reminder>/gi;
|
|
1250
1741
|
function CodeBlockPre({ children, node: _node, ...props }) {
|
|
1251
|
-
const preRef =
|
|
1252
|
-
const [copied, setCopied] =
|
|
1253
|
-
const [language, setLanguage] =
|
|
1254
|
-
|
|
1742
|
+
const preRef = useRef6(null);
|
|
1743
|
+
const [copied, setCopied] = useState7(false);
|
|
1744
|
+
const [language, setLanguage] = useState7("");
|
|
1745
|
+
useEffect5(() => {
|
|
1255
1746
|
const codeEl = preRef.current?.querySelector("code");
|
|
1256
1747
|
setLanguage(codeEl?.className.match(/language-(\S+)/)?.[1] ?? "");
|
|
1257
1748
|
}, []);
|
|
@@ -1262,10 +1753,10 @@ function CodeBlockPre({ children, node: _node, ...props }) {
|
|
|
1262
1753
|
setTimeout(() => setCopied(false), 2e3);
|
|
1263
1754
|
}
|
|
1264
1755
|
};
|
|
1265
|
-
return /* @__PURE__ */
|
|
1266
|
-
/* @__PURE__ */
|
|
1267
|
-
/* @__PURE__ */
|
|
1268
|
-
/* @__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(
|
|
1269
1760
|
"button",
|
|
1270
1761
|
{
|
|
1271
1762
|
type: "button",
|
|
@@ -1275,13 +1766,13 @@ function CodeBlockPre({ children, node: _node, ...props }) {
|
|
|
1275
1766
|
copied ? "text-[hsl(var(--primary))]" : "text-[hsl(var(--muted-foreground))] hover:bg-[hsl(var(--accent))] hover:text-[hsl(var(--foreground))]"
|
|
1276
1767
|
),
|
|
1277
1768
|
children: [
|
|
1278
|
-
copied ? /* @__PURE__ */
|
|
1279
|
-
/* @__PURE__ */
|
|
1769
|
+
copied ? /* @__PURE__ */ jsx7(Check, { size: 12 }) : /* @__PURE__ */ jsx7(Copy, { size: 12 }),
|
|
1770
|
+
/* @__PURE__ */ jsx7("span", { children: copied ? "\u5DF2\u590D\u5236" : "\u590D\u5236" })
|
|
1280
1771
|
]
|
|
1281
1772
|
}
|
|
1282
1773
|
)
|
|
1283
1774
|
] }),
|
|
1284
|
-
/* @__PURE__ */
|
|
1775
|
+
/* @__PURE__ */ jsx7(
|
|
1285
1776
|
"pre",
|
|
1286
1777
|
{
|
|
1287
1778
|
ref: preRef,
|
|
@@ -1293,36 +1784,22 @@ function CodeBlockPre({ children, node: _node, ...props }) {
|
|
|
1293
1784
|
] });
|
|
1294
1785
|
}
|
|
1295
1786
|
function ExternalAnchor({ node: _node, children, ...props }) {
|
|
1296
|
-
return /* @__PURE__ */
|
|
1787
|
+
return /* @__PURE__ */ jsx7("a", { ...props, target: "_blank", rel: "noopener noreferrer", children });
|
|
1297
1788
|
}
|
|
1298
1789
|
var MARKDOWN_COMPONENTS = {
|
|
1299
1790
|
pre: CodeBlockPre,
|
|
1300
1791
|
a: ExternalAnchor
|
|
1301
1792
|
};
|
|
1302
|
-
var CUSTOM_ALLOWED_TAGS = {
|
|
1303
|
-
[FILE_CARD_TAG]: ["dataPath", "dataName", "data-path", "data-name"]
|
|
1304
|
-
};
|
|
1305
1793
|
function MarkdownContent({ children, className, mode, sessionId }) {
|
|
1306
|
-
const
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
return collapseMediaTags(withoutReminders, { streaming });
|
|
1311
|
-
}, [children, sessionId, streaming]);
|
|
1312
|
-
const components = useMemo4(
|
|
1313
|
-
() => ({
|
|
1314
|
-
...MARKDOWN_COMPONENTS,
|
|
1315
|
-
[FILE_CARD_TAG]: (fileCardProps) => /* @__PURE__ */ jsx6(FileCard, { ...fileCardProps, sessionId })
|
|
1316
|
-
}),
|
|
1317
|
-
[sessionId]
|
|
1318
|
-
);
|
|
1319
|
-
return /* @__PURE__ */ jsx6(
|
|
1794
|
+
const resolvedChildren = useMemo5(() => {
|
|
1795
|
+
return children.replace(SYSTEM_REMINDER_RE, "");
|
|
1796
|
+
}, [children]);
|
|
1797
|
+
return /* @__PURE__ */ jsx7(
|
|
1320
1798
|
_r,
|
|
1321
1799
|
{
|
|
1322
1800
|
className: cn("blade-chat-markdown break-words", className),
|
|
1323
1801
|
mode: mode ?? "static",
|
|
1324
|
-
components,
|
|
1325
|
-
allowedTags: CUSTOM_ALLOWED_TAGS,
|
|
1802
|
+
components: MARKDOWN_COMPONENTS,
|
|
1326
1803
|
children: resolvedChildren
|
|
1327
1804
|
},
|
|
1328
1805
|
sessionId ?? "no-session"
|
|
@@ -1330,17 +1807,17 @@ function MarkdownContent({ children, className, mode, sessionId }) {
|
|
|
1330
1807
|
}
|
|
1331
1808
|
|
|
1332
1809
|
// src/components/Shimmer.tsx
|
|
1333
|
-
import { jsx as
|
|
1810
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
1334
1811
|
function Shimmer({ children = "\u6B63\u5728\u601D\u8003...", className }) {
|
|
1335
|
-
return /* @__PURE__ */
|
|
1812
|
+
return /* @__PURE__ */ jsx8("span", { className: cn("blade-shimmer-text text-sm font-medium", className), children });
|
|
1336
1813
|
}
|
|
1337
1814
|
|
|
1338
1815
|
// src/components/ToolCallBlock.tsx
|
|
1339
|
-
import { useState as
|
|
1816
|
+
import { useState as useState9 } from "react";
|
|
1340
1817
|
|
|
1341
1818
|
// src/components/AskUserQuestionBlock.tsx
|
|
1342
|
-
import { useEffect as
|
|
1343
|
-
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";
|
|
1344
1821
|
function AskUserQuestionBlock({
|
|
1345
1822
|
data,
|
|
1346
1823
|
answered,
|
|
@@ -1349,16 +1826,16 @@ function AskUserQuestionBlock({
|
|
|
1349
1826
|
answerData,
|
|
1350
1827
|
onAnswer
|
|
1351
1828
|
}) {
|
|
1352
|
-
const [selections, setSelections] =
|
|
1353
|
-
const [customTexts, setCustomTexts] =
|
|
1354
|
-
const [usingCustom, setUsingCustom] =
|
|
1355
|
-
const [submitted, setSubmitted] =
|
|
1356
|
-
|
|
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(() => {
|
|
1357
1834
|
if (sessionStatus === "failed" || sessionStatus === "interrupted") {
|
|
1358
1835
|
setSubmitted(false);
|
|
1359
1836
|
}
|
|
1360
1837
|
}, [sessionStatus]);
|
|
1361
|
-
const displayAnswerState =
|
|
1838
|
+
const displayAnswerState = useMemo6(() => {
|
|
1362
1839
|
if (!(answered && answerData)) {
|
|
1363
1840
|
return { selections, customTexts, usingCustom };
|
|
1364
1841
|
}
|
|
@@ -1443,7 +1920,7 @@ ${parts.join("\n")}`;
|
|
|
1443
1920
|
setSubmitted(true);
|
|
1444
1921
|
onAnswer(text, toolCallId, nextAnswerData);
|
|
1445
1922
|
};
|
|
1446
|
-
return /* @__PURE__ */
|
|
1923
|
+
return /* @__PURE__ */ jsxs7(
|
|
1447
1924
|
"div",
|
|
1448
1925
|
{
|
|
1449
1926
|
className: cn(
|
|
@@ -1451,12 +1928,12 @@ ${parts.join("\n")}`;
|
|
|
1451
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"
|
|
1452
1929
|
),
|
|
1453
1930
|
children: [
|
|
1454
|
-
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: [
|
|
1455
1932
|
"\u5B50\u667A\u80FD\u4F53\u300C",
|
|
1456
1933
|
data.source_loop.description,
|
|
1457
1934
|
"\u300D\u5728\u7B49\u5F85\u4F60\u7684\u56DE\u7B54"
|
|
1458
1935
|
] }),
|
|
1459
|
-
data.questions.map((q, qIdx) => /* @__PURE__ */
|
|
1936
|
+
data.questions.map((q, qIdx) => /* @__PURE__ */ jsx9(
|
|
1460
1937
|
QuestionCard,
|
|
1461
1938
|
{
|
|
1462
1939
|
question: q,
|
|
@@ -1471,7 +1948,7 @@ ${parts.join("\n")}`;
|
|
|
1471
1948
|
},
|
|
1472
1949
|
q.question
|
|
1473
1950
|
)),
|
|
1474
|
-
!answered && !submitted && onAnswer && /* @__PURE__ */
|
|
1951
|
+
!answered && !submitted && onAnswer && /* @__PURE__ */ jsx9(
|
|
1475
1952
|
"button",
|
|
1476
1953
|
{
|
|
1477
1954
|
type: "button",
|
|
@@ -1481,14 +1958,14 @@ ${parts.join("\n")}`;
|
|
|
1481
1958
|
children: allAnswered ? "\u786E\u8BA4" : "\u8BF7\u5148\u9009\u62E9\u4E00\u4E2A\u9009\u9879"
|
|
1482
1959
|
}
|
|
1483
1960
|
),
|
|
1484
|
-
submitted && !answered && /* @__PURE__ */
|
|
1961
|
+
submitted && !answered && /* @__PURE__ */ jsxs7(
|
|
1485
1962
|
"button",
|
|
1486
1963
|
{
|
|
1487
1964
|
type: "button",
|
|
1488
1965
|
disabled: true,
|
|
1489
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",
|
|
1490
1967
|
children: [
|
|
1491
|
-
/* @__PURE__ */
|
|
1968
|
+
/* @__PURE__ */ jsx9(LoaderCircle, { size: 14, className: "animate-spin" }),
|
|
1492
1969
|
"\u786E\u8BA4\u4E2D"
|
|
1493
1970
|
]
|
|
1494
1971
|
}
|
|
@@ -1509,30 +1986,30 @@ function QuestionCard({
|
|
|
1509
1986
|
onCustomChange
|
|
1510
1987
|
}) {
|
|
1511
1988
|
const multi = question.multiSelect ?? false;
|
|
1512
|
-
return /* @__PURE__ */
|
|
1513
|
-
/* @__PURE__ */
|
|
1514
|
-
/* @__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(
|
|
1515
1992
|
MessageSquareMore,
|
|
1516
1993
|
{
|
|
1517
1994
|
size: answered ? 12 : 13,
|
|
1518
1995
|
className: "mt-0.5 shrink-0 text-[hsl(var(--primary))]"
|
|
1519
1996
|
}
|
|
1520
1997
|
),
|
|
1521
|
-
/* @__PURE__ */
|
|
1998
|
+
/* @__PURE__ */ jsx9(
|
|
1522
1999
|
"div",
|
|
1523
2000
|
{
|
|
1524
2001
|
className: cn(
|
|
1525
2002
|
"min-w-0 flex-1 font-medium text-[hsl(var(--foreground))]",
|
|
1526
2003
|
answered ? "text-xs" : "text-sm"
|
|
1527
2004
|
),
|
|
1528
|
-
children: /* @__PURE__ */
|
|
2005
|
+
children: /* @__PURE__ */ jsx9(MarkdownContent, { className: "blade-chat-prose", children: question.question })
|
|
1529
2006
|
}
|
|
1530
2007
|
)
|
|
1531
2008
|
] }),
|
|
1532
|
-
/* @__PURE__ */
|
|
2009
|
+
/* @__PURE__ */ jsxs7("div", { className: cn("flex flex-col pl-5", answered ? "gap-1" : "gap-1.5"), children: [
|
|
1533
2010
|
question.options.map((opt, optIdx) => {
|
|
1534
2011
|
const isSel = selected.has(optIdx);
|
|
1535
|
-
return /* @__PURE__ */
|
|
2012
|
+
return /* @__PURE__ */ jsxs7(
|
|
1536
2013
|
"button",
|
|
1537
2014
|
{
|
|
1538
2015
|
type: "button",
|
|
@@ -1546,14 +2023,14 @@ function QuestionCard({
|
|
|
1546
2023
|
answered && "cursor-default opacity-70"
|
|
1547
2024
|
),
|
|
1548
2025
|
children: [
|
|
1549
|
-
multi && /* @__PURE__ */
|
|
2026
|
+
multi && /* @__PURE__ */ jsx9(
|
|
1550
2027
|
"div",
|
|
1551
2028
|
{
|
|
1552
2029
|
className: cn(
|
|
1553
2030
|
"mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded border transition-colors",
|
|
1554
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))]"
|
|
1555
2032
|
),
|
|
1556
|
-
children: isSel && /* @__PURE__ */
|
|
2033
|
+
children: isSel && /* @__PURE__ */ jsx9(
|
|
1557
2034
|
Check,
|
|
1558
2035
|
{
|
|
1559
2036
|
size: 9,
|
|
@@ -1562,9 +2039,9 @@ function QuestionCard({
|
|
|
1562
2039
|
)
|
|
1563
2040
|
}
|
|
1564
2041
|
),
|
|
1565
|
-
/* @__PURE__ */
|
|
1566
|
-
/* @__PURE__ */
|
|
1567
|
-
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(
|
|
1568
2045
|
"div",
|
|
1569
2046
|
{
|
|
1570
2047
|
className: cn(
|
|
@@ -1581,7 +2058,7 @@ function QuestionCard({
|
|
|
1581
2058
|
opt.label
|
|
1582
2059
|
);
|
|
1583
2060
|
}),
|
|
1584
|
-
answered && !isCustom ? null : /* @__PURE__ */
|
|
2061
|
+
answered && !isCustom ? null : /* @__PURE__ */ jsxs7(
|
|
1585
2062
|
"div",
|
|
1586
2063
|
{
|
|
1587
2064
|
className: cn(
|
|
@@ -1591,8 +2068,8 @@ function QuestionCard({
|
|
|
1591
2068
|
answered && "cursor-default opacity-70"
|
|
1592
2069
|
),
|
|
1593
2070
|
children: [
|
|
1594
|
-
/* @__PURE__ */
|
|
1595
|
-
/* @__PURE__ */
|
|
2071
|
+
/* @__PURE__ */ jsx9("span", { className: "shrink-0 text-xs text-[hsl(var(--muted-foreground))]", children: "\u5176\u4ED6\uFF1A" }),
|
|
2072
|
+
/* @__PURE__ */ jsx9(
|
|
1596
2073
|
"input",
|
|
1597
2074
|
{
|
|
1598
2075
|
type: "text",
|
|
@@ -1660,7 +2137,7 @@ function normalizeOptionItem(value) {
|
|
|
1660
2137
|
}
|
|
1661
2138
|
|
|
1662
2139
|
// src/components/ToolCallBlock.tsx
|
|
1663
|
-
import { Fragment, jsx as
|
|
2140
|
+
import { Fragment, jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1664
2141
|
function resolveAskQuestionState({
|
|
1665
2142
|
toolStatus,
|
|
1666
2143
|
hasAnswerData,
|
|
@@ -1680,12 +2157,12 @@ function ToolCallBlock({
|
|
|
1680
2157
|
sessionStatus,
|
|
1681
2158
|
renderer
|
|
1682
2159
|
}) {
|
|
1683
|
-
const [expanded, setExpanded] =
|
|
2160
|
+
const [expanded, setExpanded] = useState9(false);
|
|
1684
2161
|
const normalizedName = formatToolName(toolCall.name);
|
|
1685
2162
|
if (renderer) {
|
|
1686
2163
|
const custom = renderer(toolCall);
|
|
1687
2164
|
if (custom !== null && custom !== void 0) {
|
|
1688
|
-
return /* @__PURE__ */
|
|
2165
|
+
return /* @__PURE__ */ jsx10(Fragment, { children: custom });
|
|
1689
2166
|
}
|
|
1690
2167
|
}
|
|
1691
2168
|
if (normalizedName === "AskUserQuestion") {
|
|
@@ -1697,7 +2174,7 @@ function ToolCallBlock({
|
|
|
1697
2174
|
});
|
|
1698
2175
|
const canAnswer = questionState.awaitingAnswer && Boolean(onAnswer);
|
|
1699
2176
|
if (askData) {
|
|
1700
|
-
return /* @__PURE__ */
|
|
2177
|
+
return /* @__PURE__ */ jsx10(
|
|
1701
2178
|
AskUserQuestionBlock,
|
|
1702
2179
|
{
|
|
1703
2180
|
data: askData,
|
|
@@ -1710,24 +2187,24 @@ function ToolCallBlock({
|
|
|
1710
2187
|
);
|
|
1711
2188
|
}
|
|
1712
2189
|
if (toolCall.status === "pending") {
|
|
1713
|
-
return /* @__PURE__ */
|
|
1714
|
-
/* @__PURE__ */
|
|
1715
|
-
/* @__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" })
|
|
1716
2193
|
] });
|
|
1717
2194
|
}
|
|
1718
|
-
return /* @__PURE__ */
|
|
1719
|
-
/* @__PURE__ */
|
|
1720
|
-
/* @__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" })
|
|
1721
2198
|
] });
|
|
1722
2199
|
}
|
|
1723
2200
|
const tone = getToolTone(toolCall.status);
|
|
1724
2201
|
const displayName = getToolDisplayLabel(toolCall);
|
|
1725
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))]";
|
|
1726
|
-
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 });
|
|
1727
2204
|
const statusTextClass = tone === "red" ? "text-[hsl(var(--muted-foreground))]" : tone === "amber" ? "text-amber-300" : tone === "blue" ? "text-blue-300" : "text-[hsl(var(--primary))]";
|
|
1728
|
-
return /* @__PURE__ */
|
|
1729
|
-
/* @__PURE__ */
|
|
1730
|
-
/* @__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(
|
|
1731
2208
|
"button",
|
|
1732
2209
|
{
|
|
1733
2210
|
type: "button",
|
|
@@ -1735,7 +2212,7 @@ function ToolCallBlock({
|
|
|
1735
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",
|
|
1736
2213
|
"aria-expanded": expanded,
|
|
1737
2214
|
children: [
|
|
1738
|
-
/* @__PURE__ */
|
|
2215
|
+
/* @__PURE__ */ jsx10(
|
|
1739
2216
|
ChevronRight,
|
|
1740
2217
|
{
|
|
1741
2218
|
size: 11,
|
|
@@ -1745,24 +2222,24 @@ function ToolCallBlock({
|
|
|
1745
2222
|
)
|
|
1746
2223
|
}
|
|
1747
2224
|
),
|
|
1748
|
-
/* @__PURE__ */
|
|
2225
|
+
/* @__PURE__ */ jsxs8("span", { className: cn("flex shrink-0 items-center gap-1 text-[10px]", statusTextClass), children: [
|
|
1749
2226
|
statusIcon,
|
|
1750
|
-
/* @__PURE__ */
|
|
2227
|
+
/* @__PURE__ */ jsx10("span", { children: getToolStatusLabel(toolCall.status) })
|
|
1751
2228
|
] }),
|
|
1752
|
-
/* @__PURE__ */
|
|
2229
|
+
/* @__PURE__ */ jsx10("span", { className: "min-w-0 flex-1 truncate font-medium text-[hsl(var(--foreground))]", children: displayName })
|
|
1753
2230
|
]
|
|
1754
2231
|
}
|
|
1755
2232
|
),
|
|
1756
|
-
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) })
|
|
1757
2234
|
] }),
|
|
1758
|
-
expanded && /* @__PURE__ */
|
|
1759
|
-
/* @__PURE__ */
|
|
1760
|
-
/* @__PURE__ */
|
|
1761
|
-
/* @__PURE__ */
|
|
1762
|
-
/* @__PURE__ */
|
|
1763
|
-
toolCall.result != null && /* @__PURE__ */
|
|
1764
|
-
/* @__PURE__ */
|
|
1765
|
-
/* @__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) })
|
|
1766
2243
|
] })
|
|
1767
2244
|
] })
|
|
1768
2245
|
] });
|
|
@@ -1780,11 +2257,11 @@ function buildAskUserPayload(argumentsJson) {
|
|
|
1780
2257
|
}
|
|
1781
2258
|
|
|
1782
2259
|
// src/components/AssistantTurnBlock.tsx
|
|
1783
|
-
import { jsx as
|
|
2260
|
+
import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1784
2261
|
function ThinkingBlock({ reasoning, isStreaming }) {
|
|
1785
|
-
const [open, setOpen] =
|
|
1786
|
-
return /* @__PURE__ */
|
|
1787
|
-
/* @__PURE__ */
|
|
2262
|
+
const [open, setOpen] = useState10(false);
|
|
2263
|
+
return /* @__PURE__ */ jsxs9("div", { className: "blade-chat-thinking ml-4 text-sm", children: [
|
|
2264
|
+
/* @__PURE__ */ jsxs9(
|
|
1788
2265
|
"button",
|
|
1789
2266
|
{
|
|
1790
2267
|
type: "button",
|
|
@@ -1792,14 +2269,14 @@ function ThinkingBlock({ reasoning, isStreaming }) {
|
|
|
1792
2269
|
"aria-expanded": open,
|
|
1793
2270
|
className: "inline-flex items-center gap-1.5 text-[hsl(var(--muted-foreground))] transition-colors hover:text-[hsl(var(--foreground))]",
|
|
1794
2271
|
children: [
|
|
1795
|
-
/* @__PURE__ */
|
|
1796
|
-
isStreaming ? /* @__PURE__ */
|
|
1797
|
-
/* @__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: [
|
|
1798
2275
|
"\xB7 ",
|
|
1799
2276
|
new Intl.NumberFormat("zh-CN").format(reasoning.length),
|
|
1800
2277
|
" \u5B57"
|
|
1801
2278
|
] }),
|
|
1802
|
-
/* @__PURE__ */
|
|
2279
|
+
/* @__PURE__ */ jsx11(
|
|
1803
2280
|
ChevronDown,
|
|
1804
2281
|
{
|
|
1805
2282
|
size: 12,
|
|
@@ -1809,7 +2286,7 @@ function ThinkingBlock({ reasoning, isStreaming }) {
|
|
|
1809
2286
|
]
|
|
1810
2287
|
}
|
|
1811
2288
|
),
|
|
1812
|
-
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 })
|
|
1813
2290
|
] });
|
|
1814
2291
|
}
|
|
1815
2292
|
function getMessageText(message) {
|
|
@@ -1835,21 +2312,21 @@ function AssistantTurnBlock({
|
|
|
1835
2312
|
(message) => getMessageText(message) || message.reasoning || (message.tool_calls?.length ?? 0) > 0
|
|
1836
2313
|
);
|
|
1837
2314
|
const latestReasoningIndex = isStreaming ? findLatestReasoningMessageIndex(messages) : -1;
|
|
1838
|
-
return /* @__PURE__ */
|
|
1839
|
-
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" }),
|
|
1840
2317
|
messages.map((message, index) => {
|
|
1841
2318
|
const isLast = index === messages.length - 1;
|
|
1842
2319
|
const streamingThis = isStreaming && isLast;
|
|
1843
2320
|
const text = getMessageText(message);
|
|
1844
2321
|
const toolCalls = message.tool_calls ?? [];
|
|
1845
2322
|
const showReasoning = !!message.reasoning && (!isStreaming || index === latestReasoningIndex);
|
|
1846
|
-
return /* @__PURE__ */
|
|
2323
|
+
return /* @__PURE__ */ jsxs9(
|
|
1847
2324
|
"div",
|
|
1848
2325
|
{
|
|
1849
2326
|
className: "flex flex-col gap-3",
|
|
1850
2327
|
children: [
|
|
1851
|
-
showReasoning && message.reasoning && /* @__PURE__ */
|
|
1852
|
-
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(
|
|
1853
2330
|
MarkdownContent,
|
|
1854
2331
|
{
|
|
1855
2332
|
mode: streamingThis ? "streaming" : "static",
|
|
@@ -1858,8 +2335,8 @@ function AssistantTurnBlock({
|
|
|
1858
2335
|
children: text
|
|
1859
2336
|
}
|
|
1860
2337
|
) }),
|
|
1861
|
-
toolCalls.length > 0 && /* @__PURE__ */
|
|
1862
|
-
(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(
|
|
1863
2340
|
ToolCallBlock,
|
|
1864
2341
|
{
|
|
1865
2342
|
toolCall,
|
|
@@ -1877,13 +2354,13 @@ function AssistantTurnBlock({
|
|
|
1877
2354
|
message.entry_id ?? `${message.timestamp ?? "assistant"}-${index}`
|
|
1878
2355
|
);
|
|
1879
2356
|
}),
|
|
1880
|
-
isStreaming && !hasAnyContent && /* @__PURE__ */
|
|
2357
|
+
isStreaming && !hasAnyContent && /* @__PURE__ */ jsx11(Shimmer, { className: "ml-4", children: "\u6B63\u5728\u751F\u6210..." })
|
|
1881
2358
|
] });
|
|
1882
2359
|
}
|
|
1883
2360
|
|
|
1884
2361
|
// src/components/RenderErrorBoundary.tsx
|
|
1885
2362
|
import { Component } from "react";
|
|
1886
|
-
import { jsx as
|
|
2363
|
+
import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1887
2364
|
function getFirstComponentName(componentStack) {
|
|
1888
2365
|
const match = componentStack.match(/\n\s+at\s+([^\s(]+)/);
|
|
1889
2366
|
return match?.[1] ?? null;
|
|
@@ -1916,62 +2393,448 @@ var RenderErrorBoundary = class extends Component {
|
|
|
1916
2393
|
return children;
|
|
1917
2394
|
}
|
|
1918
2395
|
const componentName = getFirstComponentName(componentStack);
|
|
1919
|
-
return /* @__PURE__ */
|
|
1920
|
-
/* @__PURE__ */
|
|
1921
|
-
/* @__PURE__ */
|
|
1922
|
-
/* @__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: [
|
|
1923
2400
|
label,
|
|
1924
2401
|
"\u6E32\u67D3\u5931\u8D25"
|
|
1925
2402
|
] }),
|
|
1926
|
-
/* @__PURE__ */
|
|
2403
|
+
/* @__PURE__ */ jsxs10("div", { className: "mt-1 break-words text-xs leading-5 text-amber-100/75", children: [
|
|
1927
2404
|
componentName ? `\u7EC4\u4EF6\uFF1A${componentName}\u3002` : null,
|
|
1928
2405
|
error.message || "\u53D1\u751F\u4E86\u672A\u9884\u671F\u7684\u6E32\u67D3\u9519\u8BEF\u3002"
|
|
1929
2406
|
] }),
|
|
1930
|
-
details ? /* @__PURE__ */
|
|
2407
|
+
details ? /* @__PURE__ */ jsx12("div", { className: "mt-1 truncate text-xs text-amber-100/55", children: details }) : null
|
|
1931
2408
|
] })
|
|
1932
2409
|
] }) });
|
|
1933
2410
|
}
|
|
1934
2411
|
};
|
|
1935
2412
|
|
|
1936
|
-
// src/components/
|
|
1937
|
-
import {
|
|
1938
|
-
import { jsx as
|
|
1939
|
-
function
|
|
1940
|
-
|
|
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
|
+
}
|
|
1941
2421
|
}
|
|
1942
|
-
function
|
|
1943
|
-
return
|
|
2422
|
+
function basename(path) {
|
|
2423
|
+
return path.split(/[\\/]/).filter(Boolean).pop() || path;
|
|
1944
2424
|
}
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
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",
|
|
1953
2439
|
{
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
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 }) {
|
|
2810
|
+
const text = getTextContent2(message.content).trim();
|
|
2811
|
+
const fileParts = getFileParts(message.content);
|
|
2812
|
+
const imageParts = getImageParts(message.content);
|
|
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(
|
|
2815
|
+
"img",
|
|
2816
|
+
{
|
|
2817
|
+
src: part.image_url.url,
|
|
2818
|
+
alt: "\u7528\u6237\u4E0A\u4F20\u7684\u56FE\u7247",
|
|
2819
|
+
className: "max-h-64 rounded-xl border border-[hsl(var(--user-msg-border))] object-cover"
|
|
2820
|
+
},
|
|
2821
|
+
part.image_url.url
|
|
1959
2822
|
)) }),
|
|
1960
|
-
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(
|
|
1961
2824
|
"div",
|
|
1962
2825
|
{
|
|
1963
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))]",
|
|
1964
2827
|
children: [
|
|
1965
|
-
/* @__PURE__ */
|
|
1966
|
-
/* @__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 })
|
|
1967
2830
|
]
|
|
1968
2831
|
},
|
|
1969
2832
|
`${part.name}-${part.data.length}`
|
|
1970
2833
|
)) }),
|
|
1971
|
-
text && /* @__PURE__ */
|
|
1972
|
-
text && isSending(message) && /* @__PURE__ */
|
|
1973
|
-
/* @__PURE__ */
|
|
1974
|
-
/* @__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" })
|
|
1975
2838
|
] })
|
|
1976
2839
|
] }) });
|
|
1977
2840
|
}
|
|
@@ -1980,11 +2843,11 @@ function ErrorMessageBlock({
|
|
|
1980
2843
|
className
|
|
1981
2844
|
}) {
|
|
1982
2845
|
const text = getTextContent2(message.content);
|
|
1983
|
-
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 }) });
|
|
1984
2847
|
}
|
|
1985
2848
|
|
|
1986
2849
|
// src/components/MessageList.tsx
|
|
1987
|
-
import { jsx as
|
|
2850
|
+
import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1988
2851
|
function parseModeChange(message) {
|
|
1989
2852
|
if (message.kind !== "mode_change" || typeof message.content !== "string") {
|
|
1990
2853
|
return null;
|
|
@@ -2018,6 +2881,8 @@ function getMessageResetSignature(messages) {
|
|
|
2018
2881
|
}
|
|
2019
2882
|
function MessageList({
|
|
2020
2883
|
messages,
|
|
2884
|
+
postChatFollowup,
|
|
2885
|
+
onSuggestion,
|
|
2021
2886
|
isStreaming,
|
|
2022
2887
|
sessionStatus,
|
|
2023
2888
|
askAnswers,
|
|
@@ -2025,9 +2890,13 @@ function MessageList({
|
|
|
2025
2890
|
toolCallRenderer,
|
|
2026
2891
|
emptyState,
|
|
2027
2892
|
className,
|
|
2028
|
-
sessionId
|
|
2893
|
+
sessionId,
|
|
2894
|
+
isViewer = false,
|
|
2895
|
+
onFollowupInteraction,
|
|
2896
|
+
resultFeedbackByEntry = /* @__PURE__ */ new Map(),
|
|
2897
|
+
onResultFeedbackSaved
|
|
2029
2898
|
}) {
|
|
2030
|
-
const renderBlocks =
|
|
2899
|
+
const renderBlocks = useMemo7(() => {
|
|
2031
2900
|
const visible = messages.filter((message) => {
|
|
2032
2901
|
if ((message.loop_name ?? "root") !== "root") return false;
|
|
2033
2902
|
if (isHiddenInternalMessage(message)) return false;
|
|
@@ -2073,7 +2942,7 @@ function MessageList({
|
|
|
2073
2942
|
blocks.push({
|
|
2074
2943
|
type: "message",
|
|
2075
2944
|
message,
|
|
2076
|
-
key: message.entry_id ?? `${message.role}-${blocks.length}`
|
|
2945
|
+
key: message.render_id ?? message.entry_id ?? `${message.role}-${blocks.length}`
|
|
2077
2946
|
});
|
|
2078
2947
|
}
|
|
2079
2948
|
flushAssistant();
|
|
@@ -2100,63 +2969,86 @@ function MessageList({
|
|
|
2100
2969
|
}
|
|
2101
2970
|
return blocks;
|
|
2102
2971
|
}, [messages, isStreaming]);
|
|
2103
|
-
return /* @__PURE__ */
|
|
2104
|
-
/* @__PURE__ */
|
|
2105
|
-
renderBlocks.length === 0 ? emptyState ?? /* @__PURE__ */
|
|
2106
|
-
/* @__PURE__ */
|
|
2107
|
-
/* @__PURE__ */
|
|
2108
|
-
/* @__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" })
|
|
2109
2978
|
] }) : renderBlocks.map((block) => {
|
|
2110
2979
|
if (block.type === "message") {
|
|
2111
|
-
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);
|
|
2112
2981
|
}
|
|
2113
2982
|
if (block.type === "assistant_turn") {
|
|
2114
|
-
|
|
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(
|
|
2115
2992
|
RenderErrorBoundary,
|
|
2116
2993
|
{
|
|
2117
2994
|
label: "\u52A9\u624B\u6D88\u606F",
|
|
2118
2995
|
details: block.key,
|
|
2119
2996
|
resetKey: getMessageResetSignature(block.messages),
|
|
2120
|
-
children:
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
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
|
+
]
|
|
2132
3024
|
}
|
|
2133
3025
|
) }, block.key);
|
|
2134
3026
|
}
|
|
2135
3027
|
if (block.type === "compaction") {
|
|
2136
|
-
return /* @__PURE__ */
|
|
3028
|
+
return /* @__PURE__ */ jsxs13(
|
|
2137
3029
|
"div",
|
|
2138
3030
|
{
|
|
2139
3031
|
className: "flex items-center gap-2 text-xs text-[hsl(var(--muted-foreground))]",
|
|
2140
3032
|
children: [
|
|
2141
|
-
/* @__PURE__ */
|
|
2142
|
-
/* @__PURE__ */
|
|
3033
|
+
/* @__PURE__ */ jsx15(Layers, { size: 12 }),
|
|
3034
|
+
/* @__PURE__ */ jsx15("span", { children: "\u4E0A\u4E0B\u6587\u5DF2\u538B\u7F29" })
|
|
2143
3035
|
]
|
|
2144
3036
|
},
|
|
2145
3037
|
block.key
|
|
2146
3038
|
);
|
|
2147
3039
|
}
|
|
2148
|
-
return /* @__PURE__ */
|
|
3040
|
+
return /* @__PURE__ */ jsx15(PlanningDivider, { kind: block.kind }, block.key);
|
|
2149
3041
|
}),
|
|
2150
|
-
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
|
|
2151
3043
|
] }) }) }),
|
|
2152
|
-
/* @__PURE__ */
|
|
2153
|
-
/* @__PURE__ */
|
|
3044
|
+
/* @__PURE__ */ jsx15(AutoScrollOnUserSend, { userMessageCount: messages.filter((m) => isUserMessage(m)).length }),
|
|
3045
|
+
/* @__PURE__ */ jsx15(ScrollToBottomButton, {})
|
|
2154
3046
|
] }) });
|
|
2155
3047
|
}
|
|
2156
3048
|
function AutoScrollOnUserSend({ userMessageCount }) {
|
|
2157
3049
|
const { scrollToBottom } = useStickToBottomContext();
|
|
2158
|
-
const previousCountRef =
|
|
2159
|
-
|
|
3050
|
+
const previousCountRef = useRef8(userMessageCount);
|
|
3051
|
+
useEffect8(() => {
|
|
2160
3052
|
if (userMessageCount > previousCountRef.current) {
|
|
2161
3053
|
scrollToBottom("instant");
|
|
2162
3054
|
}
|
|
@@ -2166,9 +3058,9 @@ function AutoScrollOnUserSend({ userMessageCount }) {
|
|
|
2166
3058
|
}
|
|
2167
3059
|
function ScrollToBottomButton() {
|
|
2168
3060
|
const { isAtBottom, scrollToBottom } = useStickToBottomContext();
|
|
2169
|
-
const [visible, setVisible] =
|
|
2170
|
-
const hideTimerRef =
|
|
2171
|
-
|
|
3061
|
+
const [visible, setVisible] = useState12(false);
|
|
3062
|
+
const hideTimerRef = useRef8(null);
|
|
3063
|
+
useEffect8(() => {
|
|
2172
3064
|
if (isAtBottom) {
|
|
2173
3065
|
if (!hideTimerRef.current) {
|
|
2174
3066
|
hideTimerRef.current = setTimeout(() => {
|
|
@@ -2190,7 +3082,7 @@ function ScrollToBottomButton() {
|
|
|
2190
3082
|
}
|
|
2191
3083
|
};
|
|
2192
3084
|
}, [isAtBottom]);
|
|
2193
|
-
const handleClick =
|
|
3085
|
+
const handleClick = useCallback6(() => {
|
|
2194
3086
|
if (hideTimerRef.current) {
|
|
2195
3087
|
clearTimeout(hideTimerRef.current);
|
|
2196
3088
|
hideTimerRef.current = null;
|
|
@@ -2199,7 +3091,7 @@ function ScrollToBottomButton() {
|
|
|
2199
3091
|
scrollToBottom();
|
|
2200
3092
|
}, [scrollToBottom]);
|
|
2201
3093
|
if (!visible) return null;
|
|
2202
|
-
return /* @__PURE__ */
|
|
3094
|
+
return /* @__PURE__ */ jsxs13(
|
|
2203
3095
|
"button",
|
|
2204
3096
|
{
|
|
2205
3097
|
type: "button",
|
|
@@ -2207,34 +3099,120 @@ function ScrollToBottomButton() {
|
|
|
2207
3099
|
"aria-label": "\u6EDA\u52A8\u5230\u5E95\u90E8",
|
|
2208
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))]",
|
|
2209
3101
|
children: [
|
|
2210
|
-
/* @__PURE__ */
|
|
2211
|
-
/* @__PURE__ */
|
|
3102
|
+
/* @__PURE__ */ jsx15(ChevronDown, { size: 14 }),
|
|
3103
|
+
/* @__PURE__ */ jsx15("span", { className: "blade-chat-scroll-bottom-label", children: "\u6EDA\u52A8\u5230\u5E95\u90E8" })
|
|
2212
3104
|
]
|
|
2213
3105
|
}
|
|
2214
3106
|
);
|
|
2215
3107
|
}
|
|
2216
3108
|
function PlanningDivider({ kind }) {
|
|
2217
|
-
return /* @__PURE__ */
|
|
2218
|
-
/* @__PURE__ */
|
|
2219
|
-
/* @__PURE__ */
|
|
2220
|
-
/* @__PURE__ */
|
|
2221
|
-
/* @__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" })
|
|
2222
3114
|
] }),
|
|
2223
|
-
/* @__PURE__ */
|
|
3115
|
+
/* @__PURE__ */ jsx15("div", { className: "h-px flex-1 bg-gradient-to-r from-transparent via-amber-400/40 to-transparent" })
|
|
2224
3116
|
] });
|
|
2225
3117
|
}
|
|
2226
3118
|
|
|
2227
|
-
// src/components/
|
|
2228
|
-
import { jsx as
|
|
3119
|
+
// src/components/ChatSurface.tsx
|
|
3120
|
+
import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
2229
3121
|
function themeAttr(theme) {
|
|
2230
3122
|
return theme === "dark" ? "dark" : void 0;
|
|
2231
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";
|
|
2232
3210
|
function isUnauthorizedError(error) {
|
|
2233
3211
|
return error instanceof BladeApiError && error.status === 401;
|
|
2234
3212
|
}
|
|
2235
3213
|
function LoginCard({ client, onLoggedIn }) {
|
|
2236
|
-
const [loggingIn, setLoggingIn] =
|
|
2237
|
-
const [loginError, setLoginError] =
|
|
3214
|
+
const [loggingIn, setLoggingIn] = useState13(false);
|
|
3215
|
+
const [loginError, setLoginError] = useState13(null);
|
|
2238
3216
|
const handleLogin = async () => {
|
|
2239
3217
|
setLoggingIn(true);
|
|
2240
3218
|
setLoginError(null);
|
|
@@ -2247,11 +3225,11 @@ function LoginCard({ client, onLoggedIn }) {
|
|
|
2247
3225
|
setLoggingIn(false);
|
|
2248
3226
|
}
|
|
2249
3227
|
};
|
|
2250
|
-
return /* @__PURE__ */
|
|
2251
|
-
/* @__PURE__ */
|
|
2252
|
-
/* @__PURE__ */
|
|
2253
|
-
/* @__PURE__ */
|
|
2254
|
-
/* @__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(
|
|
2255
3233
|
"button",
|
|
2256
3234
|
{
|
|
2257
3235
|
type: "button",
|
|
@@ -2261,20 +3239,20 @@ function LoginCard({ client, onLoggedIn }) {
|
|
|
2261
3239
|
children: loggingIn ? "\u767B\u5F55\u4E2D\u2026" : "\u767B\u5F55"
|
|
2262
3240
|
}
|
|
2263
3241
|
),
|
|
2264
|
-
loginError && /* @__PURE__ */
|
|
3242
|
+
loginError && /* @__PURE__ */ jsx17("div", { className: "text-xs text-[hsl(var(--destructive))]", children: loginError })
|
|
2265
3243
|
] }) });
|
|
2266
3244
|
}
|
|
2267
|
-
function
|
|
3245
|
+
function AgentChat(props) {
|
|
2268
3246
|
const client = useBladeClient();
|
|
2269
|
-
const [attempt, setAttempt] =
|
|
2270
|
-
const [needLogin, setNeedLogin] =
|
|
3247
|
+
const [attempt, setAttempt] = useState13(0);
|
|
3248
|
+
const [needLogin, setNeedLogin] = useState13(() => !client.hasToken());
|
|
2271
3249
|
if (needLogin) {
|
|
2272
|
-
return /* @__PURE__ */
|
|
3250
|
+
return /* @__PURE__ */ jsx17(
|
|
2273
3251
|
"div",
|
|
2274
3252
|
{
|
|
2275
3253
|
"data-theme": themeAttr(props.theme),
|
|
2276
3254
|
className: cn("blade-chat flex min-h-0 flex-1 flex-col", props.classNames?.root),
|
|
2277
|
-
children: /* @__PURE__ */
|
|
3255
|
+
children: /* @__PURE__ */ jsx17(
|
|
2278
3256
|
LoginCard,
|
|
2279
3257
|
{
|
|
2280
3258
|
client,
|
|
@@ -2287,7 +3265,7 @@ function ChatView(props) {
|
|
|
2287
3265
|
}
|
|
2288
3266
|
);
|
|
2289
3267
|
}
|
|
2290
|
-
return /* @__PURE__ */
|
|
3268
|
+
return /* @__PURE__ */ jsx17(ChatSessionView, { ...props, onUnauthorized: () => setNeedLogin(true) }, attempt);
|
|
2291
3269
|
}
|
|
2292
3270
|
function ChatSessionView({
|
|
2293
3271
|
sessionId,
|
|
@@ -2300,20 +3278,61 @@ function ChatSessionView({
|
|
|
2300
3278
|
slots,
|
|
2301
3279
|
placeholder,
|
|
2302
3280
|
theme,
|
|
3281
|
+
onFollowupInteraction,
|
|
2303
3282
|
onUnauthorized
|
|
2304
3283
|
}) {
|
|
3284
|
+
const client = useBladeClient();
|
|
2305
3285
|
const { session, state, error } = useAgentSession(sessionId, {
|
|
2306
3286
|
createOptions,
|
|
2307
3287
|
onSessionCreated
|
|
2308
3288
|
});
|
|
2309
|
-
const
|
|
2310
|
-
const [
|
|
2311
|
-
|
|
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(() => {
|
|
2312
3331
|
if (session) {
|
|
2313
3332
|
onSessionReady?.(session);
|
|
2314
3333
|
}
|
|
2315
3334
|
}, [session, onSessionReady]);
|
|
2316
|
-
|
|
3335
|
+
useEffect9(() => {
|
|
2317
3336
|
if (!session) return;
|
|
2318
3337
|
const offAttach = session.on("attachRequested", ({ label, content }) => {
|
|
2319
3338
|
setInputText((prev) => `${prev ? `${prev}
|
|
@@ -2329,12 +3348,12 @@ ${content}`);
|
|
|
2329
3348
|
offInsert();
|
|
2330
3349
|
};
|
|
2331
3350
|
}, [session]);
|
|
2332
|
-
|
|
3351
|
+
useEffect9(() => {
|
|
2333
3352
|
if (isUnauthorizedError(error)) {
|
|
2334
3353
|
onUnauthorized();
|
|
2335
3354
|
}
|
|
2336
3355
|
}, [error, onUnauthorized]);
|
|
2337
|
-
|
|
3356
|
+
useEffect9(() => {
|
|
2338
3357
|
if (!session || !commands) return;
|
|
2339
3358
|
const unsubscribes = Object.entries(commands).map(
|
|
2340
3359
|
([action, handler]) => session.onCommand(action, (payload) => handler(payload))
|
|
@@ -2346,7 +3365,7 @@ ${content}`);
|
|
|
2346
3365
|
const isStreaming = state?.isStreaming ?? false;
|
|
2347
3366
|
const isStopping = stopRequested && isStreaming;
|
|
2348
3367
|
const connectError = error && !isUnauthorizedError(error) ? error.message || "\u8FDE\u63A5\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5" : null;
|
|
2349
|
-
const errorMessage = connectError ?? state?.errorMessage ?? null;
|
|
3368
|
+
const errorMessage = connectError ?? state?.errorMessage ?? replay.error?.message ?? null;
|
|
2350
3369
|
const handleSend = (text) => {
|
|
2351
3370
|
setStopRequested(false);
|
|
2352
3371
|
return session?.send(text, { mode: state?.mode ?? void 0 }) ?? Promise.resolve(false);
|
|
@@ -2355,73 +3374,287 @@ ${content}`);
|
|
|
2355
3374
|
setStopRequested(true);
|
|
2356
3375
|
void session?.stop();
|
|
2357
3376
|
};
|
|
2358
|
-
return /* @__PURE__ */
|
|
2359
|
-
|
|
3377
|
+
return /* @__PURE__ */ jsx17(
|
|
3378
|
+
ChatSurface,
|
|
2360
3379
|
{
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
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,
|
|
2369
3390
|
{
|
|
2370
|
-
|
|
2371
|
-
|
|
3391
|
+
isReplay: replay.isReplay,
|
|
3392
|
+
speed: replay.speed,
|
|
3393
|
+
canControl: replay.canControl,
|
|
3394
|
+
onSpeedChange: (next) => void replay.setSpeed(next),
|
|
3395
|
+
onExit: () => void replay.exitToAutonomous()
|
|
2372
3396
|
}
|
|
2373
3397
|
),
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
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",
|
|
2381
3504
|
{
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
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);
|
|
2391
3525
|
},
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
className: classNames?.messageList,
|
|
2395
|
-
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"
|
|
2396
3528
|
}
|
|
2397
3529
|
),
|
|
2398
|
-
/* @__PURE__ */
|
|
2399
|
-
|
|
3530
|
+
/* @__PURE__ */ jsx18(
|
|
3531
|
+
"button",
|
|
2400
3532
|
{
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
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"
|
|
2409
3541
|
}
|
|
2410
|
-
)
|
|
2411
|
-
|
|
2412
|
-
|
|
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
|
|
2413
3616
|
}
|
|
2414
3617
|
);
|
|
2415
3618
|
}
|
|
2416
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
|
+
|
|
2417
3644
|
// src/index.ts
|
|
2418
3645
|
export * from "@blade-hq/agent-client";
|
|
2419
3646
|
export {
|
|
3647
|
+
AgentChat,
|
|
2420
3648
|
BladeProvider,
|
|
2421
3649
|
ChatView,
|
|
3650
|
+
LlmChat,
|
|
2422
3651
|
MarkdownContent,
|
|
3652
|
+
ReplayBar,
|
|
3653
|
+
ReplayMismatchPrompt,
|
|
2423
3654
|
useAgentSession,
|
|
2424
|
-
useBladeClient
|
|
3655
|
+
useBladeClient,
|
|
3656
|
+
useLlmChat,
|
|
3657
|
+
useReplay
|
|
2425
3658
|
};
|
|
2426
3659
|
/*! Bundled license information:
|
|
2427
3660
|
|
|
@@ -2429,6 +3662,8 @@ lucide-react/dist/esm/shared/src/utils.js:
|
|
|
2429
3662
|
lucide-react/dist/esm/defaultAttributes.js:
|
|
2430
3663
|
lucide-react/dist/esm/Icon.js:
|
|
2431
3664
|
lucide-react/dist/esm/createLucideIcon.js:
|
|
3665
|
+
lucide-react/dist/esm/icons/arrow-right.js:
|
|
3666
|
+
lucide-react/dist/esm/icons/arrow-up-right.js:
|
|
2432
3667
|
lucide-react/dist/esm/icons/arrow-up.js:
|
|
2433
3668
|
lucide-react/dist/esm/icons/bot.js:
|
|
2434
3669
|
lucide-react/dist/esm/icons/brain.js:
|
|
@@ -2438,12 +3673,16 @@ lucide-react/dist/esm/icons/chevron-right.js:
|
|
|
2438
3673
|
lucide-react/dist/esm/icons/circle-alert.js:
|
|
2439
3674
|
lucide-react/dist/esm/icons/copy.js:
|
|
2440
3675
|
lucide-react/dist/esm/icons/file-text.js:
|
|
3676
|
+
lucide-react/dist/esm/icons/globe.js:
|
|
2441
3677
|
lucide-react/dist/esm/icons/layers.js:
|
|
2442
3678
|
lucide-react/dist/esm/icons/lightbulb.js:
|
|
2443
3679
|
lucide-react/dist/esm/icons/loader-circle.js:
|
|
2444
3680
|
lucide-react/dist/esm/icons/lock-keyhole.js:
|
|
2445
3681
|
lucide-react/dist/esm/icons/message-square-more.js:
|
|
2446
3682
|
lucide-react/dist/esm/icons/message-square.js:
|
|
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:
|
|
2447
3686
|
lucide-react/dist/esm/icons/square.js:
|
|
2448
3687
|
lucide-react/dist/esm/icons/triangle-alert.js:
|
|
2449
3688
|
lucide-react/dist/esm/icons/x.js:
|