@blade-hq/agent-react 2608.0.2 → 2608.0.3

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,7 +99,452 @@ function useAgentSession(sessionId, options = {}) {
99
99
  return { session, state, error };
100
100
  }
101
101
 
102
- // src/components/ChatView.tsx
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
103
548
  import { BladeApiError } 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
@@ -376,6 +821,19 @@ var Music = createLucideIcon("Music", [
376
821
  ["circle", { cx: "18", cy: "16", r: "3", key: "1hluhg" }]
377
822
  ]);
378
823
 
824
+ // ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/play.js
825
+ var Play = createLucideIcon("Play", [
826
+ ["polygon", { points: "6 3 20 12 6 21 6 3", key: "1oa8hb" }]
827
+ ]);
828
+
829
+ // ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/settings-2.js
830
+ var Settings2 = createLucideIcon("Settings2", [
831
+ ["path", { d: "M20 7h-9", key: "3s1dr2" }],
832
+ ["path", { d: "M14 17H5", key: "gfn3mx" }],
833
+ ["circle", { cx: "17", cy: "17", r: "3", key: "18b49y" }],
834
+ ["circle", { cx: "7", cy: "7", r: "3", key: "dfmy0x" }]
835
+ ]);
836
+
379
837
  // ../../node_modules/.pnpm/lucide-react@0.468.0_react@19.2.4/node_modules/lucide-react/dist/esm/icons/square.js
