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

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