@blade-hq/agent-react 2608.0.5 → 2608.0.7-beta.0

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