380
838
  var Square = createLucideIcon("Square", [
381
839
  ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", key: "afitv7" }]
@@ -400,8 +858,8 @@ var X = createLucideIcon("X", [
400
858
  ["path", { d: "m6 6 12 12", key: "d8bk6v" }]
401
859
  ]);
402
860
 
403
- // src/components/ChatView.tsx
404
- import { useEffect as useEffect6, useState as useState10 } from "react";
861
+ // src/components/AgentChat.tsx
862
+ import { useEffect as useEffect7, useState as useState12 } from "react";
405
863
 
406
864
  // src/lib/utils.ts
407
865
  function cn(...inputs) {
@@ -416,8 +874,120 @@ async function copyToClipboard(text) {
416
874
  }
417
875
  }
418
876
 
419
- // src/components/ChatInput.tsx
877
+ // src/components/ReplayBar.tsx
420
878
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
879
+ var SPEED_OPTIONS = [1, 2, 5];
880
+ function ReplayBar({
881
+ isReplay,
882
+ speed,
883
+ onSpeedChange,
884
+ onExit,
885
+ canControl = true,
886
+ className
887
+ }) {
888
+ if (!isReplay) return null;
889
+ return /* @__PURE__ */ jsxs(
890
+ "div",
891
+ {
892
+ className: cn(
893
+ "flex flex-wrap items-center gap-2 border-b border-[hsl(var(--border))] bg-[hsl(var(--muted))]/40 px-4 py-2 text-xs",
894
+ className
895
+ ),
896
+ children: [
897
+ /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5 font-medium text-[hsl(var(--foreground))]", children: [
898
+ /* @__PURE__ */ jsx2(Play, { size: 13 }),
899
+ "\u56DE\u653E\u6A21\u5F0F"
900
+ ] }),
901
+ /* @__PURE__ */ jsx2("span", { className: "text-[hsl(var(--muted-foreground))]", children: "\u6B63\u5728\u91CD\u73B0\u4E4B\u524D\u7684\u5BF9\u8BDD" }),
902
+ /* @__PURE__ */ jsxs("div", { className: "ml-auto flex items-center gap-2", children: [
903
+ /* @__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(
904
+ "button",
905
+ {
906
+ type: "button",
907
+ onClick: () => onSpeedChange(option),
908
+ "aria-pressed": speed === option,
909
+ disabled: !canControl,
910
+ className: cn(
911
+ "h-6 rounded px-2 font-medium transition-colors",
912
+ 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))]",
913
+ !canControl && "cursor-not-allowed opacity-50"
914
+ ),
915
+ children: [
916
+ option,
917
+ "x"
918
+ ]
919
+ },
920
+ option
921
+ )) }),
922
+ /* @__PURE__ */ jsx2(
923
+ "button",
924
+ {
925
+ type: "button",
926
+ onClick: onExit,
927
+ disabled: !canControl,
928
+ className: cn(
929
+ "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))]",
930
+ !canControl && "cursor-not-allowed opacity-50 hover:bg-transparent"
931
+ ),
932
+ children: "\u9000\u51FA\u56DE\u653E"
933
+ }
934
+ )
935
+ ] })
936
+ ]
937
+ }
938
+ );
939
+ }
940
+
941
+ // src/components/ReplayMismatchPrompt.tsx
942
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
943
+ function ReplayMismatchPrompt({ mismatch, className }) {
944
+ if (!mismatch) return null;
945
+ return /* @__PURE__ */ jsxs2(
946
+ "div",
947
+ {
948
+ className: cn(
949
+ "mx-auto my-3 max-w-3xl rounded-2xl border border-[hsl(var(--border))] bg-[hsl(var(--card))] px-4 py-3 text-sm",
950
+ className
951
+ ),
952
+ children: [
953
+ /* @__PURE__ */ jsx3("div", { className: "font-medium text-[hsl(var(--foreground))]", children: "\u8FD9\u53E5\u8BDD\u548C\u4E4B\u524D\u5F55\u5236\u7684\u4E0D\u4E00\u6837" }),
954
+ /* @__PURE__ */ jsxs2("dl", { className: "mt-2 space-y-1 text-xs text-[hsl(var(--muted-foreground))]", children: [
955
+ /* @__PURE__ */ jsxs2("div", { className: "flex gap-2", children: [
956
+ /* @__PURE__ */ jsx3("dt", { className: "shrink-0", children: "\u5F55\u5236\u7684\u662F" }),
957
+ /* @__PURE__ */ jsx3("dd", { className: "min-w-0 break-words text-[hsl(var(--foreground))]", children: mismatch.expectedMessage || "\uFF08\u7A7A\uFF09" })
958
+ ] }),
959
+ /* @__PURE__ */ jsxs2("div", { className: "flex gap-2", children: [
960
+ /* @__PURE__ */ jsx3("dt", { className: "shrink-0", children: "\u4F60\u8F93\u5165\u7684" }),
961
+ /* @__PURE__ */ jsx3("dd", { className: "min-w-0 break-words text-[hsl(var(--foreground))]", children: mismatch.actualMessage || "\uFF08\u7A7A\uFF09" })
962
+ ] })
963
+ ] }),
964
+ /* @__PURE__ */ jsxs2("div", { className: "mt-3 flex flex-wrap gap-2", children: [
965
+ /* @__PURE__ */ jsx3(
966
+ "button",
967
+ {
968
+ type: "button",
969
+ onClick: () => mismatch.resolve("keep_replay"),
970
+ 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",
971
+ children: "\u6309\u5F55\u5236\u5185\u5BB9\u7EE7\u7EED"
972
+ }
973
+ ),
974
+ /* @__PURE__ */ jsx3(
975
+ "button",
976
+ {
977
+ type: "button",
978
+ onClick: () => mismatch.resolve("continue_replay"),
979
+ 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))]",
980
+ children: "\u4ECE\u8FD9\u91CC\u5F00\u59CB\u771F\u7684\u8FD0\u884C"
981
+ }
982
+ )
983
+ ] })
984
+ ]
985
+ }
986
+ );
987
+ }
988
+
989
+ // src/components/ChatInput.tsx
990
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
421
991
  function ChatInput({
422
992
  value,
423
993
  onValueChange,
@@ -442,8 +1012,8 @@ function ChatInput({
442
1012
  void handleSend();
443
1013
  }
444
1014
  };
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(
1015
+ 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: [
1016
+ /* @__PURE__ */ jsx4(
447
1017
  "textarea",
448
1018
  {
449
1019
  value,
@@ -460,7 +1030,7 @@ function ChatInput({
460
1030
  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
1031
  }
462
1032
  ),
463
- isStreaming ? /* @__PURE__ */ jsx2(
1033
+ isStreaming ? /* @__PURE__ */ jsx4(
464
1034
  "button",
465
1035
  {
466
1036
  type: "button",
@@ -469,9 +1039,9 @@ function ChatInput({
469
1039
  "aria-label": isStopping ? "\u6B63\u5728\u505C\u6B62" : "\u505C\u6B62\u56DE\u590D",
470
1040
  title: isStopping ? "\u6B63\u5728\u505C\u6B62" : "\u505C\u6B62\u56DE\u590D",
471
1041
  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" })
1042
+ children: isStopping ? /* @__PURE__ */ jsx4(LoaderCircle, { size: 14, className: "animate-spin" }) : /* @__PURE__ */ jsx4(Square, { size: 12, fill: "currentColor" })
473
1043
  }
474
- ) : /* @__PURE__ */ jsx2(
1044
+ ) : /* @__PURE__ */ jsx4(
475
1045
  "button",
476
1046
  {
477
1047
  type: "button",
@@ -480,20 +1050,20 @@ function ChatInput({
480
1050
  "aria-label": "\u53D1\u9001\u6D88\u606F",
481
1051
  title: "\u53D1\u9001\u6D88\u606F",
482
1052
  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 })
1053
+ children: /* @__PURE__ */ jsx4(ArrowUp, { size: 15 })
484
1054
  }
485
1055
  )
486
1056
  ] }) });
487
1057
  }
488
1058
 
489
1059
  // src/components/ConnectionBanner.tsx
490
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
1060
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
491
1061
  function ConnectionBanner({ connection, className }) {
492
1062
  if (connection === "connected" || connection === "connecting") {
493
1063
  return null;
494
1064
  }
495
1065
  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(
1066
+ return /* @__PURE__ */ jsx5("div", { className: cn("blade-chat-banner bg-[hsl(var(--background))] px-5 pt-3", className), children: /* @__PURE__ */ jsxs4(
497
1067
  "div",
498
1068
  {
499
1069
  className: cn(
@@ -501,10 +1071,10 @@ function ConnectionBanner({ connection, className }) {
501
1071
  reconnecting ? "border-amber-500/25 bg-amber-500/10 text-amber-100" : "border-rose-500/25 bg-rose-500/10 text-rose-100"
502
1072
  ),
503
1073
  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" })
1074
+ /* @__PURE__ */ jsx5("span", { className: "mt-0.5 shrink-0", children: reconnecting ? /* @__PURE__ */ jsx5(LoaderCircle, { size: 14, className: "animate-spin" }) : /* @__PURE__ */ jsx5(TriangleAlert, { size: 14 }) }),
1075
+ /* @__PURE__ */ jsxs4("div", { className: "min-w-0", children: [
1076
+ /* @__PURE__ */ jsx5("div", { className: "text-sm font-medium", children: reconnecting ? "\u8FDE\u63A5\u5DF2\u65AD\u5F00\uFF0C\u6B63\u5728\u91CD\u8FDE\u2026" : "\u8FDE\u63A5\u5DF2\u65AD\u5F00" }),
1077
+ /* @__PURE__ */ jsx5("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" })
508
1078
  ] })
509
1079
  ]
510
1080
  }
@@ -513,10 +1083,10 @@ function ConnectionBanner({ connection, className }) {
513
1083
 
514
1084
  // src/components/MessageList.tsx
515
1085
  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";
1086
+ import { useCallback as useCallback5, useEffect as useEffect6, useMemo as useMemo7, useRef as useRef6, useState as useState11 } from "react";
517
1087
 
518
1088
  // ../../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";
1089
+ import { useCallback as useCallback3, useMemo as useMemo3, useRef as useRef3, useState as useState4 } from "react";
520
1090
  var DEFAULT_SPRING_ANIMATION = {
521
1091
  /**
522
1092
  * A value from 0 to 1, on how much to damp the animation.
@@ -553,12 +1123,12 @@ globalThis.document?.addEventListener("click", () => {
553
1123
  mouseDown = false;
554
1124
  });
555
1125
  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);
1126
+ const [escapedFromLock, updateEscapedFromLock] = useState4(false);
1127
+ const [isAtBottom, updateIsAtBottom] = useState4(options.initial !== false);
1128
+ const [isNearBottom, setIsNearBottom] = useState4(false);
1129
+ const optionsRef = useRef3(null);
560
1130
  optionsRef.current = options;
561
- const isSelecting = useCallback(() => {
1131
+ const isSelecting = useCallback3(() => {
562
1132
  if (!mouseDown) {
563
1133
  return false;
564
1134
  }
@@ -569,15 +1139,15 @@ var useStickToBottom = (options = {}) => {
569
1139
  const range = selection.getRangeAt(0);
570
1140
  return range.commonAncestorContainer.contains(scrollRef.current) || scrollRef.current?.contains(range.commonAncestorContainer);
571
1141
  }, []);
572
- const setIsAtBottom = useCallback((isAtBottom2) => {
1142
+ const setIsAtBottom = useCallback3((isAtBottom2) => {
573
1143
  state.isAtBottom = isAtBottom2;
574
1144
  updateIsAtBottom(isAtBottom2);
575
1145
  }, []);
576
- const setEscapedFromLock = useCallback((escapedFromLock2) => {
1146
+ const setEscapedFromLock = useCallback3((escapedFromLock2) => {
577
1147
  state.escapedFromLock = escapedFromLock2;
578
1148
  updateEscapedFromLock(escapedFromLock2);
579
1149
  }, []);
580
- const state = useMemo2(() => {
1150
+ const state = useMemo3(() => {
581
1151
  let lastCalculation;
582
1152
  return {
583
1153
  escapedFromLock,
@@ -630,7 +1200,7 @@ var useStickToBottom = (options = {}) => {
630
1200
  }
631
1201
  };
632
1202
  }, []);
633
- const scrollToBottom = useCallback((scrollOptions = {}) => {
1203
+ const scrollToBottom = useCallback3((scrollOptions = {}) => {
634
1204
  if (typeof scrollOptions === "string") {
635
1205
  scrollOptions = { animation: scrollOptions };
636
1206
  }
@@ -715,11 +1285,11 @@ var useStickToBottom = (options = {}) => {
715
1285
  }
716
1286
  return next();
717
1287
  }, [setIsAtBottom, isSelecting, state]);
718
- const stopScroll = useCallback(() => {
1288
+ const stopScroll = useCallback3(() => {
719
1289
  setEscapedFromLock(true);
720
1290
  setIsAtBottom(false);
721
1291
  }, [setEscapedFromLock, setIsAtBottom]);
722
- const handleScroll = useCallback(({ target }) => {
1292
+ const handleScroll = useCallback3(({ target }) => {
723
1293
  if (target !== scrollRef.current) {
724
1294
  return;
725
1295
  }
@@ -758,7 +1328,7 @@ var useStickToBottom = (options = {}) => {
758
1328
  }
759
1329
  }, 1);
760
1330
  }, [setEscapedFromLock, setIsAtBottom, isSelecting, state]);
761
- const handleWheel = useCallback(({ target, deltaY }) => {
1331
+ const handleWheel = useCallback3(({ target, deltaY }) => {
762
1332
  let element = target;
763
1333
  while (!["scroll", "auto"].includes(getComputedStyle(element).overflow)) {
764
1334
  if (!element.parentElement) {
@@ -828,7 +1398,7 @@ var useStickToBottom = (options = {}) => {
828
1398
  };
829
1399
  };
830
1400
  function useRefCallback(callback, deps) {
831
- const result = useCallback((ref) => {
1401
+ const result = useCallback3((ref) => {
832
1402
  result.current = ref;
833
1403
  return callback(ref);
834
1404
  }, deps);
@@ -860,11 +1430,11 @@ function mergeAnimations(...animations) {
860
1430
 
861
1431
  // ../../node_modules/.pnpm/use-stick-to-bottom@1.1.3_react@19.2.4/node_modules/use-stick-to-bottom/dist/StickToBottom.js
862
1432
  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";
1433
+ import { createContext as createContext2, useContext as useContext2, useEffect as useEffect3, useImperativeHandle, useLayoutEffect, useMemo as useMemo4, useRef as useRef4 } from "react";
864
1434
  var StickToBottomContext = createContext2(null);
865
- var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect2;
1435
+ var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect3;
866
1436
  function StickToBottom({ instance, children, resize, initial, mass, damping, stiffness, targetScrollTop: currentTargetScrollTop, contextRef, ...props }) {
867
- const customTargetScrollTop = useRef3(null);
1437
+ const customTargetScrollTop = useRef4(null);
868
1438
  const targetScrollTop = React.useCallback((target, elements) => {
869
1439
  const get = context?.targetScrollTop ?? currentTargetScrollTop;
870
1440
  return get?.(target, elements) ?? target;
@@ -878,7 +1448,7 @@ function StickToBottom({ instance, children, resize, initial, mass, damping, sti
878
1448
  targetScrollTop
879
1449
  });
880
1450
  const { scrollRef, contentRef, scrollToBottom, stopScroll, isAtBottom, escapedFromLock, state } = instance ?? defaultInstance;
881
- const context = useMemo3(() => ({
1451
+ const context = useMemo4(() => ({
882
1452
  scrollToBottom,
883
1453
  stopScroll,
884
1454
  scrollRef,
@@ -941,10 +1511,10 @@ function useStickToBottomContext() {
941
1511
 
942
1512
  // src/components/AssistantTurnBlock.tsx
943
1513
  import { getTextContent, normalizeMessageContent } from "@blade-hq/agent-client";
944
- import { useState as useState8 } from "react";
1514
+ import { useState as useState10 } from "react";
945
1515
 
946
1516
  // src/components/AgentLoopBlock.tsx
947
- import { useState as useState3 } from "react";
1517
+ import { useState as useState5 } from "react";
948
1518
 
949
1519
  // src/components/display-utils.ts
950
1520
  var TOOL_NAME_ALIASES = {
@@ -1066,7 +1636,7 @@ function formatToolResult(result) {
1066
1636
  }
1067
1637
 
1068
1638
  // src/components/AgentLoopBlock.tsx
1069
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1639
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1070
1640
  function parseAgentDescription(argumentsJson) {
1071
1641
  try {
1072
1642
  const parsed = JSON.parse(argumentsJson);
@@ -1076,12 +1646,12 @@ function parseAgentDescription(argumentsJson) {
1076
1646
  }
1077
1647
  }
1078
1648
  function AgentLoopBlock({ toolCall }) {
1079
- const [expanded, setExpanded] = useState3(false);
1649
+ const [expanded, setExpanded] = useState5(false);
1080
1650
  const description = parseAgentDescription(toolCall.arguments);
1081
1651
  const running = toolCall.status === "pending" || toolCall.status === "awaiting_answer";
1082
1652
  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(
1653
+ return /* @__PURE__ */ jsxs5("div", { className: "blade-chat-agent-loop ml-4 text-xs", children: [
1654
+ /* @__PURE__ */ jsxs5(
1085
1655
  "div",
1086
1656
  {
1087
1657
  className: cn(
@@ -1089,7 +1659,7 @@ function AgentLoopBlock({ toolCall }) {
1089
1659
  failed ? "border-l-[hsl(var(--muted-foreground)/0.5)]" : running ? "border-l-blue-500" : "border-l-[hsl(var(--primary))]"
1090
1660
  ),
1091
1661
  children: [
1092
- /* @__PURE__ */ jsxs3(
1662
+ /* @__PURE__ */ jsxs5(
1093
1663
  "button",
1094
1664
  {
1095
1665
  type: "button",
@@ -1097,7 +1667,7 @@ function AgentLoopBlock({ toolCall }) {
1097
1667
  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
1668
  "aria-expanded": expanded,
1099
1669
  children: [
1100
- /* @__PURE__ */ jsx4(
1670
+ /* @__PURE__ */ jsx6(
1101
1671
  ChevronRight,
1102
1672
  {
1103
1673
  size: 11,
@@ -1107,8 +1677,8 @@ function AgentLoopBlock({ toolCall }) {
1107
1677
  )
1108
1678
  }
1109
1679
  ),
1110
- /* @__PURE__ */ jsx4(Bot, { size: 12, className: "shrink-0 text-[hsl(var(--muted-foreground))]" }),
1111
- /* @__PURE__ */ jsxs3(
1680
+ /* @__PURE__ */ jsx6(Bot, { size: 12, className: "shrink-0 text-[hsl(var(--muted-foreground))]" }),
1681
+ /* @__PURE__ */ jsxs5(
1112
1682
  "span",
1113
1683
  {
1114
1684
  className: cn(
@@ -1116,35 +1686,35 @@ function AgentLoopBlock({ toolCall }) {
1116
1686
  failed ? "text-[hsl(var(--muted-foreground))]" : running ? "text-blue-300" : "text-[hsl(var(--primary))]"
1117
1687
  ),
1118
1688
  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" })
1689
+ running ? /* @__PURE__ */ jsx6(LoaderCircle, { size: 11, className: "animate-spin" }) : failed ? /* @__PURE__ */ jsx6(X, { size: 11 }) : /* @__PURE__ */ jsx6(Check, { size: 11 }),
1690
+ /* @__PURE__ */ jsx6("span", { children: running ? "\u6267\u884C\u4E2D" : failed ? "\u5DF2\u7EC8\u6B62" : "\u5B8C\u6210" })
1121
1691
  ]
1122
1692
  }
1123
1693
  ),
1124
- /* @__PURE__ */ jsxs3("span", { className: "min-w-0 flex-1 truncate font-medium text-[hsl(var(--foreground))]", children: [
1694
+ /* @__PURE__ */ jsxs5("span", { className: "min-w-0 flex-1 truncate font-medium text-[hsl(var(--foreground))]", children: [
1125
1695
  "\u5B50\u667A\u80FD\u4F53\uFF1A",
1126
1696
  description
1127
1697
  ] })
1128
1698
  ]
1129
1699
  }
1130
1700
  ),
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) })
1701
+ 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
1702
  ]
1133
1703
  }
1134
1704
  ),
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) })
1705
+ expanded && toolCall.result != null && /* @__PURE__ */ jsxs5("div", { className: "ml-4 mt-1 rounded-xl bg-[hsl(var(--card))] px-3 py-3", children: [
1706
+ /* @__PURE__ */ jsx6("div", { className: "mb-1 text-[10px] uppercase tracking-wider text-[hsl(var(--muted-foreground))]", children: "\u7ED3\u679C" }),
1707
+ /* @__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
1708
  ] })
1139
1709
  ] });
1140
1710
  }
1141
1711
 
1142
1712
  // src/components/MarkdownContent.tsx
1143
1713
  import {
1144
- useEffect as useEffect3,
1145
- useMemo as useMemo4,
1146
- useRef as useRef4,
1147
- useState as useState5
1714
+ useEffect as useEffect4,
1715
+ useMemo as useMemo5,
1716
+ useRef as useRef5,
1717
+ useState as useState7
1148
1718
  } from "react";
1149
1719
 
1150
1720
  // src/lib/media-tags.ts
@@ -1191,8 +1761,8 @@ function collapseMediaTags(text, options) {
1191
1761
  }
1192
1762
 
1193
1763
  // src/components/FileCard.tsx
1194
- import { useState as useState4 } from "react";
1195
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1764
+ import { useState as useState6 } from "react";
1765
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1196
1766
  var IMAGE_EXTS = /* @__PURE__ */ new Set(["png", "jpg", "jpeg", "gif", "svg", "webp", "ico", "bmp"]);
1197
1767
  var SHEET_EXTS = /* @__PURE__ */ new Set(["xlsx", "xls", "xlsm", "xlsb", "csv"]);
1198
1768
  var TEXT_EXTS = /* @__PURE__ */ new Set(["txt", "md", "markdown", "pdf", "doc", "docx", "rtf"]);
@@ -1234,8 +1804,8 @@ function FileCard({
1234
1804
  ...props
1235
1805
  }) {
1236
1806
  const client = useBladeClient();
1237
- const [downloading, setDownloading] = useState4(false);
1238
- const [failed, setFailed] = useState4(false);
1807
+ const [downloading, setDownloading] = useState6(false);
1808
+ const [failed, setFailed] = useState6(false);
1239
1809
  const nodeProperties = node && typeof node === "object" && "properties" in node ? node.properties : void 0;
1240
1810
  const path = pathAttribute ?? dataPath ?? stringValue(nodeProperties?.["data-path"]) ?? stringValue(nodeProperties?.dataPath) ?? "";
1241
1811
  const name = nameAttribute ?? dataName ?? stringValue(nodeProperties?.["data-name"]) ?? stringValue(nodeProperties?.dataName) ?? extractChildrenText(children) ?? "";
@@ -1253,8 +1823,8 @@ function FileCard({
1253
1823
  setDownloading(false);
1254
1824
  }
1255
1825
  };
1256
- return /* @__PURE__ */ jsxs4("span", { ...props, className: cn("blade-chat-file-card", className), children: [
1257
- /* @__PURE__ */ jsxs4(
1826
+ return /* @__PURE__ */ jsxs6("span", { ...props, className: cn("blade-chat-file-card", className), children: [
1827
+ /* @__PURE__ */ jsxs6(
1258
1828
  "button",
1259
1829
  {
1260
1830
  type: "button",
@@ -1263,24 +1833,24 @@ function FileCard({
1263
1833
  "aria-label": `\u4E0B\u8F7D\u6587\u4EF6\uFF1A${name}`,
1264
1834
  title: failed ? "\u4E0B\u8F7D\u5931\u8D25\uFF0C\u70B9\u51FB\u91CD\u8BD5" : `\u4E0B\u8F7D ${name}`,
1265
1835
  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" })
1836
+ /* @__PURE__ */ jsx7("span", { className: "blade-chat-file-card-icon", children: downloading ? /* @__PURE__ */ jsx7(LoaderCircle, { size: 15, className: "animate-spin" }) : /* @__PURE__ */ jsx7(Icon2, { size: 15 }) }),
1837
+ /* @__PURE__ */ jsx7("span", { className: "blade-chat-file-card-name", children: name }),
1838
+ /* @__PURE__ */ jsx7(Download, { size: 13, className: "blade-chat-file-card-action" })
1269
1839
  ]
1270
1840
  }
1271
1841
  ),
1272
- failed && /* @__PURE__ */ jsx5("span", { className: "blade-chat-file-card-error", children: "\u4E0B\u8F7D\u5931\u8D25" })
1842
+ failed && /* @__PURE__ */ jsx7("span", { className: "blade-chat-file-card-error", children: "\u4E0B\u8F7D\u5931\u8D25" })
1273
1843
  ] });
1274
1844
  }
1275
1845
 
1276
1846
  // src/components/MarkdownContent.tsx
1277
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1847
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1278
1848
  var SYSTEM_REMINDER_RE = /<system-reminder>[\s\S]*?<\/system-reminder>/gi;
1279
1849
  function CodeBlockPre({ children, node: _node, ...props }) {
1280
- const preRef = useRef4(null);
1281
- const [copied, setCopied] = useState5(false);
1282
- const [language, setLanguage] = useState5("");
1283
- useEffect3(() => {
1850
+ const preRef = useRef5(null);
1851
+ const [copied, setCopied] = useState7(false);
1852
+ const [language, setLanguage] = useState7("");
1853
+ useEffect4(() => {
1284
1854
  const codeEl = preRef.current?.querySelector("code");
1285
1855
  setLanguage(codeEl?.className.match(/language-(\S+)/)?.[1] ?? "");
1286
1856
  }, []);
@@ -1291,10 +1861,10 @@ function CodeBlockPre({ children, node: _node, ...props }) {
1291
1861
  setTimeout(() => setCopied(false), 2e3);
1292
1862
  }
1293
1863
  };
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(
1864
+ return /* @__PURE__ */ jsxs7("div", { className: "blade-chat-codeblock not-prose my-3 overflow-hidden rounded-xl border border-[hsl(var(--border))]", children: [
1865
+ /* @__PURE__ */ jsxs7("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: [
1866
+ /* @__PURE__ */ jsx8("span", { className: "font-mono text-[12px] text-[hsl(var(--muted-foreground))]", children: language || "code" }),
1867
+ /* @__PURE__ */ jsxs7(
1298
1868
  "button",
1299
1869
  {
1300
1870
  type: "button",
@@ -1304,13 +1874,13 @@ function CodeBlockPre({ children, node: _node, ...props }) {
1304
1874
  copied ? "text-[hsl(var(--primary))]" : "text-[hsl(var(--muted-foreground))] hover:bg-[hsl(var(--accent))] hover:text-[hsl(var(--foreground))]"
1305
1875
  ),
1306
1876
  children: [
1307
- copied ? /* @__PURE__ */ jsx6(Check, { size: 12 }) : /* @__PURE__ */ jsx6(Copy, { size: 12 }),
1308
- /* @__PURE__ */ jsx6("span", { children: copied ? "\u5DF2\u590D\u5236" : "\u590D\u5236" })
1877
+ copied ? /* @__PURE__ */ jsx8(Check, { size: 12 }) : /* @__PURE__ */ jsx8(Copy, { size: 12 }),
1878
+ /* @__PURE__ */ jsx8("span", { children: copied ? "\u5DF2\u590D\u5236" : "\u590D\u5236" })
1309
1879
  ]
1310
1880
  }
1311
1881
  )
1312
1882
  ] }),
1313
- /* @__PURE__ */ jsx6(
1883
+ /* @__PURE__ */ jsx8(
1314
1884
  "pre",
1315
1885
  {
1316
1886
  ref: preRef,
@@ -1322,7 +1892,7 @@ function CodeBlockPre({ children, node: _node, ...props }) {
1322
1892
  ] });
1323
1893
  }
1324
1894
  function ExternalAnchor({ node: _node, children, ...props }) {
1325
- return /* @__PURE__ */ jsx6("a", { ...props, target: "_blank", rel: "noopener noreferrer", children });
1895
+ return /* @__PURE__ */ jsx8("a", { ...props, target: "_blank", rel: "noopener noreferrer", children });
1326
1896
  }
1327
1897
  var MARKDOWN_COMPONENTS = {
1328
1898
  pre: CodeBlockPre,
@@ -1333,19 +1903,19 @@ var CUSTOM_ALLOWED_TAGS = {
1333
1903
  };
1334
1904
  function MarkdownContent({ children, className, mode, sessionId }) {
1335
1905
  const streaming = mode === "streaming";
1336
- const resolvedChildren = useMemo4(() => {
1906
+ const resolvedChildren = useMemo5(() => {
1337
1907
  const withoutReminders = children.replace(SYSTEM_REMINDER_RE, "");
1338
1908
  if (!sessionId) return withoutReminders;
1339
1909
  return collapseMediaTags(withoutReminders, { streaming });
1340
1910
  }, [children, sessionId, streaming]);
1341
- const components = useMemo4(
1911
+ const components = useMemo5(
1342
1912
  () => ({
1343
1913
  ...MARKDOWN_COMPONENTS,
1344
- [FILE_CARD_TAG]: (fileCardProps) => /* @__PURE__ */ jsx6(FileCard, { ...fileCardProps, sessionId })
1914
+ [FILE_CARD_TAG]: (fileCardProps) => /* @__PURE__ */ jsx8(FileCard, { ...fileCardProps, sessionId })
1345
1915
  }),
1346
1916
  [sessionId]
1347
1917
  );
1348
- return /* @__PURE__ */ jsx6(
1918
+ return /* @__PURE__ */ jsx8(
1349
1919
  _r,
1350
1920
  {
1351
1921
  className: cn("blade-chat-markdown break-words", className),
@@ -1359,17 +1929,17 @@ function MarkdownContent({ children, className, mode, sessionId }) {
1359
1929
  }
1360
1930
 
1361
1931
  // src/components/Shimmer.tsx
1362
- import { jsx as jsx7 } from "react/jsx-runtime";
1932
+ import { jsx as jsx9 } from "react/jsx-runtime";
1363
1933
  function Shimmer({ children = "\u6B63\u5728\u601D\u8003...", className }) {
1364
- return /* @__PURE__ */ jsx7("span", { className: cn("blade-shimmer-text text-sm font-medium", className), children });
1934
+ return /* @__PURE__ */ jsx9("span", { className: cn("blade-shimmer-text text-sm font-medium", className), children });
1365
1935
  }
1366
1936
 
1367
1937
  // src/components/ToolCallBlock.tsx
1368
- import { useState as useState7 } from "react";
1938
+ import { useState as useState9 } from "react";
1369
1939
 
1370
1940
  // 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";
1941
+ import { useEffect as useEffect5, useMemo as useMemo6, useState as useState8 } from "react";
1942
+ import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
1373
1943
  function AskUserQuestionBlock({
1374
1944
  data,
1375
1945
  answered,
@@ -1378,16 +1948,16 @@ function AskUserQuestionBlock({
1378
1948
  answerData,
1379
1949
  onAnswer
1380
1950
  }) {
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(() => {
1951
+ const [selections, setSelections] = useState8(/* @__PURE__ */ new Map());
1952
+ const [customTexts, setCustomTexts] = useState8(/* @__PURE__ */ new Map());
1953
+ const [usingCustom, setUsingCustom] = useState8(/* @__PURE__ */ new Set());
1954
+ const [submitted, setSubmitted] = useState8(false);
1955
+ useEffect5(() => {
1386
1956
  if (sessionStatus === "failed" || sessionStatus === "interrupted") {
1387
1957
  setSubmitted(false);
1388
1958
  }
1389
1959
  }, [sessionStatus]);
1390
- const displayAnswerState = useMemo5(() => {
1960
+ const displayAnswerState = useMemo6(() => {
1391
1961
  if (!(answered && answerData)) {
1392
1962
  return { selections, customTexts, usingCustom };
1393
1963
  }
@@ -1472,7 +2042,7 @@ ${parts.join("\n")}`;
1472
2042
  setSubmitted(true);
1473
2043
  onAnswer(text, toolCallId, nextAnswerData);
1474
2044
  };
1475
- return /* @__PURE__ */ jsxs6(
2045
+ return /* @__PURE__ */ jsxs8(
1476
2046
  "div",
1477
2047
  {
1478
2048
  className: cn(
@@ -1480,12 +2050,12 @@ ${parts.join("\n")}`;
1480
2050
  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
2051
  ),
1482
2052
  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: [
2053
+ data.source_loop?.description && /* @__PURE__ */ jsxs8("div", { className: "rounded-lg bg-[hsl(var(--muted)/0.35)] px-3 py-2 text-xs text-[hsl(var(--muted-foreground))]", children: [
1484
2054
  "\u5B50\u667A\u80FD\u4F53\u300C",
1485
2055
  data.source_loop.description,
1486
2056
  "\u300D\u5728\u7B49\u5F85\u4F60\u7684\u56DE\u7B54"
1487
2057
  ] }),
1488
- data.questions.map((q, qIdx) => /* @__PURE__ */ jsx8(
2058
+ data.questions.map((q, qIdx) => /* @__PURE__ */ jsx10(
1489
2059
  QuestionCard,
1490
2060
  {
1491
2061
  question: q,
@@ -1500,7 +2070,7 @@ ${parts.join("\n")}`;
1500
2070
  },
1501
2071
  q.question
1502
2072
  )),
1503
- !answered && !submitted && onAnswer && /* @__PURE__ */ jsx8(
2073
+ !answered && !submitted && onAnswer && /* @__PURE__ */ jsx10(
1504
2074
  "button",
1505
2075
  {
1506
2076
  type: "button",
@@ -1510,14 +2080,14 @@ ${parts.join("\n")}`;
1510
2080
  children: allAnswered ? "\u786E\u8BA4" : "\u8BF7\u5148\u9009\u62E9\u4E00\u4E2A\u9009\u9879"
1511
2081
  }
1512
2082
  ),
1513
- submitted && !answered && /* @__PURE__ */ jsxs6(
2083
+ submitted && !answered && /* @__PURE__ */ jsxs8(
1514
2084
  "button",
1515
2085
  {
1516
2086
  type: "button",
1517
2087
  disabled: true,
1518
2088
  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
2089
  children: [
1520
- /* @__PURE__ */ jsx8(LoaderCircle, { size: 14, className: "animate-spin" }),
2090
+ /* @__PURE__ */ jsx10(LoaderCircle, { size: 14, className: "animate-spin" }),
1521
2091
  "\u786E\u8BA4\u4E2D"
1522
2092
  ]
1523
2093
  }
@@ -1538,30 +2108,30 @@ function QuestionCard({
1538
2108
  onCustomChange
1539
2109
  }) {
1540
2110
  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(
2111
+ return /* @__PURE__ */ jsxs8("div", { children: [
2112
+ /* @__PURE__ */ jsxs8("div", { className: cn("flex items-start gap-2", answered ? "mb-2" : "mb-3"), children: [
2113
+ /* @__PURE__ */ jsx10(
1544
2114
  MessageSquareMore,
1545
2115
  {
1546
2116
  size: answered ? 12 : 13,
1547
2117
  className: "mt-0.5 shrink-0 text-[hsl(var(--primary))]"
1548
2118
  }
1549
2119
  ),
1550
- /* @__PURE__ */ jsx8(
2120
+ /* @__PURE__ */ jsx10(
1551
2121
  "div",
1552
2122
  {
1553
2123
  className: cn(
1554
2124
  "min-w-0 flex-1 font-medium text-[hsl(var(--foreground))]",
1555
2125
  answered ? "text-xs" : "text-sm"
1556
2126
  ),
1557
- children: /* @__PURE__ */ jsx8(MarkdownContent, { className: "blade-chat-prose", children: question.question })
2127
+ children: /* @__PURE__ */ jsx10(MarkdownContent, { className: "blade-chat-prose", children: question.question })
1558
2128
  }
1559
2129
  )
1560
2130
  ] }),
1561
- /* @__PURE__ */ jsxs6("div", { className: cn("flex flex-col pl-5", answered ? "gap-1" : "gap-1.5"), children: [
2131
+ /* @__PURE__ */ jsxs8("div", { className: cn("flex flex-col pl-5", answered ? "gap-1" : "gap-1.5"), children: [
1562
2132
  question.options.map((opt, optIdx) => {
1563
2133
  const isSel = selected.has(optIdx);
1564
- return /* @__PURE__ */ jsxs6(
2134
+ return /* @__PURE__ */ jsxs8(
1565
2135
  "button",
1566
2136
  {
1567
2137
  type: "button",
@@ -1575,14 +2145,14 @@ function QuestionCard({
1575
2145
  answered && "cursor-default opacity-70"
1576
2146
  ),
1577
2147
  children: [
1578
- multi && /* @__PURE__ */ jsx8(
2148
+ multi && /* @__PURE__ */ jsx10(
1579
2149
  "div",
1580
2150
  {
1581
2151
  className: cn(
1582
2152
  "mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded border transition-colors",
1583
2153
  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
2154
  ),
1585
- children: isSel && /* @__PURE__ */ jsx8(
2155
+ children: isSel && /* @__PURE__ */ jsx10(
1586
2156
  Check,
1587
2157
  {
1588
2158
  size: 9,
@@ -1591,9 +2161,9 @@ function QuestionCard({
1591
2161
  )
1592
2162
  }
1593
2163
  ),
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(
2164
+ /* @__PURE__ */ jsxs8("div", { className: "min-w-0", children: [
2165
+ /* @__PURE__ */ jsx10("div", { className: cn("font-medium", answered ? "text-xs" : "text-[13px]"), children: opt.label }),
2166
+ opt.description && /* @__PURE__ */ jsx10(
1597
2167
  "div",
1598
2168
  {
1599
2169
  className: cn(
@@ -1610,7 +2180,7 @@ function QuestionCard({
1610
2180
  opt.label
1611
2181
  );
1612
2182
  }),
1613
- answered && !isCustom ? null : /* @__PURE__ */ jsxs6(
2183
+ answered && !isCustom ? null : /* @__PURE__ */ jsxs8(
1614
2184
  "div",
1615
2185
  {
1616
2186
  className: cn(
@@ -1620,8 +2190,8 @@ function QuestionCard({
1620
2190
  answered && "cursor-default opacity-70"
1621
2191
  ),
1622
2192
  children: [
1623
- /* @__PURE__ */ jsx8("span", { className: "shrink-0 text-xs text-[hsl(var(--muted-foreground))]", children: "\u5176\u4ED6\uFF1A" }),
1624
- /* @__PURE__ */ jsx8(
2193
+ /* @__PURE__ */ jsx10("span", { className: "shrink-0 text-xs text-[hsl(var(--muted-foreground))]", children: "\u5176\u4ED6\uFF1A" }),
2194
+ /* @__PURE__ */ jsx10(
1625
2195
  "input",
1626
2196
  {
1627
2197
  type: "text",
@@ -1689,7 +2259,7 @@ function normalizeOptionItem(value) {
1689
2259
  }
1690
2260
 
1691
2261
  // src/components/ToolCallBlock.tsx
1692
- import { Fragment, jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
2262
+ import { Fragment, jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
1693
2263
  function resolveAskQuestionState({
1694
2264
  toolStatus,
1695
2265
  hasAnswerData,
@@ -1709,12 +2279,12 @@ function ToolCallBlock({
1709
2279
  sessionStatus,
1710
2280
  renderer
1711
2281
  }) {
1712
- const [expanded, setExpanded] = useState7(false);
2282
+ const [expanded, setExpanded] = useState9(false);
1713
2283
  const normalizedName = formatToolName(toolCall.name);
1714
2284
  if (renderer) {
1715
2285
  const custom = renderer(toolCall);
1716
2286
  if (custom !== null && custom !== void 0) {
1717
- return /* @__PURE__ */ jsx9(Fragment, { children: custom });
2287
+ return /* @__PURE__ */ jsx11(Fragment, { children: custom });
1718
2288
  }
1719
2289
  }
1720
2290
  if (normalizedName === "AskUserQuestion") {
@@ -1726,7 +2296,7 @@ function ToolCallBlock({
1726
2296
  });
1727
2297
  const canAnswer = questionState.awaitingAnswer && Boolean(onAnswer);
1728
2298
  if (askData) {
1729
- return /* @__PURE__ */ jsx9(
2299
+ return /* @__PURE__ */ jsx11(
1730
2300
  AskUserQuestionBlock,
1731
2301
  {
1732
2302
  data: askData,
@@ -1739,24 +2309,24 @@ function ToolCallBlock({
1739
2309
  );
1740
2310
  }
1741
2311
  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" })
2312
+ return /* @__PURE__ */ jsxs9("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: [
2313
+ /* @__PURE__ */ jsx11(LoaderCircle, { size: 14, className: "animate-spin" }),
2314
+ /* @__PURE__ */ jsx11("span", { children: "\u6B63\u5728\u51C6\u5907\u95EE\u9898\u2026" })
1745
2315
  ] });
1746
2316
  }
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" })
2317
+ return /* @__PURE__ */ jsxs9("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: [
2318
+ /* @__PURE__ */ jsx11("div", { className: "font-semibold", children: "\u9009\u62E9\u9898\u5185\u5BB9\u6682\u65F6\u65E0\u6CD5\u663E\u793A" }),
2319
+ /* @__PURE__ */ jsx11("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
2320
  ] });
1751
2321
  }
1752
2322
  const tone = getToolTone(toolCall.status);
1753
2323
  const displayName = getToolDisplayLabel(toolCall);
1754
2324
  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 });
2325
+ const statusIcon = toolCall.status === "pending" ? /* @__PURE__ */ jsx11(LoaderCircle, { size: 11, className: "animate-spin" }) : toolCall.status === "awaiting_answer" ? /* @__PURE__ */ jsx11(MessageSquareMore, { size: 11 }) : toolCall.status === "cancelled" || toolCall.status === "error" ? /* @__PURE__ */ jsx11(X, { size: 11 }) : /* @__PURE__ */ jsx11(Check, { size: 11 });
1756
2326
  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(
2327
+ return /* @__PURE__ */ jsxs9("div", { className: "blade-chat-tool ml-4 text-xs", children: [
2328
+ /* @__PURE__ */ jsxs9("div", { className: cn("border-l-[3px] flex items-center gap-2 px-3 py-2", toneClass), children: [
2329
+ /* @__PURE__ */ jsxs9(
1760
2330
  "button",
1761
2331
  {
1762
2332
  type: "button",
@@ -1764,7 +2334,7 @@ function ToolCallBlock({
1764
2334
  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
2335
  "aria-expanded": expanded,
1766
2336
  children: [
1767
- /* @__PURE__ */ jsx9(
2337
+ /* @__PURE__ */ jsx11(
1768
2338
  ChevronRight,
1769
2339
  {
1770
2340
  size: 11,
@@ -1774,24 +2344,24 @@ function ToolCallBlock({
1774
2344
  )
1775
2345
  }
1776
2346
  ),
1777
- /* @__PURE__ */ jsxs7("span", { className: cn("flex shrink-0 items-center gap-1 text-[10px]", statusTextClass), children: [
2347
+ /* @__PURE__ */ jsxs9("span", { className: cn("flex shrink-0 items-center gap-1 text-[10px]", statusTextClass), children: [
1778
2348
  statusIcon,
1779
- /* @__PURE__ */ jsx9("span", { children: getToolStatusLabel(toolCall.status) })
2349
+ /* @__PURE__ */ jsx11("span", { children: getToolStatusLabel(toolCall.status) })
1780
2350
  ] }),
1781
- /* @__PURE__ */ jsx9("span", { className: "min-w-0 flex-1 truncate font-medium text-[hsl(var(--foreground))]", children: displayName })
2351
+ /* @__PURE__ */ jsx11("span", { className: "min-w-0 flex-1 truncate font-medium text-[hsl(var(--foreground))]", children: displayName })
1782
2352
  ]
1783
2353
  }
1784
2354
  ),
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) })
2355
+ typeof toolCall.duration_ms === "number" && toolCall.duration_ms > 0 && /* @__PURE__ */ jsx11("span", { className: "shrink-0 font-mono text-[10px] text-[hsl(var(--muted-foreground))]", children: formatToolDuration(toolCall.duration_ms) })
1786
2356
  ] }),
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) })
2357
+ expanded && /* @__PURE__ */ jsxs9("div", { className: "blade-chat-tool-detail ml-4 mt-1 rounded-xl bg-[hsl(var(--card))] px-3 py-3", children: [
2358
+ /* @__PURE__ */ jsx11("div", { className: "mb-1 text-[10px] uppercase tracking-wider text-[hsl(var(--muted-foreground))]", children: "\u5DE5\u5177" }),
2359
+ /* @__PURE__ */ jsx11("div", { className: "mb-3 font-mono text-[11px] text-[hsl(var(--foreground))]", children: normalizedName }),
2360
+ /* @__PURE__ */ jsx11("div", { className: "mb-1 text-[10px] uppercase tracking-wider text-[hsl(var(--muted-foreground))]", children: "\u53C2\u6570" }),
2361
+ /* @__PURE__ */ jsx11("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) }),
2362
+ toolCall.result != null && /* @__PURE__ */ jsxs9(Fragment, { children: [
2363
+ /* @__PURE__ */ jsx11("div", { className: "mb-1 mt-3 text-[10px] uppercase tracking-wider text-[hsl(var(--muted-foreground))]", children: "\u7ED3\u679C" }),
2364
+ /* @__PURE__ */ jsx11("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
2365
  ] })
1796
2366
  ] })
1797
2367
  ] });
@@ -1809,11 +2379,11 @@ function buildAskUserPayload(argumentsJson) {
1809
2379
  }
1810
2380
 
1811
2381
  // src/components/AssistantTurnBlock.tsx
1812
- import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
2382
+ import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
1813
2383
  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(
2384
+ const [open, setOpen] = useState10(false);
2385
+ return /* @__PURE__ */ jsxs10("div", { className: "blade-chat-thinking ml-4 text-sm", children: [
2386
+ /* @__PURE__ */ jsxs10(
1817
2387
  "button",
1818
2388
  {
1819
2389
  type: "button",
@@ -1821,14 +2391,14 @@ function ThinkingBlock({ reasoning, isStreaming }) {
1821
2391
  "aria-expanded": open,
1822
2392
  className: "inline-flex items-center gap-1.5 text-[hsl(var(--muted-foreground))] transition-colors hover:text-[hsl(var(--foreground))]",
1823
2393
  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: [
2394
+ /* @__PURE__ */ jsx12(Brain, { size: 12, className: "shrink-0" }),
2395
+ isStreaming ? /* @__PURE__ */ jsx12(Shimmer, { className: "text-xs", children: "\u6B63\u5728\u601D\u8003" }) : /* @__PURE__ */ jsx12("span", { children: "\u601D\u8003\u8FC7\u7A0B" }),
2396
+ /* @__PURE__ */ jsxs10("span", { className: "text-[hsl(var(--muted-foreground))]/70", children: [
1827
2397
  "\xB7 ",
1828
2398
  new Intl.NumberFormat("zh-CN").format(reasoning.length),
1829
2399
  " \u5B57"
1830
2400
  ] }),
1831
- /* @__PURE__ */ jsx10(
2401
+ /* @__PURE__ */ jsx12(
1832
2402
  ChevronDown,
1833
2403
  {
1834
2404
  size: 12,
@@ -1838,7 +2408,7 @@ function ThinkingBlock({ reasoning, isStreaming }) {
1838
2408
  ]
1839
2409
  }
1840
2410
  ),
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 })
2411
+ open && /* @__PURE__ */ jsx12("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
2412
  ] });
1843
2413
  }
1844
2414
  function getMessageText(message) {
@@ -1864,21 +2434,21 @@ function AssistantTurnBlock({
1864
2434
  (message) => getMessageText(message) || message.reasoning || (message.tool_calls?.length ?? 0) > 0
1865
2435
  );
1866
2436
  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" }),
2437
+ return /* @__PURE__ */ jsxs10("div", { className: "blade-chat-assistant-turn flex flex-col gap-3", children: [
2438
+ hasInterrupted && /* @__PURE__ */ jsx12("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
2439
  messages.map((message, index) => {
1870
2440
  const isLast = index === messages.length - 1;
1871
2441
  const streamingThis = isStreaming && isLast;
1872
2442
  const text = getMessageText(message);
1873
2443
  const toolCalls = message.tool_calls ?? [];
1874
2444
  const showReasoning = !!message.reasoning && (!isStreaming || index === latestReasoningIndex);
1875
- return /* @__PURE__ */ jsxs8(
2445
+ return /* @__PURE__ */ jsxs10(
1876
2446
  "div",
1877
2447
  {
1878
2448
  className: "flex flex-col gap-3",
1879
2449
  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(
2450
+ showReasoning && message.reasoning && /* @__PURE__ */ jsx12(ThinkingBlock, { reasoning: message.reasoning, isStreaming: streamingThis && !text }),
2451
+ text && /* @__PURE__ */ jsx12("div", { className: "blade-chat-assistant-text text-[15px] leading-8 text-[hsl(var(--foreground))]", children: /* @__PURE__ */ jsx12(
1882
2452
  MarkdownContent,
1883
2453
  {
1884
2454
  mode: streamingThis ? "streaming" : "static",
@@ -1887,8 +2457,8 @@ function AssistantTurnBlock({
1887
2457
  children: text
1888
2458
  }
1889
2459
  ) }),
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(
2460
+ toolCalls.length > 0 && /* @__PURE__ */ jsx12("div", { className: "flex flex-col gap-2", children: toolCalls.map(
2461
+ (toolCall) => formatToolName(toolCall.name) === "Agent" ? /* @__PURE__ */ jsx12(AgentLoopBlock, { toolCall }, toolCall.id) : /* @__PURE__ */ jsx12(
1892
2462
  ToolCallBlock,
1893
2463
  {
1894
2464
  toolCall,
@@ -1906,13 +2476,13 @@ function AssistantTurnBlock({
1906
2476
  message.entry_id ?? `${message.timestamp ?? "assistant"}-${index}`
1907
2477
  );
1908
2478
  }),
1909
- isStreaming && !hasAnyContent && /* @__PURE__ */ jsx10(Shimmer, { className: "ml-4", children: "\u6B63\u5728\u751F\u6210..." })
2479
+ isStreaming && !hasAnyContent && /* @__PURE__ */ jsx12(Shimmer, { className: "ml-4", children: "\u6B63\u5728\u751F\u6210..." })
1910
2480
  ] });
1911
2481
  }
1912
2482
 
1913
2483
  // src/components/RenderErrorBoundary.tsx
1914
2484
  import { Component } from "react";
1915
- import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
2485
+ import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
1916
2486
  function getFirstComponentName(componentStack) {
1917
2487
  const match = componentStack.match(/\n\s+at\s+([^\s(]+)/);
1918
2488
  return match?.[1] ?? null;
@@ -1945,18 +2515,18 @@ var RenderErrorBoundary = class extends Component {
1945
2515
  return children;
1946
2516
  }
1947
2517
  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: [
2518
+ return /* @__PURE__ */ jsx13("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__ */ jsxs11("div", { className: "flex items-start gap-2", children: [
2519
+ /* @__PURE__ */ jsx13(TriangleAlert, { className: "mt-0.5 h-4 w-4 shrink-0 text-amber-300" }),
2520
+ /* @__PURE__ */ jsxs11("div", { className: "min-w-0 flex-1", children: [
2521
+ /* @__PURE__ */ jsxs11("div", { className: "font-medium", children: [
1952
2522
  label,
1953
2523
  "\u6E32\u67D3\u5931\u8D25"
1954
2524
  ] }),
1955
- /* @__PURE__ */ jsxs9("div", { className: "mt-1 break-words text-xs leading-5 text-amber-100/75", children: [
2525
+ /* @__PURE__ */ jsxs11("div", { className: "mt-1 break-words text-xs leading-5 text-amber-100/75", children: [
1956
2526
  componentName ? `\u7EC4\u4EF6\uFF1A${componentName}\u3002` : null,
1957
2527
  error.message || "\u53D1\u751F\u4E86\u672A\u9884\u671F\u7684\u6E32\u67D3\u9519\u8BEF\u3002"
1958
2528
  ] }),
1959
- details ? /* @__PURE__ */ jsx11("div", { className: "mt-1 truncate text-xs text-amber-100/55", children: details }) : null
2529
+ details ? /* @__PURE__ */ jsx13("div", { className: "mt-1 truncate text-xs text-amber-100/55", children: details }) : null
1960
2530
  ] })
1961
2531
  ] }) });
1962
2532
  }
@@ -1964,7 +2534,7 @@ var RenderErrorBoundary = class extends Component {
1964
2534
 
1965
2535
  // src/components/UserMessageBubble.tsx
1966
2536
  import { getFileParts, getImageParts, getTextContent as getTextContent2 } from "@blade-hq/agent-client";
1967
- import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
2537
+ import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
1968
2538
  function isUserMessage(message) {
1969
2539
  return message.role === "user";
1970
2540
  }
@@ -1976,8 +2546,8 @@ function UserMessageBubble({ message, className }) {
1976
2546
  const text = getTextContent2(message.content).trim();
1977
2547
  const fileParts = getFileParts(message.content);
1978
2548
  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(
2549
+ 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: [
2550
+ imageParts.length > 0 && /* @__PURE__ */ jsx14("div", { className: "grid gap-2", children: imageParts.map((part) => /* @__PURE__ */ jsx14(
1981
2551
  "img",
1982
2552
  {
1983
2553
  src: part.image_url.url,
@@ -1986,21 +2556,21 @@ function UserMessageBubble({ message, className }) {
1986
2556
  },
1987
2557
  part.image_url.url
1988
2558
  )) }),
1989
- fileParts.length > 0 && /* @__PURE__ */ jsx12("div", { className: "flex flex-col items-end gap-1.5", children: fileParts.map((part) => /* @__PURE__ */ jsxs10(
2559
+ fileParts.length > 0 && /* @__PURE__ */ jsx14("div", { className: "flex flex-col items-end gap-1.5", children: fileParts.map((part) => /* @__PURE__ */ jsxs12(
1990
2560
  "div",
1991
2561
  {
1992
2562
  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
2563
  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 })
2564
+ /* @__PURE__ */ jsx14(FileText, { size: 12, className: "shrink-0" }),
2565
+ /* @__PURE__ */ jsx14("span", { className: "max-w-56 truncate", title: part.name, children: part.name })
1996
2566
  ]
1997
2567
  },
1998
2568
  `${part.name}-${part.data.length}`
1999
2569
  )) }),
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" })
2570
+ 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 }) }),
2571
+ 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: [
2572
+ /* @__PURE__ */ jsx14(LoaderCircle, { size: 11, className: "animate-spin", "aria-hidden": "true" }),
2573
+ /* @__PURE__ */ jsx14("span", { children: "\u53D1\u9001\u4E2D" })
2004
2574
  ] })
2005
2575
  ] }) });
2006
2576
  }
@@ -2009,11 +2579,11 @@ function ErrorMessageBlock({
2009
2579
  className
2010
2580
  }) {
2011
2581
  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 }) });
2582
+ 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
2583
  }
2014
2584
 
2015
2585
  // src/components/MessageList.tsx
2016
- import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
2586
+ import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
2017
2587
  function parseModeChange(message) {
2018
2588
  if (message.kind !== "mode_change" || typeof message.content !== "string") {
2019
2589
  return null;
@@ -2056,7 +2626,7 @@ function MessageList({
2056
2626
  className,
2057
2627
  sessionId
2058
2628
  }) {
2059
- const renderBlocks = useMemo6(() => {
2629
+ const renderBlocks = useMemo7(() => {
2060
2630
  const visible = messages.filter((message) => {
2061
2631
  if ((message.loop_name ?? "root") !== "root") return false;
2062
2632
  if (isHiddenInternalMessage(message)) return false;
@@ -2129,24 +2699,24 @@ function MessageList({
2129
2699
  }
2130
2700
  return blocks;
2131
2701
  }, [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" })
2702
+ 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: [
2703
+ /* @__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: [
2704
+ renderBlocks.length === 0 ? emptyState ?? /* @__PURE__ */ jsxs13("div", { className: "blade-chat-empty", children: [
2705
+ /* @__PURE__ */ jsx15(MessageSquare, { size: 40, strokeWidth: 1.5 }),
2706
+ /* @__PURE__ */ jsx15("span", { className: "text-base font-medium", children: "\u5F00\u59CB\u5BF9\u8BDD" }),
2707
+ /* @__PURE__ */ jsx15("span", { className: "text-sm opacity-60", children: "\u5728\u4E0B\u65B9\u8F93\u5165\u6D88\u606F\u5F00\u59CB\u804A\u5929" })
2138
2708
  ] }) : renderBlocks.map((block) => {
2139
2709
  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);
2710
+ 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
2711
  }
2142
2712
  if (block.type === "assistant_turn") {
2143
- return /* @__PURE__ */ jsx13("div", { "data-entry-id": block.messages[0]?.entry_id, children: /* @__PURE__ */ jsx13(
2713
+ return /* @__PURE__ */ jsx15("div", { "data-entry-id": block.messages[0]?.entry_id, children: /* @__PURE__ */ jsx15(
2144
2714
  RenderErrorBoundary,
2145
2715
  {
2146
2716
  label: "\u52A9\u624B\u6D88\u606F",
2147
2717
  details: block.key,
2148
2718
  resetKey: getMessageResetSignature(block.messages),
2149
- children: /* @__PURE__ */ jsx13(
2719
+ children: /* @__PURE__ */ jsx15(
2150
2720
  AssistantTurnBlock,
2151
2721
  {
2152
2722
  messages: block.messages,
@@ -2162,30 +2732,30 @@ function MessageList({
2162
2732
  ) }, block.key);
2163
2733
  }
2164
2734
  if (block.type === "compaction") {
2165
- return /* @__PURE__ */ jsxs11(
2735
+ return /* @__PURE__ */ jsxs13(
2166
2736
  "div",
2167
2737
  {
2168
2738
  className: "flex items-center gap-2 text-xs text-[hsl(var(--muted-foreground))]",
2169
2739
  children: [
2170
- /* @__PURE__ */ jsx13(Layers, { size: 12 }),
2171
- /* @__PURE__ */ jsx13("span", { children: "\u4E0A\u4E0B\u6587\u5DF2\u538B\u7F29" })
2740
+ /* @__PURE__ */ jsx15(Layers, { size: 12 }),
2741
+ /* @__PURE__ */ jsx15("span", { children: "\u4E0A\u4E0B\u6587\u5DF2\u538B\u7F29" })
2172
2742
  ]
2173
2743
  },
2174
2744
  block.key
2175
2745
  );
2176
2746
  }
2177
- return /* @__PURE__ */ jsx13(PlanningDivider, { kind: block.kind }, block.key);
2747
+ return /* @__PURE__ */ jsx15(PlanningDivider, { kind: block.kind }, block.key);
2178
2748
  }),
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
2749
+ 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
2750
  ] }) }) }),
2181
- /* @__PURE__ */ jsx13(AutoScrollOnUserSend, { userMessageCount: messages.filter((m) => isUserMessage(m)).length }),
2182
- /* @__PURE__ */ jsx13(ScrollToBottomButton, {})
2751
+ /* @__PURE__ */ jsx15(AutoScrollOnUserSend, { userMessageCount: messages.filter((m) => isUserMessage(m)).length }),
2752
+ /* @__PURE__ */ jsx15(ScrollToBottomButton, {})
2183
2753
  ] }) });
2184
2754
  }
2185
2755
  function AutoScrollOnUserSend({ userMessageCount }) {
2186
2756
  const { scrollToBottom } = useStickToBottomContext();
2187
- const previousCountRef = useRef5(userMessageCount);
2188
- useEffect5(() => {
2757
+ const previousCountRef = useRef6(userMessageCount);
2758
+ useEffect6(() => {
2189
2759
  if (userMessageCount > previousCountRef.current) {
2190
2760
  scrollToBottom("instant");
2191
2761
  }
@@ -2195,9 +2765,9 @@ function AutoScrollOnUserSend({ userMessageCount }) {
2195
2765
  }
2196
2766
  function ScrollToBottomButton() {
2197
2767
  const { isAtBottom, scrollToBottom } = useStickToBottomContext();
2198
- const [visible, setVisible] = useState9(false);
2199
- const hideTimerRef = useRef5(null);
2200
- useEffect5(() => {
2768
+ const [visible, setVisible] = useState11(false);
2769
+ const hideTimerRef = useRef6(null);
2770
+ useEffect6(() => {
2201
2771
  if (isAtBottom) {
2202
2772
  if (!hideTimerRef.current) {
2203
2773
  hideTimerRef.current = setTimeout(() => {
@@ -2219,7 +2789,7 @@ function ScrollToBottomButton() {
2219
2789
  }
2220
2790
  };
2221
2791
  }, [isAtBottom]);
2222
- const handleClick = useCallback3(() => {
2792
+ const handleClick = useCallback5(() => {
2223
2793
  if (hideTimerRef.current) {
2224
2794
  clearTimeout(hideTimerRef.current);
2225
2795
  hideTimerRef.current = null;
@@ -2228,7 +2798,7 @@ function ScrollToBottomButton() {
2228
2798
  scrollToBottom();
2229
2799
  }, [scrollToBottom]);
2230
2800
  if (!visible) return null;
2231
- return /* @__PURE__ */ jsxs11(
2801
+ return /* @__PURE__ */ jsxs13(
2232
2802
  "button",
2233
2803
  {
2234
2804
  type: "button",
@@ -2236,34 +2806,108 @@ function ScrollToBottomButton() {
2236
2806
  "aria-label": "\u6EDA\u52A8\u5230\u5E95\u90E8",
2237
2807
  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
2808
  children: [
2239
- /* @__PURE__ */ jsx13(ChevronDown, { size: 14 }),
2240
- /* @__PURE__ */ jsx13("span", { className: "blade-chat-scroll-bottom-label", children: "\u6EDA\u52A8\u5230\u5E95\u90E8" })
2809
+ /* @__PURE__ */ jsx15(ChevronDown, { size: 14 }),
2810
+ /* @__PURE__ */ jsx15("span", { className: "blade-chat-scroll-bottom-label", children: "\u6EDA\u52A8\u5230\u5E95\u90E8" })
2241
2811
  ]
2242
2812
  }
2243
2813
  );
2244
2814
  }
2245
2815
  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" })
2816
+ return /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-3 py-1", children: [
2817
+ /* @__PURE__ */ jsx15("div", { className: "h-px flex-1 bg-gradient-to-r from-transparent via-amber-400/40 to-transparent" }),
2818
+ /* @__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: [
2819
+ /* @__PURE__ */ jsx15(Lightbulb, { size: 12 }),
2820
+ /* @__PURE__ */ jsx15("span", { children: kind === "enter" ? "\u8FDB\u5165\u89C4\u5212\u6A21\u5F0F" : "\u89C4\u5212\u5B8C\u6210" })
2251
2821
  ] }),
2252
- /* @__PURE__ */ jsx13("div", { className: "h-px flex-1 bg-gradient-to-r from-transparent via-amber-400/40 to-transparent" })
2822
+ /* @__PURE__ */ jsx15("div", { className: "h-px flex-1 bg-gradient-to-r from-transparent via-amber-400/40 to-transparent" })
2253
2823
  ] });
2254
2824
  }
2255
2825
 
2256
- // src/components/ChatView.tsx
2257
- import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
2826
+ // src/components/ChatSurface.tsx
2827
+ import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
2258
2828
  function themeAttr(theme) {
2259
2829
  return theme === "dark" ? "dark" : void 0;
2260
2830
  }
2831
+ function ChatSurface({
2832
+ theme,
2833
+ classNames,
2834
+ renderers,
2835
+ slots,
2836
+ placeholder,
2837
+ connection,
2838
+ errorMessage,
2839
+ messages,
2840
+ isStreaming,
2841
+ isStopping,
2842
+ inputText,
2843
+ onInputChange,
2844
+ onSend,
2845
+ onStop,
2846
+ sessionStatus,
2847
+ askAnswers,
2848
+ onAnswer,
2849
+ sessionId,
2850
+ beforeInput,
2851
+ banner
2852
+ }) {
2853
+ return /* @__PURE__ */ jsxs14(
2854
+ "div",
2855
+ {
2856
+ "data-theme": themeAttr(theme),
2857
+ className: cn(
2858
+ "blade-chat flex min-h-0 flex-1 flex-col overflow-hidden",
2859
+ classNames?.root
2860
+ ),
2861
+ children: [
2862
+ /* @__PURE__ */ jsx16(ConnectionBanner, { connection, className: classNames?.banner }),
2863
+ banner,
2864
+ errorMessage && /* @__PURE__ */ jsxs14("div", { className: "blade-chat-error-bar flex items-start gap-2 border-b px-4 py-3 text-sm", children: [
2865
+ /* @__PURE__ */ jsx16(CircleAlert, { size: 16, className: "mt-0.5 shrink-0" }),
2866
+ /* @__PURE__ */ jsx16("span", { children: errorMessage })
2867
+ ] }),
2868
+ slots?.header,
2869
+ /* @__PURE__ */ jsx16(
2870
+ MessageList,
2871
+ {
2872
+ messages,
2873
+ isStreaming,
2874
+ sessionStatus,
2875
+ askAnswers,
2876
+ onAnswer,
2877
+ toolCallRenderer: renderers?.toolCall,
2878
+ emptyState: slots?.emptyState,
2879
+ className: classNames?.messageList,
2880
+ sessionId
2881
+ }
2882
+ ),
2883
+ beforeInput,
2884
+ /* @__PURE__ */ jsx16(
2885
+ ChatInput,
2886
+ {
2887
+ value: inputText,
2888
+ onValueChange: onInputChange,
2889
+ onSend,
2890
+ onStop,
2891
+ isStreaming,
2892
+ isStopping,
2893
+ placeholder,
2894
+ className: classNames?.chatInput
2895
+ }
2896
+ ),
2897
+ slots?.footer
2898
+ ]
2899
+ }
2900
+ );
2901
+ }
2902
+
2903
+ // src/components/AgentChat.tsx
2904
+ import { Fragment as Fragment2, jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
2261
2905
  function isUnauthorizedError(error) {
2262
2906
  return error instanceof BladeApiError && error.status === 401;
2263
2907
  }
2264
2908
  function LoginCard({ client, onLoggedIn }) {
2265
- const [loggingIn, setLoggingIn] = useState10(false);
2266
- const [loginError, setLoginError] = useState10(null);
2909
+ const [loggingIn, setLoggingIn] = useState12(false);
2910
+ const [loginError, setLoginError] = useState12(null);
2267
2911
  const handleLogin = async () => {
2268
2912
  setLoggingIn(true);
2269
2913
  setLoginError(null);
@@ -2276,11 +2920,11 @@ function LoginCard({ client, onLoggedIn }) {
2276
2920
  setLoggingIn(false);
2277
2921
  }
2278
2922
  };
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(
2923
+ 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: [
2924
+ /* @__PURE__ */ jsx17(LockKeyhole, { size: 28, className: "text-[hsl(var(--muted-foreground))]" }),
2925
+ /* @__PURE__ */ jsx17("div", { className: "text-base font-medium text-[hsl(var(--foreground))]", children: "\u9700\u8981\u767B\u5F55\u540E\u4F7F\u7528" }),
2926
+ /* @__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" }),
2927
+ /* @__PURE__ */ jsx17(
2284
2928
  "button",
2285
2929
  {
2286
2930
  type: "button",
@@ -2290,20 +2934,20 @@ function LoginCard({ client, onLoggedIn }) {
2290
2934
  children: loggingIn ? "\u767B\u5F55\u4E2D\u2026" : "\u767B\u5F55"
2291
2935
  }
2292
2936
  ),
2293
- loginError && /* @__PURE__ */ jsx14("div", { className: "text-xs text-[hsl(var(--destructive))]", children: loginError })
2937
+ loginError && /* @__PURE__ */ jsx17("div", { className: "text-xs text-[hsl(var(--destructive))]", children: loginError })
2294
2938
  ] }) });
2295
2939
  }
2296
- function ChatView(props) {
2940
+ function AgentChat(props) {
2297
2941
  const client = useBladeClient();
2298
- const [attempt, setAttempt] = useState10(0);
2299
- const [needLogin, setNeedLogin] = useState10(() => !client.hasToken());
2942
+ const [attempt, setAttempt] = useState12(0);
2943
+ const [needLogin, setNeedLogin] = useState12(() => !client.hasToken());
2300
2944
  if (needLogin) {
2301
- return /* @__PURE__ */ jsx14(
2945
+ return /* @__PURE__ */ jsx17(
2302
2946
  "div",
2303
2947
  {
2304
2948
  "data-theme": themeAttr(props.theme),
2305
2949
  className: cn("blade-chat flex min-h-0 flex-1 flex-col", props.classNames?.root),
2306
- children: /* @__PURE__ */ jsx14(
2950
+ children: /* @__PURE__ */ jsx17(
2307
2951
  LoginCard,
2308
2952
  {
2309
2953
  client,
@@ -2316,7 +2960,7 @@ function ChatView(props) {
2316
2960
  }
2317
2961
  );
2318
2962
  }
2319
- return /* @__PURE__ */ jsx14(ChatSessionView, { ...props, onUnauthorized: () => setNeedLogin(true) }, attempt);
2963
+ return /* @__PURE__ */ jsx17(ChatSessionView, { ...props, onUnauthorized: () => setNeedLogin(true) }, attempt);
2320
2964
  }
2321
2965
  function ChatSessionView({
2322
2966
  sessionId,
@@ -2335,14 +2979,15 @@ function ChatSessionView({
2335
2979
  createOptions,
2336
2980
  onSessionCreated
2337
2981
  });
2338
- const [stopRequested, setStopRequested] = useState10(false);
2339
- const [inputText, setInputText] = useState10("");
2340
- useEffect6(() => {
2982
+ const replay = useReplay(session);
2983
+ const [stopRequested, setStopRequested] = useState12(false);
2984
+ const [inputText, setInputText] = useState12("");
2985
+ useEffect7(() => {
2341
2986
  if (session) {
2342
2987
  onSessionReady?.(session);
2343
2988
  }
2344
2989
  }, [session, onSessionReady]);
2345
- useEffect6(() => {
2990
+ useEffect7(() => {
2346
2991
  if (!session) return;
2347
2992
  const offAttach = session.on("attachRequested", ({ label, content }) => {
2348
2993
  setInputText((prev) => `${prev ? `${prev}
@@ -2358,12 +3003,12 @@ ${content}`);
2358
3003
  offInsert();
2359
3004
  };
2360
3005
  }, [session]);
2361
- useEffect6(() => {
3006
+ useEffect7(() => {
2362
3007
  if (isUnauthorizedError(error)) {
2363
3008
  onUnauthorized();
2364
3009
  }
2365
3010
  }, [error, onUnauthorized]);
2366
- useEffect6(() => {
3011
+ useEffect7(() => {
2367
3012
  if (!session || !commands) return;
2368
3013
  const unsubscribes = Object.entries(commands).map(
2369
3014
  ([action, handler]) => session.onCommand(action, (payload) => handler(payload))
@@ -2375,7 +3020,7 @@ ${content}`);
2375
3020
  const isStreaming = state?.isStreaming ?? false;
2376
3021
  const isStopping = stopRequested && isStreaming;
2377
3022
  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;
3023
+ const errorMessage = connectError ?? state?.errorMessage ?? replay.error?.message ?? null;
2379
3024
  const handleSend = (text) => {
2380
3025
  setStopRequested(false);
2381
3026
  return session?.send(text, { mode: state?.mode ?? void 0 }) ?? Promise.resolve(false);
@@ -2384,73 +3029,281 @@ ${content}`);
2384
3029
  setStopRequested(true);
2385
3030
  void session?.stop();
2386
3031
  };
2387
- return /* @__PURE__ */ jsxs12(
2388
- "div",
3032
+ return /* @__PURE__ */ jsx17(
3033
+ ChatSurface,
2389
3034
  {
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,
3035
+ theme,
3036
+ classNames,
3037
+ renderers,
3038
+ slots,
3039
+ placeholder,
3040
+ connection: state?.connection ?? "connecting",
3041
+ banner: /* @__PURE__ */ jsxs15(Fragment2, { children: [
3042
+ /* @__PURE__ */ jsx17(
3043
+ ReplayBar,
2398
3044
  {
2399
- connection: state?.connection ?? "connecting",
2400
- className: classNames?.banner
3045
+ isReplay: replay.isReplay,
3046
+ speed: replay.speed,
3047
+ canControl: replay.canControl,
3048
+ onSpeedChange: (next) => void replay.setSpeed(next),
3049
+ onExit: () => void replay.exitToAutonomous()
2401
3050
  }
2402
3051
  ),
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,
3052
+ /* @__PURE__ */ jsx17(ReplayMismatchPrompt, { mismatch: replay.mismatch })
3053
+ ] }),
3054
+ errorMessage,
3055
+ messages: state?.messages ?? [],
3056
+ isStreaming,
3057
+ isStopping,
3058
+ inputText,
3059
+ onInputChange: setInputText,
3060
+ onSend: handleSend,
3061
+ onStop: handleStop,
3062
+ sessionStatus: state?.status ?? void 0,
3063
+ askAnswers: state?.askAnswers,
3064
+ onAnswer: (answer, toolCallId, answerData) => {
3065
+ void session?.send(answer, {
3066
+ mode: state?.mode ?? void 0,
3067
+ askUserAnswer: { tool_call_id: toolCallId, ...answerData }
3068
+ });
3069
+ },
3070
+ sessionId: session?.sessionId
3071
+ }
3072
+ );
3073
+ }
3074
+
3075
+ // src/components/LlmChat.tsx
3076
+ import { useEffect as useEffect8, useMemo as useMemo8, useState as useState14 } from "react";
3077
+
3078
+ // src/components/LlmAdvancedSettings.tsx
3079
+ import { useState as useState13 } from "react";
3080
+ import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
3081
+ var FIELDS = [
3082
+ { id: "baseURL", label: "\u6A21\u578B\u670D\u52A1\u5730\u5740", placeholder: "http://\u5185\u7F51\u5730\u5740/v1" },
3083
+ { id: "model", label: "\u6A21\u578B", placeholder: "\u6A21\u578B\u540D\u79F0" },
3084
+ { id: "apiKey", label: "\u5BC6\u94A5", placeholder: "\u7559\u7A7A\u8868\u793A\u4E0D\u6539", secret: true }
3085
+ ];
3086
+ function normalizeAdvanced(value) {
3087
+ if (!value) return null;
3088
+ const config = value === true ? {} : value;
3089
+ return {
3090
+ baseURL: config.baseURL ?? true,
3091
+ model: config.model ?? true,
3092
+ apiKey: config.apiKey ?? false,
3093
+ storage: config.storage ?? "local"
3094
+ };
3095
+ }
3096
+ function storageKeyFor(settings, baseURL) {
3097
+ const explicit = typeof settings === "object" ? settings.storageKey : void 0;
3098
+ return `blade-llm-override:${explicit ?? baseURL}`;
3099
+ }
3100
+ function readOverride(settings, baseURL) {
3101
+ const normalized = normalizeAdvanced(settings);
3102
+ if (!normalized || normalized.storage !== "local" || typeof localStorage === "undefined") return {};
3103
+ try {
3104
+ const raw = localStorage.getItem(storageKeyFor(settings, baseURL));
3105
+ if (!raw) return {};
3106
+ const stored = JSON.parse(raw);
3107
+ return Object.fromEntries(
3108
+ Object.entries(stored).filter(([key]) => normalized[key])
3109
+ );
3110
+ } catch {
3111
+ return {};
3112
+ }
3113
+ }
3114
+ function writeOverride(settings, baseURL, override) {
3115
+ const normalized = normalizeAdvanced(settings);
3116
+ if (!normalized || normalized.storage !== "local" || typeof localStorage === "undefined") return;
3117
+ try {
3118
+ const key = storageKeyFor(settings, baseURL);
3119
+ if (Object.keys(override).length === 0) localStorage.removeItem(key);
3120
+ else localStorage.setItem(key, JSON.stringify(override));
3121
+ } catch {
3122
+ }
3123
+ }
3124
+ function LlmAdvancedSettingsBar({ settings, defaults, override, onChange }) {
3125
+ const normalized = normalizeAdvanced(settings);
3126
+ const [open, setOpen] = useState13(false);
3127
+ const [draft, setDraft] = useState13(override);
3128
+ if (!normalized) return null;
3129
+ const fields = FIELDS.filter((field) => normalized[field.id]);
3130
+ const dirty = Object.keys(override).length > 0;
3131
+ return /* @__PURE__ */ jsxs16("div", { className: "blade-chat-advanced border-t border-[hsl(var(--border))] px-4 py-2 text-xs", children: [
3132
+ /* @__PURE__ */ jsxs16(
3133
+ "button",
3134
+ {
3135
+ type: "button",
3136
+ onClick: () => {
3137
+ setDraft(override);
3138
+ setOpen((value) => !value);
3139
+ },
3140
+ className: "flex items-center gap-1.5 text-[hsl(var(--muted-foreground))] transition-colors hover:text-[hsl(var(--foreground))]",
3141
+ children: [
3142
+ /* @__PURE__ */ jsx18(Settings2, { size: 13 }),
3143
+ "\u9AD8\u7EA7\u8BBE\u7F6E",
3144
+ dirty && /* @__PURE__ */ jsx18("span", { className: "text-[hsl(var(--primary))]", children: "\uFF08\u5DF2\u81EA\u5B9A\u4E49\uFF09" })
3145
+ ]
3146
+ }
3147
+ ),
3148
+ open && /* @__PURE__ */ jsxs16("div", { className: "mt-2 flex flex-col gap-2", children: [
3149
+ fields.map((field) => /* @__PURE__ */ jsxs16("label", { className: "flex flex-col gap-1", children: [
3150
+ /* @__PURE__ */ jsx18("span", { className: "text-[hsl(var(--muted-foreground))]", children: field.label }),
3151
+ /* @__PURE__ */ jsx18(
3152
+ "input",
2410
3153
  {
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
- });
3154
+ type: field.secret ? "password" : "text",
3155
+ value: draft[field.id] ?? "",
3156
+ placeholder: field.id === "apiKey" ? field.placeholder : defaults[field.id] || field.placeholder,
3157
+ onChange: (event) => setDraft({ ...draft, [field.id]: event.target.value }),
3158
+ className: "rounded-md border border-[hsl(var(--border))] bg-[hsl(var(--background))] px-2 py-1 text-xs text-[hsl(var(--foreground))] outline-none"
3159
+ }
3160
+ )
3161
+ ] }, field.id)),
3162
+ 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" }),
3163
+ /* @__PURE__ */ jsxs16("div", { className: "flex gap-2", children: [
3164
+ /* @__PURE__ */ jsx18(
3165
+ "button",
3166
+ {
3167
+ type: "button",
3168
+ onClick: () => {
3169
+ const next = Object.fromEntries(
3170
+ Object.entries(draft).filter(([, value]) => String(value ?? "").trim() !== "")
3171
+ );
3172
+ onChange(next);
3173
+ setOpen(false);
2420
3174
  },
2421
- toolCallRenderer: renderers?.toolCall,
2422
- emptyState: slots?.emptyState,
2423
- className: classNames?.messageList,
2424
- sessionId: session?.sessionId
3175
+ className: "rounded-md bg-[hsl(var(--primary))] px-2.5 py-1 font-medium text-[hsl(var(--primary-foreground))]",
3176
+ children: "\u4FDD\u5B58"
2425
3177
  }
2426
3178
  ),
2427
- /* @__PURE__ */ jsx14(
2428
- ChatInput,
3179
+ /* @__PURE__ */ jsx18(
3180
+ "button",
2429
3181
  {
2430
- value: inputText,
2431
- onValueChange: setInputText,
2432
- onSend: handleSend,
2433
- onStop: handleStop,
2434
- isStreaming,
2435
- isStopping,
2436
- placeholder,
2437
- className: classNames?.chatInput
3182
+ type: "button",
3183
+ onClick: () => {
3184
+ setDraft({});
3185
+ onChange({});
3186
+ setOpen(false);
3187
+ },
3188
+ className: "rounded-md border border-[hsl(var(--border))] px-2.5 py-1 text-[hsl(var(--foreground))]",
3189
+ children: "\u6062\u590D\u9ED8\u8BA4"
2438
3190
  }
2439
- ),
2440
- slots?.footer
2441
- ]
3191
+ )
3192
+ ] })
3193
+ ] })
3194
+ ] });
3195
+ }
3196
+
3197
+ // src/components/LlmChat.tsx
3198
+ import { jsx as jsx19 } from "react/jsx-runtime";
3199
+ function LlmChat({
3200
+ classNames,
3201
+ renderers,
3202
+ slots,
3203
+ placeholder,
3204
+ theme,
3205
+ onReady,
3206
+ advanced,
3207
+ onOverrideChange,
3208
+ ...options
3209
+ }) {
3210
+ const [override, setOverride] = useState14(() => readOverride(advanced, options.baseURL));
3211
+ const effective = { ...options, ...override };
3212
+ const { messages, isStreaming, error, send, stop, reset } = useLlmChat(effective);
3213
+ const [inputText, setInputText] = useState14("");
3214
+ const [stopRequested, setStopRequested] = useState14(false);
3215
+ const handle = useMemo8(
3216
+ () => ({
3217
+ insertText: (text) => setInputText((prev) => prev ? `${prev}
3218
+ ${text}` : text),
3219
+ send,
3220
+ reset
3221
+ }),
3222
+ [send, reset]
3223
+ );
3224
+ useEffect8(() => {
3225
+ onReady?.(handle);
3226
+ }, [handle, onReady]);
3227
+ return /* @__PURE__ */ jsx19(
3228
+ ChatSurface,
3229
+ {
3230
+ theme,
3231
+ classNames,
3232
+ renderers,
3233
+ slots,
3234
+ placeholder,
3235
+ connection: "connected",
3236
+ errorMessage: error,
3237
+ messages,
3238
+ isStreaming,
3239
+ isStopping: stopRequested && isStreaming,
3240
+ inputText,
3241
+ onInputChange: setInputText,
3242
+ onSend: async (text) => {
3243
+ setStopRequested(false);
3244
+ if (!text.trim() || isStreaming) return false;
3245
+ void send(text);
3246
+ return true;
3247
+ },
3248
+ onStop: () => {
3249
+ setStopRequested(true);
3250
+ stop();
3251
+ },
3252
+ beforeInput: advanced ? /* @__PURE__ */ jsx19(
3253
+ LlmAdvancedSettingsBar,
3254
+ {
3255
+ settings: advanced,
3256
+ defaults: { baseURL: options.baseURL, model: options.model },
3257
+ override,
3258
+ onChange: (next) => {
3259
+ setOverride(next);
3260
+ writeOverride(advanced, options.baseURL, next);
3261
+ onOverrideChange?.(next);
3262
+ }
3263
+ }
3264
+ ) : void 0
2442
3265
  }
2443
3266
  );
2444
3267
  }
2445
3268
 
3269
+ // src/components/ChatView.tsx
3270
+ import { jsx as jsx20 } from "react/jsx-runtime";
3271
+ function ChatView(props) {
3272
+ const { mode, llm, onLlmReady, ...rest } = props;
3273
+ if (mode === "llm") {
3274
+ if (!llm) {
3275
+ throw new Error('ChatView: mode="llm" \u9700\u8981\u540C\u65F6\u4F20 llm={{ baseURL, model }}');
3276
+ }
3277
+ return /* @__PURE__ */ jsx20(
3278
+ LlmChat,
3279
+ {
3280
+ ...llm,
3281
+ classNames: rest.classNames,
3282
+ renderers: rest.renderers,
3283
+ slots: rest.slots,
3284
+ placeholder: rest.placeholder,
3285
+ theme: rest.theme,
3286
+ onReady: onLlmReady
3287
+ }
3288
+ );
3289
+ }
3290
+ return /* @__PURE__ */ jsx20(AgentChat, { ...rest });
3291
+ }
3292
+
2446
3293
  // src/index.ts
2447
3294
  export * from "@blade-hq/agent-client";
2448
3295
  export {
3296
+ AgentChat,
2449
3297
  BladeProvider,
2450
3298
  ChatView,
3299
+ LlmChat,
2451
3300
  MarkdownContent,
3301
+ ReplayBar,
3302
+ ReplayMismatchPrompt,
2452
3303
  useAgentSession,
2453
- useBladeClient
3304
+ useBladeClient,
3305
+ useLlmChat,
3306
+ useReplay
2454
3307
  };
2455
3308
  /*! Bundled license information:
2456
3309
 
@@ -2481,6 +3334,8 @@ lucide-react/dist/esm/icons/lock-keyhole.js:
2481
3334
  lucide-react/dist/esm/icons/message-square-more.js:
2482
3335
  lucide-react/dist/esm/icons/message-square.js:
2483
3336
  lucide-react/dist/esm/icons/music.js:
3337
+ lucide-react/dist/esm/icons/play.js:
3338
+ lucide-react/dist/esm/icons/settings-2.js:
2484
3339
  lucide-react/dist/esm/icons/square.js:
2485
3340
  lucide-react/dist/esm/icons/triangle-alert.js:
2486
3341
  lucide-react/dist/esm/icons/x.js: