@huyooo/ai-chat-frontend-react 0.2.12 → 0.2.14

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.
Files changed (110) hide show
  1. package/README.md +99 -84
  2. package/dist/KaTeX_AMS-Regular-CYEKBG2K.woff +0 -0
  3. package/dist/KaTeX_AMS-Regular-JKX5W2C4.ttf +0 -0
  4. package/dist/KaTeX_AMS-Regular-U6PRYMIZ.woff2 +0 -0
  5. package/dist/KaTeX_Caligraphic-Bold-5QL5CMTE.woff2 +0 -0
  6. package/dist/KaTeX_Caligraphic-Bold-WZ3QSGD3.woff +0 -0
  7. package/dist/KaTeX_Caligraphic-Bold-ZTS3R3HK.ttf +0 -0
  8. package/dist/KaTeX_Caligraphic-Regular-3LKEU76G.woff +0 -0
  9. package/dist/KaTeX_Caligraphic-Regular-A7XRTZ5Q.ttf +0 -0
  10. package/dist/KaTeX_Caligraphic-Regular-KX5MEWCF.woff2 +0 -0
  11. package/dist/KaTeX_Fraktur-Bold-2QVFK6NQ.woff2 +0 -0
  12. package/dist/KaTeX_Fraktur-Bold-T4SWXBMT.woff +0 -0
  13. package/dist/KaTeX_Fraktur-Bold-WGHVTYOR.ttf +0 -0
  14. package/dist/KaTeX_Fraktur-Regular-2PEIFJSJ.woff2 +0 -0
  15. package/dist/KaTeX_Fraktur-Regular-5U4OPH2X.ttf +0 -0
  16. package/dist/KaTeX_Fraktur-Regular-PQMHCIK6.woff +0 -0
  17. package/dist/KaTeX_Main-Bold-2GA4IZIN.woff +0 -0
  18. package/dist/KaTeX_Main-Bold-W5FBVCZM.ttf +0 -0
  19. package/dist/KaTeX_Main-Bold-YP5VVQRP.woff2 +0 -0
  20. package/dist/KaTeX_Main-BoldItalic-4P4C7HJH.woff +0 -0
  21. package/dist/KaTeX_Main-BoldItalic-N4V3DX7S.woff2 +0 -0
  22. package/dist/KaTeX_Main-BoldItalic-ODMLBJJQ.ttf +0 -0
  23. package/dist/KaTeX_Main-Italic-I43T2HSR.ttf +0 -0
  24. package/dist/KaTeX_Main-Italic-RELBIK7M.woff2 +0 -0
  25. package/dist/KaTeX_Main-Italic-SASNQFN2.woff +0 -0
  26. package/dist/KaTeX_Main-Regular-ARRPAO67.woff2 +0 -0
  27. package/dist/KaTeX_Main-Regular-P5I74A2A.woff +0 -0
  28. package/dist/KaTeX_Main-Regular-W74P5G27.ttf +0 -0
  29. package/dist/KaTeX_Math-BoldItalic-6EBV3DK5.woff +0 -0
  30. package/dist/KaTeX_Math-BoldItalic-K4WTGH3J.woff2 +0 -0
  31. package/dist/KaTeX_Math-BoldItalic-VB447A4D.ttf +0 -0
  32. package/dist/KaTeX_Math-Italic-6KGCHLFN.woff2 +0 -0
  33. package/dist/KaTeX_Math-Italic-KKK3USB2.woff +0 -0
  34. package/dist/KaTeX_Math-Italic-SON4MRCA.ttf +0 -0
  35. package/dist/KaTeX_SansSerif-Bold-RRNVJFFW.woff2 +0 -0
  36. package/dist/KaTeX_SansSerif-Bold-STQ6RXC7.ttf +0 -0
  37. package/dist/KaTeX_SansSerif-Bold-X5M5EMOD.woff +0 -0
  38. package/dist/KaTeX_SansSerif-Italic-HMPFTM52.woff2 +0 -0
  39. package/dist/KaTeX_SansSerif-Italic-PSN4QKYX.woff +0 -0
  40. package/dist/KaTeX_SansSerif-Italic-WTBAZBGY.ttf +0 -0
  41. package/dist/KaTeX_SansSerif-Regular-2TL3USAE.ttf +0 -0
  42. package/dist/KaTeX_SansSerif-Regular-OQCII6EP.woff +0 -0
  43. package/dist/KaTeX_SansSerif-Regular-XIQ62X4E.woff2 +0 -0
  44. package/dist/KaTeX_Script-Regular-72OLXYNA.ttf +0 -0
  45. package/dist/KaTeX_Script-Regular-A5IFOEBS.woff +0 -0
  46. package/dist/KaTeX_Script-Regular-APUWIHLP.woff2 +0 -0
  47. package/dist/KaTeX_Size1-Regular-4HRHTS65.woff +0 -0
  48. package/dist/KaTeX_Size1-Regular-5LRUTBFT.woff2 +0 -0
  49. package/dist/KaTeX_Size1-Regular-7K6AASVL.ttf +0 -0
  50. package/dist/KaTeX_Size2-Regular-222HN3GT.ttf +0 -0
  51. package/dist/KaTeX_Size2-Regular-K5ZHAIS6.woff +0 -0
  52. package/dist/KaTeX_Size2-Regular-LELKET5D.woff2 +0 -0
  53. package/dist/KaTeX_Size3-Regular-TLFPAHDE.woff +0 -0
  54. package/dist/KaTeX_Size3-Regular-UFCO6WCA.ttf +0 -0
  55. package/dist/KaTeX_Size3-Regular-WQRQ47UD.woff2 +0 -0
  56. package/dist/KaTeX_Size4-Regular-7PGNVPQK.ttf +0 -0
  57. package/dist/KaTeX_Size4-Regular-CDMV7U5C.woff2 +0 -0
  58. package/dist/KaTeX_Size4-Regular-PKMWZHNC.woff +0 -0
  59. package/dist/KaTeX_Typewriter-Regular-3F5K6SQ6.ttf +0 -0
  60. package/dist/KaTeX_Typewriter-Regular-MJMFSK64.woff +0 -0
  61. package/dist/KaTeX_Typewriter-Regular-VBYJ4NRC.woff2 +0 -0
  62. package/dist/index.css +2156 -603
  63. package/dist/index.css.map +1 -1
  64. package/dist/index.d.ts +126 -92
  65. package/dist/index.js +1605 -976
  66. package/dist/index.js.map +1 -1
  67. package/dist/style.css +130 -0
  68. package/package.json +3 -3
  69. package/src/components/ChatPanel.tsx +82 -19
  70. package/src/components/common/SettingsPanel.css +81 -0
  71. package/src/components/common/SettingsPanel.tsx +96 -1
  72. package/src/components/input/ChatInput.css +0 -1
  73. package/src/components/input/ChatInput.tsx +48 -26
  74. package/src/components/input/DropdownSelector.css +66 -0
  75. package/src/components/input/DropdownSelector.tsx +157 -19
  76. package/src/components/message/MessageBubble.css +5 -2
  77. package/src/components/message/MessageBubble.tsx +44 -35
  78. package/src/components/message/PartsRenderer.css +8 -0
  79. package/src/components/message/PartsRenderer.tsx +137 -83
  80. package/src/components/message/parts/CollapsibleCard.css +4 -2
  81. package/src/components/message/parts/CollapsibleCard.tsx +4 -1
  82. package/src/components/message/parts/ImagePart.css +0 -1
  83. package/src/components/message/parts/TextPart.css +574 -5
  84. package/src/components/message/parts/TextPart.tsx +201 -8
  85. package/src/components/message/parts/ToolCallPart.css +139 -115
  86. package/src/components/message/parts/ToolCallPart.tsx +138 -134
  87. package/src/components/message/parts/ToolResultPart.css +0 -1
  88. package/src/components/message/parts/index.ts +3 -1
  89. package/src/components/message/parts/visual-predicate.ts +43 -0
  90. package/src/components/message/parts/visual-render.ts +19 -0
  91. package/src/components/message/parts/visual.ts +12 -0
  92. package/src/context/RenderersContext.tsx +19 -25
  93. package/src/hooks/useChat.ts +567 -79
  94. package/src/hooks/useImageUpload.ts +104 -12
  95. package/src/hooks/useVoiceInput.ts +17 -0
  96. package/src/index.ts +19 -16
  97. package/src/styles.css +130 -0
  98. package/src/types/index.ts +52 -68
  99. package/src/components/message/ContentRenderer.tsx +0 -63
  100. package/src/components/message/ToolResultRenderer.tsx +0 -21
  101. package/src/components/message/blocks/CodeBlock.tsx +0 -60
  102. package/src/components/message/blocks/TextBlock.tsx +0 -15
  103. package/src/components/message/blocks/blocks.css +0 -141
  104. package/src/components/message/blocks/index.ts +0 -6
  105. package/src/components/message/parts/ToolResultPart.tsx +0 -96
  106. package/src/components/message/tool-results/DefaultToolResult.tsx +0 -26
  107. package/src/components/message/tool-results/SearchResults.tsx +0 -69
  108. package/src/components/message/tool-results/WeatherCard.tsx +0 -63
  109. package/src/components/message/tool-results/index.ts +0 -7
  110. package/src/components/message/tool-results/tool-results.css +0 -181
package/dist/index.js CHANGED
@@ -5,12 +5,81 @@ function getMessageText(message) {
5
5
 
6
6
  // src/hooks/useChat.ts
7
7
  import { useState, useCallback, useRef, useMemo, useEffect } from "react";
8
+ var AT_LINE_RE = /^\s*@\s*(.+?)\s*$/;
9
+ var MAX_FILE_CHARS = 2e4;
10
+ function stripAtContextLines(text) {
11
+ return text.split("\n").filter((line) => !AT_LINE_RE.test(line)).join("\n").trim();
12
+ }
13
+ function extractAtContextRefs(text) {
14
+ const refs = [];
15
+ const kept = [];
16
+ for (const line of text.split("\n")) {
17
+ const m = line.match(AT_LINE_RE);
18
+ if (m?.[1]) {
19
+ refs.push(m[1]);
20
+ } else {
21
+ kept.push(line);
22
+ }
23
+ }
24
+ return {
25
+ refs: Array.from(new Set(refs.map((r) => r.trim()).filter(Boolean))),
26
+ cleanedText: kept.join("\n").trim()
27
+ };
28
+ }
29
+ async function buildPromptWithAtContext(adapter, text) {
30
+ const { refs, cleanedText } = extractAtContextRefs(text);
31
+ if (refs.length === 0) return text;
32
+ const blocks = [];
33
+ for (const ref of refs) {
34
+ const resolved = await adapter.resolvePath?.(ref) || ref;
35
+ try {
36
+ const stat = await adapter.stat?.(resolved);
37
+ if (stat?.isDirectory) {
38
+ const items = await adapter.listDir?.(resolved);
39
+ const listing = (items || []).slice(0, 200).map((f) => `${f.isDirectory ? "DIR " : "FILE"} ${f.path}`).join("\n");
40
+ blocks.push(
41
+ `\u3010\u76EE\u5F55\u3011${resolved}
42
+ \`\`\`text
43
+ ` + (listing || "(\u7A7A\u76EE\u5F55/\u65E0\u6CD5\u5217\u51FA)") + "\n```"
44
+ );
45
+ continue;
46
+ }
47
+ const content = await adapter.readFile?.(resolved);
48
+ if (typeof content === "string") {
49
+ const truncated = content.length > MAX_FILE_CHARS ? content.slice(0, MAX_FILE_CHARS) + `
50
+
51
+ ... (\u5DF2\u622A\u65AD\uFF0C\u539F\u59CB\u957F\u5EA6 ${content.length})` : content;
52
+ blocks.push(
53
+ `\u3010\u6587\u4EF6\u3011${resolved}
54
+ \`\`\`text
55
+ ` + truncated + "\n```"
56
+ );
57
+ } else {
58
+ blocks.push(`\u3010\u5F15\u7528\u3011${resolved}
59
+ \uFF08\u65E0\u6CD5\u8BFB\u53D6\u6587\u4EF6\u5185\u5BB9\uFF09`);
60
+ }
61
+ } catch (error) {
62
+ blocks.push(`\u3010\u5F15\u7528\u3011${resolved}
63
+ \uFF08\u8BFB\u53D6\u5931\u8D25\uFF1A${error instanceof Error ? error.message : String(error)}\uFF09`);
64
+ }
65
+ }
66
+ const question = cleanedText || stripAtContextLines(text) || text;
67
+ return `\u4EE5\u4E0B\u662F\u7528\u6237\u901A\u8FC7 @ \u5F15\u7528\u63D0\u4F9B\u7684\u4E0A\u4E0B\u6587\uFF1A
68
+
69
+ ${blocks.join("\n\n")}
70
+
71
+ \u7528\u6237\u95EE\u9898\uFF1A
72
+ ${question}`;
73
+ }
8
74
  function generateId() {
9
75
  return Date.now().toString(36) + Math.random().toString(36).substr(2);
10
76
  }
11
77
  function extractTextContent(parts) {
12
78
  return parts.filter((p) => p.type === "text").map((p) => p.text).join("");
13
79
  }
80
+ function shouldSaveEvent(event) {
81
+ return event.type === "thinking_end" || event.type === "tool_call_result" || event.type === "text_delta" || event.type === "done" || event.type === "error" || event.type === "abort";
82
+ }
14
83
  function parseToolResult(result) {
15
84
  if (result === void 0 || result === null) return null;
16
85
  if (typeof result === "string") {
@@ -23,6 +92,7 @@ function parseToolResult(result) {
23
92
  return result;
24
93
  }
25
94
  function convertToMessage(record) {
95
+ const timestamp = typeof record.timestamp === "number" && Number.isFinite(record.timestamp) ? record.timestamp : 0;
26
96
  let parts = [];
27
97
  if (record.steps) {
28
98
  try {
@@ -49,7 +119,8 @@ function convertToMessage(record) {
49
119
  name: step.name,
50
120
  args: step.args,
51
121
  result: parseToolResult(step.result),
52
- status: step.status || "done"
122
+ status: step.status || "done",
123
+ output: step.output
53
124
  });
54
125
  } else if (step.type === "text") {
55
126
  parts.push({
@@ -77,12 +148,13 @@ function convertToMessage(record) {
77
148
  id: record.id,
78
149
  role: record.role,
79
150
  parts,
151
+ images: record.images ?? [],
80
152
  model: record.model || void 0,
81
153
  mode: record.mode || void 0,
82
154
  webSearchEnabled: record.webSearchEnabled ?? void 0,
83
155
  thinkingEnabled: record.thinkingEnabled ?? void 0,
84
156
  loading: false,
85
- timestamp: record.timestamp
157
+ timestamp
86
158
  };
87
159
  }
88
160
  function useChat(options) {
@@ -95,6 +167,10 @@ function useChat(options) {
95
167
  } = options;
96
168
  const sessionStatesRef = useRef(/* @__PURE__ */ new Map());
97
169
  const [stateVersion, setStateVersion] = useState(0);
170
+ const [enabledTools, setEnabledTools] = useState(void 0);
171
+ const enabledToolsRef = useRef(enabledTools);
172
+ enabledToolsRef.current = enabledTools;
173
+ const [allTools, setAllTools] = useState([]);
98
174
  const DEFAULT_AUTO_RUN_CONFIG = {
99
175
  mode: "run-everything"
100
176
  };
@@ -124,16 +200,65 @@ function useChat(options) {
124
200
  throw error;
125
201
  }
126
202
  }, [adapter]);
203
+ const loadEnabledTools = useCallback(async () => {
204
+ if (!adapter.getAllSettings) return;
205
+ try {
206
+ const settings = await adapter.getAllSettings();
207
+ const toolsJson = settings["enabledTools"];
208
+ if (!toolsJson) {
209
+ setEnabledTools(void 0);
210
+ return;
211
+ }
212
+ const parsed = JSON.parse(toolsJson);
213
+ if (Array.isArray(parsed) && parsed.every((x) => typeof x === "string")) {
214
+ setEnabledTools(parsed);
215
+ } else {
216
+ setEnabledTools(void 0);
217
+ }
218
+ } catch (error) {
219
+ console.error("[useChat] \u52A0\u8F7D enabledTools \u5931\u8D25:", error);
220
+ }
221
+ }, [adapter]);
222
+ const saveEnabledTools = useCallback(async (tools) => {
223
+ if (!adapter.setSetting) return;
224
+ try {
225
+ if (tools === void 0) {
226
+ await adapter.deleteSetting?.("enabledTools");
227
+ setEnabledTools(void 0);
228
+ return;
229
+ }
230
+ await adapter.setSetting("enabledTools", JSON.stringify(tools));
231
+ setEnabledTools(tools);
232
+ } catch (error) {
233
+ console.error("[useChat] \u4FDD\u5B58 enabledTools \u5931\u8D25:", error);
234
+ throw error;
235
+ }
236
+ }, [adapter]);
237
+ const loadAllTools = useCallback(async () => {
238
+ if (!adapter.getAllTools) {
239
+ console.warn("[useChat] adapter.getAllTools \u4E0D\u5B58\u5728");
240
+ return;
241
+ }
242
+ try {
243
+ const tools = await adapter.getAllTools();
244
+ setAllTools(tools);
245
+ } catch (error) {
246
+ console.error("[useChat] \u52A0\u8F7D\u5DE5\u5177\u5217\u8868\u5931\u8D25:", error);
247
+ }
248
+ }, [adapter]);
127
249
  useEffect(() => {
128
250
  loadAutoRunConfig();
129
- }, [loadAutoRunConfig]);
251
+ loadEnabledTools();
252
+ loadAllTools();
253
+ }, [loadAutoRunConfig, loadEnabledTools, loadAllTools]);
130
254
  const forceUpdate = useCallback(() => setStateVersion((v) => v + 1), []);
131
255
  const getSessionState = useCallback((sessionId) => {
132
256
  if (!sessionStatesRef.current.has(sessionId)) {
133
257
  sessionStatesRef.current.set(sessionId, {
134
258
  messages: [],
135
259
  isLoading: false,
136
- abortController: null
260
+ abortController: null,
261
+ activeAssistantMessageId: null
137
262
  });
138
263
  }
139
264
  return sessionStatesRef.current.get(sessionId);
@@ -173,17 +298,18 @@ function useChat(options) {
173
298
  const list = await adapter.getSessions();
174
299
  setSessions(list);
175
300
  if (list.length > 0 && !currentSessionIdRef.current) {
176
- setCurrentSessionId(list[0].id);
177
- const state = getSessionState(list[0].id);
301
+ const firstVisible = list.find((s) => !s.hidden) || list[0];
302
+ setCurrentSessionId(firstVisible.id);
303
+ const state = getSessionState(firstVisible.id);
178
304
  if (state.messages.length === 0) {
179
- const savedMessages = await adapter.getMessages(list[0].id);
305
+ const savedMessages = await adapter.getMessages(firstVisible.id);
180
306
  state.messages = savedMessages.map(convertToMessage);
181
307
  forceUpdate();
182
308
  }
183
- setModeState(list[0].mode);
184
- setModelState(list[0].model);
185
- setWebSearchState(list[0].webSearchEnabled);
186
- setThinkingState(list[0].thinkingEnabled);
309
+ setModeState(firstVisible.mode);
310
+ setModelState(firstVisible.model);
311
+ setWebSearchState(firstVisible.webSearchEnabled);
312
+ setThinkingState(firstVisible.thinkingEnabled);
187
313
  }
188
314
  } catch (error) {
189
315
  console.error("\u52A0\u8F7D\u4F1A\u8BDD\u5931\u8D25:", error);
@@ -225,7 +351,8 @@ function useChat(options) {
225
351
  sessionStatesRef.current.set(session.id, {
226
352
  messages: [],
227
353
  isLoading: false,
228
- abortController: null
354
+ abortController: null,
355
+ activeAssistantMessageId: null
229
356
  });
230
357
  setCurrentSessionId(session.id);
231
358
  forceUpdate();
@@ -350,8 +477,6 @@ function useChat(options) {
350
477
  if (lastThinkingIndex >= 0) {
351
478
  const part = parts[lastThinkingIndex];
352
479
  parts[lastThinkingIndex] = { ...part, text: part.text + data.content };
353
- } else {
354
- parts.push({ type: "thinking", text: data.content, status: "running" });
355
480
  }
356
481
  break;
357
482
  }
@@ -372,7 +497,13 @@ function useChat(options) {
372
497
  }
373
498
  case "search_start": {
374
499
  const data = event.data;
375
- parts.push({ type: "search", query: data.query, status: "running" });
500
+ const searchPart = { type: "search", query: data.query, status: "running" };
501
+ const firstTextIndex = parts.findIndex((p) => p.type === "text");
502
+ if (firstTextIndex >= 0) {
503
+ parts.splice(firstTextIndex, 0, searchPart);
504
+ } else {
505
+ parts.push(searchPart);
506
+ }
376
507
  break;
377
508
  }
378
509
  case "search_result": {
@@ -400,40 +531,38 @@ function useChat(options) {
400
531
  adapter.respondToolApproval?.(data.id, true);
401
532
  break;
402
533
  }
403
- const existingIndex = parts.findLastIndex(
534
+ const existingIndex = parts.findIndex(
404
535
  (p) => p.type === "tool_call" && p.id === data.id
405
536
  );
406
537
  if (existingIndex >= 0) {
407
538
  const part = parts[existingIndex];
408
- parts[existingIndex] = { ...part, status: "pending", result: null };
539
+ parts[existingIndex] = { ...part, status: "pending" };
409
540
  } else {
410
541
  parts.push({
411
542
  type: "tool_call",
412
543
  id: data.id,
413
544
  name: data.name,
414
545
  args: data.args,
415
- status: "pending",
416
- result: null
546
+ status: "pending"
417
547
  });
418
548
  }
419
549
  break;
420
550
  }
421
551
  case "tool_call_start": {
422
552
  const data = event.data;
423
- const existingIndex = parts.findLastIndex(
553
+ const existingIndex = parts.findIndex(
424
554
  (p) => p.type === "tool_call" && p.id === data.id
425
555
  );
426
556
  if (existingIndex >= 0) {
427
557
  const part = parts[existingIndex];
428
- parts[existingIndex] = { ...part, status: "running", result: null };
558
+ parts[existingIndex] = { ...part, status: "running" };
429
559
  } else {
430
560
  parts.push({
431
561
  type: "tool_call",
432
562
  id: data.id,
433
563
  name: data.name,
434
564
  args: data.args,
435
- status: "running",
436
- result: null
565
+ status: "running"
437
566
  });
438
567
  }
439
568
  break;
@@ -455,7 +584,6 @@ function useChat(options) {
455
584
  const toolCall = parts[toolCallIndex];
456
585
  parts[toolCallIndex] = {
457
586
  ...toolCall,
458
- result: parsedResult,
459
587
  status
460
588
  };
461
589
  } else {
@@ -464,15 +592,26 @@ function useChat(options) {
464
592
  id: data.id,
465
593
  name: data.name,
466
594
  args: {},
467
- result: parsedResult,
468
595
  status
469
596
  });
470
597
  }
471
- const existingResultIndex = parts.findIndex(
472
- (p) => p.type === "tool_result" && p.id === data.id
473
- );
474
- if (existingResultIndex >= 0) {
475
- parts.splice(existingResultIndex, 1);
598
+ if (data.resultType && data.success && typeof parsedResult === "object" && parsedResult !== null) {
599
+ const updatedToolCallIndex = parts.findIndex(
600
+ (p) => p.type === "tool_call" && p.id === data.id
601
+ );
602
+ const resultPart = {
603
+ type: data.resultType,
604
+ ...parsedResult
605
+ };
606
+ const existingResultIndex = parts.findIndex(
607
+ (p, i) => i > updatedToolCallIndex && p.type === data.resultType
608
+ );
609
+ if (existingResultIndex >= 0) {
610
+ parts[existingResultIndex] = resultPart;
611
+ } else {
612
+ const insertAt = updatedToolCallIndex >= 0 ? updatedToolCallIndex + 1 : parts.length;
613
+ parts.splice(insertAt, 0, resultPart);
614
+ }
476
615
  }
477
616
  if (onToolComplete) {
478
617
  const sideEffects = data.sideEffects;
@@ -484,6 +623,24 @@ function useChat(options) {
484
623
  }
485
624
  break;
486
625
  }
626
+ case "tool_call_output": {
627
+ const data = event.data;
628
+ const toolCallIndex = parts.findIndex(
629
+ (p) => p.type === "tool_call" && p.id === data.id
630
+ );
631
+ if (toolCallIndex >= 0) {
632
+ const toolCall = parts[toolCallIndex];
633
+ const prevStdout = toolCall.output?.stdout ?? "";
634
+ const prevStderr = toolCall.output?.stderr ?? "";
635
+ const MAX = 12e4;
636
+ const nextOutput = data.stream === "stdout" ? { stdout: (prevStdout + (data.chunk || "")).slice(-MAX), stderr: prevStderr } : { stdout: prevStdout, stderr: (prevStderr + (data.chunk || "")).slice(-MAX) };
637
+ parts[toolCallIndex] = {
638
+ ...toolCall,
639
+ output: nextOutput
640
+ };
641
+ }
642
+ break;
643
+ }
487
644
  case "text_delta": {
488
645
  const data = event.data;
489
646
  parts = parts.map(
@@ -491,7 +648,7 @@ function useChat(options) {
491
648
  );
492
649
  const lastTextIndex = parts.findLastIndex((p) => p.type === "text");
493
650
  const lastPart = parts[parts.length - 1];
494
- const shouldCreateNew = lastPart && (lastPart.type === "tool_call" && ["done", "error", "skipped", "cancelled"].includes(lastPart.status) || lastPart.type === "search" && lastPart.status === "done" || lastPart.type === "thinking" && lastPart.status === "done");
651
+ const shouldCreateNew = lastPart && (lastPart.type === "tool_call" && ["done", "error", "skipped", "cancelled"].includes(lastPart.status) || lastPart.type === "thinking" && lastPart.status === "done");
495
652
  if (shouldCreateNew || lastTextIndex < 0) {
496
653
  parts.push({ type: "text", text: data.content });
497
654
  } else {
@@ -538,8 +695,30 @@ function useChat(options) {
538
695
  state.messages[messageIndex] = updatedMsg;
539
696
  forceUpdate();
540
697
  }, [onToolComplete, forceUpdate]);
698
+ const saveMessageToDb = useCallback(async (sessionId, messageIndex, messageId) => {
699
+ const state = sessionStatesRef.current.get(sessionId);
700
+ if (!state) return;
701
+ const msg = state.messages[messageIndex];
702
+ if (!msg) return;
703
+ try {
704
+ await adapter.updateMessage?.({
705
+ id: messageId,
706
+ content: extractTextContent(msg.parts),
707
+ steps: JSON.stringify(msg.parts)
708
+ });
709
+ } catch (error) {
710
+ console.error("[useChat/react] \u4FDD\u5B58\u6D88\u606F\u5931\u8D25:", error);
711
+ }
712
+ }, [adapter]);
713
+ const handleEvent = useCallback(async (sessionId, messageIndex, event, messageId, options2 = { saveToDb: false }) => {
714
+ updateSessionMessage(sessionId, messageIndex, event);
715
+ if (options2.saveToDb && shouldSaveEvent(event)) {
716
+ await saveMessageToDb(sessionId, messageIndex, messageId);
717
+ }
718
+ }, [updateSessionMessage, saveMessageToDb]);
541
719
  const sendMessage = useCallback(async (text, images) => {
542
- if (!text.trim()) return;
720
+ const hasContent = text.trim() || images && images.length > 0;
721
+ if (!hasContent) return;
543
722
  let sessionId = currentSessionIdRef.current;
544
723
  if (sessionId) {
545
724
  const currentState = sessionStatesRef.current.get(sessionId);
@@ -559,7 +738,8 @@ function useChat(options) {
559
738
  sessionStatesRef.current.set(session.id, {
560
739
  messages: [],
561
740
  isLoading: false,
562
- abortController: null
741
+ abortController: null,
742
+ activeAssistantMessageId: null
563
743
  });
564
744
  setCurrentSessionId(session.id);
565
745
  sessionId = session.id;
@@ -574,18 +754,20 @@ function useChat(options) {
574
754
  role: "user",
575
755
  parts: [{ type: "text", text }],
576
756
  images,
577
- timestamp: /* @__PURE__ */ new Date()
757
+ timestamp: Date.now()
578
758
  };
579
759
  state.messages = [...state.messages, userMsg];
580
760
  forceUpdate();
581
761
  try {
582
762
  await adapter.saveMessage({
763
+ id: userMsg.id,
583
764
  sessionId,
584
765
  role: "user",
585
- content: text
766
+ content: text,
767
+ images: images || []
586
768
  });
587
769
  if (state.messages.length === 1) {
588
- const title = text.slice(0, 20) + (text.length > 20 ? "..." : "");
770
+ const title = text.trim() ? text.slice(0, 20) + (text.length > 20 ? "..." : "") : images && images.length > 0 ? "\u56FE\u7247\u6D88\u606F" : "\u65B0\u5BF9\u8BDD";
589
771
  await adapter.updateSession(sessionId, { title });
590
772
  setSessions((prev) => prev.map(
591
773
  (s) => s.id === sessionId ? { ...s, title } : s
@@ -605,17 +787,19 @@ function useChat(options) {
605
787
  webSearchEnabled: webSearchRef.current,
606
788
  thinkingEnabled: thinkingRef.current,
607
789
  loading: true,
608
- timestamp: /* @__PURE__ */ new Date()
790
+ timestamp: Date.now()
609
791
  };
610
792
  state.messages = [...state.messages, assistantMsg];
611
793
  const requestAbortController = new AbortController();
612
794
  state.isLoading = true;
613
795
  state.abortController = requestAbortController;
796
+ state.activeAssistantMessageId = assistantMsgId;
614
797
  forceUpdate();
615
798
  const sendModel = modelRef.current;
616
799
  const sendMode = modeRef.current;
617
800
  const sendWebSearch = webSearchRef.current;
618
801
  const sendThinking = thinkingRef.current;
802
+ const sendEnabledTools = enabledToolsRef.current;
619
803
  try {
620
804
  await adapter.saveMessage({
621
805
  id: assistantMsgId,
@@ -647,16 +831,20 @@ function useChat(options) {
647
831
  try {
648
832
  const history = state.messages.slice(0, -2).map((msg) => ({
649
833
  role: msg.role,
650
- content: extractTextContent(msg.parts)
834
+ // history 中去掉 @ 引用行(避免后续轮次出现“空引用”干扰)
835
+ content: stripAtContextLines(extractTextContent(msg.parts))
651
836
  }));
837
+ const cleanAutoRunConfig = { ...autoRunConfigRef.current };
838
+ const promptMessage = await buildPromptWithAtContext(adapter, text);
652
839
  for await (const event of adapter.sendMessage(
653
- text,
840
+ promptMessage,
654
841
  {
655
842
  mode: sendMode,
656
843
  model: sendModel,
657
844
  enableWebSearch: sendWebSearch,
658
845
  thinkingMode: sendThinking ? "enabled" : "disabled",
659
- autoRunConfig,
846
+ enabledTools: sendEnabledTools,
847
+ autoRunConfig: cleanAutoRunConfig,
660
848
  history
661
849
  // 传递历史消息
662
850
  },
@@ -665,21 +853,26 @@ function useChat(options) {
665
853
  // 传递 sessionId 用于事件过滤
666
854
  )) {
667
855
  if (requestAbortController.signal.aborted) break;
668
- updateSessionMessage(sessionId, assistantMsgIndex, event);
669
- const shouldSave = event.type === "thinking_end" || event.type === "tool_call_result" || event.type === "text_delta" || event.type === "done" || event.type === "error" || event.type === "abort";
670
- if (shouldSave) {
671
- await saveMessageProgress();
672
- }
856
+ await handleEvent(sessionId, assistantMsgIndex, event, assistantMsgId, {
857
+ saveToDb: true
858
+ // 重要事件需要保存
859
+ });
673
860
  if (event.type === "done" || event.type === "error") {
674
861
  break;
675
862
  }
676
863
  }
677
864
  } catch (error) {
678
865
  console.error("\u53D1\u9001\u6D88\u606F\u5931\u8D25:", error);
679
- updateSessionMessage(sessionId, assistantMsgIndex, {
680
- type: "error",
681
- data: { message: error instanceof Error ? error.message : String(error) }
682
- });
866
+ await handleEvent(
867
+ sessionId,
868
+ assistantMsgIndex,
869
+ {
870
+ type: "error",
871
+ data: { message: error instanceof Error ? error.message : String(error) }
872
+ },
873
+ assistantMsgId,
874
+ { saveToDb: true }
875
+ );
683
876
  } finally {
684
877
  state.isLoading = false;
685
878
  const finalMsg = state.messages[assistantMsgIndex];
@@ -687,21 +880,57 @@ function useChat(options) {
687
880
  state.messages = [...state.messages];
688
881
  state.messages[assistantMsgIndex] = { ...finalMsg, loading: false };
689
882
  }
690
- await saveMessageProgress();
883
+ await saveMessageToDb(sessionId, assistantMsgIndex, assistantMsgId);
691
884
  state.abortController = null;
885
+ state.activeAssistantMessageId = null;
692
886
  forceUpdate();
693
887
  }
694
- }, [adapter, getSessionState, updateSessionMessage, forceUpdate]);
888
+ }, [adapter, getSessionState, handleEvent, saveMessageToDb, forceUpdate]);
889
+ const patchPartsAsAborted = useCallback((parts) => {
890
+ return parts.map((p) => {
891
+ if (p.type === "thinking" && p.status === "running") {
892
+ return { ...p, status: "done" };
893
+ }
894
+ if (p.type === "search" && p.status === "running") {
895
+ return { ...p, status: "done" };
896
+ }
897
+ if (p.type === "tool_call") {
898
+ const tool = p;
899
+ if (tool.status === "running" || tool.status === "pending") {
900
+ return { ...tool, status: "cancelled" };
901
+ }
902
+ }
903
+ return p;
904
+ });
905
+ }, []);
906
+ const cancelActiveAssistantMessage = useCallback((sessionId) => {
907
+ const state = sessionStatesRef.current.get(sessionId);
908
+ if (!state) return;
909
+ if (!state.activeAssistantMessageId) return;
910
+ const idx = state.messages.findIndex((m) => m.id === state.activeAssistantMessageId);
911
+ if (idx < 0) return;
912
+ const msg = state.messages[idx];
913
+ state.messages = [...state.messages];
914
+ state.messages[idx] = {
915
+ ...msg,
916
+ loading: false,
917
+ aborted: true,
918
+ parts: patchPartsAsAborted(msg.parts)
919
+ };
920
+ }, [patchPartsAsAborted]);
695
921
  const cancelRequest = useCallback(() => {
696
922
  if (!currentSessionIdRef.current) return;
697
- const state = sessionStatesRef.current.get(currentSessionIdRef.current);
923
+ const sessionId = currentSessionIdRef.current;
924
+ const state = sessionStatesRef.current.get(sessionId);
925
+ cancelActiveAssistantMessage(sessionId);
698
926
  if (state) {
699
927
  state.abortController?.abort();
700
928
  state.isLoading = false;
929
+ state.activeAssistantMessageId = null;
701
930
  forceUpdate();
702
931
  }
703
932
  adapter.cancel();
704
- }, [adapter, forceUpdate]);
933
+ }, [adapter, forceUpdate, cancelActiveAssistantMessage]);
705
934
  const copyMessage = useCallback(async (messageId) => {
706
935
  if (!currentSessionIdRef.current) return;
707
936
  const state = sessionStatesRef.current.get(currentSessionIdRef.current);
@@ -728,26 +957,147 @@ function useChat(options) {
728
957
  console.error("\u590D\u5236\u5931\u8D25:", err);
729
958
  }
730
959
  }, [forceUpdate]);
960
+ const resendFromIndex = useCallback(async (index, text) => {
961
+ if (!currentSessionIdRef.current) return;
962
+ const sessionId = currentSessionIdRef.current;
963
+ const state = sessionStatesRef.current.get(sessionId);
964
+ if (!state) return;
965
+ const targetMsg = state.messages[index];
966
+ if (!targetMsg || targetMsg.role !== "user") {
967
+ sendMessage(text);
968
+ return;
969
+ }
970
+ if (state.isLoading) {
971
+ state.abortController?.abort();
972
+ adapter.cancel();
973
+ state.isLoading = false;
974
+ state.abortController = null;
975
+ }
976
+ const updatedUserMsg = {
977
+ ...targetMsg,
978
+ parts: [{ type: "text", text }]
979
+ };
980
+ state.messages = [...state.messages.slice(0, index), updatedUserMsg];
981
+ forceUpdate();
982
+ try {
983
+ await adapter.deleteMessagesAfterMessageId(sessionId, updatedUserMsg.id);
984
+ } catch (error) {
985
+ console.error("[useChat/react] deleteMessagesAfterMessageId \u5931\u8D25:", error);
986
+ }
987
+ try {
988
+ await adapter.updateMessage?.({
989
+ id: updatedUserMsg.id,
990
+ content: text
991
+ });
992
+ } catch (error) {
993
+ console.warn("[useChat/react] updateMessage(user) \u5931\u8D25\uFF0C\u5DF2\u5FFD\u7565:", error);
994
+ }
995
+ const assistantMsgIndex = state.messages.length;
996
+ const assistantMsgId = generateId();
997
+ const assistantMsg = {
998
+ id: assistantMsgId,
999
+ role: "assistant",
1000
+ parts: [],
1001
+ model: modelRef.current,
1002
+ mode: modeRef.current,
1003
+ webSearchEnabled: webSearchRef.current,
1004
+ thinkingEnabled: thinkingRef.current,
1005
+ loading: true,
1006
+ timestamp: Date.now()
1007
+ };
1008
+ state.messages = [...state.messages, assistantMsg];
1009
+ const requestAbortController = new AbortController();
1010
+ state.isLoading = true;
1011
+ state.abortController = requestAbortController;
1012
+ state.activeAssistantMessageId = assistantMsgId;
1013
+ forceUpdate();
1014
+ const sendModel = modelRef.current;
1015
+ const sendMode = modeRef.current;
1016
+ const sendWebSearch = webSearchRef.current;
1017
+ const sendThinking = thinkingRef.current;
1018
+ const sendEnabledTools = enabledToolsRef.current;
1019
+ try {
1020
+ await adapter.saveMessage({
1021
+ id: assistantMsgId,
1022
+ sessionId,
1023
+ role: "assistant",
1024
+ content: "",
1025
+ model: sendModel,
1026
+ mode: sendMode,
1027
+ webSearchEnabled: sendWebSearch,
1028
+ thinkingEnabled: sendThinking,
1029
+ steps: "[]"
1030
+ });
1031
+ } catch (error) {
1032
+ console.error("[useChat/react] \u521B\u5EFA assistant \u6D88\u606F\u5931\u8D25:", error);
1033
+ }
1034
+ try {
1035
+ const history = state.messages.slice(0, index).map((msg) => ({
1036
+ role: msg.role,
1037
+ content: stripAtContextLines(extractTextContent(msg.parts))
1038
+ }));
1039
+ const cleanAutoRunConfig = { ...autoRunConfigRef.current };
1040
+ const images = updatedUserMsg.images;
1041
+ const promptMessage = await buildPromptWithAtContext(adapter, text);
1042
+ for await (const event of adapter.sendMessage(
1043
+ promptMessage,
1044
+ {
1045
+ mode: sendMode,
1046
+ model: sendModel,
1047
+ enableWebSearch: sendWebSearch,
1048
+ thinkingMode: sendThinking ? "enabled" : "disabled",
1049
+ enabledTools: sendEnabledTools,
1050
+ autoRunConfig: cleanAutoRunConfig,
1051
+ history
1052
+ },
1053
+ images,
1054
+ sessionId
1055
+ )) {
1056
+ if (requestAbortController.signal.aborted) break;
1057
+ await handleEvent(sessionId, assistantMsgIndex, event, assistantMsgId, {
1058
+ saveToDb: true
1059
+ // 重要事件需要保存
1060
+ });
1061
+ if (event.type === "done" || event.type === "error") break;
1062
+ }
1063
+ } catch (error) {
1064
+ console.error("[useChat/react] \u5206\u53C9\u91CD\u53D1\u5931\u8D25:", error);
1065
+ await handleEvent(
1066
+ sessionId,
1067
+ assistantMsgIndex,
1068
+ {
1069
+ type: "error",
1070
+ data: { message: error instanceof Error ? error.message : String(error) }
1071
+ },
1072
+ assistantMsgId,
1073
+ { saveToDb: true }
1074
+ );
1075
+ } finally {
1076
+ const s = sessionStatesRef.current.get(sessionId);
1077
+ if (s) {
1078
+ s.isLoading = false;
1079
+ s.abortController = null;
1080
+ s.activeAssistantMessageId = null;
1081
+ const finalMsg = s.messages[assistantMsgIndex];
1082
+ if (finalMsg) {
1083
+ s.messages[assistantMsgIndex] = { ...finalMsg, loading: false };
1084
+ }
1085
+ forceUpdate();
1086
+ }
1087
+ await saveMessageToDb(sessionId, assistantMsgIndex, assistantMsgId);
1088
+ }
1089
+ }, [adapter, forceUpdate, sendMessage, handleEvent, saveMessageToDb]);
731
1090
  const regenerateMessage = useCallback((messageIndex) => {
732
1091
  if (!currentSessionIdRef.current) return;
733
1092
  const state = sessionStatesRef.current.get(currentSessionIdRef.current);
734
1093
  if (!state) return;
735
1094
  if (messageIndex > 0 && state.messages[messageIndex - 1]?.role === "user") {
736
- const userMsg = state.messages[messageIndex - 1];
1095
+ const userIndex = messageIndex - 1;
1096
+ const userMsg = state.messages[userIndex];
737
1097
  const userText = extractTextContent(userMsg.parts);
738
- state.messages = state.messages.slice(0, messageIndex - 1);
739
- forceUpdate();
740
- sendMessage(userText, userMsg.images);
1098
+ void resendFromIndex(userIndex, userText);
741
1099
  }
742
- }, [sendMessage, forceUpdate]);
743
- const resendFromIndex = useCallback((index, text) => {
744
- if (!currentSessionIdRef.current) return;
745
- const state = sessionStatesRef.current.get(currentSessionIdRef.current);
746
- if (!state) return;
747
- state.messages = state.messages.slice(0, index);
748
- forceUpdate();
749
- sendMessage(text);
750
- }, [sendMessage, forceUpdate]);
1100
+ }, [resendFromIndex]);
751
1101
  const setWorkingDirectory = useCallback((dir) => {
752
1102
  if (adapter.setCwd) {
753
1103
  adapter.setCwd(dir);
@@ -813,7 +1163,11 @@ function useChat(options) {
813
1163
  // 自动运行配置
814
1164
  autoRunConfig,
815
1165
  loadAutoRunConfig,
816
- saveAutoRunConfig
1166
+ saveAutoRunConfig,
1167
+ // 工具管理
1168
+ enabledTools,
1169
+ allTools,
1170
+ saveEnabledTools
817
1171
  };
818
1172
  }
819
1173
 
@@ -831,19 +1185,17 @@ function useChatInputContext() {
831
1185
  // src/context/RenderersContext.tsx
832
1186
  import { createContext as createContext2 } from "react";
833
1187
  import { jsx as jsx2 } from "react/jsx-runtime";
834
- var BlockRenderersContext = createContext2({});
835
- var ToolRenderersContext = createContext2({});
836
- var RenderersProvider = ({
837
- blockRenderers = {},
838
- toolRenderers = {},
1188
+ var PartRenderersContext = createContext2({});
1189
+ var PartRenderersProvider = ({
1190
+ partRenderers = {},
839
1191
  children
840
1192
  }) => {
841
- return /* @__PURE__ */ jsx2(BlockRenderersContext.Provider, { value: blockRenderers, children: /* @__PURE__ */ jsx2(ToolRenderersContext.Provider, { value: toolRenderers, children }) });
1193
+ return /* @__PURE__ */ jsx2(PartRenderersContext.Provider, { value: partRenderers, children });
842
1194
  };
843
1195
 
844
1196
  // src/components/ChatPanel.tsx
845
- import { useEffect as useEffect16, useRef as useRef11, useCallback as useCallback15, useMemo as useMemo14, useState as useState19, forwardRef as forwardRef8, useImperativeHandle as useImperativeHandle8 } from "react";
846
- import { Icon as Icon22 } from "@iconify/react";
1197
+ import { useEffect as useEffect17, useRef as useRef12, useCallback as useCallback16, useMemo as useMemo14, useState as useState19, forwardRef as forwardRef8, useImperativeHandle as useImperativeHandle8 } from "react";
1198
+ import { Icon as Icon21 } from "@iconify/react";
847
1199
 
848
1200
  // src/components/header/ChatHeader.tsx
849
1201
  import { useState as useState2, useRef as useRef2, useEffect as useEffect2, useCallback as useCallback2 } from "react";
@@ -1193,7 +1545,10 @@ var WelcomeMessage = ({ config: propsConfig, onQuickAction }) => {
1193
1545
 
1194
1546
  // src/components/message/MessageBubble.tsx
1195
1547
  import { useMemo as useMemo12 } from "react";
1196
- import { Icon as Icon18 } from "@iconify/react";
1548
+ import { Icon as Icon17 } from "@iconify/react";
1549
+
1550
+ // src/components/message/PartsRenderer.tsx
1551
+ import { useContext as useContext2, useMemo as useMemo5 } from "react";
1197
1552
 
1198
1553
  // src/components/message/parts/CollapsibleCard.tsx
1199
1554
  import { Icon as Icon3 } from "@iconify/react";
@@ -1213,11 +1568,18 @@ var CollapsibleCard = ({
1213
1568
  }) => {
1214
1569
  return /* @__PURE__ */ jsxs3("div", { className: `collapsible-card ${expanded ? "expanded" : ""}`, children: [
1215
1570
  /* @__PURE__ */ jsxs3("div", { className: "card-header", children: [
1216
- /* @__PURE__ */ jsxs3("div", { className: "card-header-left", onClick: () => onExpandedChange(!expanded), children: [
1217
- /* @__PURE__ */ jsx5("div", { className: "card-icon", style: { color: iconColor }, children: spinning ? /* @__PURE__ */ jsx5(Icon3, { icon: "lucide:loader-2", width: 14, className: "spinning" }) : /* @__PURE__ */ jsx5(Icon3, { icon, width: 14 }) }),
1218
- /* @__PURE__ */ jsx5("span", { className: "card-title", style: titleColor ? { color: titleColor } : void 0, children: title }),
1219
- subtitle && /* @__PURE__ */ jsx5("span", { className: "card-subtitle", children: subtitle })
1220
- ] }),
1571
+ /* @__PURE__ */ jsxs3(
1572
+ "div",
1573
+ {
1574
+ className: `card-header-left ${collapsible ? "clickable" : ""}`,
1575
+ onClick: () => collapsible && onExpandedChange(!expanded),
1576
+ children: [
1577
+ /* @__PURE__ */ jsx5("div", { className: "card-icon", style: { color: iconColor }, children: spinning ? /* @__PURE__ */ jsx5(Icon3, { icon: "lucide:loader-2", width: 14, className: "spinning" }) : /* @__PURE__ */ jsx5(Icon3, { icon, width: 14 }) }),
1578
+ /* @__PURE__ */ jsx5("span", { className: "card-title", style: titleColor ? { color: titleColor } : void 0, children: title }),
1579
+ subtitle && /* @__PURE__ */ jsx5("span", { className: "card-subtitle", children: subtitle })
1580
+ ]
1581
+ }
1582
+ ),
1221
1583
  /* @__PURE__ */ jsxs3("div", { className: "card-header-right", children: [
1222
1584
  headerActions,
1223
1585
  collapsible && /* @__PURE__ */ jsx5(
@@ -1239,22 +1601,224 @@ var CollapsibleCard = ({
1239
1601
  };
1240
1602
 
1241
1603
  // src/components/message/parts/TextPart.tsx
1604
+ import { useEffect as useEffect3, useState as useState3, useCallback as useCallback3, useRef as useRef3 } from "react";
1605
+ import {
1606
+ createStreamParseState,
1607
+ finishStreamParse,
1608
+ parseContentStream,
1609
+ highlightCode,
1610
+ renderMarkdown as renderMarkdown2,
1611
+ initMermaid,
1612
+ renderMermaidDiagrams,
1613
+ encodeMermaidCodeToBase64Url
1614
+ } from "@huyooo/ai-chat-shared";
1615
+
1616
+ // src/components/message/parts/visual-predicate.ts
1617
+ function normalizeLanguage(lang) {
1618
+ return (lang || "").trim().toLowerCase();
1619
+ }
1620
+ function isMermaidLanguage(lang) {
1621
+ return normalizeLanguage(lang) === "mermaid";
1622
+ }
1623
+ function isLatexLanguage(lang) {
1624
+ const l = normalizeLanguage(lang);
1625
+ return l === "latex" || l === "katex" || l === "tex";
1626
+ }
1627
+ function isLatexDocument(code) {
1628
+ const t = (code || "").trim();
1629
+ return /\\documentclass\b|\\usepackage\b|\\begin\{document\}|\\end\{document\}/.test(t);
1630
+ }
1631
+ function canVisualizeLatex(code) {
1632
+ const t = (code || "").trim();
1633
+ if (!t) return false;
1634
+ if (isLatexDocument(t)) return false;
1635
+ return true;
1636
+ }
1637
+ function hasLatexDelimiters(code) {
1638
+ const t = (code || "").trim();
1639
+ if (!t) return false;
1640
+ return /\$\$[\s\S]*\$\$/.test(t) || /\\\[[\s\S]*\\\]/.test(t) || /\\\([\s\S]*\\\)/.test(t) || /(?<!\$)\$(?!\$)[\s\S]*?\$(?!\$)/.test(t);
1641
+ }
1642
+ function shouldShowVisualToggle(lang, code) {
1643
+ if (isMermaidLanguage(lang)) return true;
1644
+ if (isLatexLanguage(lang)) return canVisualizeLatex(code || "");
1645
+ return false;
1646
+ }
1647
+
1648
+ // src/components/message/parts/visual-render.ts
1242
1649
  import { renderMarkdown } from "@huyooo/ai-chat-shared";
1243
- import { jsx as jsx6 } from "react/jsx-runtime";
1650
+ function renderFencedLatexToHtml(code) {
1651
+ const t = (code || "").trim();
1652
+ if (!t) return "";
1653
+ if (!canVisualizeLatex(t)) {
1654
+ return renderMarkdown(
1655
+ `> \u4E0D\u652F\u6301\u5C06\u5B8C\u6574 LaTeX \u6587\u6863\u4F5C\u4E3A\u516C\u5F0F\u6E32\u67D3\uFF0C\u8BF7\u5207\u6362\u5230\u4EE3\u7801\u89C6\u56FE\u67E5\u770B\u3002
1656
+
1657
+ \`\`\`latex
1658
+ ${t}
1659
+ \`\`\``
1660
+ );
1661
+ }
1662
+ if (hasLatexDelimiters(t)) return renderMarkdown(t);
1663
+ return renderMarkdown(`$$
1664
+ ${t}
1665
+ $$`);
1666
+ }
1667
+
1668
+ // src/components/message/parts/TextPart.tsx
1669
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
1670
+ initMermaid();
1244
1671
  var TextPart = ({ text }) => {
1672
+ const containerRef = useRef3(null);
1673
+ const streamStateRef = useRef3(createStreamParseState());
1674
+ const lastTextRef = useRef3("");
1675
+ const autoSwitchedVisualRef = useRef3(/* @__PURE__ */ new Set());
1676
+ const [blocks, setBlocks] = useState3([]);
1677
+ const [visualShowMap, setVisualShowMap] = useState3({});
1678
+ const [copiedId, setCopiedId] = useState3(null);
1679
+ const copyCode = useCallback3(async (content, id) => {
1680
+ try {
1681
+ await navigator.clipboard.writeText(content);
1682
+ setCopiedId(id);
1683
+ setTimeout(() => setCopiedId(null), 2e3);
1684
+ } catch (err) {
1685
+ console.error("\u590D\u5236\u5931\u8D25:", err);
1686
+ }
1687
+ }, []);
1245
1688
  if (!text) return null;
1246
- const html = renderMarkdown(text);
1247
- return /* @__PURE__ */ jsx6(
1248
- "div",
1249
- {
1250
- className: "text-part markdown-content",
1251
- dangerouslySetInnerHTML: { __html: html }
1689
+ useEffect3(() => {
1690
+ const prev = lastTextRef.current;
1691
+ const current = text || "";
1692
+ let nextState = streamStateRef.current;
1693
+ if (prev && current.startsWith(prev)) {
1694
+ const delta = current.slice(prev.length);
1695
+ if (delta) nextState = parseContentStream(delta, nextState);
1696
+ } else {
1697
+ nextState = createStreamParseState();
1698
+ nextState = parseContentStream(current, nextState);
1699
+ }
1700
+ streamStateRef.current = nextState;
1701
+ lastTextRef.current = current;
1702
+ const nextBlocks = finishStreamParse(nextState);
1703
+ setBlocks(nextBlocks);
1704
+ const inProgressVisualId = nextState.inCodeBlock && ["mermaid", "latex", "katex", "tex"].includes((nextState.codeLanguage || "").toLowerCase()) ? nextState.codeBlockId : null;
1705
+ const completedVisualIds = [];
1706
+ for (const b of nextState.blocks) {
1707
+ if (b.type !== "code") continue;
1708
+ const lang = (b.language || "").toLowerCase();
1709
+ if (!["mermaid", "latex", "katex", "tex"].includes(lang)) continue;
1710
+ if (inProgressVisualId && b.id === inProgressVisualId) continue;
1711
+ if (["latex", "katex", "tex"].includes(lang) && !canVisualizeLatex(b.content)) continue;
1712
+ if (!autoSwitchedVisualRef.current.has(b.id)) completedVisualIds.push(b.id);
1713
+ }
1714
+ if (completedVisualIds.length > 0) {
1715
+ for (const id of completedVisualIds) autoSwitchedVisualRef.current.add(id);
1716
+ setVisualShowMap((prevMap) => {
1717
+ const nextMap = { ...prevMap };
1718
+ for (const id of completedVisualIds) {
1719
+ if (nextMap[id] === void 0) nextMap[id] = true;
1720
+ }
1721
+ return nextMap;
1722
+ });
1723
+ window.setTimeout(() => {
1724
+ if (containerRef.current) renderMermaidDiagrams(containerRef.current);
1725
+ }, 0);
1726
+ }
1727
+ }, [text]);
1728
+ const toggleVisualView = (id) => {
1729
+ const willShowVisual = !visualShowMap[id];
1730
+ setVisualShowMap((prev) => ({ ...prev, [id]: willShowVisual }));
1731
+ if (willShowVisual) {
1732
+ window.setTimeout(() => {
1733
+ if (containerRef.current) {
1734
+ renderMermaidDiagrams(containerRef.current);
1735
+ }
1736
+ }, 0);
1252
1737
  }
1253
- );
1738
+ };
1739
+ return /* @__PURE__ */ jsx6("div", { className: "text-part", ref: containerRef, children: blocks.map((block) => {
1740
+ if (block.type === "text") {
1741
+ return /* @__PURE__ */ jsx6(
1742
+ "div",
1743
+ {
1744
+ className: "text-block",
1745
+ dangerouslySetInnerHTML: { __html: renderMarkdown2(block.content) }
1746
+ },
1747
+ block.id
1748
+ );
1749
+ } else if (block.type === "code") {
1750
+ const isMermaid = isMermaidLanguage(block.language);
1751
+ const isLatex = isLatexLanguage(block.language);
1752
+ const canVisualizeLatexBlock = isLatex && canVisualizeLatex(block.content);
1753
+ const showVisual = !!visualShowMap[block.id];
1754
+ return /* @__PURE__ */ jsxs4("div", { className: "code-block-wrapper", children: [
1755
+ /* @__PURE__ */ jsxs4("div", { className: "code-header", children: [
1756
+ /* @__PURE__ */ jsx6("span", { className: "code-language", children: block.language || "plaintext" }),
1757
+ /* @__PURE__ */ jsxs4("div", { className: "code-actions", children: [
1758
+ shouldShowVisualToggle(block.language, block.content) && /* @__PURE__ */ jsxs4(
1759
+ "button",
1760
+ {
1761
+ className: "code-action-btn",
1762
+ onClick: () => toggleVisualView(block.id),
1763
+ title: showVisual ? "\u5207\u6362\u4E3A\u4EE3\u7801\u89C6\u56FE" : "\u5207\u6362\u4E3A\u53EF\u89C6\u5316\u89C6\u56FE",
1764
+ children: [
1765
+ /* @__PURE__ */ jsxs4("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
1766
+ /* @__PURE__ */ jsx6("polyline", { points: "17 1 21 5 17 9" }),
1767
+ /* @__PURE__ */ jsx6("path", { d: "M3 11V9a4 4 0 0 1 4-4h14" }),
1768
+ /* @__PURE__ */ jsx6("polyline", { points: "7 23 3 19 7 15" }),
1769
+ /* @__PURE__ */ jsx6("path", { d: "M21 13v2a4 4 0 0 1-4 4H3" })
1770
+ ] }),
1771
+ /* @__PURE__ */ jsx6("span", { children: "\u5207\u6362\u89C6\u56FE" })
1772
+ ]
1773
+ }
1774
+ ),
1775
+ /* @__PURE__ */ jsxs4(
1776
+ "button",
1777
+ {
1778
+ className: "code-action-btn",
1779
+ onClick: () => copyCode(block.content, block.id),
1780
+ title: copiedId === block.id ? "\u5DF2\u590D\u5236" : "\u590D\u5236\u4EE3\u7801",
1781
+ children: [
1782
+ copiedId !== block.id ? /* @__PURE__ */ jsxs4("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
1783
+ /* @__PURE__ */ jsx6("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }),
1784
+ /* @__PURE__ */ jsx6("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })
1785
+ ] }) : /* @__PURE__ */ jsx6("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx6("polyline", { points: "20 6 9 17 4 12" }) }),
1786
+ /* @__PURE__ */ jsx6("span", { children: copiedId === block.id ? "\u5DF2\u590D\u5236" : "\u590D\u5236" })
1787
+ ]
1788
+ }
1789
+ )
1790
+ ] })
1791
+ ] }),
1792
+ isMermaid && showVisual && /* @__PURE__ */ jsx6(
1793
+ "div",
1794
+ {
1795
+ className: "mermaid-placeholder",
1796
+ "data-mermaid-code-b64": encodeMermaidCodeToBase64Url(block.content),
1797
+ children: /* @__PURE__ */ jsx6("div", { className: "mermaid-loading", children: "\u52A0\u8F7D\u56FE\u8868\u4E2D..." })
1798
+ }
1799
+ ),
1800
+ isLatex && showVisual && canVisualizeLatexBlock && /* @__PURE__ */ jsx6(
1801
+ "div",
1802
+ {
1803
+ className: "latex-rendered",
1804
+ dangerouslySetInnerHTML: { __html: renderFencedLatexToHtml(block.content) }
1805
+ }
1806
+ ),
1807
+ (!(isMermaid || isLatex) || !showVisual || isLatex && !canVisualizeLatexBlock) && /* @__PURE__ */ jsx6("pre", { className: "code-block", children: /* @__PURE__ */ jsx6(
1808
+ "code",
1809
+ {
1810
+ className: block.language ? `language-${block.language}` : "",
1811
+ dangerouslySetInnerHTML: { __html: highlightCode(block.content, block.language) }
1812
+ }
1813
+ ) })
1814
+ ] }, block.id);
1815
+ }
1816
+ return null;
1817
+ }) });
1254
1818
  };
1255
1819
 
1256
1820
  // src/components/message/parts/ThinkingPart.tsx
1257
- import { useState as useState3, useEffect as useEffect3 } from "react";
1821
+ import { useState as useState4, useEffect as useEffect4 } from "react";
1258
1822
  import { jsx as jsx7 } from "react/jsx-runtime";
1259
1823
  var ThinkingPart = ({
1260
1824
  text,
@@ -1267,8 +1831,8 @@ var ThinkingPart = ({
1267
1831
  if (expandedType === "close") return false;
1268
1832
  return status === "running";
1269
1833
  };
1270
- const [expanded, setExpanded] = useState3(getInitialExpanded);
1271
- useEffect3(() => {
1834
+ const [expanded, setExpanded] = useState4(getInitialExpanded);
1835
+ useEffect4(() => {
1272
1836
  if (expandedType === "auto") {
1273
1837
  setExpanded(status === "running");
1274
1838
  }
@@ -1288,8 +1852,8 @@ var ThinkingPart = ({
1288
1852
  };
1289
1853
 
1290
1854
  // src/components/message/parts/SearchPart.tsx
1291
- import { useState as useState4, useEffect as useEffect4 } from "react";
1292
- import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
1855
+ import { useState as useState5, useEffect as useEffect5 } from "react";
1856
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
1293
1857
  var SearchPart = ({
1294
1858
  results,
1295
1859
  status,
@@ -1300,8 +1864,8 @@ var SearchPart = ({
1300
1864
  if (expandedType === "close") return false;
1301
1865
  return status === "running";
1302
1866
  };
1303
- const [expanded, setExpanded] = useState4(getInitialExpanded);
1304
- useEffect4(() => {
1867
+ const [expanded, setExpanded] = useState5(getInitialExpanded);
1868
+ useEffect5(() => {
1305
1869
  if (expandedType === "auto") {
1306
1870
  setExpanded(status === "running");
1307
1871
  }
@@ -1315,7 +1879,7 @@ var SearchPart = ({
1315
1879
  spinning: status === "running",
1316
1880
  expanded,
1317
1881
  onExpandedChange: setExpanded,
1318
- children: results && results.length > 0 && /* @__PURE__ */ jsx8("div", { className: "search-results", children: results.map((item, i) => /* @__PURE__ */ jsxs4(
1882
+ children: results && results.length > 0 && /* @__PURE__ */ jsx8("div", { className: "search-results", children: results.map((item, i) => /* @__PURE__ */ jsxs5(
1319
1883
  "a",
1320
1884
  {
1321
1885
  href: item.url,
@@ -1335,13 +1899,13 @@ var SearchPart = ({
1335
1899
  };
1336
1900
 
1337
1901
  // src/components/message/parts/ToolCallPart.tsx
1338
- import { useState as useState7, useMemo as useMemo4, useEffect as useEffect6, useCallback as useCallback5, useRef as useRef4 } from "react";
1902
+ import { useState as useState8, useMemo as useMemo4, useEffect as useEffect7, useCallback as useCallback6, useRef as useRef5 } from "react";
1339
1903
  import { Icon as Icon6 } from "@iconify/react";
1340
1904
 
1341
1905
  // src/components/input/DropdownSelector.tsx
1342
- import { useState as useState5, useRef as useRef3, useCallback as useCallback3, useEffect as useEffect5, useMemo as useMemo3 } from "react";
1906
+ import { useState as useState6, useRef as useRef4, useCallback as useCallback4, useEffect as useEffect6, useMemo as useMemo3, useLayoutEffect } from "react";
1343
1907
  import { Icon as Icon4 } from "@iconify/react";
1344
- import { Fragment, jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
1908
+ import { Fragment, jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
1345
1909
  function DropdownSelector({
1346
1910
  value,
1347
1911
  options,
@@ -1351,9 +1915,13 @@ function DropdownSelector({
1351
1915
  disabled = false,
1352
1916
  align = "left"
1353
1917
  }) {
1354
- const selectorRef = useRef3(null);
1355
- const [menuOpen, setMenuOpen] = useState5(false);
1356
- const [dropdownDirection, setDropdownDirection] = useState5("up");
1918
+ const selectorRef = useRef4(null);
1919
+ const tooltipRef = useRef4(null);
1920
+ const hoveredItemRef = useRef4(null);
1921
+ const [menuOpen, setMenuOpen] = useState6(false);
1922
+ const [dropdownDirection, setDropdownDirection] = useState6("up");
1923
+ const [hoveredOption, setHoveredOption] = useState6(null);
1924
+ const [tooltipPosition, setTooltipPosition] = useState6({ top: 0, left: 0 });
1357
1925
  const hasOptions = useMemo3(() => {
1358
1926
  if (groupedOptions && Object.keys(groupedOptions).length > 0) {
1359
1927
  return Object.values(groupedOptions).some((group) => group.length > 0);
@@ -1373,14 +1941,14 @@ function DropdownSelector({
1373
1941
  if (!options) return [];
1374
1942
  return [...options].sort((a, b) => a.label.localeCompare(b.label));
1375
1943
  }, [options]);
1376
- const calculateDropdownDirection = useCallback3(() => {
1944
+ const calculateDropdownDirection = useCallback4(() => {
1377
1945
  if (!selectorRef.current) return "up";
1378
1946
  const rect = selectorRef.current.getBoundingClientRect();
1379
1947
  const spaceAbove = rect.top;
1380
1948
  const spaceBelow = window.innerHeight - rect.bottom;
1381
1949
  return spaceAbove < 200 && spaceBelow > spaceAbove ? "down" : "up";
1382
1950
  }, []);
1383
- const toggleMenu = useCallback3(
1951
+ const toggleMenu = useCallback4(
1384
1952
  (e) => {
1385
1953
  e.stopPropagation();
1386
1954
  if (disabled) return;
@@ -1391,14 +1959,52 @@ function DropdownSelector({
1391
1959
  },
1392
1960
  [menuOpen, calculateDropdownDirection, disabled]
1393
1961
  );
1394
- const selectOption = useCallback3(
1962
+ const selectOption = useCallback4(
1395
1963
  (optValue) => {
1396
1964
  onSelect?.(optValue);
1397
1965
  setMenuOpen(false);
1398
1966
  },
1399
1967
  [onSelect]
1400
1968
  );
1401
- useEffect5(() => {
1969
+ const handleItemHover = useCallback4((event, opt) => {
1970
+ const target = event.currentTarget;
1971
+ hoveredItemRef.current = target;
1972
+ setHoveredOption(opt);
1973
+ if (!opt.tooltip) {
1974
+ setTooltipPosition({ top: 0, left: 0 });
1975
+ return;
1976
+ }
1977
+ if (!target) return;
1978
+ const rect = target.getBoundingClientRect();
1979
+ setTooltipPosition({
1980
+ top: rect.top,
1981
+ left: rect.right + 8
1982
+ });
1983
+ }, []);
1984
+ useLayoutEffect(() => {
1985
+ if (!hoveredOption?.tooltip || !hoveredItemRef.current) return;
1986
+ const rafId = requestAnimationFrame(() => {
1987
+ if (!tooltipRef.current || !hoveredItemRef.current) return;
1988
+ const tooltip = tooltipRef.current;
1989
+ const item = hoveredItemRef.current;
1990
+ const itemRect = item.getBoundingClientRect();
1991
+ const tooltipRect = tooltip.getBoundingClientRect();
1992
+ let top = itemRect.top;
1993
+ let left = itemRect.right + 8;
1994
+ if (left + tooltipRect.width > window.innerWidth) {
1995
+ left = itemRect.left - tooltipRect.width - 8;
1996
+ }
1997
+ if (top + tooltipRect.height > window.innerHeight) {
1998
+ top = window.innerHeight - tooltipRect.height - 8;
1999
+ }
2000
+ if (top < 8) {
2001
+ top = 8;
2002
+ }
2003
+ setTooltipPosition({ top, left });
2004
+ });
2005
+ return () => cancelAnimationFrame(rafId);
2006
+ }, [hoveredOption]);
2007
+ useEffect6(() => {
1402
2008
  const handleClickOutside = (event) => {
1403
2009
  const target = event.target;
1404
2010
  if (selectorRef.current && !selectorRef.current.contains(target)) {
@@ -1408,7 +2014,7 @@ function DropdownSelector({
1408
2014
  document.addEventListener("click", handleClickOutside);
1409
2015
  return () => document.removeEventListener("click", handleClickOutside);
1410
2016
  }, []);
1411
- return /* @__PURE__ */ jsxs5(
2017
+ return /* @__PURE__ */ jsxs6(
1412
2018
  "div",
1413
2019
  {
1414
2020
  ref: selectorRef,
@@ -1418,46 +2024,95 @@ function DropdownSelector({
1418
2024
  currentOption?.icon && /* @__PURE__ */ jsx9(Icon4, { icon: currentOption.icon, width: 14 }),
1419
2025
  /* @__PURE__ */ jsx9("span", { className: "selector-text", children: currentOption?.label || placeholder }),
1420
2026
  /* @__PURE__ */ jsx9(Icon4, { icon: "lucide:chevron-down", width: 14, className: "chevron" }),
1421
- menuOpen && /* @__PURE__ */ jsxs5(
2027
+ menuOpen && /* @__PURE__ */ jsxs6(
1422
2028
  "div",
1423
2029
  {
1424
2030
  className: `dropdown-menu dropdown-${dropdownDirection}${align === "right" ? " dropdown-align-right" : ""}`,
1425
2031
  onClick: (e) => e.stopPropagation(),
1426
2032
  children: [
1427
2033
  !hasOptions && /* @__PURE__ */ jsx9("div", { className: "dropdown-empty", children: "\u6682\u65E0\u6570\u636E" }),
1428
- groupedOptions && Object.keys(groupedOptions).length > 0 ? /* @__PURE__ */ jsx9(Fragment, { children: Object.entries(groupedOptions).map(([groupName, groupItems]) => /* @__PURE__ */ jsxs5("div", { children: [
2034
+ groupedOptions && Object.keys(groupedOptions).length > 0 ? /* @__PURE__ */ jsx9(Fragment, { children: Object.entries(groupedOptions).map(([groupName, groupItems]) => /* @__PURE__ */ jsxs6("div", { children: [
1429
2035
  /* @__PURE__ */ jsx9("div", { className: "group-title", children: groupName }),
1430
- groupItems.map((opt) => /* @__PURE__ */ jsxs5(
1431
- "button",
2036
+ groupItems.map((opt) => /* @__PURE__ */ jsx9(
2037
+ "div",
1432
2038
  {
1433
- className: `dropdown-item${value === opt.value ? " active" : ""}`,
1434
- onClick: () => selectOption(opt.value),
1435
- children: [
1436
- opt.icon && /* @__PURE__ */ jsx9(Icon4, { icon: opt.icon, width: 14 }),
1437
- /* @__PURE__ */ jsx9("span", { className: "option-label", children: opt.label }),
1438
- /* @__PURE__ */ jsx9("span", { className: "option-right", children: value === opt.value && /* @__PURE__ */ jsx9(Icon4, { icon: "lucide:check", width: 14, className: "check-icon" }) })
1439
- ]
2039
+ className: "dropdown-item-wrapper",
2040
+ onMouseEnter: (e) => handleItemHover(e, opt),
2041
+ onMouseLeave: () => {
2042
+ setHoveredOption(null);
2043
+ hoveredItemRef.current = null;
2044
+ },
2045
+ children: /* @__PURE__ */ jsxs6(
2046
+ "button",
2047
+ {
2048
+ className: `dropdown-item${value === opt.value ? " active" : ""}`,
2049
+ onClick: () => selectOption(opt.value),
2050
+ children: [
2051
+ opt.icon && /* @__PURE__ */ jsx9(Icon4, { icon: opt.icon, width: 14 }),
2052
+ /* @__PURE__ */ jsx9("span", { className: "option-label", children: opt.label }),
2053
+ /* @__PURE__ */ jsx9("span", { className: "option-right", children: value === opt.value && /* @__PURE__ */ jsx9(Icon4, { icon: "lucide:check", width: 14, className: "check-icon" }) })
2054
+ ]
2055
+ }
2056
+ )
1440
2057
  },
1441
2058
  opt.value
1442
2059
  ))
1443
2060
  ] }, groupName)) }) : (
1444
2061
  /* 扁平列表模式:无分组数据时使用 */
1445
- sortedOptions.map((opt) => /* @__PURE__ */ jsxs5(
1446
- "button",
2062
+ sortedOptions.map((opt) => /* @__PURE__ */ jsx9(
2063
+ "div",
1447
2064
  {
1448
- className: `dropdown-item${value === opt.value ? " active" : ""}`,
1449
- onClick: () => selectOption(opt.value),
1450
- children: [
1451
- opt.icon && /* @__PURE__ */ jsx9(Icon4, { icon: opt.icon, width: 14 }),
1452
- /* @__PURE__ */ jsx9("span", { className: "option-label", children: opt.label }),
1453
- /* @__PURE__ */ jsx9("span", { className: "option-right", children: value === opt.value && /* @__PURE__ */ jsx9(Icon4, { icon: "lucide:check", width: 14, className: "check-icon" }) })
1454
- ]
2065
+ className: "dropdown-item-wrapper",
2066
+ onMouseEnter: (e) => handleItemHover(e, opt),
2067
+ onMouseLeave: () => {
2068
+ setHoveredOption(null);
2069
+ hoveredItemRef.current = null;
2070
+ },
2071
+ children: /* @__PURE__ */ jsxs6(
2072
+ "button",
2073
+ {
2074
+ className: `dropdown-item${value === opt.value ? " active" : ""}`,
2075
+ onClick: () => selectOption(opt.value),
2076
+ children: [
2077
+ opt.icon && /* @__PURE__ */ jsx9(Icon4, { icon: opt.icon, width: 14 }),
2078
+ /* @__PURE__ */ jsx9("span", { className: "option-label", children: opt.label }),
2079
+ /* @__PURE__ */ jsx9("span", { className: "option-right", children: value === opt.value && /* @__PURE__ */ jsx9(Icon4, { icon: "lucide:check", width: 14, className: "check-icon" }) })
2080
+ ]
2081
+ }
2082
+ )
1455
2083
  },
1456
2084
  opt.value
1457
2085
  ))
1458
2086
  )
1459
2087
  ]
1460
2088
  }
2089
+ ),
2090
+ hoveredOption?.tooltip && /* @__PURE__ */ jsxs6(
2091
+ "div",
2092
+ {
2093
+ ref: tooltipRef,
2094
+ className: "ai-chat-model-tooltip",
2095
+ style: {
2096
+ position: "fixed",
2097
+ top: `${tooltipPosition.top}px`,
2098
+ left: `${tooltipPosition.left}px`,
2099
+ zIndex: 1e4
2100
+ },
2101
+ children: [
2102
+ hoveredOption.tooltip.features && hoveredOption.tooltip.features.length > 0 && /* @__PURE__ */ jsxs6("div", { className: "ai-chat-model-tooltip__section", children: [
2103
+ /* @__PURE__ */ jsx9("div", { className: "ai-chat-model-tooltip__title", children: "\u53EF\u7528\u529F\u80FD" }),
2104
+ /* @__PURE__ */ jsx9("div", { className: "ai-chat-model-tooltip__features", children: hoveredOption.tooltip.features.map((feature) => /* @__PURE__ */ jsxs6("div", { className: "ai-chat-model-tooltip__feature", children: [
2105
+ /* @__PURE__ */ jsx9(Icon4, { icon: "lucide:check", width: 12, className: "ai-chat-model-tooltip__check" }),
2106
+ /* @__PURE__ */ jsx9("span", { children: feature })
2107
+ ] }, feature)) })
2108
+ ] }),
2109
+ hoveredOption.tooltip.cost && hoveredOption.tooltip.cost.length > 0 && /* @__PURE__ */ jsxs6("div", { className: "ai-chat-model-tooltip__section", children: [
2110
+ /* @__PURE__ */ jsx9("div", { className: "ai-chat-model-tooltip__title", children: "\u5F00\u9500" }),
2111
+ /* @__PURE__ */ jsx9("div", { className: "ai-chat-model-tooltip__cost", children: hoveredOption.tooltip.cost.map((line, i) => /* @__PURE__ */ jsx9("div", { children: line }, i)) })
2112
+ ] }),
2113
+ hoveredOption.tooltip.description && /* @__PURE__ */ jsx9("div", { className: "ai-chat-model-tooltip__section", children: /* @__PURE__ */ jsx9("div", { className: "ai-chat-model-tooltip__description", children: hoveredOption.tooltip.description }) })
2114
+ ]
2115
+ }
1461
2116
  )
1462
2117
  ]
1463
2118
  }
@@ -1465,7 +2120,7 @@ function DropdownSelector({
1465
2120
  }
1466
2121
 
1467
2122
  // src/components/common/CopyButton.tsx
1468
- import { useState as useState6, useCallback as useCallback4 } from "react";
2123
+ import { useState as useState7, useCallback as useCallback5 } from "react";
1469
2124
  import { Icon as Icon5 } from "@iconify/react";
1470
2125
  import { jsx as jsx10 } from "react/jsx-runtime";
1471
2126
  var CopyButton = ({
@@ -1474,8 +2129,8 @@ var CopyButton = ({
1474
2129
  size = 14,
1475
2130
  onCopy
1476
2131
  }) => {
1477
- const [copied, setCopied] = useState6(false);
1478
- const handleCopy = useCallback4(async (e) => {
2132
+ const [copied, setCopied] = useState7(false);
2133
+ const handleCopy = useCallback5(async (e) => {
1479
2134
  e.stopPropagation();
1480
2135
  try {
1481
2136
  await navigator.clipboard.writeText(text);
@@ -1498,7 +2153,8 @@ var CopyButton = ({
1498
2153
  };
1499
2154
 
1500
2155
  // src/components/message/parts/ToolCallPart.tsx
1501
- import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
2156
+ import { highlightCode as highlightCode2 } from "@huyooo/ai-chat-shared";
2157
+ import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
1502
2158
  var modeOptions = [
1503
2159
  { value: "manual", label: "\u6267\u884C\u524D\u8BE2\u95EE\u6211" },
1504
2160
  { value: "run-everything", label: "\u81EA\u52A8\u6267\u884C" }
@@ -1514,24 +2170,20 @@ var statusIcons = {
1514
2170
  var statusColors = {
1515
2171
  error: "var(--chat-error, #ef4444)",
1516
2172
  done: "var(--chat-success, #22c55e)",
1517
- cancelled: "var(--chat-warning, #f59e0b)",
1518
- skipped: "var(--chat-text-muted, #888)",
1519
2173
  running: "var(--chat-accent, #3b82f6)",
1520
- pending: "var(--chat-text-muted, #888)"
2174
+ pending: "var(--chat-text-muted, #888)",
2175
+ cancelled: "var(--chat-warning, #f59e0b)",
2176
+ skipped: "var(--chat-text-muted, #888)"
1521
2177
  };
1522
2178
  var statusDisplayIcons = {
1523
2179
  done: "lucide:check-circle-2",
1524
2180
  error: "lucide:x-circle",
1525
- running: "lucide:loader-2",
1526
- pending: "lucide:clock",
1527
2181
  cancelled: "lucide:ban",
1528
2182
  skipped: "lucide:skip-forward"
1529
2183
  };
1530
2184
  var statusTexts = {
1531
2185
  done: "\u6210\u529F",
1532
2186
  error: "\u9519\u8BEF",
1533
- running: "\u8FD0\u884C\u4E2D",
1534
- pending: "\u7B49\u5F85\u4E2D",
1535
2187
  cancelled: "\u5DF2\u53D6\u6D88",
1536
2188
  skipped: "\u5DF2\u8DF3\u8FC7"
1537
2189
  };
@@ -1539,23 +2191,25 @@ var ToolCallPart = ({
1539
2191
  id,
1540
2192
  name,
1541
2193
  args,
2194
+ output,
1542
2195
  status,
1543
- result,
1544
2196
  expandedType = "auto",
1545
2197
  adapter,
2198
+ onCancelToolCall,
1546
2199
  autoRunConfig,
1547
2200
  onSaveConfig
1548
2201
  }) => {
1549
2202
  const currentMode = autoRunConfig?.mode ?? "run-everything";
1550
- const commandDisplay = useMemo4(() => {
2203
+ const argsType = useMemo4(() => {
1551
2204
  if (name === "execute_command" && args?.command) {
1552
- const cmd = String(args.command);
1553
- const parts = cmd.trim().split(/\s+/);
1554
- return parts.slice(0, 3).join(" ") + (parts.length > 3 ? "..." : "");
2205
+ return "command";
1555
2206
  }
1556
- return name;
2207
+ if (args && Object.keys(args).length > 0) {
2208
+ return "json";
2209
+ }
2210
+ return "text";
1557
2211
  }, [name, args]);
1558
- const commandText = useMemo4(() => {
2212
+ const argsText = useMemo4(() => {
1559
2213
  if (name === "execute_command" && args?.command) {
1560
2214
  return String(args.command);
1561
2215
  }
@@ -1568,22 +2222,18 @@ var ToolCallPart = ({
1568
2222
  }
1569
2223
  return name;
1570
2224
  }, [name, args]);
1571
- const formattedResult = useMemo4(() => {
1572
- if (result === null) return "";
1573
- if (typeof result === "string") return result;
1574
- try {
1575
- return JSON.stringify(result, null, 2);
1576
- } catch {
1577
- return String(result);
2225
+ const highlightedJson = useMemo4(() => {
2226
+ if (argsType === "json") {
2227
+ return highlightCode2(argsText, "json");
1578
2228
  }
1579
- }, [result]);
1580
- const getInitialExpanded = () => {
2229
+ return "";
2230
+ }, [argsType, argsText]);
2231
+ const [expanded, setExpanded] = useState8(() => {
1581
2232
  if (expandedType === "open") return true;
1582
2233
  if (expandedType === "close") return false;
1583
2234
  return status === "pending" || status === "running";
1584
- };
1585
- const [expanded, setExpanded] = useState7(getInitialExpanded);
1586
- useEffect6(() => {
2235
+ });
2236
+ useEffect7(() => {
1587
2237
  if (expandedType === "auto") {
1588
2238
  setExpanded(status === "pending" || status === "running");
1589
2239
  }
@@ -1599,45 +2249,60 @@ var ToolCallPart = ({
1599
2249
  };
1600
2250
  return name + (suffixes[status] ?? "");
1601
2251
  }, [name, status]);
1602
- const handleModeChange = useCallback5(async (value) => {
2252
+ const handleModeChange = useCallback6(async (value) => {
1603
2253
  if (!onSaveConfig || !autoRunConfig) return;
1604
- try {
1605
- await onSaveConfig({
1606
- ...autoRunConfig,
1607
- mode: value
1608
- });
1609
- } catch (error) {
1610
- console.error("[ToolCallPart] \u4FDD\u5B58\u914D\u7F6E\u5931\u8D25:", error);
1611
- }
2254
+ await onSaveConfig({ ...autoRunConfig, mode: value });
1612
2255
  }, [onSaveConfig, autoRunConfig]);
1613
- const handleSkip = useCallback5(async () => {
1614
- if (!adapter?.respondToolApproval) return;
1615
- try {
1616
- await adapter.respondToolApproval(id, false);
1617
- } catch (error) {
1618
- console.error("[ToolCallPart] \u8DF3\u8FC7\u5931\u8D25:", error);
1619
- }
2256
+ const handleSkip = useCallback6(async () => {
2257
+ await adapter?.respondToolApproval?.(id, false);
1620
2258
  }, [adapter, id]);
1621
- const handleRun = useCallback5(async () => {
1622
- if (!adapter?.respondToolApproval) return;
1623
- try {
1624
- await adapter.respondToolApproval(id, true);
1625
- } catch (error) {
1626
- console.error("[ToolCallPart] \u8FD0\u884C\u5931\u8D25:", error);
1627
- }
2259
+ const handleRun = useCallback6(async () => {
2260
+ await adapter?.respondToolApproval?.(id, true);
1628
2261
  }, [adapter, id]);
1629
- const handleCancel = useCallback5(() => {
2262
+ const handleCancel = useCallback6(() => {
2263
+ if (onCancelToolCall) {
2264
+ onCancelToolCall(id);
2265
+ return;
2266
+ }
1630
2267
  adapter?.cancel?.();
1631
- }, [adapter]);
1632
- const prevModeRef = useRef4(currentMode);
1633
- useEffect6(() => {
2268
+ }, [adapter, id, onCancelToolCall]);
2269
+ const hasOutput = Boolean(output?.stdout || output?.stderr);
2270
+ const [activeStream, setActiveStream] = useState8("stdout");
2271
+ const activeOutputText = useMemo4(() => {
2272
+ return activeStream === "stdout" ? output?.stdout ?? "" : output?.stderr ?? "";
2273
+ }, [activeStream, output]);
2274
+ const saveLog = useCallback6(() => {
2275
+ const stdout = output?.stdout ?? "";
2276
+ const stderr = output?.stderr ?? "";
2277
+ const content = `# tool: ${name}
2278
+ # id: ${id}
2279
+ # time: ${(/* @__PURE__ */ new Date()).toISOString()}
2280
+
2281
+ ===== STDOUT =====
2282
+ ${stdout}
2283
+
2284
+ ===== STDERR =====
2285
+ ${stderr}
2286
+ `;
2287
+ const blob = new Blob([content], { type: "text/plain;charset=utf-8" });
2288
+ const url = URL.createObjectURL(blob);
2289
+ const a = document.createElement("a");
2290
+ a.href = url;
2291
+ a.download = `tool-${name}-${id}.log`;
2292
+ document.body.appendChild(a);
2293
+ a.click();
2294
+ document.body.removeChild(a);
2295
+ URL.revokeObjectURL(url);
2296
+ }, [id, name, output]);
2297
+ const prevModeRef = useRef5(currentMode);
2298
+ useEffect7(() => {
1634
2299
  const prevMode = prevModeRef.current;
1635
2300
  if (prevMode === "manual" && currentMode === "run-everything" && status === "pending") {
1636
2301
  handleRun();
1637
2302
  }
1638
2303
  prevModeRef.current = currentMode;
1639
2304
  }, [currentMode, status, handleRun]);
1640
- return /* @__PURE__ */ jsx11(
2305
+ return /* @__PURE__ */ jsxs7(
1641
2306
  CollapsibleCard,
1642
2307
  {
1643
2308
  icon: statusIcons[status] ?? "lucide:clock",
@@ -1646,125 +2311,79 @@ var ToolCallPart = ({
1646
2311
  expanded,
1647
2312
  onExpandedChange: setExpanded,
1648
2313
  spinning: status === "running",
1649
- headerActions: /* @__PURE__ */ jsx11(CopyButton, { text: commandText, title: "\u590D\u5236" }),
1650
- children: /* @__PURE__ */ jsxs6("div", { className: "tool-call-content", children: [
1651
- /* @__PURE__ */ jsxs6("div", { className: "tool-call-command", children: [
1652
- /* @__PURE__ */ jsxs6("div", { className: "command-header", children: [
1653
- /* @__PURE__ */ jsx11("span", { className: "command-label", children: "\u6267\u884C\uFF1A" }),
1654
- /* @__PURE__ */ jsx11("span", { className: "command-name", children: commandDisplay })
1655
- ] }),
1656
- /* @__PURE__ */ jsx11("code", { className: "command-code", children: commandText })
1657
- ] }),
1658
- result !== null && /* @__PURE__ */ jsxs6("div", { className: "tool-call-result", children: [
1659
- /* @__PURE__ */ jsxs6("div", { className: "result-header", children: [
1660
- /* @__PURE__ */ jsx11("span", { className: "result-label", children: "\u7ED3\u679C\uFF1A" }),
1661
- /* @__PURE__ */ jsx11("span", { className: "result-name" })
2314
+ headerActions: /* @__PURE__ */ jsx11(CopyButton, { text: argsText, title: "\u590D\u5236" }),
2315
+ children: [
2316
+ /* @__PURE__ */ jsx11("pre", { className: `tool-args ${argsType}`, children: argsType === "json" ? /* @__PURE__ */ jsx11("code", { dangerouslySetInnerHTML: { __html: highlightedJson } }) : argsText }),
2317
+ hasOutput && /* @__PURE__ */ jsxs7("div", { className: "output-panel", children: [
2318
+ /* @__PURE__ */ jsxs7("div", { className: "output-header", children: [
2319
+ /* @__PURE__ */ jsxs7("div", { className: "output-tabs", children: [
2320
+ /* @__PURE__ */ jsx11(
2321
+ "button",
2322
+ {
2323
+ className: `tab${activeStream === "stdout" ? " active" : ""}`,
2324
+ type: "button",
2325
+ onClick: () => setActiveStream("stdout"),
2326
+ children: "\u6B63\u5E38\u8F93\u51FA"
2327
+ }
2328
+ ),
2329
+ /* @__PURE__ */ jsx11(
2330
+ "button",
2331
+ {
2332
+ className: `tab${activeStream === "stderr" ? " active" : ""}`,
2333
+ type: "button",
2334
+ onClick: () => setActiveStream("stderr"),
2335
+ children: "\u5F02\u5E38/\u544A\u8B66\u8F93\u51FA"
2336
+ }
2337
+ )
2338
+ ] }),
2339
+ /* @__PURE__ */ jsxs7("div", { className: "output-actions", children: [
2340
+ /* @__PURE__ */ jsxs7("button", { className: "btn btn-save", type: "button", onClick: saveLog, children: [
2341
+ /* @__PURE__ */ jsx11(Icon6, { icon: "lucide:download", width: 14 }),
2342
+ "\u4FDD\u5B58\u65E5\u5FD7"
2343
+ ] }),
2344
+ /* @__PURE__ */ jsx11(CopyButton, { text: activeOutputText, title: "\u590D\u5236\u8F93\u51FA" })
2345
+ ] })
1662
2346
  ] }),
1663
- /* @__PURE__ */ jsx11("code", { className: "result-content", children: formattedResult })
2347
+ /* @__PURE__ */ jsx11("pre", { className: "output-body chat-scrollbar", children: /* @__PURE__ */ jsx11("code", { children: activeOutputText || "(\u65E0\u8F93\u51FA)" }) })
1664
2348
  ] }),
1665
- /* @__PURE__ */ jsxs6("div", { className: "tool-call-footer", children: [
1666
- /* @__PURE__ */ jsx11("div", { className: "footer-left", children: /* @__PURE__ */ jsx11(
2349
+ /* @__PURE__ */ jsxs7("div", { className: "tool-footer", children: [
2350
+ /* @__PURE__ */ jsx11(
1667
2351
  DropdownSelector,
1668
2352
  {
1669
2353
  value: currentMode,
1670
2354
  options: modeOptions,
1671
2355
  onSelect: handleModeChange
1672
2356
  }
1673
- ) }),
1674
- /* @__PURE__ */ jsx11("div", { className: "footer-right", children: status === "pending" ? /* @__PURE__ */ jsxs6("div", { className: "action-buttons", children: [
2357
+ ),
2358
+ /* @__PURE__ */ jsx11("div", { className: "tool-actions", children: status === "pending" ? /* @__PURE__ */ jsxs7(Fragment2, { children: [
1675
2359
  /* @__PURE__ */ jsx11("button", { className: "btn btn-skip", onClick: handleSkip, children: "\u8DF3\u8FC7" }),
1676
- /* @__PURE__ */ jsxs6("button", { className: "btn btn-run", onClick: handleRun, children: [
2360
+ /* @__PURE__ */ jsxs7("button", { className: "btn btn-run", onClick: handleRun, children: [
1677
2361
  "\u8FD0\u884C",
1678
2362
  /* @__PURE__ */ jsx11(Icon6, { icon: "lucide:arrow-right", width: 14 })
1679
2363
  ] })
1680
- ] }) : status === "running" ? (
1681
- /* running 状态:显示运行中状态 + 取消按钮 */
1682
- /* @__PURE__ */ jsxs6(Fragment2, { children: [
1683
- /* @__PURE__ */ jsxs6("div", { className: "execution-status running", children: [
1684
- /* @__PURE__ */ jsx11(Icon6, { icon: "lucide:loader-2", width: 14, className: "spinning" }),
1685
- /* @__PURE__ */ jsx11("span", { children: "\u8FD0\u884C\u4E2D" })
1686
- ] }),
1687
- /* @__PURE__ */ jsxs6("button", { className: "btn btn-cancel", onClick: handleCancel, children: [
1688
- /* @__PURE__ */ jsx11(Icon6, { icon: "lucide:x", width: 14 }),
1689
- "\u53D6\u6D88"
1690
- ] })
1691
- ] })
1692
- ) : (
1693
- /* done/error 状态:只显示状态 */
1694
- /* @__PURE__ */ jsxs6("div", { className: `execution-status ${status}`, children: [
1695
- /* @__PURE__ */ jsx11(Icon6, { icon: statusDisplayIcons[status] ?? "lucide:clock", width: 14 }),
1696
- /* @__PURE__ */ jsx11("span", { children: statusTexts[status] ?? "" })
2364
+ ] }) : status === "running" ? /* @__PURE__ */ jsxs7(Fragment2, { children: [
2365
+ /* @__PURE__ */ jsxs7("span", { className: "status-text running", children: [
2366
+ /* @__PURE__ */ jsx11(Icon6, { icon: "lucide:loader-2", width: 14, className: "spinning" }),
2367
+ "\u8FD0\u884C\u4E2D"
2368
+ ] }),
2369
+ /* @__PURE__ */ jsxs7("button", { className: "btn btn-cancel", onClick: handleCancel, children: [
2370
+ /* @__PURE__ */ jsx11(Icon6, { icon: "lucide:x", width: 14 }),
2371
+ "\u53D6\u6D88"
1697
2372
  ] })
1698
- ) })
2373
+ ] }) : /* @__PURE__ */ jsxs7("span", { className: `status-text ${status}`, children: [
2374
+ /* @__PURE__ */ jsx11(Icon6, { icon: statusDisplayIcons[status] ?? "lucide:clock", width: 14 }),
2375
+ statusTexts[status] ?? ""
2376
+ ] }) })
1699
2377
  ] })
1700
- ] })
2378
+ ]
1701
2379
  }
1702
2380
  );
1703
2381
  };
1704
2382
 
1705
- // src/components/message/parts/ToolResultPart.tsx
1706
- import { useState as useState8, useContext as useContext2, useMemo as useMemo5 } from "react";
1707
- import { Icon as Icon7 } from "@iconify/react";
1708
- import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
1709
- var ToolResultPart = ({
1710
- name,
1711
- args,
1712
- result,
1713
- status,
1714
- expandedType = "auto"
1715
- }) => {
1716
- const toolRenderers = useContext2(ToolRenderersContext);
1717
- const getInitialExpanded = () => {
1718
- if (expandedType === "open") return true;
1719
- return false;
1720
- };
1721
- const [expanded, setExpanded] = useState8(getInitialExpanded);
1722
- const CustomRenderer = useMemo5(() => {
1723
- return toolRenderers[name];
1724
- }, [toolRenderers, name]);
1725
- const formattedResult = useMemo5(() => {
1726
- if (typeof result === "string") {
1727
- return result;
1728
- }
1729
- return JSON.stringify(result, null, 2);
1730
- }, [result]);
1731
- const rendererStatus = useMemo5(() => {
1732
- if (status === "error") return "error";
1733
- if (status === "cancelled") return "error";
1734
- return "completed";
1735
- }, [status]);
1736
- if (CustomRenderer) {
1737
- return /* @__PURE__ */ jsx12("div", { className: "tool-result-part", children: /* @__PURE__ */ jsx12(
1738
- CustomRenderer,
1739
- {
1740
- toolName: name,
1741
- toolArgs: args || {},
1742
- toolResult: result,
1743
- status: rendererStatus
1744
- }
1745
- ) });
1746
- }
1747
- return /* @__PURE__ */ jsx12("div", { className: "tool-result-part", children: /* @__PURE__ */ jsxs7("div", { className: `default-result ${expanded ? "expanded" : ""}`, children: [
1748
- /* @__PURE__ */ jsxs7("div", { className: "result-header", onClick: () => setExpanded(!expanded), children: [
1749
- /* @__PURE__ */ jsx12("div", { className: "result-icon", children: status === "error" ? /* @__PURE__ */ jsx12(Icon7, { icon: "lucide:x-circle", width: 14, className: "error" }) : /* @__PURE__ */ jsx12(Icon7, { icon: "lucide:check-circle", width: 14, className: "success" }) }),
1750
- /* @__PURE__ */ jsx12("span", { className: "result-name", children: name }),
1751
- /* @__PURE__ */ jsx12(
1752
- Icon7,
1753
- {
1754
- icon: expanded ? "lucide:chevron-up" : "lucide:chevron-down",
1755
- width: 14,
1756
- className: "result-chevron"
1757
- }
1758
- )
1759
- ] }),
1760
- expanded && /* @__PURE__ */ jsx12("div", { className: "result-content", children: /* @__PURE__ */ jsx12("pre", { children: formattedResult }) })
1761
- ] }) });
1762
- };
1763
-
1764
2383
  // src/components/message/parts/ImagePart.tsx
1765
2384
  import { useState as useState9 } from "react";
1766
- import { Icon as Icon8 } from "@iconify/react";
1767
- import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
2385
+ import { Icon as Icon7 } from "@iconify/react";
2386
+ import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
1768
2387
  var ImagePart = ({ url, alt }) => {
1769
2388
  const [loading, setLoading] = useState9(true);
1770
2389
  const [error, setError] = useState9(false);
@@ -1780,7 +2399,7 @@ var ImagePart = ({ url, alt }) => {
1780
2399
  window.open(url, "_blank");
1781
2400
  };
1782
2401
  return /* @__PURE__ */ jsxs8("div", { className: "image-part", children: [
1783
- !error && /* @__PURE__ */ jsx13(
2402
+ !error && /* @__PURE__ */ jsx12(
1784
2403
  "img",
1785
2404
  {
1786
2405
  src: url,
@@ -1792,17 +2411,17 @@ var ImagePart = ({ url, alt }) => {
1792
2411
  style: { display: loading ? "none" : "block" }
1793
2412
  }
1794
2413
  ),
1795
- loading && !error && /* @__PURE__ */ jsx13("div", { className: "image-loading", children: /* @__PURE__ */ jsx13(Icon8, { icon: "lucide:loader-2", width: 24, className: "spinning" }) }),
2414
+ loading && !error && /* @__PURE__ */ jsx12("div", { className: "image-loading", children: /* @__PURE__ */ jsx12(Icon7, { icon: "lucide:loader-2", width: 24, className: "spinning" }) }),
1796
2415
  error && /* @__PURE__ */ jsxs8("div", { className: "image-error", children: [
1797
- /* @__PURE__ */ jsx13(Icon8, { icon: "lucide:image-off", width: 24 }),
1798
- /* @__PURE__ */ jsx13("span", { children: "\u56FE\u7247\u52A0\u8F7D\u5931\u8D25" })
2416
+ /* @__PURE__ */ jsx12(Icon7, { icon: "lucide:image-off", width: 24 }),
2417
+ /* @__PURE__ */ jsx12("span", { children: "\u56FE\u7247\u52A0\u8F7D\u5931\u8D25" })
1799
2418
  ] })
1800
2419
  ] });
1801
2420
  };
1802
2421
 
1803
2422
  // src/components/message/parts/ErrorPart.tsx
1804
2423
  import { useState as useState10 } from "react";
1805
- import { jsx as jsx14 } from "react/jsx-runtime";
2424
+ import { jsx as jsx13 } from "react/jsx-runtime";
1806
2425
  var categoryLabels = {
1807
2426
  api: "API \u9519\u8BEF",
1808
2427
  network: "\u7F51\u7EDC\u9519\u8BEF",
@@ -1815,7 +2434,7 @@ var categoryLabels = {
1815
2434
  var ErrorPart = ({ message, category }) => {
1816
2435
  const [expanded, setExpanded] = useState10(true);
1817
2436
  const categoryLabel = categoryLabels[category || "unknown"] || category || "\u8BF7\u6C42\u5931\u8D25";
1818
- return /* @__PURE__ */ jsx14(
2437
+ return /* @__PURE__ */ jsx13(
1819
2438
  CollapsibleCard,
1820
2439
  {
1821
2440
  icon: "lucide:alert-circle",
@@ -1824,107 +2443,124 @@ var ErrorPart = ({ message, category }) => {
1824
2443
  titleColor: "var(--chat-error, #ef4444)",
1825
2444
  expanded,
1826
2445
  onExpandedChange: setExpanded,
1827
- children: /* @__PURE__ */ jsx14("div", { className: "error-content", children: message })
2446
+ children: /* @__PURE__ */ jsx13("div", { className: "error-content", children: message })
1828
2447
  }
1829
2448
  );
1830
2449
  };
1831
2450
 
1832
2451
  // src/components/message/PartsRenderer.tsx
1833
- import { jsx as jsx15 } from "react/jsx-runtime";
2452
+ import { jsx as jsx14 } from "react/jsx-runtime";
2453
+ function isText(part) {
2454
+ return part.type === "text";
2455
+ }
2456
+ function isThinking(part) {
2457
+ return part.type === "thinking";
2458
+ }
2459
+ function isSearch(part) {
2460
+ return part.type === "search";
2461
+ }
2462
+ function isToolCall(part) {
2463
+ return part.type === "tool_call";
2464
+ }
2465
+ function isImage(part) {
2466
+ return part.type === "image";
2467
+ }
2468
+ function isError(part) {
2469
+ return part.type === "error";
2470
+ }
1834
2471
  var PartsRenderer = ({
1835
2472
  parts,
1836
2473
  expandedType = "auto",
2474
+ partRenderers: propRenderers,
1837
2475
  adapter,
2476
+ onCancelToolCall,
1838
2477
  autoRunConfig,
1839
- onSaveConfig
1840
- }) => {
1841
- return /* @__PURE__ */ jsx15("div", { className: "parts-renderer", children: parts.map((part, index) => {
1842
- switch (part.type) {
1843
- case "text":
1844
- return /* @__PURE__ */ jsx15(TextPart, { text: part.text }, index);
1845
- case "thinking":
1846
- return /* @__PURE__ */ jsx15(
1847
- ThinkingPart,
1848
- {
1849
- text: part.text,
1850
- status: part.status,
1851
- duration: part.duration,
1852
- expandedType
1853
- },
1854
- index
1855
- );
1856
- case "search":
1857
- return /* @__PURE__ */ jsx15(
1858
- SearchPart,
1859
- {
1860
- query: part.query,
1861
- results: part.results,
1862
- status: part.status,
1863
- expandedType
1864
- },
1865
- index
1866
- );
1867
- case "tool_call":
1868
- return /* @__PURE__ */ jsx15(
1869
- ToolCallPart,
1870
- {
1871
- id: part.id,
1872
- name: part.name,
1873
- args: part.args,
1874
- status: part.status,
1875
- result: part.result,
1876
- expandedType,
1877
- adapter,
1878
- autoRunConfig,
1879
- onSaveConfig
1880
- },
1881
- index
1882
- );
1883
- case "tool_result":
1884
- return /* @__PURE__ */ jsx15(
1885
- ToolResultPart,
1886
- {
1887
- id: part.id,
1888
- name: part.name,
1889
- args: part.args,
1890
- result: part.result,
1891
- status: part.status,
1892
- expandedType
1893
- },
1894
- index
1895
- );
1896
- case "image":
1897
- return /* @__PURE__ */ jsx15(
1898
- ImagePart,
1899
- {
1900
- url: part.url
1901
- },
1902
- index
1903
- );
1904
- case "error":
1905
- return /* @__PURE__ */ jsx15(
1906
- ErrorPart,
1907
- {
1908
- message: part.message,
1909
- category: part.category,
1910
- retryable: part.retryable
1911
- },
1912
- index
1913
- );
1914
- default:
1915
- return null;
2478
+ onSaveConfig
2479
+ }) => {
2480
+ const contextRenderers = useContext2(PartRenderersContext);
2481
+ const partRenderers = propRenderers ?? contextRenderers;
2482
+ const visibleParts = useMemo5(() => parts, [parts]);
2483
+ const renderPart = (part) => {
2484
+ const CustomRenderer = partRenderers[part.type];
2485
+ if (CustomRenderer) {
2486
+ return /* @__PURE__ */ jsx14(CustomRenderer, { ...part });
1916
2487
  }
1917
- }) });
2488
+ if (isText(part)) {
2489
+ return /* @__PURE__ */ jsx14(TextPart, { text: part.text });
2490
+ }
2491
+ if (isThinking(part)) {
2492
+ return /* @__PURE__ */ jsx14(
2493
+ ThinkingPart,
2494
+ {
2495
+ text: part.text,
2496
+ status: part.status,
2497
+ duration: part.duration,
2498
+ expandedType
2499
+ }
2500
+ );
2501
+ }
2502
+ if (isSearch(part)) {
2503
+ return /* @__PURE__ */ jsx14(
2504
+ SearchPart,
2505
+ {
2506
+ query: part.query,
2507
+ results: part.results,
2508
+ status: part.status,
2509
+ expandedType
2510
+ }
2511
+ );
2512
+ }
2513
+ if (isToolCall(part)) {
2514
+ return /* @__PURE__ */ jsx14(
2515
+ ToolCallPart,
2516
+ {
2517
+ id: part.id,
2518
+ name: part.name,
2519
+ args: part.args,
2520
+ output: part.output,
2521
+ status: part.status,
2522
+ expandedType,
2523
+ adapter,
2524
+ onCancelToolCall,
2525
+ autoRunConfig,
2526
+ onSaveConfig
2527
+ }
2528
+ );
2529
+ }
2530
+ if (isImage(part)) {
2531
+ return /* @__PURE__ */ jsx14(ImagePart, { url: part.url });
2532
+ }
2533
+ if (isError(part)) {
2534
+ return /* @__PURE__ */ jsx14(
2535
+ ErrorPart,
2536
+ {
2537
+ message: part.message,
2538
+ category: part.category,
2539
+ retryable: part.retryable
2540
+ }
2541
+ );
2542
+ }
2543
+ return /* @__PURE__ */ jsx14("div", { className: "unknown-part", children: /* @__PURE__ */ jsx14("pre", { children: JSON.stringify(part, null, 2) }) });
2544
+ };
2545
+ return /* @__PURE__ */ jsx14("div", { className: "parts-renderer", children: visibleParts.map((part, index) => /* @__PURE__ */ jsx14(
2546
+ "div",
2547
+ {
2548
+ className: "part-item",
2549
+ style: { marginTop: index > 0 ? "8px" : "0" },
2550
+ children: renderPart(part)
2551
+ },
2552
+ index
2553
+ )) });
1918
2554
  };
1919
2555
 
1920
2556
  // src/components/input/ChatInput.tsx
1921
- import { useState as useState16, useRef as useRef9, useCallback as useCallback12, useEffect as useEffect12, forwardRef as forwardRef7, useImperativeHandle as useImperativeHandle7, useLayoutEffect, useMemo as useMemo11 } from "react";
1922
- import { Icon as Icon17 } from "@iconify/react";
2557
+ import { useState as useState16, useRef as useRef10, useCallback as useCallback13, useEffect as useEffect13, forwardRef as forwardRef7, useImperativeHandle as useImperativeHandle7, useLayoutEffect as useLayoutEffect2, useMemo as useMemo11 } from "react";
2558
+ import { Icon as Icon16 } from "@iconify/react";
1923
2559
 
1924
2560
  // src/components/input/AtFilePicker.tsx
1925
- import { useCallback as useCallback7, useEffect as useEffect8, useMemo as useMemo7, useRef as useRef6, useState as useState12 } from "react";
2561
+ import { useCallback as useCallback8, useEffect as useEffect9, useMemo as useMemo7, useRef as useRef7, useState as useState12 } from "react";
1926
2562
  import { createPortal } from "react-dom";
1927
- import { Icon as Icon15 } from "@iconify/react";
2563
+ import { Icon as Icon14 } from "@iconify/react";
1928
2564
 
1929
2565
  // src/utils/fileIcon.ts
1930
2566
  function getFileIcon(nameOrPath) {
@@ -1965,9 +2601,9 @@ function dirname(p) {
1965
2601
  }
1966
2602
 
1967
2603
  // src/components/input/at-views/AtFilesView.tsx
1968
- import { useCallback as useCallback6, useEffect as useEffect7, useImperativeHandle, useMemo as useMemo6, useRef as useRef5, useState as useState11, forwardRef } from "react";
1969
- import { Icon as Icon9 } from "@iconify/react";
1970
- import { jsx as jsx16, jsxs as jsxs9 } from "react/jsx-runtime";
2604
+ import { useCallback as useCallback7, useEffect as useEffect8, useImperativeHandle, useMemo as useMemo6, useRef as useRef6, useState as useState11, forwardRef } from "react";
2605
+ import { Icon as Icon8 } from "@iconify/react";
2606
+ import { jsx as jsx15, jsxs as jsxs9 } from "react/jsx-runtime";
1971
2607
  var AtFilesView = forwardRef(({
1972
2608
  adapter,
1973
2609
  initialDir,
@@ -1980,16 +2616,16 @@ var AtFilesView = forwardRef(({
1980
2616
  const [loading, setLoading] = useState11(false);
1981
2617
  const [currentPath, setCurrentPath] = useState11("");
1982
2618
  const [entries, setEntries] = useState11([]);
1983
- const initializedRef = useRef5(false);
2619
+ const initializedRef = useRef6(false);
1984
2620
  const filteredEntries = useMemo6(() => {
1985
2621
  const q = query.trim().toLowerCase();
1986
2622
  if (!q) return entries;
1987
2623
  return entries.filter((f) => f.name.toLowerCase().includes(q));
1988
2624
  }, [entries, query]);
1989
- useEffect7(() => {
2625
+ useEffect8(() => {
1990
2626
  onUpdateCount(filteredEntries.length);
1991
2627
  }, [filteredEntries.length, onUpdateCount]);
1992
- const loadDir = useCallback6(async (dir) => {
2628
+ const loadDir = useCallback7(async (dir) => {
1993
2629
  if (!adapter.listDir) return;
1994
2630
  setLoading(true);
1995
2631
  try {
@@ -2006,19 +2642,19 @@ var AtFilesView = forwardRef(({
2006
2642
  setLoading(false);
2007
2643
  }
2008
2644
  }, [adapter, onSetActive]);
2009
- const goUp = useCallback6(async () => {
2645
+ const goUp = useCallback7(async () => {
2010
2646
  if (!adapter.parentDir) return;
2011
2647
  const parent = await adapter.parentDir(currentPath);
2012
2648
  if (parent && parent !== currentPath) {
2013
2649
  await loadDir(parent);
2014
2650
  }
2015
2651
  }, [adapter, currentPath, loadDir]);
2016
- const goHome = useCallback6(async () => {
2652
+ const goHome = useCallback7(async () => {
2017
2653
  if (!adapter.homeDir) return;
2018
2654
  const home = await adapter.homeDir();
2019
2655
  await loadDir(home);
2020
2656
  }, [adapter, loadDir]);
2021
- const handleEntryClick = useCallback6((f) => {
2657
+ const handleEntryClick = useCallback7((f) => {
2022
2658
  if (f.isDirectory) {
2023
2659
  loadDir(f.path);
2024
2660
  return;
@@ -2037,7 +2673,7 @@ var AtFilesView = forwardRef(({
2037
2673
  }
2038
2674
  }
2039
2675
  }), [activeIndex, filteredEntries, handleEntryClick]);
2040
- useEffect7(() => {
2676
+ useEffect8(() => {
2041
2677
  if (initializedRef.current) return;
2042
2678
  initializedRef.current = true;
2043
2679
  const init = async () => {
@@ -2047,22 +2683,22 @@ var AtFilesView = forwardRef(({
2047
2683
  init();
2048
2684
  }, [adapter, initialDir, loadDir]);
2049
2685
  return /* @__PURE__ */ jsxs9("div", { className: "at-files-view", children: [
2050
- /* @__PURE__ */ jsx16("div", { className: "at-view-section-title", children: "\u6587\u4EF6\u548C\u6587\u4EF6\u5939" }),
2686
+ /* @__PURE__ */ jsx15("div", { className: "at-view-section-title", children: "\u6587\u4EF6\u548C\u6587\u4EF6\u5939" }),
2051
2687
  /* @__PURE__ */ jsxs9("div", { className: "at-view-pathbar", children: [
2052
- /* @__PURE__ */ jsx16("button", { className: "at-view-pathbtn", title: "\u8FD4\u56DE\u4E0A\u7EA7", onClick: goUp, children: /* @__PURE__ */ jsx16(Icon9, { icon: "lucide:arrow-up", width: 12 }) }),
2053
- /* @__PURE__ */ jsx16("button", { className: "at-view-pathbtn", title: "\u4E3B\u76EE\u5F55", onClick: goHome, children: /* @__PURE__ */ jsx16(Icon9, { icon: "lucide:home", width: 12 }) }),
2054
- /* @__PURE__ */ jsx16("div", { className: "at-view-pathtext", title: currentPath || "", children: currentPath || "-" })
2688
+ /* @__PURE__ */ jsx15("button", { className: "at-view-pathbtn", title: "\u8FD4\u56DE\u4E0A\u7EA7", onClick: goUp, children: /* @__PURE__ */ jsx15(Icon8, { icon: "lucide:arrow-up", width: 12 }) }),
2689
+ /* @__PURE__ */ jsx15("button", { className: "at-view-pathbtn", title: "\u4E3B\u76EE\u5F55", onClick: goHome, children: /* @__PURE__ */ jsx15(Icon8, { icon: "lucide:home", width: 12 }) }),
2690
+ /* @__PURE__ */ jsx15("div", { className: "at-view-pathtext", title: currentPath || "", children: currentPath || "-" })
2055
2691
  ] }),
2056
- /* @__PURE__ */ jsx16("div", { className: "at-view-list", children: loading ? /* @__PURE__ */ jsx16("div", { className: "at-view-empty", children: "\u52A0\u8F7D\u4E2D..." }) : filteredEntries.length === 0 ? /* @__PURE__ */ jsx16("div", { className: "at-view-empty", children: query.trim() ? "\u6CA1\u6709\u627E\u5230\u5339\u914D\u9879" : "\u76EE\u5F55\u4E3A\u7A7A" }) : filteredEntries.map((f, i) => /* @__PURE__ */ jsxs9(
2692
+ /* @__PURE__ */ jsx15("div", { className: "at-view-list", children: loading ? /* @__PURE__ */ jsx15("div", { className: "at-view-empty", children: "\u52A0\u8F7D\u4E2D..." }) : filteredEntries.length === 0 ? /* @__PURE__ */ jsx15("div", { className: "at-view-empty", children: query.trim() ? "\u6CA1\u6709\u627E\u5230\u5339\u914D\u9879" : "\u76EE\u5F55\u4E3A\u7A7A" }) : filteredEntries.map((f, i) => /* @__PURE__ */ jsxs9(
2057
2693
  "button",
2058
2694
  {
2059
2695
  className: `at-view-item${activeIndex === i ? " active" : ""}`,
2060
2696
  onMouseEnter: () => onSetActive(i),
2061
2697
  onClick: () => handleEntryClick(f),
2062
2698
  children: [
2063
- /* @__PURE__ */ jsx16(Icon9, { icon: f.isDirectory ? "lucide:folder" : getFileIcon(f.name), width: 14, className: "at-view-item-icon" }),
2064
- /* @__PURE__ */ jsx16("span", { className: "at-view-item-name", children: f.name }),
2065
- /* @__PURE__ */ jsx16("span", { className: "at-view-item-path", children: f.path })
2699
+ /* @__PURE__ */ jsx15(Icon8, { icon: f.isDirectory ? "lucide:folder" : getFileIcon(f.name), width: 14, className: "at-view-item-icon" }),
2700
+ /* @__PURE__ */ jsx15("span", { className: "at-view-item-name", children: f.name }),
2701
+ /* @__PURE__ */ jsx15("span", { className: "at-view-item-path", children: f.path })
2066
2702
  ]
2067
2703
  },
2068
2704
  f.path
@@ -2073,8 +2709,8 @@ AtFilesView.displayName = "AtFilesView";
2073
2709
 
2074
2710
  // src/components/input/at-views/AtDocsView.tsx
2075
2711
  import { forwardRef as forwardRef2, useImperativeHandle as useImperativeHandle2 } from "react";
2076
- import { Icon as Icon10 } from "@iconify/react";
2077
- import { jsx as jsx17, jsxs as jsxs10 } from "react/jsx-runtime";
2712
+ import { Icon as Icon9 } from "@iconify/react";
2713
+ import { jsx as jsx16, jsxs as jsxs10 } from "react/jsx-runtime";
2078
2714
  var AtDocsView = forwardRef2((_, ref) => {
2079
2715
  useImperativeHandle2(ref, () => ({
2080
2716
  getActivePath: () => null,
@@ -2082,18 +2718,18 @@ var AtDocsView = forwardRef2((_, ref) => {
2082
2718
  }
2083
2719
  }), []);
2084
2720
  return /* @__PURE__ */ jsxs10("div", { className: "at-placeholder-view", children: [
2085
- /* @__PURE__ */ jsx17(Icon10, { icon: "lucide:book-open", width: 32, className: "at-placeholder-icon" }),
2086
- /* @__PURE__ */ jsx17("div", { className: "at-placeholder-title", children: "\u6587\u6863" }),
2087
- /* @__PURE__ */ jsx17("div", { className: "at-placeholder-desc", children: "\u529F\u80FD\u5F85\u5B9E\u73B0" }),
2088
- /* @__PURE__ */ jsx17("div", { className: "at-placeholder-hint", children: "\u5C06\u652F\u6301\u5F15\u7528\u9879\u76EE\u6587\u6863\u3001API \u6587\u6863\u7B49" })
2721
+ /* @__PURE__ */ jsx16(Icon9, { icon: "lucide:book-open", width: 32, className: "at-placeholder-icon" }),
2722
+ /* @__PURE__ */ jsx16("div", { className: "at-placeholder-title", children: "\u6587\u6863" }),
2723
+ /* @__PURE__ */ jsx16("div", { className: "at-placeholder-desc", children: "\u529F\u80FD\u5F85\u5B9E\u73B0" }),
2724
+ /* @__PURE__ */ jsx16("div", { className: "at-placeholder-hint", children: "\u5C06\u652F\u6301\u5F15\u7528\u9879\u76EE\u6587\u6863\u3001API \u6587\u6863\u7B49" })
2089
2725
  ] });
2090
2726
  });
2091
2727
  AtDocsView.displayName = "AtDocsView";
2092
2728
 
2093
2729
  // src/components/input/at-views/AtTerminalsView.tsx
2094
2730
  import { forwardRef as forwardRef3, useImperativeHandle as useImperativeHandle3 } from "react";
2095
- import { Icon as Icon11 } from "@iconify/react";
2096
- import { jsx as jsx18, jsxs as jsxs11 } from "react/jsx-runtime";
2731
+ import { Icon as Icon10 } from "@iconify/react";
2732
+ import { jsx as jsx17, jsxs as jsxs11 } from "react/jsx-runtime";
2097
2733
  var AtTerminalsView = forwardRef3((_, ref) => {
2098
2734
  useImperativeHandle3(ref, () => ({
2099
2735
  getActivePath: () => null,
@@ -2101,18 +2737,18 @@ var AtTerminalsView = forwardRef3((_, ref) => {
2101
2737
  }
2102
2738
  }), []);
2103
2739
  return /* @__PURE__ */ jsxs11("div", { className: "at-placeholder-view", children: [
2104
- /* @__PURE__ */ jsx18(Icon11, { icon: "lucide:terminal", width: 32, className: "at-placeholder-icon" }),
2105
- /* @__PURE__ */ jsx18("div", { className: "at-placeholder-title", children: "\u7EC8\u7AEF" }),
2106
- /* @__PURE__ */ jsx18("div", { className: "at-placeholder-desc", children: "\u529F\u80FD\u5F85\u5B9E\u73B0" }),
2107
- /* @__PURE__ */ jsx18("div", { className: "at-placeholder-hint", children: "\u5C06\u652F\u6301\u5F15\u7528\u7EC8\u7AEF\u8F93\u51FA\u3001\u547D\u4EE4\u5386\u53F2\u7B49" })
2740
+ /* @__PURE__ */ jsx17(Icon10, { icon: "lucide:terminal", width: 32, className: "at-placeholder-icon" }),
2741
+ /* @__PURE__ */ jsx17("div", { className: "at-placeholder-title", children: "\u7EC8\u7AEF" }),
2742
+ /* @__PURE__ */ jsx17("div", { className: "at-placeholder-desc", children: "\u529F\u80FD\u5F85\u5B9E\u73B0" }),
2743
+ /* @__PURE__ */ jsx17("div", { className: "at-placeholder-hint", children: "\u5C06\u652F\u6301\u5F15\u7528\u7EC8\u7AEF\u8F93\u51FA\u3001\u547D\u4EE4\u5386\u53F2\u7B49" })
2108
2744
  ] });
2109
2745
  });
2110
2746
  AtTerminalsView.displayName = "AtTerminalsView";
2111
2747
 
2112
2748
  // src/components/input/at-views/AtChatsView.tsx
2113
2749
  import { forwardRef as forwardRef4, useImperativeHandle as useImperativeHandle4 } from "react";
2114
- import { Icon as Icon12 } from "@iconify/react";
2115
- import { jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
2750
+ import { Icon as Icon11 } from "@iconify/react";
2751
+ import { jsx as jsx18, jsxs as jsxs12 } from "react/jsx-runtime";
2116
2752
  var AtChatsView = forwardRef4((_, ref) => {
2117
2753
  useImperativeHandle4(ref, () => ({
2118
2754
  getActivePath: () => null,
@@ -2120,18 +2756,18 @@ var AtChatsView = forwardRef4((_, ref) => {
2120
2756
  }
2121
2757
  }), []);
2122
2758
  return /* @__PURE__ */ jsxs12("div", { className: "at-placeholder-view", children: [
2123
- /* @__PURE__ */ jsx19(Icon12, { icon: "lucide:message-square", width: 32, className: "at-placeholder-icon" }),
2124
- /* @__PURE__ */ jsx19("div", { className: "at-placeholder-title", children: "\u5386\u53F2\u5BF9\u8BDD" }),
2125
- /* @__PURE__ */ jsx19("div", { className: "at-placeholder-desc", children: "\u529F\u80FD\u5F85\u5B9E\u73B0" }),
2126
- /* @__PURE__ */ jsx19("div", { className: "at-placeholder-hint", children: "\u5C06\u652F\u6301\u5F15\u7528\u4E4B\u524D\u7684\u5BF9\u8BDD\u8BB0\u5F55" })
2759
+ /* @__PURE__ */ jsx18(Icon11, { icon: "lucide:message-square", width: 32, className: "at-placeholder-icon" }),
2760
+ /* @__PURE__ */ jsx18("div", { className: "at-placeholder-title", children: "\u5386\u53F2\u5BF9\u8BDD" }),
2761
+ /* @__PURE__ */ jsx18("div", { className: "at-placeholder-desc", children: "\u529F\u80FD\u5F85\u5B9E\u73B0" }),
2762
+ /* @__PURE__ */ jsx18("div", { className: "at-placeholder-hint", children: "\u5C06\u652F\u6301\u5F15\u7528\u4E4B\u524D\u7684\u5BF9\u8BDD\u8BB0\u5F55" })
2127
2763
  ] });
2128
2764
  });
2129
2765
  AtChatsView.displayName = "AtChatsView";
2130
2766
 
2131
2767
  // src/components/input/at-views/AtBranchView.tsx
2132
2768
  import { forwardRef as forwardRef5, useImperativeHandle as useImperativeHandle5 } from "react";
2133
- import { Icon as Icon13 } from "@iconify/react";
2134
- import { jsx as jsx20, jsxs as jsxs13 } from "react/jsx-runtime";
2769
+ import { Icon as Icon12 } from "@iconify/react";
2770
+ import { jsx as jsx19, jsxs as jsxs13 } from "react/jsx-runtime";
2135
2771
  var AtBranchView = forwardRef5((_, ref) => {
2136
2772
  useImperativeHandle5(ref, () => ({
2137
2773
  getActivePath: () => null,
@@ -2139,18 +2775,18 @@ var AtBranchView = forwardRef5((_, ref) => {
2139
2775
  }
2140
2776
  }), []);
2141
2777
  return /* @__PURE__ */ jsxs13("div", { className: "at-placeholder-view", children: [
2142
- /* @__PURE__ */ jsx20(Icon13, { icon: "lucide:git-branch", width: 32, className: "at-placeholder-icon" }),
2143
- /* @__PURE__ */ jsx20("div", { className: "at-placeholder-title", children: "\u5206\u652F\u5DEE\u5F02" }),
2144
- /* @__PURE__ */ jsx20("div", { className: "at-placeholder-desc", children: "\u529F\u80FD\u5F85\u5B9E\u73B0" }),
2145
- /* @__PURE__ */ jsx20("div", { className: "at-placeholder-hint", children: "\u5C06\u652F\u6301\u5F15\u7528\u5F53\u524D\u5206\u652F\u4E0E\u4E3B\u5206\u652F\u7684\u5DEE\u5F02" })
2778
+ /* @__PURE__ */ jsx19(Icon12, { icon: "lucide:git-branch", width: 32, className: "at-placeholder-icon" }),
2779
+ /* @__PURE__ */ jsx19("div", { className: "at-placeholder-title", children: "\u5206\u652F\u5DEE\u5F02" }),
2780
+ /* @__PURE__ */ jsx19("div", { className: "at-placeholder-desc", children: "\u529F\u80FD\u5F85\u5B9E\u73B0" }),
2781
+ /* @__PURE__ */ jsx19("div", { className: "at-placeholder-hint", children: "\u5C06\u652F\u6301\u5F15\u7528\u5F53\u524D\u5206\u652F\u4E0E\u4E3B\u5206\u652F\u7684\u5DEE\u5F02" })
2146
2782
  ] });
2147
2783
  });
2148
2784
  AtBranchView.displayName = "AtBranchView";
2149
2785
 
2150
2786
  // src/components/input/at-views/AtBrowserView.tsx
2151
2787
  import { forwardRef as forwardRef6, useImperativeHandle as useImperativeHandle6 } from "react";
2152
- import { Icon as Icon14 } from "@iconify/react";
2153
- import { jsx as jsx21, jsxs as jsxs14 } from "react/jsx-runtime";
2788
+ import { Icon as Icon13 } from "@iconify/react";
2789
+ import { jsx as jsx20, jsxs as jsxs14 } from "react/jsx-runtime";
2154
2790
  var AtBrowserView = forwardRef6((_, ref) => {
2155
2791
  useImperativeHandle6(ref, () => ({
2156
2792
  getActivePath: () => null,
@@ -2158,16 +2794,16 @@ var AtBrowserView = forwardRef6((_, ref) => {
2158
2794
  }
2159
2795
  }), []);
2160
2796
  return /* @__PURE__ */ jsxs14("div", { className: "at-placeholder-view", children: [
2161
- /* @__PURE__ */ jsx21(Icon14, { icon: "lucide:globe", width: 32, className: "at-placeholder-icon" }),
2162
- /* @__PURE__ */ jsx21("div", { className: "at-placeholder-title", children: "\u7F51\u9875" }),
2163
- /* @__PURE__ */ jsx21("div", { className: "at-placeholder-desc", children: "\u529F\u80FD\u5F85\u5B9E\u73B0" }),
2164
- /* @__PURE__ */ jsx21("div", { className: "at-placeholder-hint", children: "\u5C06\u652F\u6301\u5F15\u7528\u7F51\u9875\u5185\u5BB9\u3001URL \u7B49" })
2797
+ /* @__PURE__ */ jsx20(Icon13, { icon: "lucide:globe", width: 32, className: "at-placeholder-icon" }),
2798
+ /* @__PURE__ */ jsx20("div", { className: "at-placeholder-title", children: "\u7F51\u9875" }),
2799
+ /* @__PURE__ */ jsx20("div", { className: "at-placeholder-desc", children: "\u529F\u80FD\u5F85\u5B9E\u73B0" }),
2800
+ /* @__PURE__ */ jsx20("div", { className: "at-placeholder-hint", children: "\u5C06\u652F\u6301\u5F15\u7528\u7F51\u9875\u5185\u5BB9\u3001URL \u7B49" })
2165
2801
  ] });
2166
2802
  });
2167
2803
  AtBrowserView.displayName = "AtBrowserView";
2168
2804
 
2169
2805
  // src/components/input/AtFilePicker.tsx
2170
- import { Fragment as Fragment3, jsx as jsx22, jsxs as jsxs15 } from "react/jsx-runtime";
2806
+ import { Fragment as Fragment3, jsx as jsx21, jsxs as jsxs15 } from "react/jsx-runtime";
2171
2807
  var RECENT_KEY = "ai-chat.at.recentPaths";
2172
2808
  var MAX_RECENT = 6;
2173
2809
  var DROPDOWN_WIDTH = 332;
@@ -2188,9 +2824,9 @@ function AtFilePicker({
2188
2824
  onClose,
2189
2825
  onSelect
2190
2826
  }) {
2191
- const searchRef = useRef6(null);
2192
- const bodyRef = useRef6(null);
2193
- const viewRef = useRef6(null);
2827
+ const searchRef = useRef7(null);
2828
+ const bodyRef = useRef7(null);
2829
+ const viewRef = useRef7(null);
2194
2830
  const [query, setQuery] = useState12("");
2195
2831
  const [currentView, setCurrentView] = useState12("categories");
2196
2832
  const [activeKey, setActiveKey] = useState12("");
@@ -2199,8 +2835,8 @@ function AtFilePicker({
2199
2835
  const [viewItemCount, setViewItemCount] = useState12(0);
2200
2836
  const [dropdownStyle, setDropdownStyle] = useState12({});
2201
2837
  const [isScrolling, setIsScrolling] = useState12(false);
2202
- const scrollTimerRef = useRef6(null);
2203
- const updateDropdownPosition = useCallback7(() => {
2838
+ const scrollTimerRef = useRef7(null);
2839
+ const updateDropdownPosition = useCallback8(() => {
2204
2840
  if (!anchorEl) {
2205
2841
  setDropdownStyle({});
2206
2842
  return;
@@ -2244,7 +2880,7 @@ function AtFilePicker({
2244
2880
  });
2245
2881
  }
2246
2882
  }, [anchorEl]);
2247
- const loadRecent = useCallback7(() => {
2883
+ const loadRecent = useCallback8(() => {
2248
2884
  try {
2249
2885
  const raw = localStorage.getItem(RECENT_KEY);
2250
2886
  if (!raw) {
@@ -2261,7 +2897,7 @@ function AtFilePicker({
2261
2897
  setRecentList([]);
2262
2898
  }
2263
2899
  }, []);
2264
- const pushRecent = useCallback7((path) => {
2900
+ const pushRecent = useCallback8((path) => {
2265
2901
  setRecentList((prev) => {
2266
2902
  const next = [path, ...prev.filter((p) => p !== path)].slice(0, MAX_RECENT);
2267
2903
  try {
@@ -2271,7 +2907,7 @@ function AtFilePicker({
2271
2907
  return next;
2272
2908
  });
2273
2909
  }, []);
2274
- const getSearchPlaceholder = useCallback7(() => {
2910
+ const getSearchPlaceholder = useCallback8(() => {
2275
2911
  if (currentView === "categories") return "\u6DFB\u52A0\u6587\u4EF6\u3001\u6587\u4EF6\u5939\u3001\u6587\u6863...";
2276
2912
  const cat = categories.find((c) => c.id === currentView);
2277
2913
  return cat?.placeholder || "Search...";
@@ -2282,32 +2918,32 @@ function AtFilePicker({
2282
2918
  if (!q) return recentList;
2283
2919
  return recentList.filter((p) => p.toLowerCase().includes(q));
2284
2920
  }, [currentView, query, recentList]);
2285
- const goBackToCategories = useCallback7(() => {
2921
+ const goBackToCategories = useCallback8(() => {
2286
2922
  setCurrentView("categories");
2287
2923
  setQuery("");
2288
2924
  setActiveKey("");
2289
2925
  setViewActiveIndex(-1);
2290
2926
  requestAnimationFrame(() => searchRef.current?.focus());
2291
2927
  }, []);
2292
- const handleCategoryClick = useCallback7((cat) => {
2928
+ const handleCategoryClick = useCallback8((cat) => {
2293
2929
  setCurrentView(cat.id);
2294
2930
  setQuery("");
2295
2931
  setActiveKey("");
2296
2932
  setViewActiveIndex(-1);
2297
2933
  requestAnimationFrame(() => searchRef.current?.focus());
2298
2934
  }, []);
2299
- const handleEsc = useCallback7(() => {
2935
+ const handleEsc = useCallback8(() => {
2300
2936
  if (currentView !== "categories") {
2301
2937
  goBackToCategories();
2302
2938
  } else {
2303
2939
  onClose();
2304
2940
  }
2305
2941
  }, [currentView, goBackToCategories, onClose]);
2306
- const selectPath = useCallback7((path) => {
2942
+ const selectPath = useCallback8((path) => {
2307
2943
  pushRecent(path);
2308
2944
  onSelect(path);
2309
2945
  }, [pushRecent, onSelect]);
2310
- const handleScroll = useCallback7(() => {
2946
+ const handleScroll = useCallback8(() => {
2311
2947
  setIsScrolling(true);
2312
2948
  if (scrollTimerRef.current) {
2313
2949
  clearTimeout(scrollTimerRef.current);
@@ -2317,15 +2953,15 @@ function AtFilePicker({
2317
2953
  scrollTimerRef.current = null;
2318
2954
  }, 150);
2319
2955
  }, []);
2320
- const handleSetActiveKey = useCallback7((key) => {
2956
+ const handleSetActiveKey = useCallback8((key) => {
2321
2957
  if (isScrolling) return;
2322
2958
  setActiveKey(key);
2323
2959
  }, [isScrolling]);
2324
- const handleSetViewActiveIndex = useCallback7((index) => {
2960
+ const handleSetViewActiveIndex = useCallback8((index) => {
2325
2961
  if (isScrolling) return;
2326
2962
  setViewActiveIndex(index);
2327
2963
  }, [isScrolling]);
2328
- const scrollToActive = useCallback7(() => {
2964
+ const scrollToActive = useCallback8(() => {
2329
2965
  requestAnimationFrame(() => {
2330
2966
  if (!bodyRef.current) return;
2331
2967
  let activeElement = null;
@@ -2366,7 +3002,7 @@ function AtFilePicker({
2366
3002
  }
2367
3003
  });
2368
3004
  }, [currentView, activeKey, filteredRecent.length]);
2369
- const move = useCallback7((delta) => {
3005
+ const move = useCallback8((delta) => {
2370
3006
  if (currentView === "categories") {
2371
3007
  const recentCount = filteredRecent.length;
2372
3008
  const catCount = categories.length;
@@ -2394,7 +3030,7 @@ function AtFilePicker({
2394
3030
  scrollToActive();
2395
3031
  }
2396
3032
  }, [currentView, filteredRecent.length, activeKey, viewItemCount, viewActiveIndex, scrollToActive]);
2397
- const confirmActive = useCallback7(() => {
3033
+ const confirmActive = useCallback8(() => {
2398
3034
  if (currentView === "categories") {
2399
3035
  if (activeKey.startsWith("recent:")) {
2400
3036
  const idx = Number(activeKey.split(":")[1]);
@@ -2409,7 +3045,7 @@ function AtFilePicker({
2409
3045
  viewRef.current?.confirmActive();
2410
3046
  }
2411
3047
  }, [currentView, activeKey, filteredRecent, selectPath, handleCategoryClick]);
2412
- const handleKeyDown = useCallback7((e) => {
3048
+ const handleKeyDown = useCallback8((e) => {
2413
3049
  if (e.key === "ArrowDown") {
2414
3050
  e.preventDefault();
2415
3051
  move(1);
@@ -2424,7 +3060,7 @@ function AtFilePicker({
2424
3060
  handleEsc();
2425
3061
  }
2426
3062
  }, [move, confirmActive, handleEsc]);
2427
- useEffect8(() => {
3063
+ useEffect9(() => {
2428
3064
  if (!visible) return;
2429
3065
  loadRecent();
2430
3066
  setQuery("");
@@ -2434,7 +3070,7 @@ function AtFilePicker({
2434
3070
  updateDropdownPosition();
2435
3071
  requestAnimationFrame(() => searchRef.current?.focus());
2436
3072
  }, [visible, loadRecent, updateDropdownPosition]);
2437
- useEffect8(() => {
3073
+ useEffect9(() => {
2438
3074
  if (!visible) return;
2439
3075
  const handleResize = () => updateDropdownPosition();
2440
3076
  window.addEventListener("resize", handleResize);
@@ -2448,7 +3084,7 @@ function AtFilePicker({
2448
3084
  }
2449
3085
  };
2450
3086
  }, [visible, updateDropdownPosition]);
2451
- useEffect8(() => {
3087
+ useEffect9(() => {
2452
3088
  if (visible) {
2453
3089
  updateDropdownPosition();
2454
3090
  }
@@ -2464,17 +3100,17 @@ function AtFilePicker({
2464
3100
  };
2465
3101
  switch (currentView) {
2466
3102
  case "files":
2467
- return /* @__PURE__ */ jsx22(AtFilesView, { ref: viewRef, adapter, initialDir, ...commonProps });
3103
+ return /* @__PURE__ */ jsx21(AtFilesView, { ref: viewRef, adapter, initialDir, ...commonProps });
2468
3104
  case "docs":
2469
- return /* @__PURE__ */ jsx22(AtDocsView, { ref: viewRef, ...commonProps });
3105
+ return /* @__PURE__ */ jsx21(AtDocsView, { ref: viewRef, ...commonProps });
2470
3106
  case "terminals":
2471
- return /* @__PURE__ */ jsx22(AtTerminalsView, { ref: viewRef, ...commonProps });
3107
+ return /* @__PURE__ */ jsx21(AtTerminalsView, { ref: viewRef, ...commonProps });
2472
3108
  case "chats":
2473
- return /* @__PURE__ */ jsx22(AtChatsView, { ref: viewRef, ...commonProps });
3109
+ return /* @__PURE__ */ jsx21(AtChatsView, { ref: viewRef, ...commonProps });
2474
3110
  case "branch":
2475
- return /* @__PURE__ */ jsx22(AtBranchView, { ref: viewRef, ...commonProps });
3111
+ return /* @__PURE__ */ jsx21(AtBranchView, { ref: viewRef, ...commonProps });
2476
3112
  case "browser":
2477
- return /* @__PURE__ */ jsx22(AtBrowserView, { ref: viewRef, ...commonProps });
3113
+ return /* @__PURE__ */ jsx21(AtBrowserView, { ref: viewRef, ...commonProps });
2478
3114
  default:
2479
3115
  return null;
2480
3116
  }
@@ -2482,8 +3118,8 @@ function AtFilePicker({
2482
3118
  return createPortal(
2483
3119
  /* @__PURE__ */ jsxs15("div", { className: "at-picker-dropdown", style: dropdownStyle, onClick: (e) => e.stopPropagation(), children: [
2484
3120
  /* @__PURE__ */ jsxs15("div", { className: "at-picker-header", children: [
2485
- currentView !== "categories" ? /* @__PURE__ */ jsx22("button", { className: "at-picker-back", onClick: goBackToCategories, children: /* @__PURE__ */ jsx22(Icon15, { icon: "lucide:arrow-left", width: 14 }) }) : /* @__PURE__ */ jsx22(Icon15, { icon: "lucide:search", width: 14, className: "at-picker-search-icon" }),
2486
- /* @__PURE__ */ jsx22(
3121
+ currentView !== "categories" ? /* @__PURE__ */ jsx21("button", { className: "at-picker-back", onClick: goBackToCategories, children: /* @__PURE__ */ jsx21(Icon14, { icon: "lucide:arrow-left", width: 14 }) }) : /* @__PURE__ */ jsx21(Icon14, { icon: "lucide:search", width: 14, className: "at-picker-search-icon" }),
3122
+ /* @__PURE__ */ jsx21(
2487
3123
  "input",
2488
3124
  {
2489
3125
  ref: searchRef,
@@ -2500,37 +3136,37 @@ function AtFilePicker({
2500
3136
  }
2501
3137
  )
2502
3138
  ] }),
2503
- /* @__PURE__ */ jsx22(
3139
+ /* @__PURE__ */ jsx21(
2504
3140
  "div",
2505
3141
  {
2506
3142
  ref: bodyRef,
2507
3143
  className: `at-picker-body chat-scrollbar${isScrolling ? " is-scrolling" : ""}`,
2508
3144
  onScroll: handleScroll,
2509
3145
  children: currentView === "categories" ? /* @__PURE__ */ jsxs15(Fragment3, { children: [
2510
- filteredRecent.length > 0 && /* @__PURE__ */ jsx22("div", { className: "at-picker-section at-picker-recent", children: /* @__PURE__ */ jsx22("div", { className: "at-picker-list", children: filteredRecent.map((p, i) => /* @__PURE__ */ jsxs15(
3146
+ filteredRecent.length > 0 && /* @__PURE__ */ jsx21("div", { className: "at-picker-section at-picker-recent", children: /* @__PURE__ */ jsx21("div", { className: "at-picker-list", children: filteredRecent.map((p, i) => /* @__PURE__ */ jsxs15(
2511
3147
  "button",
2512
3148
  {
2513
3149
  className: `at-picker-item${activeKey === `recent:${i}` ? " active" : ""}`,
2514
3150
  onMouseEnter: () => handleSetActiveKey(`recent:${i}`),
2515
3151
  onClick: () => selectPath(p),
2516
3152
  children: [
2517
- /* @__PURE__ */ jsx22(Icon15, { icon: getFileIcon(p), width: 14, className: "at-picker-item-icon" }),
2518
- /* @__PURE__ */ jsx22("span", { className: "at-picker-item-name", children: basename(p) }),
2519
- /* @__PURE__ */ jsx22("span", { className: "at-picker-item-path", children: dirname(p) })
3153
+ /* @__PURE__ */ jsx21(Icon14, { icon: getFileIcon(p), width: 14, className: "at-picker-item-icon" }),
3154
+ /* @__PURE__ */ jsx21("span", { className: "at-picker-item-name", children: basename(p) }),
3155
+ /* @__PURE__ */ jsx21("span", { className: "at-picker-item-path", children: dirname(p) })
2520
3156
  ]
2521
3157
  },
2522
3158
  p
2523
3159
  )) }) }),
2524
- /* @__PURE__ */ jsx22("div", { className: "at-picker-section", children: /* @__PURE__ */ jsx22("div", { className: "at-picker-list", children: categories.map((cat, i) => /* @__PURE__ */ jsxs15(
3160
+ /* @__PURE__ */ jsx21("div", { className: "at-picker-section", children: /* @__PURE__ */ jsx21("div", { className: "at-picker-list", children: categories.map((cat, i) => /* @__PURE__ */ jsxs15(
2525
3161
  "button",
2526
3162
  {
2527
3163
  className: `at-picker-item at-picker-category${activeKey === `cat:${i}` ? " active" : ""}`,
2528
3164
  onMouseEnter: () => handleSetActiveKey(`cat:${i}`),
2529
3165
  onClick: () => handleCategoryClick(cat),
2530
3166
  children: [
2531
- /* @__PURE__ */ jsx22(Icon15, { icon: cat.icon, width: 14, className: "at-picker-item-icon" }),
2532
- /* @__PURE__ */ jsx22("span", { className: "at-picker-item-name", children: cat.label }),
2533
- /* @__PURE__ */ jsx22(Icon15, { icon: "lucide:chevron-right", width: 12, className: "at-picker-chevron" })
3167
+ /* @__PURE__ */ jsx21(Icon14, { icon: cat.icon, width: 14, className: "at-picker-item-icon" }),
3168
+ /* @__PURE__ */ jsx21("span", { className: "at-picker-item-name", children: cat.label }),
3169
+ /* @__PURE__ */ jsx21(Icon14, { icon: "lucide:chevron-right", width: 12, className: "at-picker-chevron" })
2534
3170
  ]
2535
3171
  },
2536
3172
  cat.id
@@ -2538,7 +3174,7 @@ function AtFilePicker({
2538
3174
  ] }) : renderViewComponent()
2539
3175
  }
2540
3176
  ),
2541
- /* @__PURE__ */ jsx22("div", { className: "at-picker-footer", children: /* @__PURE__ */ jsxs15("span", { className: "at-picker-hint", children: [
3177
+ /* @__PURE__ */ jsx21("div", { className: "at-picker-footer", children: /* @__PURE__ */ jsxs15("span", { className: "at-picker-hint", children: [
2542
3178
  "\u2191\u2193 \u5BFC\u822A \xB7 Enter \u9009\u62E9 \xB7 Esc ",
2543
3179
  currentView === "categories" ? "\u5173\u95ED" : "\u8FD4\u56DE"
2544
3180
  ] }) })
@@ -2548,10 +3184,10 @@ function AtFilePicker({
2548
3184
  }
2549
3185
 
2550
3186
  // src/components/input/ImagePreviewModal.tsx
2551
- import { useCallback as useCallback8, useEffect as useEffect9, useState as useState13 } from "react";
3187
+ import { useCallback as useCallback9, useEffect as useEffect10, useState as useState13 } from "react";
2552
3188
  import { createPortal as createPortal2 } from "react-dom";
2553
- import { Icon as Icon16 } from "@iconify/react";
2554
- import { jsx as jsx23, jsxs as jsxs16 } from "react/jsx-runtime";
3189
+ import { Icon as Icon15 } from "@iconify/react";
3190
+ import { jsx as jsx22, jsxs as jsxs16 } from "react/jsx-runtime";
2555
3191
  function ImagePreviewModal({
2556
3192
  visible,
2557
3193
  images,
@@ -2560,25 +3196,25 @@ function ImagePreviewModal({
2560
3196
  onIndexChange
2561
3197
  }) {
2562
3198
  const [currentIndex, setCurrentIndex] = useState13(initialIndex);
2563
- useEffect9(() => {
3199
+ useEffect10(() => {
2564
3200
  if (visible) {
2565
3201
  setCurrentIndex(initialIndex);
2566
3202
  }
2567
3203
  }, [visible, initialIndex]);
2568
- useEffect9(() => {
3204
+ useEffect10(() => {
2569
3205
  onIndexChange?.(currentIndex);
2570
3206
  }, [currentIndex, onIndexChange]);
2571
- const prev = useCallback8(() => {
3207
+ const prev = useCallback9(() => {
2572
3208
  if (currentIndex > 0) {
2573
3209
  setCurrentIndex(currentIndex - 1);
2574
3210
  }
2575
3211
  }, [currentIndex]);
2576
- const next = useCallback8(() => {
3212
+ const next = useCallback9(() => {
2577
3213
  if (currentIndex < images.length - 1) {
2578
3214
  setCurrentIndex(currentIndex + 1);
2579
3215
  }
2580
3216
  }, [currentIndex, images.length]);
2581
- useEffect9(() => {
3217
+ useEffect10(() => {
2582
3218
  if (!visible) return;
2583
3219
  const handleKeyDown = (e) => {
2584
3220
  if (e.key === "Escape") {
@@ -2602,9 +3238,9 @@ function ImagePreviewModal({
2602
3238
  " / ",
2603
3239
  images.length
2604
3240
  ] }),
2605
- /* @__PURE__ */ jsx23("button", { className: "preview-close-btn", title: "\u5173\u95ED (Esc)", onClick: onClose, children: /* @__PURE__ */ jsx23(Icon16, { icon: "lucide:x", width: 18 }) })
3241
+ /* @__PURE__ */ jsx22("button", { className: "preview-close-btn", title: "\u5173\u95ED (Esc)", onClick: onClose, children: /* @__PURE__ */ jsx22(Icon15, { icon: "lucide:x", width: 18 }) })
2606
3242
  ] }),
2607
- images.length > 1 && /* @__PURE__ */ jsx23(
3243
+ images.length > 1 && /* @__PURE__ */ jsx22(
2608
3244
  "button",
2609
3245
  {
2610
3246
  className: `preview-nav-btn preview-nav-prev${currentIndex <= 0 ? " disabled" : ""}`,
@@ -2613,11 +3249,11 @@ function ImagePreviewModal({
2613
3249
  e.stopPropagation();
2614
3250
  prev();
2615
3251
  },
2616
- children: /* @__PURE__ */ jsx23(Icon16, { icon: "lucide:chevron-left", width: 20 })
3252
+ children: /* @__PURE__ */ jsx22(Icon15, { icon: "lucide:chevron-left", width: 20 })
2617
3253
  }
2618
3254
  ),
2619
- /* @__PURE__ */ jsx23("div", { className: "preview-main", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx23("img", { src: currentSrc, alt: "\u9884\u89C8", className: "preview-image" }) }),
2620
- images.length > 1 && /* @__PURE__ */ jsx23(
3255
+ /* @__PURE__ */ jsx22("div", { className: "preview-main", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx22("img", { src: currentSrc, alt: "\u9884\u89C8", className: "preview-image" }) }),
3256
+ images.length > 1 && /* @__PURE__ */ jsx22(
2621
3257
  "button",
2622
3258
  {
2623
3259
  className: `preview-nav-btn preview-nav-next${currentIndex >= images.length - 1 ? " disabled" : ""}`,
@@ -2626,7 +3262,7 @@ function ImagePreviewModal({
2626
3262
  e.stopPropagation();
2627
3263
  next();
2628
3264
  },
2629
- children: /* @__PURE__ */ jsx23(Icon16, { icon: "lucide:chevron-right", width: 20 })
3265
+ children: /* @__PURE__ */ jsx22(Icon15, { icon: "lucide:chevron-right", width: 20 })
2630
3266
  }
2631
3267
  )
2632
3268
  ] }),
@@ -2635,9 +3271,15 @@ function ImagePreviewModal({
2635
3271
  }
2636
3272
 
2637
3273
  // src/hooks/useImageUpload.ts
2638
- import { useState as useState14, useCallback as useCallback9, useMemo as useMemo8 } from "react";
3274
+ import { useState as useState14, useCallback as useCallback10, useMemo as useMemo8 } from "react";
2639
3275
  function useImageUpload(options = {}) {
2640
- const { maxImages = 5, maxSize = 10 * 1024 * 1024 } = options;
3276
+ const {
3277
+ maxImages = 5,
3278
+ maxSize = 10 * 1024 * 1024,
3279
+ maxWidth = 4e3,
3280
+ maxHeight = 4e3,
3281
+ quality = 0.85
3282
+ } = options;
2641
3283
  const [images, setImages] = useState14([]);
2642
3284
  const [isDragOver, setIsDragOver] = useState14(false);
2643
3285
  const [previewVisible, setPreviewVisible] = useState14(false);
@@ -2651,23 +3293,52 @@ function useImageUpload(options = {}) {
2651
3293
  [images]
2652
3294
  );
2653
3295
  const hasImages = images.length > 0;
2654
- const readImageFile = useCallback9((file) => {
3296
+ const compressImage = useCallback10((file) => {
2655
3297
  return new Promise((resolve, reject) => {
2656
3298
  const reader = new FileReader();
2657
3299
  reader.onload = () => {
2658
3300
  const dataUrl = reader.result;
2659
- const base64 = dataUrl.split(",")[1];
2660
- resolve({
2661
- dataUrl,
2662
- base64,
2663
- mimeType: file.type
2664
- });
3301
+ const img = new Image();
3302
+ img.onload = () => {
3303
+ let { width, height } = img;
3304
+ if (width > maxWidth || height > maxHeight) {
3305
+ const ratio = Math.min(maxWidth / width, maxHeight / height);
3306
+ width = Math.round(width * ratio);
3307
+ height = Math.round(height * ratio);
3308
+ }
3309
+ const canvas = document.createElement("canvas");
3310
+ canvas.width = width;
3311
+ canvas.height = height;
3312
+ const ctx = canvas.getContext("2d");
3313
+ if (!ctx) {
3314
+ reject(new Error("\u65E0\u6CD5\u521B\u5EFA canvas context"));
3315
+ return;
3316
+ }
3317
+ ctx.drawImage(img, 0, 0, width, height);
3318
+ const mimeType = file.type === "image/png" ? "image/png" : "image/jpeg";
3319
+ const compressedDataUrl = canvas.toDataURL(mimeType, quality);
3320
+ const base64 = compressedDataUrl.split(",")[1];
3321
+ resolve({
3322
+ dataUrl: compressedDataUrl,
3323
+ base64,
3324
+ mimeType
3325
+ });
3326
+ };
3327
+ img.onerror = () => {
3328
+ reject(new Error("\u56FE\u7247\u52A0\u8F7D\u5931\u8D25"));
3329
+ };
3330
+ img.src = dataUrl;
3331
+ };
3332
+ reader.onerror = () => {
3333
+ reject(new Error("\u8BFB\u53D6\u6587\u4EF6\u5931\u8D25"));
2665
3334
  };
2666
- reader.onerror = reject;
2667
3335
  reader.readAsDataURL(file);
2668
3336
  });
2669
- }, []);
2670
- const processFiles = useCallback9(
3337
+ }, [maxWidth, maxHeight, quality]);
3338
+ const readImageFile = useCallback10((file) => {
3339
+ return compressImage(file);
3340
+ }, [compressImage]);
3341
+ const processFiles = useCallback10(
2671
3342
  async (files) => {
2672
3343
  const remaining = maxImages - images.length;
2673
3344
  const toProcess = files.slice(0, remaining);
@@ -2690,7 +3361,7 @@ function useImageUpload(options = {}) {
2690
3361
  },
2691
3362
  [images.length, maxImages, maxSize, readImageFile]
2692
3363
  );
2693
- const handleImageSelect = useCallback9(
3364
+ const handleImageSelect = useCallback10(
2694
3365
  (event) => {
2695
3366
  const files = event.target.files;
2696
3367
  if (files) {
@@ -2700,7 +3371,7 @@ function useImageUpload(options = {}) {
2700
3371
  },
2701
3372
  [processFiles]
2702
3373
  );
2703
- const handlePaste = useCallback9(
3374
+ const handlePaste = useCallback10(
2704
3375
  (event) => {
2705
3376
  const items = event.clipboardData?.items;
2706
3377
  if (!items) return;
@@ -2718,16 +3389,16 @@ function useImageUpload(options = {}) {
2718
3389
  },
2719
3390
  [processFiles]
2720
3391
  );
2721
- const handleDragOver = useCallback9((event) => {
3392
+ const handleDragOver = useCallback10((event) => {
2722
3393
  event.preventDefault();
2723
3394
  if (event.dataTransfer?.types.includes("Files")) {
2724
3395
  setIsDragOver(true);
2725
3396
  }
2726
3397
  }, []);
2727
- const handleDragLeave = useCallback9(() => {
3398
+ const handleDragLeave = useCallback10(() => {
2728
3399
  setIsDragOver(false);
2729
3400
  }, []);
2730
- const handleDrop = useCallback9(
3401
+ const handleDrop = useCallback10(
2731
3402
  (event) => {
2732
3403
  event.preventDefault();
2733
3404
  setIsDragOver(false);
@@ -2739,25 +3410,47 @@ function useImageUpload(options = {}) {
2739
3410
  },
2740
3411
  [processFiles]
2741
3412
  );
2742
- const openPreview = useCallback9((index) => {
3413
+ const openPreview = useCallback10((index) => {
2743
3414
  setPreviewIndex(index);
2744
3415
  setPreviewVisible(true);
2745
3416
  }, []);
2746
- const closePreview = useCallback9(() => {
3417
+ const closePreview = useCallback10(() => {
2747
3418
  setPreviewVisible(false);
2748
3419
  }, []);
2749
- const removeImage = useCallback9((index) => {
3420
+ const removeImage = useCallback10((index) => {
2750
3421
  setImages((prev) => prev.filter((_, i) => i !== index));
2751
3422
  }, []);
2752
- const clearImages = useCallback9(() => {
3423
+ const clearImages = useCallback10(() => {
2753
3424
  setImages([]);
2754
3425
  }, []);
2755
- const addImages = useCallback9(
3426
+ const addImages = useCallback10(
2756
3427
  (files) => {
2757
3428
  processFiles(files);
2758
3429
  },
2759
3430
  [processFiles]
2760
3431
  );
3432
+ const initImages = useCallback10((data) => {
3433
+ const items = [];
3434
+ for (const item of data) {
3435
+ if (item.startsWith("data:")) {
3436
+ const matches = item.match(/^data:([^;]+);base64,(.+)$/);
3437
+ if (matches) {
3438
+ items.push({
3439
+ dataUrl: item,
3440
+ base64: matches[2],
3441
+ mimeType: matches[1]
3442
+ });
3443
+ }
3444
+ } else {
3445
+ items.push({
3446
+ dataUrl: item,
3447
+ base64: "",
3448
+ mimeType: "image/unknown"
3449
+ });
3450
+ }
3451
+ }
3452
+ setImages(items);
3453
+ }, []);
2761
3454
  return {
2762
3455
  // 状态
2763
3456
  images,
@@ -2781,15 +3474,16 @@ function useImageUpload(options = {}) {
2781
3474
  // 图片操作
2782
3475
  removeImage,
2783
3476
  clearImages,
2784
- addImages
3477
+ addImages,
3478
+ initImages
2785
3479
  };
2786
3480
  }
2787
3481
 
2788
3482
  // src/hooks/useVoiceToTextInput.ts
2789
- import { useCallback as useCallback11, useEffect as useEffect11, useMemo as useMemo10, useRef as useRef8 } from "react";
3483
+ import { useCallback as useCallback12, useEffect as useEffect12, useMemo as useMemo10, useRef as useRef9 } from "react";
2790
3484
 
2791
3485
  // src/hooks/useVoiceInput.ts
2792
- import { useCallback as useCallback10, useEffect as useEffect10, useMemo as useMemo9, useRef as useRef7, useState as useState15 } from "react";
3486
+ import { useCallback as useCallback11, useEffect as useEffect11, useMemo as useMemo9, useRef as useRef8, useState as useState15 } from "react";
2793
3487
  function float32ToInt16(float32Array) {
2794
3488
  const int16Array = new Int16Array(float32Array.length);
2795
3489
  for (let i = 0; i < float32Array.length; i++) {
@@ -2812,6 +3506,7 @@ function resample(audioData, fromSampleRate, toSampleRate) {
2812
3506
  }
2813
3507
  return result;
2814
3508
  }
3509
+ var asrWarmupDone = false;
2815
3510
  async function setupAudioWorkletCapture(opts) {
2816
3511
  const { audioContext, source, chunkSize, onChunk } = opts;
2817
3512
  const workletCode = `
@@ -2894,35 +3589,45 @@ registerProcessor('pcm-capture', PcmCaptureProcessor);
2894
3589
  }
2895
3590
  function useVoiceInput(adapter, config = {}) {
2896
3591
  const { sampleRate = 16e3, sendInterval = 200, enablePunc = true, enableItn = true } = config;
3592
+ useEffect11(() => {
3593
+ if (adapter && !asrWarmupDone && typeof adapter.asrWarmup === "function") {
3594
+ asrWarmupDone = true;
3595
+ const timer = setTimeout(() => {
3596
+ adapter.asrWarmup?.().catch(() => {
3597
+ });
3598
+ }, 800);
3599
+ return () => clearTimeout(timer);
3600
+ }
3601
+ }, [adapter]);
2897
3602
  const [status, setStatus] = useState15("idle");
2898
3603
  const [currentText, setCurrentText] = useState15("");
2899
3604
  const [finalText, setFinalText] = useState15("");
2900
3605
  const [error, setError] = useState15(null);
2901
- const statusRef = useRef7("idle");
2902
- const currentTextRef = useRef7("");
2903
- const finalTextRef = useRef7("");
2904
- const mediaStreamRef = useRef7(null);
2905
- const audioContextRef = useRef7(null);
2906
- const workletCleanupRef = useRef7(null);
2907
- const sourceRef = useRef7(null);
2908
- const audioBufferRef = useRef7([]);
2909
- const sendTimerRef = useRef7(null);
2910
- const cleanupFnsRef = useRef7([]);
3606
+ const statusRef = useRef8("idle");
3607
+ const currentTextRef = useRef8("");
3608
+ const finalTextRef = useRef8("");
3609
+ const mediaStreamRef = useRef8(null);
3610
+ const audioContextRef = useRef8(null);
3611
+ const workletCleanupRef = useRef8(null);
3612
+ const sourceRef = useRef8(null);
3613
+ const audioBufferRef = useRef8([]);
3614
+ const sendTimerRef = useRef8(null);
3615
+ const cleanupFnsRef = useRef8([]);
2911
3616
  const isRecording = useMemo9(() => status === "connecting" || status === "recording", [status]);
2912
- useEffect10(() => {
3617
+ useEffect11(() => {
2913
3618
  statusRef.current = status;
2914
3619
  }, [status]);
2915
- useEffect10(() => {
3620
+ useEffect11(() => {
2916
3621
  currentTextRef.current = currentText;
2917
3622
  }, [currentText]);
2918
- useEffect10(() => {
3623
+ useEffect11(() => {
2919
3624
  finalTextRef.current = finalText;
2920
3625
  }, [finalText]);
2921
- const setStatusSafe = useCallback10((next) => {
3626
+ const setStatusSafe = useCallback11((next) => {
2922
3627
  statusRef.current = next;
2923
3628
  setStatus(next);
2924
3629
  }, []);
2925
- const cleanup = useCallback10(() => {
3630
+ const cleanup = useCallback11(() => {
2926
3631
  if (sendTimerRef.current) {
2927
3632
  clearInterval(sendTimerRef.current);
2928
3633
  sendTimerRef.current = null;
@@ -2951,7 +3656,7 @@ function useVoiceInput(adapter, config = {}) {
2951
3656
  cleanupFnsRef.current = [];
2952
3657
  audioBufferRef.current = [];
2953
3658
  }, []);
2954
- const sendAudioChunk = useCallback10(async () => {
3659
+ const sendAudioChunk = useCallback11(async () => {
2955
3660
  if (!adapter?.asrSendAudio) return;
2956
3661
  const buf = audioBufferRef.current;
2957
3662
  if (!buf.length) return;
@@ -2969,7 +3674,7 @@ function useVoiceInput(adapter, config = {}) {
2969
3674
  console.error("[VoiceInput] \u53D1\u9001\u97F3\u9891\u5931\u8D25:", result.error);
2970
3675
  }
2971
3676
  }, [adapter]);
2972
- const start = useCallback10(async () => {
3677
+ const start = useCallback11(async () => {
2973
3678
  if (statusRef.current === "connecting" || statusRef.current === "recording") return;
2974
3679
  if (!adapter) {
2975
3680
  setError("Adapter \u672A\u521D\u59CB\u5316");
@@ -3060,7 +3765,7 @@ function useVoiceInput(adapter, config = {}) {
3060
3765
  cleanup();
3061
3766
  }
3062
3767
  }, [adapter, cleanup, enableItn, enablePunc, sampleRate, sendAudioChunk, sendInterval, setStatusSafe]);
3063
- const stop = useCallback10(async () => {
3768
+ const stop = useCallback11(async () => {
3064
3769
  if (statusRef.current === "connecting") {
3065
3770
  if (adapter?.asrStop) {
3066
3771
  adapter.asrStop().catch(() => {
@@ -3103,7 +3808,7 @@ function useVoiceInput(adapter, config = {}) {
3103
3808
  }
3104
3809
  return finalTextRef.current || currentTextRef.current;
3105
3810
  }, [adapter, cleanup, sendAudioChunk, setStatusSafe]);
3106
- const cancel = useCallback10(() => {
3811
+ const cancel = useCallback11(() => {
3107
3812
  if (adapter?.asrStop) {
3108
3813
  adapter.asrStop().catch(() => {
3109
3814
  });
@@ -3114,7 +3819,7 @@ function useVoiceInput(adapter, config = {}) {
3114
3819
  setError(null);
3115
3820
  setStatusSafe("idle");
3116
3821
  }, [adapter, cleanup, setStatusSafe]);
3117
- useEffect10(() => {
3822
+ useEffect11(() => {
3118
3823
  return () => cancel();
3119
3824
  }, [cancel]);
3120
3825
  return {
@@ -3133,19 +3838,19 @@ function useVoiceInput(adapter, config = {}) {
3133
3838
  function useVoiceToTextInput(opts) {
3134
3839
  const { adapter, inputText, setInputText, hasImages, isLoading } = opts;
3135
3840
  const voiceInput = useVoiceInput(adapter);
3136
- const prefixRef = useRef8("");
3841
+ const prefixRef = useRef9("");
3137
3842
  const isVoiceActive = useMemo10(
3138
3843
  () => voiceInput.status === "connecting" || voiceInput.status === "recording",
3139
3844
  [voiceInput.status]
3140
3845
  );
3141
- useEffect11(() => {
3846
+ useEffect12(() => {
3142
3847
  if (!isVoiceActive) return;
3143
3848
  const prefix = prefixRef.current;
3144
3849
  const t = voiceInput.currentText;
3145
3850
  const next = prefix ? t ? `${prefix} ${t}` : prefix : t;
3146
3851
  setInputText(next);
3147
3852
  }, [isVoiceActive, setInputText, voiceInput.currentText]);
3148
- const toggleVoice = useCallback11(async () => {
3853
+ const toggleVoice = useCallback12(async () => {
3149
3854
  if (isLoading) return;
3150
3855
  if (!adapter) return;
3151
3856
  if (voiceInput.status === "connecting") {
@@ -3167,7 +3872,7 @@ function useVoiceToTextInput(opts) {
3167
3872
  if (isVoiceActive) return true;
3168
3873
  return !inputText.trim() && !hasImages;
3169
3874
  }, [hasImages, inputText, isLoading, isVoiceActive]);
3170
- const handleKeyDownForVoice = useCallback11(
3875
+ const handleKeyDownForVoice = useCallback12(
3171
3876
  (e) => {
3172
3877
  if (!isVoiceActive) return false;
3173
3878
  if (e.key === "Enter" && !e.shiftKey) {
@@ -3190,7 +3895,7 @@ function useVoiceToTextInput(opts) {
3190
3895
  }
3191
3896
 
3192
3897
  // src/components/input/ChatInput.tsx
3193
- import { jsx as jsx24, jsxs as jsxs17 } from "react/jsx-runtime";
3898
+ import { jsx as jsx23, jsxs as jsxs17 } from "react/jsx-runtime";
3194
3899
  var MODE_OPTIONS = [
3195
3900
  { value: "agent", label: "Agent", icon: "lucide:zap" },
3196
3901
  { value: "ask", label: "Ask", icon: "lucide:message-circle" }
@@ -3199,6 +3904,7 @@ var ChatInput = forwardRef7(
3199
3904
  ({
3200
3905
  variant = "input",
3201
3906
  value = "",
3907
+ images = [],
3202
3908
  isLoading = false,
3203
3909
  mode = "agent",
3204
3910
  model = "",
@@ -3218,41 +3924,36 @@ var ChatInput = forwardRef7(
3218
3924
  const [inputText, setInputText] = useState16(value);
3219
3925
  const [isFocused, setIsFocused] = useState16(false);
3220
3926
  const imageUpload = useImageUpload();
3221
- const imageInputRef = useRef9(null);
3222
- const inputRef = useRef9(null);
3223
- const containerRef = useRef9(null);
3224
- const atSelectorRef = useRef9(null);
3927
+ const imageInputRef = useRef10(null);
3928
+ const inputRef = useRef10(null);
3929
+ const containerRef = useRef10(null);
3930
+ const atSelectorRef = useRef10(null);
3225
3931
  const [atPickerVisible, setAtPickerVisible] = useState16(false);
3226
- const replaceRangeRef = useRef9(null);
3227
- const pendingCaretRef = useRef9(null);
3228
- const pendingFocusRef = useRef9(false);
3229
- const prevValueRef = useRef9(value);
3230
- const triggerImageUpload = useCallback12(() => {
3932
+ const replaceRangeRef = useRef10(null);
3933
+ const pendingCaretRef = useRef10(null);
3934
+ const pendingFocusRef = useRef10(false);
3935
+ const prevValueRef = useRef10(value);
3936
+ const triggerImageUpload = useCallback13(() => {
3231
3937
  imageInputRef.current?.click();
3232
3938
  }, []);
3233
- const groupedModelOptions = useMemo11(() => {
3234
- const groups = {};
3235
- models.forEach((m) => {
3236
- const groupName = m.group;
3237
- if (!groups[groupName]) {
3238
- groups[groupName] = [];
3239
- }
3240
- groups[groupName].push({
3241
- value: m.modelId,
3242
- label: m.displayName
3243
- });
3244
- });
3245
- Object.keys(groups).forEach((groupName) => {
3246
- groups[groupName].sort((a, b) => a.label.localeCompare(b.label));
3247
- });
3248
- return groups;
3249
- }, [models]);
3250
- const closeAtPicker = useCallback12(() => {
3939
+ const modelOptions = useMemo11(
3940
+ () => models.map((m) => ({
3941
+ value: m.modelId,
3942
+ label: m.displayName,
3943
+ tooltip: m.tooltip
3944
+ })),
3945
+ [models]
3946
+ );
3947
+ const currentModelSupportsThinking = useMemo11(() => {
3948
+ const currentModel = models.find((m) => m.modelId === model);
3949
+ return currentModel?.supportsThinking ?? false;
3950
+ }, [models, model]);
3951
+ const closeAtPicker = useCallback13(() => {
3251
3952
  setAtPickerVisible(false);
3252
3953
  replaceRangeRef.current = null;
3253
3954
  pendingFocusRef.current = true;
3254
3955
  }, []);
3255
- const applyAtPath = useCallback12(
3956
+ const applyAtPath = useCallback13(
3256
3957
  (path) => {
3257
3958
  const el = inputRef.current;
3258
3959
  const current = el?.value ?? inputText;
@@ -3280,16 +3981,24 @@ var ChatInput = forwardRef7(
3280
3981
  },
3281
3982
  [closeAtPicker, inputText]
3282
3983
  );
3283
- const toggleAtPicker = useCallback12(() => {
3984
+ const toggleAtPicker = useCallback13(() => {
3284
3985
  if (!adapter) return;
3285
3986
  if (!atPickerVisible) {
3286
3987
  replaceRangeRef.current = null;
3287
3988
  }
3288
3989
  setAtPickerVisible((prev) => !prev);
3289
3990
  }, [adapter, atPickerVisible]);
3290
- useEffect12(() => {
3991
+ useEffect13(() => {
3291
3992
  setInputText(value);
3292
3993
  }, [value]);
3994
+ const imagesKey = JSON.stringify(images);
3995
+ useEffect13(() => {
3996
+ if (images?.length) {
3997
+ imageUpload.initImages(images);
3998
+ } else {
3999
+ imageUpload.clearImages();
4000
+ }
4001
+ }, [imagesKey]);
3293
4002
  const showToolbar = !isMessageVariant || isFocused;
3294
4003
  const placeholder = mode === "ask" ? "\u6709\u4EC0\u4E48\u95EE\u9898\u60F3\u95EE\u6211\uFF1F" : "\u63CF\u8FF0\u4EFB\u52A1\uFF0C@ \u6DFB\u52A0\u4E0A\u4E0B\u6587";
3295
4004
  const hasContent = useMemo11(() => {
@@ -3303,21 +4012,21 @@ var ChatInput = forwardRef7(
3303
4012
  isLoading
3304
4013
  });
3305
4014
  const voiceInput = voiceCtl.voiceInput;
3306
- const toggleVoiceInput = useCallback12(async () => {
4015
+ const toggleVoiceInput = useCallback13(async () => {
3307
4016
  await voiceCtl.toggleVoice();
3308
4017
  if (voiceInput.status === "recording" || voiceInput.status === "connecting") {
3309
4018
  pendingFocusRef.current = true;
3310
4019
  pendingCaretRef.current = null;
3311
4020
  }
3312
4021
  }, [voiceCtl, voiceInput.status]);
3313
- const adjustTextareaHeight = useCallback12(() => {
4022
+ const adjustTextareaHeight = useCallback13(() => {
3314
4023
  if (inputRef.current) {
3315
4024
  inputRef.current.style.height = "auto";
3316
4025
  const scrollHeight = inputRef.current.scrollHeight;
3317
4026
  inputRef.current.style.height = `${Math.min(scrollHeight, 150)}px`;
3318
4027
  }
3319
4028
  }, []);
3320
- useLayoutEffect(() => {
4029
+ useLayoutEffect2(() => {
3321
4030
  adjustTextareaHeight();
3322
4031
  const el = inputRef.current;
3323
4032
  if (!el) return;
@@ -3345,6 +4054,11 @@ var ChatInput = forwardRef7(
3345
4054
  clear: () => {
3346
4055
  setInputText("");
3347
4056
  imageUpload.clearImages();
4057
+ if (voiceInput.status !== "idle") {
4058
+ voiceInput.cancel();
4059
+ }
4060
+ setAtPickerVisible(false);
4061
+ replaceRangeRef.current = null;
3348
4062
  if (inputRef.current) {
3349
4063
  inputRef.current.style.height = "auto";
3350
4064
  }
@@ -3372,15 +4086,15 @@ var ChatInput = forwardRef7(
3372
4086
  }),
3373
4087
  [inputText, imageUpload]
3374
4088
  );
3375
- const handleSendOrCancel = useCallback12(() => {
4089
+ const handleSendOrCancel = useCallback13(() => {
3376
4090
  if (isLoading) {
3377
4091
  onCancel?.();
3378
4092
  return;
3379
4093
  }
3380
4094
  const text = inputText.trim();
3381
4095
  if (!text && !imageUpload.hasImages) return;
3382
- const images = imageUpload.imageData.length > 0 ? imageUpload.imageData : void 0;
3383
- onSend?.(text, images);
4096
+ const images2 = imageUpload.imageData.length > 0 ? imageUpload.imageData : void 0;
4097
+ onSend?.(text, images2);
3384
4098
  if (!isMessageVariant) {
3385
4099
  setInputText("");
3386
4100
  imageUpload.clearImages();
@@ -3393,7 +4107,7 @@ var ChatInput = forwardRef7(
3393
4107
  setIsFocused(false);
3394
4108
  }
3395
4109
  }, [isLoading, inputText, imageUpload, isMessageVariant, onCancel, onSend]);
3396
- const handleKeyDown = useCallback12(
4110
+ const handleKeyDown = useCallback13(
3397
4111
  (e) => {
3398
4112
  if (voiceCtl.handleKeyDownForVoice(e)) return;
3399
4113
  if (e.key === "Enter" && !e.shiftKey) {
@@ -3403,7 +4117,7 @@ var ChatInput = forwardRef7(
3403
4117
  },
3404
4118
  [handleSendOrCancel, voiceCtl]
3405
4119
  );
3406
- useEffect12(() => {
4120
+ useEffect13(() => {
3407
4121
  const handleClickOutside = (event) => {
3408
4122
  const target = event.target;
3409
4123
  if (!target.closest(".at-picker-wrapper")) {
@@ -3416,7 +4130,7 @@ var ChatInput = forwardRef7(
3416
4130
  document.addEventListener("click", handleClickOutside);
3417
4131
  return () => document.removeEventListener("click", handleClickOutside);
3418
4132
  }, [isMessageVariant]);
3419
- return /* @__PURE__ */ jsx24(
4133
+ return /* @__PURE__ */ jsx23(
3420
4134
  "div",
3421
4135
  {
3422
4136
  className: `chat-input${isMessageVariant ? " message-variant" : ""}`.trim(),
@@ -3429,8 +4143,8 @@ var ChatInput = forwardRef7(
3429
4143
  ref: containerRef,
3430
4144
  className: `input-container${isFocused ? " focused" : ""}${imageUpload.isDragOver ? " drag-over" : ""}${voiceInput.status === "connecting" ? " connecting" : ""}${voiceInput.isRecording ? " recording" : ""}`.trim(),
3431
4145
  children: [
3432
- imageUpload.hasImages && /* @__PURE__ */ jsx24("div", { className: "images-preview", children: imageUpload.images.map((img, i) => /* @__PURE__ */ jsxs17("div", { className: "image-preview-item", children: [
3433
- /* @__PURE__ */ jsx24(
4146
+ imageUpload.hasImages && /* @__PURE__ */ jsx23("div", { className: "images-preview", children: imageUpload.images.map((img, i) => /* @__PURE__ */ jsxs17("div", { className: "image-preview-item", children: [
4147
+ /* @__PURE__ */ jsx23(
3434
4148
  "img",
3435
4149
  {
3436
4150
  src: img.dataUrl,
@@ -3439,7 +4153,7 @@ var ChatInput = forwardRef7(
3439
4153
  onClick: () => imageUpload.openPreview(i)
3440
4154
  }
3441
4155
  ),
3442
- /* @__PURE__ */ jsx24(
4156
+ /* @__PURE__ */ jsx23(
3443
4157
  "button",
3444
4158
  {
3445
4159
  className: "image-remove-btn",
@@ -3448,11 +4162,11 @@ var ChatInput = forwardRef7(
3448
4162
  e.stopPropagation();
3449
4163
  imageUpload.removeImage(i);
3450
4164
  },
3451
- children: /* @__PURE__ */ jsx24(Icon17, { icon: "lucide:x", width: 10 })
4165
+ children: /* @__PURE__ */ jsx23(Icon16, { icon: "lucide:x", width: 10 })
3452
4166
  }
3453
4167
  )
3454
4168
  ] }, i)) }),
3455
- /* @__PURE__ */ jsx24(
4169
+ /* @__PURE__ */ jsx23(
3456
4170
  ImagePreviewModal,
3457
4171
  {
3458
4172
  visible: imageUpload.previewVisible,
@@ -3462,7 +4176,7 @@ var ChatInput = forwardRef7(
3462
4176
  onIndexChange: imageUpload.setPreviewIndex
3463
4177
  }
3464
4178
  ),
3465
- /* @__PURE__ */ jsx24("div", { className: "input-field-wrapper", children: /* @__PURE__ */ jsx24(
4179
+ /* @__PURE__ */ jsx23("div", { className: "input-field-wrapper", children: /* @__PURE__ */ jsx23(
3466
4180
  "textarea",
3467
4181
  {
3468
4182
  ref: inputRef,
@@ -3497,7 +4211,7 @@ var ChatInput = forwardRef7(
3497
4211
  ) }),
3498
4212
  showToolbar && /* @__PURE__ */ jsxs17("div", { className: "input-controls", children: [
3499
4213
  /* @__PURE__ */ jsxs17("div", { className: "input-left", children: [
3500
- /* @__PURE__ */ jsx24(
4214
+ /* @__PURE__ */ jsx23(
3501
4215
  DropdownSelector,
3502
4216
  {
3503
4217
  value: mode,
@@ -3505,36 +4219,36 @@ var ChatInput = forwardRef7(
3505
4219
  onSelect: (v) => onModeChange?.(v)
3506
4220
  }
3507
4221
  ),
3508
- /* @__PURE__ */ jsx24(
4222
+ /* @__PURE__ */ jsx23(
3509
4223
  DropdownSelector,
3510
4224
  {
3511
4225
  value: model,
3512
- groupedOptions: groupedModelOptions,
4226
+ options: modelOptions,
3513
4227
  onSelect: (v) => onModelChange?.(v)
3514
4228
  }
3515
4229
  )
3516
4230
  ] }),
3517
4231
  /* @__PURE__ */ jsxs17("div", { className: "input-right", children: [
3518
- /* @__PURE__ */ jsx24(
4232
+ mode !== "ask" && currentModelSupportsThinking && /* @__PURE__ */ jsx23(
3519
4233
  "button",
3520
4234
  {
3521
4235
  className: `toggle-btn${thinkingEnabled ? " active" : ""}`,
3522
4236
  title: "\u6DF1\u5EA6\u601D\u8003",
3523
4237
  onClick: () => onThinkingChange?.(!thinkingEnabled),
3524
- children: /* @__PURE__ */ jsx24(Icon17, { icon: "lucide:sparkles", width: 18 })
4238
+ children: /* @__PURE__ */ jsx23(Icon16, { icon: "lucide:sparkles", width: 18 })
3525
4239
  }
3526
4240
  ),
3527
- /* @__PURE__ */ jsx24(
4241
+ mode !== "ask" && /* @__PURE__ */ jsx23(
3528
4242
  "button",
3529
4243
  {
3530
4244
  className: `toggle-btn${webSearchEnabled ? " active" : ""}`,
3531
4245
  title: "\u8054\u7F51\u641C\u7D22",
3532
4246
  onClick: () => onWebSearchChange?.(!webSearchEnabled),
3533
- children: /* @__PURE__ */ jsx24(Icon17, { icon: "lucide:globe", width: 18 })
4247
+ children: /* @__PURE__ */ jsx23(Icon16, { icon: "lucide:globe", width: 18 })
3534
4248
  }
3535
4249
  ),
3536
- /* @__PURE__ */ jsx24("button", { className: "icon-btn", title: "\u6DFB\u52A0\u56FE\u7247", onClick: triggerImageUpload, children: /* @__PURE__ */ jsx24(Icon17, { icon: "lucide:image", width: 18 }) }),
3537
- /* @__PURE__ */ jsx24(
4250
+ /* @__PURE__ */ jsx23("button", { className: "icon-btn", title: "\u6DFB\u52A0\u56FE\u7247", onClick: triggerImageUpload, children: /* @__PURE__ */ jsx23(Icon16, { icon: "lucide:image", width: 18 }) }),
4251
+ /* @__PURE__ */ jsx23(
3538
4252
  "input",
3539
4253
  {
3540
4254
  ref: imageInputRef,
@@ -3555,8 +4269,8 @@ var ChatInput = forwardRef7(
3555
4269
  toggleAtPicker();
3556
4270
  },
3557
4271
  children: [
3558
- /* @__PURE__ */ jsx24("button", { className: "icon-btn", title: "\u63D0\u53CA\u4E0A\u4E0B\u6587 (@)", children: /* @__PURE__ */ jsx24(Icon17, { icon: "lucide:at-sign", width: 18 }) }),
3559
- adapter && /* @__PURE__ */ jsx24(
4272
+ /* @__PURE__ */ jsx23("button", { className: "icon-btn", title: "\u63D0\u53CA\u4E0A\u4E0B\u6587 (@)", children: /* @__PURE__ */ jsx23(Icon16, { icon: "lucide:at-sign", width: 18 }) }),
4273
+ adapter && /* @__PURE__ */ jsx23(
3560
4274
  AtFilePicker,
3561
4275
  {
3562
4276
  visible: atPickerVisible,
@@ -3569,7 +4283,7 @@ var ChatInput = forwardRef7(
3569
4283
  ]
3570
4284
  }
3571
4285
  ),
3572
- /* @__PURE__ */ jsx24(
4286
+ /* @__PURE__ */ jsx23(
3573
4287
  "button",
3574
4288
  {
3575
4289
  className: `voice-btn${voiceInput.status === "connecting" ? " connecting" : ""}${voiceInput.isRecording ? " recording" : ""}`,
@@ -3577,17 +4291,17 @@ var ChatInput = forwardRef7(
3577
4291
  onClick: () => toggleVoiceInput().catch(() => {
3578
4292
  }),
3579
4293
  disabled: isLoading || !adapter,
3580
- children: voiceInput.status === "connecting" ? /* @__PURE__ */ jsx24(Icon17, { icon: "lucide:loader-2", width: 18, className: "spin" }) : voiceInput.isRecording ? /* @__PURE__ */ jsx24(Icon17, { icon: "streamline-flex:button-pause-circle-remix", width: 18 }) : /* @__PURE__ */ jsx24(Icon17, { icon: "lucide:mic", width: 18 })
4294
+ children: voiceInput.status === "connecting" ? /* @__PURE__ */ jsx23(Icon16, { icon: "lucide:loader-2", width: 18, className: "spin" }) : voiceInput.isRecording ? /* @__PURE__ */ jsx23(Icon16, { icon: "streamline-flex:button-pause-circle-remix", width: 18 }) : /* @__PURE__ */ jsx23(Icon16, { icon: "lucide:mic", width: 18 })
3581
4295
  }
3582
4296
  ),
3583
- /* @__PURE__ */ jsx24(
4297
+ /* @__PURE__ */ jsx23(
3584
4298
  "button",
3585
4299
  {
3586
4300
  className: `send-btn${isLoading ? " loading" : ""}`,
3587
4301
  title: isLoading ? "\u505C\u6B62" : voiceInput.status === "connecting" ? "\u8BED\u97F3\u8FDE\u63A5\u4E2D\uFF0C\u5148\u53D6\u6D88/\u505C\u6B62" : voiceInput.isRecording ? "\u5F55\u97F3\u4E2D\uFF0C\u5148\u505C\u6B62" : isMessageVariant ? "\u91CD\u65B0\u53D1\u9001" : "\u53D1\u9001",
3588
4302
  onClick: handleSendOrCancel,
3589
4303
  disabled: !hasContent && !isLoading || voiceInput.isRecording || voiceInput.status === "connecting",
3590
- children: isLoading ? /* @__PURE__ */ jsx24(Icon17, { icon: "streamline-flex:button-pause-circle-remix", width: 18 }) : /* @__PURE__ */ jsx24(Icon17, { icon: "streamline-flex:mail-send-email-message-circle-remix", width: 18 })
4304
+ children: isLoading ? /* @__PURE__ */ jsx23(Icon16, { icon: "streamline-flex:button-pause-circle-remix", width: 18 }) : /* @__PURE__ */ jsx23(Icon16, { icon: "uil:message", width: 18 })
3591
4305
  }
3592
4306
  )
3593
4307
  ] })
@@ -3602,7 +4316,7 @@ var ChatInput = forwardRef7(
3602
4316
  ChatInput.displayName = "ChatInput";
3603
4317
 
3604
4318
  // src/components/message/MessageBubble.tsx
3605
- import { Fragment as Fragment4, jsx as jsx25, jsxs as jsxs18 } from "react/jsx-runtime";
4319
+ import { Fragment as Fragment4, jsx as jsx24, jsxs as jsxs18 } from "react/jsx-runtime";
3606
4320
  function getModelDisplayName(modelId, models) {
3607
4321
  const found = models?.find((m) => m.modelId === modelId);
3608
4322
  if (found) return found.displayName;
@@ -3632,6 +4346,7 @@ var MessageBubble = ({
3632
4346
  onSend,
3633
4347
  stepsExpandedType = "auto",
3634
4348
  adapter,
4349
+ onCancelToolCall,
3635
4350
  autoRunConfig,
3636
4351
  onSaveConfig
3637
4352
  }) => {
@@ -3643,46 +4358,47 @@ var MessageBubble = ({
3643
4358
  }, [parts]);
3644
4359
  const hasContent = useMemo12(() => {
3645
4360
  return parts.some(
3646
- (p) => p.type === "text" && p.text || p.type === "tool_result" || p.type === "thinking" || p.type === "search"
4361
+ (p) => p.type === "text" && p.text || p.type === "thinking" || p.type === "search" || p.type === "tool_call"
3647
4362
  );
3648
4363
  }, [parts]);
3649
4364
  const loadingState = useMemo12(() => {
3650
4365
  if (!loading) {
3651
4366
  return { type: "none" };
3652
4367
  }
4368
+ const waitingText = mode === "ask" ? "\u6B63\u5728\u751F\u6210\u56DE\u7B54..." : "\u6B63\u5728\u89C4\u5212\u4E0B\u4E00\u6B65...";
3653
4369
  if (parts.length === 0) {
3654
- return { type: "text", text: "\u6B63\u5728\u601D\u8003..." };
4370
+ return { type: "text", text: waitingText };
3655
4371
  }
3656
- const lastPart = parts[parts.length - 1];
3657
- if (lastPart.type === "text") {
3658
- return { type: "none" };
3659
- }
3660
- if (lastPart.type === "tool_call") {
3661
- const status = lastPart.status;
3662
- if (status === "done" || status === "error" || status === "skipped") {
3663
- return { type: "text", text: "\u6B63\u5728\u89C4\u5212\u4E0B\u4E00\u6B65..." };
4372
+ const hasActiveActivity = parts.some((part) => {
4373
+ if (part.type === "thinking" && part.status === "running") {
4374
+ return true;
3664
4375
  }
3665
- return { type: "none" };
3666
- }
3667
- if (lastPart.type === "search") {
3668
- if (lastPart.status === "done") {
3669
- return { type: "text", text: "\u6B63\u5728\u89C4\u5212\u4E0B\u4E00\u6B65..." };
4376
+ if (part.type === "search" && part.status === "running") {
4377
+ return true;
3670
4378
  }
4379
+ if (part.type === "tool_call") {
4380
+ const status = part.status;
4381
+ if (status === "running" || status === "pending") {
4382
+ return true;
4383
+ }
4384
+ }
4385
+ return false;
4386
+ });
4387
+ if (hasActiveActivity) {
3671
4388
  return { type: "none" };
3672
4389
  }
3673
- if (lastPart.type === "thinking") {
3674
- if (lastPart.status === "done") {
3675
- return { type: "text", text: "\u6B63\u5728\u89C4\u5212\u4E0B\u4E00\u6B65..." };
3676
- }
4390
+ const lastPart = parts[parts.length - 1];
4391
+ if (lastPart.type === "text") {
3677
4392
  return { type: "none" };
3678
4393
  }
3679
- return { type: "none" };
3680
- }, [loading, parts]);
3681
- return /* @__PURE__ */ jsx25("div", { className: `message-bubble ${role}`, children: isUser ? onSend && inputContext ? /* @__PURE__ */ jsx25(
4394
+ return { type: "text", text: waitingText };
4395
+ }, [loading, parts, mode]);
4396
+ return /* @__PURE__ */ jsx24("div", { className: `message-bubble ${role}`, children: isUser ? onSend && inputContext ? /* @__PURE__ */ jsx24(
3682
4397
  ChatInput,
3683
4398
  {
3684
4399
  variant: "message",
3685
4400
  value: userText,
4401
+ images,
3686
4402
  mode: inputContext.mode,
3687
4403
  model: inputContext.model,
3688
4404
  models: inputContext.models,
@@ -3696,37 +4412,38 @@ var MessageBubble = ({
3696
4412
  onThinkingChange: inputContext.setThinking
3697
4413
  }
3698
4414
  ) : /* @__PURE__ */ jsxs18("div", { className: "user-content", children: [
3699
- /* @__PURE__ */ jsx25("div", { className: "user-text", children: userText }),
3700
- images && images.length > 0 && /* @__PURE__ */ jsx25("div", { className: "user-images", children: images.map((img, i) => /* @__PURE__ */ jsx25("img", { src: img, className: "user-image", alt: "" }, i)) }),
3701
- formattedTime && /* @__PURE__ */ jsx25("div", { className: "message-time user-time", children: formattedTime })
4415
+ /* @__PURE__ */ jsx24("div", { className: "user-text", children: userText }),
4416
+ images && images.length > 0 && /* @__PURE__ */ jsx24("div", { className: "user-images", children: images.map((img, i) => /* @__PURE__ */ jsx24("img", { src: img, className: "user-image", alt: "" }, i)) }),
4417
+ formattedTime && /* @__PURE__ */ jsx24("div", { className: "message-time user-time", children: formattedTime })
3702
4418
  ] }) : (
3703
4419
  /* 助手消息 - 使用 PartsRenderer 渲染 */
3704
4420
  /* @__PURE__ */ jsxs18(Fragment4, { children: [
3705
4421
  /* @__PURE__ */ jsxs18("div", { className: "assistant-content", children: [
3706
- /* @__PURE__ */ jsx25(
4422
+ /* @__PURE__ */ jsx24(
3707
4423
  PartsRenderer,
3708
4424
  {
3709
4425
  parts,
3710
4426
  expandedType: stepsExpandedType,
3711
4427
  adapter,
4428
+ onCancelToolCall,
3712
4429
  autoRunConfig,
3713
4430
  onSaveConfig
3714
4431
  }
3715
4432
  ),
3716
- loadingState.type === "text" && /* @__PURE__ */ jsxs18("div", { className: "loading-indicator", children: [
3717
- /* @__PURE__ */ jsx25("span", { className: "loading-text", children: loadingState.text }),
3718
- /* @__PURE__ */ jsx25("span", { className: "loading-shimmer" })
4433
+ loadingState.type === "text" && /* @__PURE__ */ jsxs18("div", { className: `loading-indicator${parts.length > 0 ? " has-content-above" : ""}`, children: [
4434
+ /* @__PURE__ */ jsx24("span", { className: "loading-text", children: loadingState.text }),
4435
+ /* @__PURE__ */ jsx24("span", { className: "loading-shimmer" })
3719
4436
  ] })
3720
4437
  ] }),
3721
4438
  hasContent && loading === false && /* @__PURE__ */ jsxs18("div", { className: "message-actions", children: [
3722
4439
  /* @__PURE__ */ jsxs18("div", { className: "message-meta", children: [
3723
- model && /* @__PURE__ */ jsx25("span", { className: "model-name", children: getModelDisplayName(model, inputContext?.models) }),
3724
- mode && /* @__PURE__ */ jsx25("span", { className: "mode-badge", children: mode === "ask" ? "Ask" : "Agent" })
4440
+ model && /* @__PURE__ */ jsx24("span", { className: "model-name", children: getModelDisplayName(model, inputContext?.models) }),
4441
+ mode && /* @__PURE__ */ jsx24("span", { className: "mode-badge", children: mode === "ask" ? "Ask" : "Agent" })
3725
4442
  ] }),
3726
4443
  /* @__PURE__ */ jsxs18("div", { className: "action-buttons", children: [
3727
- formattedTime && /* @__PURE__ */ jsx25("span", { className: "message-time assistant-time", children: formattedTime }),
3728
- /* @__PURE__ */ jsx25("button", { className: `action-btn${copied ? " copied" : ""}`, onClick: onCopy, title: "\u590D\u5236", children: /* @__PURE__ */ jsx25(Icon18, { icon: copied ? "lucide:check" : "lucide:copy", width: 14 }) }),
3729
- /* @__PURE__ */ jsx25("button", { className: "action-btn", onClick: onRegenerate, title: "\u91CD\u65B0\u751F\u6210", children: /* @__PURE__ */ jsx25(Icon18, { icon: "lucide:refresh-cw", width: 14 }) })
4444
+ formattedTime && /* @__PURE__ */ jsx24("span", { className: "message-time assistant-time", children: formattedTime }),
4445
+ /* @__PURE__ */ jsx24("button", { className: `action-btn${copied ? " copied" : ""}`, onClick: onCopy, title: "\u590D\u5236", children: /* @__PURE__ */ jsx24(Icon17, { icon: copied ? "lucide:check" : "lucide:copy", width: 14 }) }),
4446
+ /* @__PURE__ */ jsx24("button", { className: "action-btn", onClick: onRegenerate, title: "\u91CD\u65B0\u751F\u6210", children: /* @__PURE__ */ jsx24(Icon17, { icon: "lucide:refresh-cw", width: 14 }) })
3730
4447
  ] })
3731
4448
  ] })
3732
4449
  ] })
@@ -3734,10 +4451,10 @@ var MessageBubble = ({
3734
4451
  };
3735
4452
 
3736
4453
  // src/components/common/ConfirmDialog.tsx
3737
- import { useEffect as useEffect13 } from "react";
4454
+ import { useEffect as useEffect14 } from "react";
3738
4455
  import { createPortal as createPortal3 } from "react-dom";
3739
- import { Icon as Icon19 } from "@iconify/react";
3740
- import { jsx as jsx26, jsxs as jsxs19 } from "react/jsx-runtime";
4456
+ import { Icon as Icon18 } from "@iconify/react";
4457
+ import { jsx as jsx25, jsxs as jsxs19 } from "react/jsx-runtime";
3741
4458
  var ConfirmDialog = ({
3742
4459
  visible,
3743
4460
  title = "\u786E\u8BA4",
@@ -3748,7 +4465,7 @@ var ConfirmDialog = ({
3748
4465
  onConfirm,
3749
4466
  onCancel
3750
4467
  }) => {
3751
- useEffect13(() => {
4468
+ useEffect14(() => {
3752
4469
  const handleKeyDown = (e) => {
3753
4470
  if (e.key === "Escape" && visible) {
3754
4471
  onCancel?.();
@@ -3757,7 +4474,7 @@ var ConfirmDialog = ({
3757
4474
  document.addEventListener("keydown", handleKeyDown);
3758
4475
  return () => document.removeEventListener("keydown", handleKeyDown);
3759
4476
  }, [visible, onCancel]);
3760
- useEffect13(() => {
4477
+ useEffect14(() => {
3761
4478
  if (visible) {
3762
4479
  document.body.style.overflow = "hidden";
3763
4480
  } else {
@@ -3774,15 +4491,15 @@ var ConfirmDialog = ({
3774
4491
  danger: "lucide:alert-circle"
3775
4492
  };
3776
4493
  return createPortal3(
3777
- /* @__PURE__ */ jsx26("div", { className: "confirm-dialog-overlay", onClick: onCancel, children: /* @__PURE__ */ jsxs19("div", { className: "confirm-dialog", onClick: (e) => e.stopPropagation(), children: [
4494
+ /* @__PURE__ */ jsx25("div", { className: "confirm-dialog-overlay", onClick: onCancel, children: /* @__PURE__ */ jsxs19("div", { className: "confirm-dialog", onClick: (e) => e.stopPropagation(), children: [
3778
4495
  /* @__PURE__ */ jsxs19("div", { className: "confirm-dialog-header", children: [
3779
- /* @__PURE__ */ jsx26(Icon19, { icon: iconMap[type], className: `icon ${type}`, width: 18 }),
3780
- /* @__PURE__ */ jsx26("span", { className: "title", children: title })
4496
+ /* @__PURE__ */ jsx25(Icon18, { icon: iconMap[type], className: `icon ${type}`, width: 18 }),
4497
+ /* @__PURE__ */ jsx25("span", { className: "title", children: title })
3781
4498
  ] }),
3782
- /* @__PURE__ */ jsx26("div", { className: "confirm-dialog-content", children: message }),
4499
+ /* @__PURE__ */ jsx25("div", { className: "confirm-dialog-content", children: message }),
3783
4500
  /* @__PURE__ */ jsxs19("div", { className: "confirm-dialog-footer", children: [
3784
- /* @__PURE__ */ jsx26("button", { className: "btn btn-cancel", onClick: onCancel, children: cancelText }),
3785
- /* @__PURE__ */ jsx26("button", { className: `btn btn-${type}`, onClick: onConfirm, children: confirmText })
4501
+ /* @__PURE__ */ jsx25("button", { className: "btn btn-cancel", onClick: onCancel, children: cancelText }),
4502
+ /* @__PURE__ */ jsx25("button", { className: `btn btn-${type}`, onClick: onConfirm, children: confirmText })
3786
4503
  ] })
3787
4504
  ] }) }),
3788
4505
  document.body
@@ -3790,9 +4507,9 @@ var ConfirmDialog = ({
3790
4507
  };
3791
4508
 
3792
4509
  // src/components/common/Toast.tsx
3793
- import { useEffect as useEffect14 } from "react";
4510
+ import { useEffect as useEffect15 } from "react";
3794
4511
  import { createPortal as createPortal4 } from "react-dom";
3795
- import { jsx as jsx27 } from "react/jsx-runtime";
4512
+ import { jsx as jsx26 } from "react/jsx-runtime";
3796
4513
  var Toast = ({
3797
4514
  visible,
3798
4515
  message,
@@ -3800,7 +4517,7 @@ var Toast = ({
3800
4517
  duration = 2e3,
3801
4518
  onClose
3802
4519
  }) => {
3803
- useEffect14(() => {
4520
+ useEffect15(() => {
3804
4521
  if (visible && duration > 0) {
3805
4522
  const timer = setTimeout(() => {
3806
4523
  onClose();
@@ -3810,19 +4527,35 @@ var Toast = ({
3810
4527
  }, [visible, duration, onClose]);
3811
4528
  if (!visible) return null;
3812
4529
  return createPortal4(
3813
- /* @__PURE__ */ jsx27("div", { className: "toast-container", children: /* @__PURE__ */ jsx27("div", { className: `toast toast-${type}`, children: message }) }),
4530
+ /* @__PURE__ */ jsx26("div", { className: "toast-container", children: /* @__PURE__ */ jsx26("div", { className: `toast toast-${type}`, children: message }) }),
3814
4531
  document.body
3815
4532
  );
3816
4533
  };
3817
4534
 
3818
4535
  // src/components/common/SettingsPanel.tsx
3819
- import { useState as useState18, useCallback as useCallback14, useMemo as useMemo13 } from "react";
3820
- import { Icon as Icon21 } from "@iconify/react";
4536
+ import { useState as useState18, useCallback as useCallback15, useMemo as useMemo13 } from "react";
4537
+ import { Icon as Icon20 } from "@iconify/react";
4538
+
4539
+ // src/components/common/ToggleSwitch.tsx
4540
+ import { jsx as jsx27, jsxs as jsxs20 } from "react/jsx-runtime";
4541
+ var ToggleSwitch = ({ checked = false, onChange }) => {
4542
+ return /* @__PURE__ */ jsxs20("label", { className: "toggle-switch", children: [
4543
+ /* @__PURE__ */ jsx27(
4544
+ "input",
4545
+ {
4546
+ type: "checkbox",
4547
+ checked,
4548
+ onChange: (e) => onChange(e.target.checked)
4549
+ }
4550
+ ),
4551
+ /* @__PURE__ */ jsx27("span", { className: "toggle-slider" })
4552
+ ] });
4553
+ };
3821
4554
 
3822
4555
  // src/components/common/IndexingSettings.tsx
3823
- import { useState as useState17, useEffect as useEffect15, useCallback as useCallback13, useRef as useRef10 } from "react";
3824
- import { Icon as Icon20 } from "@iconify/react";
3825
- import { jsx as jsx28, jsxs as jsxs20 } from "react/jsx-runtime";
4556
+ import { useState as useState17, useEffect as useEffect16, useCallback as useCallback14, useRef as useRef11 } from "react";
4557
+ import { Icon as Icon19 } from "@iconify/react";
4558
+ import { jsx as jsx28, jsxs as jsxs21 } from "react/jsx-runtime";
3826
4559
  function IndexingSettings() {
3827
4560
  const [indexedFiles, setIndexedFiles] = useState17(0);
3828
4561
  const [isIndexing, setIsIndexing] = useState17(false);
@@ -3834,14 +4567,14 @@ function IndexingSettings() {
3834
4567
  const [showDeleteConfirm, setShowDeleteConfirm] = useState17(false);
3835
4568
  const [indexSize, setIndexSize] = useState17("0 B");
3836
4569
  const [lastUpdated, setLastUpdated] = useState17(null);
3837
- const cancelIndexingRef = useRef10(false);
3838
- const formatSize = useCallback13((bytes) => {
4570
+ const cancelIndexingRef = useRef11(false);
4571
+ const formatSize = useCallback14((bytes) => {
3839
4572
  if (bytes < 1024) return `${bytes} B`;
3840
4573
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
3841
4574
  if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
3842
4575
  return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
3843
4576
  }, []);
3844
- const formatDate = useCallback13((date) => {
4577
+ const formatDate = useCallback14((date) => {
3845
4578
  const now = /* @__PURE__ */ new Date();
3846
4579
  const diff = now.getTime() - date.getTime();
3847
4580
  const days = Math.floor(diff / (1e3 * 60 * 60 * 24));
@@ -3859,7 +4592,7 @@ function IndexingSettings() {
3859
4592
  if (days < 365) return `${Math.floor(days / 30)} \u4E2A\u6708\u524D`;
3860
4593
  return date.toLocaleDateString("zh-CN", { year: "numeric", month: "short", day: "numeric" });
3861
4594
  }, []);
3862
- const fetchIndexStats = useCallback13(async () => {
4595
+ const fetchIndexStats = useCallback14(async () => {
3863
4596
  try {
3864
4597
  const bridge = window.aiChatBridge;
3865
4598
  if (bridge?.getIndexStats) {
@@ -3880,8 +4613,8 @@ function IndexingSettings() {
3880
4613
  setLastUpdated(null);
3881
4614
  }
3882
4615
  }, [formatSize, formatDate]);
3883
- const progressCleanupRef = useRef10(null);
3884
- const setupProgressListener = useCallback13(() => {
4616
+ const progressCleanupRef = useRef11(null);
4617
+ const setupProgressListener = useCallback14(() => {
3885
4618
  if (progressCleanupRef.current) {
3886
4619
  progressCleanupRef.current();
3887
4620
  progressCleanupRef.current = null;
@@ -3926,7 +4659,7 @@ function IndexingSettings() {
3926
4659
  }
3927
4660
  });
3928
4661
  }, [fetchIndexStats]);
3929
- const checkIndexStatus = useCallback13(async () => {
4662
+ const checkIndexStatus = useCallback14(async () => {
3930
4663
  const bridge = window.aiChatBridge;
3931
4664
  if (!bridge?.getIndexStatus) {
3932
4665
  return;
@@ -3952,7 +4685,7 @@ function IndexingSettings() {
3952
4685
  console.error("\u68C0\u67E5\u7D22\u5F15\u72B6\u6001\u5931\u8D25:", error);
3953
4686
  }
3954
4687
  }, []);
3955
- const handleSync = useCallback13(async () => {
4688
+ const handleSync = useCallback14(async () => {
3956
4689
  if (isIndexing) return;
3957
4690
  const bridge = window.aiChatBridge;
3958
4691
  if (!bridge?.syncIndex) {
@@ -3979,7 +4712,7 @@ function IndexingSettings() {
3979
4712
  setCurrentFile(null);
3980
4713
  }
3981
4714
  }, [isIndexing, setupProgressListener]);
3982
- const handleCancelIndex = useCallback13(async () => {
4715
+ const handleCancelIndex = useCallback14(async () => {
3983
4716
  cancelIndexingRef.current = true;
3984
4717
  setIsIndexing(false);
3985
4718
  setCurrentFile(null);
@@ -3992,7 +4725,7 @@ function IndexingSettings() {
3992
4725
  }
3993
4726
  }
3994
4727
  }, []);
3995
- const handleDeleteIndex = useCallback13(async () => {
4728
+ const handleDeleteIndex = useCallback14(async () => {
3996
4729
  if (isIndexing) return;
3997
4730
  setShowDeleteConfirm(false);
3998
4731
  const bridge = window.aiChatBridge;
@@ -4007,7 +4740,7 @@ function IndexingSettings() {
4007
4740
  console.error("\u5220\u9664\u7D22\u5F15\u5931\u8D25:", error);
4008
4741
  }
4009
4742
  }, [isIndexing, fetchIndexStats]);
4010
- useEffect15(() => {
4743
+ useEffect16(() => {
4011
4744
  const init = async () => {
4012
4745
  const bridge = window.aiChatBridge;
4013
4746
  if (bridge?.registerIndexListener) {
@@ -4023,7 +4756,7 @@ function IndexingSettings() {
4023
4756
  };
4024
4757
  init();
4025
4758
  }, [fetchIndexStats, setupProgressListener, checkIndexStatus]);
4026
- useEffect15(() => {
4759
+ useEffect16(() => {
4027
4760
  return () => {
4028
4761
  const bridge = window.aiChatBridge;
4029
4762
  if (bridge?.unregisterIndexListener) {
@@ -4039,12 +4772,12 @@ function IndexingSettings() {
4039
4772
  }
4040
4773
  };
4041
4774
  }, []);
4042
- return /* @__PURE__ */ jsxs20("div", { className: "indexing-settings", children: [
4043
- /* @__PURE__ */ jsx28("div", { className: "setting-item indexing-section", children: /* @__PURE__ */ jsxs20("div", { className: "setting-info", children: [
4775
+ return /* @__PURE__ */ jsxs21("div", { className: "indexing-settings", children: [
4776
+ /* @__PURE__ */ jsx28("div", { className: "setting-item indexing-section", children: /* @__PURE__ */ jsxs21("div", { className: "setting-info", children: [
4044
4777
  /* @__PURE__ */ jsx28("div", { className: "setting-label", children: "\u4EE3\u7801\u5E93\u7D22\u5F15" }),
4045
4778
  /* @__PURE__ */ jsx28("p", { className: "setting-description", children: "\u5D4C\u5165\u4EE3\u7801\u5E93\u4EE5\u6539\u8FDB\u4E0A\u4E0B\u6587\u7406\u89E3\u548C\u77E5\u8BC6\u3002\u6240\u6709\u6570\u636E\uFF08\u5D4C\u5165\u5411\u91CF\u3001\u5143\u6570\u636E\u548C\u4EE3\u7801\uFF09\u90FD\u5B58\u50A8\u5728\u672C\u5730\u3002" }),
4046
- /* @__PURE__ */ jsxs20("div", { className: "indexing-content", children: [
4047
- /* @__PURE__ */ jsxs20("div", { className: "progress-container", children: [
4779
+ /* @__PURE__ */ jsxs21("div", { className: "indexing-content", children: [
4780
+ /* @__PURE__ */ jsxs21("div", { className: "progress-container", children: [
4048
4781
  /* @__PURE__ */ jsx28("div", { className: "progress-bar", children: /* @__PURE__ */ jsx28(
4049
4782
  "div",
4050
4783
  {
@@ -4052,72 +4785,72 @@ function IndexingSettings() {
4052
4785
  style: { width: `${progressPercentage}%` }
4053
4786
  }
4054
4787
  ) }),
4055
- /* @__PURE__ */ jsx28("div", { className: "progress-text", children: isIndexing ? currentStage === "scanning" ? /* @__PURE__ */ jsxs20("span", { children: [
4788
+ /* @__PURE__ */ jsx28("div", { className: "progress-text", children: isIndexing ? currentStage === "scanning" ? /* @__PURE__ */ jsxs21("span", { children: [
4056
4789
  "\u6B63\u5728\u626B\u63CF\u6587\u4EF6... (\u5DF2\u626B\u63CF ",
4057
4790
  progressIndexed.toLocaleString(),
4058
4791
  " \u4E2A\u6587\u4EF6)"
4059
- ] }) : /* @__PURE__ */ jsxs20("span", { children: [
4792
+ ] }) : /* @__PURE__ */ jsxs21("span", { children: [
4060
4793
  progressPercentage,
4061
4794
  "% (",
4062
4795
  progressIndexed,
4063
4796
  "/",
4064
4797
  progressTotal,
4065
4798
  ")"
4066
- ] }) : /* @__PURE__ */ jsxs20("span", { children: [
4799
+ ] }) : /* @__PURE__ */ jsxs21("span", { children: [
4067
4800
  indexedFiles.toLocaleString(),
4068
4801
  " \u4E2A\u6587\u4EF6"
4069
4802
  ] }) })
4070
4803
  ] }),
4071
- !isIndexing && indexedFiles > 0 && /* @__PURE__ */ jsxs20("div", { className: "stats-info", children: [
4072
- /* @__PURE__ */ jsxs20("div", { className: "stat-item", children: [
4073
- /* @__PURE__ */ jsx28(Icon20, { icon: "lucide:database", width: 14 }),
4074
- /* @__PURE__ */ jsxs20("span", { children: [
4804
+ !isIndexing && indexedFiles > 0 && /* @__PURE__ */ jsxs21("div", { className: "stats-info", children: [
4805
+ /* @__PURE__ */ jsxs21("div", { className: "stat-item", children: [
4806
+ /* @__PURE__ */ jsx28(Icon19, { icon: "lucide:database", width: 14 }),
4807
+ /* @__PURE__ */ jsxs21("span", { children: [
4075
4808
  "\u7D22\u5F15\u5927\u5C0F: ",
4076
4809
  indexSize
4077
4810
  ] })
4078
4811
  ] }),
4079
- lastUpdated && /* @__PURE__ */ jsxs20("div", { className: "stat-item", children: [
4080
- /* @__PURE__ */ jsx28(Icon20, { icon: "lucide:clock", width: 14 }),
4081
- /* @__PURE__ */ jsxs20("span", { children: [
4812
+ lastUpdated && /* @__PURE__ */ jsxs21("div", { className: "stat-item", children: [
4813
+ /* @__PURE__ */ jsx28(Icon19, { icon: "lucide:clock", width: 14 }),
4814
+ /* @__PURE__ */ jsxs21("span", { children: [
4082
4815
  "\u6700\u540E\u66F4\u65B0: ",
4083
4816
  lastUpdated
4084
4817
  ] })
4085
4818
  ] })
4086
4819
  ] }),
4087
- isIndexing && currentFile && /* @__PURE__ */ jsxs20("div", { className: "current-file", children: [
4088
- /* @__PURE__ */ jsx28(Icon20, { icon: "lucide:file-text", width: 14 }),
4820
+ isIndexing && currentFile && /* @__PURE__ */ jsxs21("div", { className: "current-file", children: [
4821
+ /* @__PURE__ */ jsx28(Icon19, { icon: "lucide:file-text", width: 14 }),
4089
4822
  /* @__PURE__ */ jsx28("span", { className: "file-path", children: currentFile })
4090
4823
  ] }),
4091
- /* @__PURE__ */ jsxs20("div", { className: "action-buttons", children: [
4092
- !isIndexing ? /* @__PURE__ */ jsxs20(
4824
+ /* @__PURE__ */ jsxs21("div", { className: "action-buttons", children: [
4825
+ !isIndexing ? /* @__PURE__ */ jsxs21(
4093
4826
  "button",
4094
4827
  {
4095
4828
  className: "edit-btn",
4096
4829
  onClick: handleSync,
4097
4830
  children: [
4098
- /* @__PURE__ */ jsx28(Icon20, { icon: "lucide:refresh-cw", width: 16 }),
4831
+ /* @__PURE__ */ jsx28(Icon19, { icon: "lucide:refresh-cw", width: 16 }),
4099
4832
  /* @__PURE__ */ jsx28("span", { children: "\u540C\u6B65" })
4100
4833
  ]
4101
4834
  }
4102
- ) : /* @__PURE__ */ jsxs20(
4835
+ ) : /* @__PURE__ */ jsxs21(
4103
4836
  "button",
4104
4837
  {
4105
4838
  className: "edit-btn",
4106
4839
  onClick: handleCancelIndex,
4107
4840
  children: [
4108
- /* @__PURE__ */ jsx28(Icon20, { icon: "lucide:x", width: 16 }),
4841
+ /* @__PURE__ */ jsx28(Icon19, { icon: "lucide:x", width: 16 }),
4109
4842
  /* @__PURE__ */ jsx28("span", { children: "\u53D6\u6D88" })
4110
4843
  ]
4111
4844
  }
4112
4845
  ),
4113
- /* @__PURE__ */ jsxs20(
4846
+ /* @__PURE__ */ jsxs21(
4114
4847
  "button",
4115
4848
  {
4116
4849
  className: "edit-btn delete-btn",
4117
4850
  disabled: isIndexing,
4118
4851
  onClick: () => setShowDeleteConfirm(true),
4119
4852
  children: [
4120
- /* @__PURE__ */ jsx28(Icon20, { icon: "lucide:trash-2", width: 16 }),
4853
+ /* @__PURE__ */ jsx28(Icon19, { icon: "lucide:trash-2", width: 16 }),
4121
4854
  /* @__PURE__ */ jsx28("span", { children: "\u5220\u9664\u7D22\u5F15" })
4122
4855
  ]
4123
4856
  }
@@ -4144,12 +4877,12 @@ function IndexingSettings() {
4144
4877
  }
4145
4878
 
4146
4879
  // src/components/common/SettingsPanel.tsx
4147
- import { Fragment as Fragment5, jsx as jsx29, jsxs as jsxs21 } from "react/jsx-runtime";
4880
+ import { Fragment as Fragment5, jsx as jsx29, jsxs as jsxs22 } from "react/jsx-runtime";
4148
4881
  var sections = [
4149
4882
  { id: "agent", label: "Agent", icon: "solar:link-round-angle-line-duotone" },
4150
4883
  { id: "indexing", label: "\u7D22\u5F15\u4E0E\u6587\u6863", icon: "lucide:database" }
4151
4884
  ];
4152
- function SettingsPanel({ visible, config, onClose, onChange }) {
4885
+ function SettingsPanel({ visible, config, allTools, enabledTools, onUpdateEnabledTools, onClose, onChange }) {
4153
4886
  const [currentSection, setCurrentSection] = useState18("agent");
4154
4887
  const currentSectionLabel = useMemo13(
4155
4888
  () => sections.find((s) => s.id === currentSection)?.label ?? "",
@@ -4159,55 +4892,125 @@ function SettingsPanel({ visible, config, onClose, onChange }) {
4159
4892
  { value: "run-everything", label: "\u8FD0\u884C\u6240\u6709\u5185\u5BB9\uFF08\u81EA\u52A8\u6267\u884C\uFF09" },
4160
4893
  { value: "manual", label: "\u624B\u52A8\u6279\u51C6\uFF08\u6BCF\u6B21\u6267\u884C\u524D\u8BE2\u95EE\uFF09" }
4161
4894
  ];
4162
- const updateConfig = useCallback14((key, value) => {
4895
+ const updateConfig = useCallback15((key, value) => {
4163
4896
  onChange({ ...config, [key]: value });
4164
4897
  }, [config, onChange]);
4165
- const handleModeChange = useCallback14((value) => {
4898
+ const handleModeChange = useCallback15((value) => {
4166
4899
  updateConfig("mode", value);
4167
4900
  }, [updateConfig]);
4168
- const handleOverlayClick = useCallback14((e) => {
4901
+ const handleOverlayClick = useCallback15((e) => {
4169
4902
  if (e.target === e.currentTarget) {
4170
4903
  onClose();
4171
4904
  }
4172
4905
  }, [onClose]);
4906
+ const allToolNames = useMemo13(() => (allTools ?? []).map((t) => t.name), [allTools]);
4907
+ const isToolEnabled = useCallback15((toolName) => {
4908
+ if (enabledTools === void 0) return true;
4909
+ return enabledTools.includes(toolName);
4910
+ }, [enabledTools]);
4911
+ const handleToolToggle = useCallback15((toolName, checked) => {
4912
+ if (!onUpdateEnabledTools) return;
4913
+ if (enabledTools === void 0) {
4914
+ if (checked) return;
4915
+ onUpdateEnabledTools(allToolNames.filter((n) => n !== toolName));
4916
+ return;
4917
+ }
4918
+ const set = new Set(enabledTools);
4919
+ if (checked) set.add(toolName);
4920
+ else set.delete(toolName);
4921
+ if (allToolNames.length > 0 && set.size === allToolNames.length) {
4922
+ onUpdateEnabledTools(void 0);
4923
+ return;
4924
+ }
4925
+ onUpdateEnabledTools(Array.from(set));
4926
+ }, [enabledTools, allToolNames, onUpdateEnabledTools]);
4927
+ const handleDisableAllTools = useCallback15(() => {
4928
+ onUpdateEnabledTools?.([]);
4929
+ }, [onUpdateEnabledTools]);
4930
+ const isAllToolsDisabled = useMemo13(
4931
+ () => enabledTools !== void 0 && enabledTools.length === 0,
4932
+ [enabledTools]
4933
+ );
4934
+ const handleEnableAllTools = useCallback15(() => {
4935
+ onUpdateEnabledTools?.(void 0);
4936
+ }, [onUpdateEnabledTools]);
4173
4937
  if (!visible) return null;
4174
- return /* @__PURE__ */ jsx29("div", { className: "settings-panel-overlay", onClick: handleOverlayClick, children: /* @__PURE__ */ jsxs21("div", { className: "settings-panel", children: [
4175
- /* @__PURE__ */ jsxs21("div", { className: "settings-sidebar", children: [
4938
+ return /* @__PURE__ */ jsx29("div", { className: "settings-panel-overlay", onClick: handleOverlayClick, children: /* @__PURE__ */ jsxs22("div", { className: "settings-panel", children: [
4939
+ /* @__PURE__ */ jsxs22("div", { className: "settings-sidebar", children: [
4176
4940
  /* @__PURE__ */ jsx29("div", { className: "sidebar-header", children: /* @__PURE__ */ jsx29("h3", { className: "sidebar-title", children: "\u8BBE\u7F6E" }) }),
4177
- /* @__PURE__ */ jsx29("div", { className: "sidebar-content", children: sections.map((section) => /* @__PURE__ */ jsxs21(
4941
+ /* @__PURE__ */ jsx29("div", { className: "sidebar-content", children: sections.map((section) => /* @__PURE__ */ jsxs22(
4178
4942
  "button",
4179
4943
  {
4180
4944
  className: `sidebar-item${currentSection === section.id ? " active" : ""}`,
4181
4945
  onClick: () => setCurrentSection(section.id),
4182
4946
  children: [
4183
- /* @__PURE__ */ jsx29(Icon21, { icon: section.icon, width: 18 }),
4947
+ /* @__PURE__ */ jsx29(Icon20, { icon: section.icon, width: 18 }),
4184
4948
  /* @__PURE__ */ jsx29("span", { children: section.label })
4185
4949
  ]
4186
4950
  },
4187
4951
  section.id
4188
4952
  )) })
4189
4953
  ] }),
4190
- /* @__PURE__ */ jsxs21("div", { className: "settings-content", children: [
4191
- /* @__PURE__ */ jsxs21("div", { className: "content-header", children: [
4954
+ /* @__PURE__ */ jsxs22("div", { className: "settings-content", children: [
4955
+ /* @__PURE__ */ jsxs22("div", { className: "content-header", children: [
4192
4956
  /* @__PURE__ */ jsx29("h2", { className: "content-title", children: currentSectionLabel }),
4193
- /* @__PURE__ */ jsx29("button", { className: "close-btn", onClick: onClose, children: /* @__PURE__ */ jsx29(Icon21, { icon: "lucide:x", width: 20 }) })
4957
+ /* @__PURE__ */ jsx29("button", { className: "close-btn", onClick: onClose, children: /* @__PURE__ */ jsx29(Icon20, { icon: "lucide:x", width: 20 }) })
4194
4958
  ] }),
4195
- /* @__PURE__ */ jsxs21("div", { className: "content-body", children: [
4196
- currentSection === "agent" && /* @__PURE__ */ jsx29(Fragment5, { children: /* @__PURE__ */ jsxs21("div", { className: "setting-item", children: [
4197
- /* @__PURE__ */ jsxs21("div", { className: "setting-info", children: [
4198
- /* @__PURE__ */ jsx29("div", { className: "setting-label", children: "\u81EA\u52A8\u8FD0\u884C\u6A21\u5F0F" }),
4199
- /* @__PURE__ */ jsx29("div", { className: "setting-description", children: "\u63A7\u5236\u5DE5\u5177\u6267\u884C\u7684\u81EA\u52A8\u8FD0\u884C\u884C\u4E3A" })
4959
+ /* @__PURE__ */ jsxs22("div", { className: "content-body", children: [
4960
+ currentSection === "agent" && /* @__PURE__ */ jsxs22(Fragment5, { children: [
4961
+ /* @__PURE__ */ jsxs22("div", { className: "setting-item", children: [
4962
+ /* @__PURE__ */ jsxs22("div", { className: "setting-info", children: [
4963
+ /* @__PURE__ */ jsx29("div", { className: "setting-label", children: "\u81EA\u52A8\u8FD0\u884C\u6A21\u5F0F" }),
4964
+ /* @__PURE__ */ jsx29("div", { className: "setting-description", children: "\u63A7\u5236\u5DE5\u5177\u6267\u884C\u7684\u81EA\u52A8\u8FD0\u884C\u884C\u4E3A" })
4965
+ ] }),
4966
+ /* @__PURE__ */ jsx29("div", { className: "setting-control", children: /* @__PURE__ */ jsx29(
4967
+ DropdownSelector,
4968
+ {
4969
+ value: config.mode ?? "run-everything",
4970
+ options: modeOptions2,
4971
+ align: "right",
4972
+ onSelect: handleModeChange
4973
+ }
4974
+ ) })
4200
4975
  ] }),
4201
- /* @__PURE__ */ jsx29("div", { className: "setting-control", children: /* @__PURE__ */ jsx29(
4202
- DropdownSelector,
4203
- {
4204
- value: config.mode ?? "run-everything",
4205
- options: modeOptions2,
4206
- align: "right",
4207
- onSelect: handleModeChange
4208
- }
4209
- ) })
4210
- ] }) }),
4976
+ /* @__PURE__ */ jsxs22("div", { className: "setting-section", children: [
4977
+ /* @__PURE__ */ jsxs22("div", { className: "setting-section-header", children: [
4978
+ /* @__PURE__ */ jsxs22("div", { className: "setting-info", children: [
4979
+ /* @__PURE__ */ jsx29("div", { className: "setting-label", children: "\u5DE5\u5177\u7BA1\u7406" }),
4980
+ /* @__PURE__ */ jsx29("div", { className: "setting-description", children: "\u63A7\u5236\u54EA\u4E9B\u5DE5\u5177\u53EF\u4EE5\u88AB AI \u4F7F\u7528" })
4981
+ ] }),
4982
+ /* @__PURE__ */ jsx29(
4983
+ "button",
4984
+ {
4985
+ className: "disable-all-btn",
4986
+ onClick: isAllToolsDisabled ? handleEnableAllTools : handleDisableAllTools,
4987
+ disabled: !allTools?.length,
4988
+ children: isAllToolsDisabled ? "\u542F\u7528\u6240\u6709\u5DE5\u5177" : "\u7981\u7528\u6240\u6709\u5DE5\u5177"
4989
+ }
4990
+ )
4991
+ ] }),
4992
+ /* @__PURE__ */ jsx29("div", { className: "tools-list", children: allTools && allTools.length > 0 ? allTools.map((tool) => /* @__PURE__ */ jsxs22("div", { className: "tool-item", children: [
4993
+ /* @__PURE__ */ jsxs22("div", { className: "tool-info", children: [
4994
+ /* @__PURE__ */ jsx29("div", { className: "tool-name", children: tool.name }),
4995
+ /* @__PURE__ */ jsx29("div", { className: "tool-description", children: tool.description })
4996
+ ] }),
4997
+ /* @__PURE__ */ jsx29(
4998
+ ToggleSwitch,
4999
+ {
5000
+ checked: isToolEnabled(tool.name),
5001
+ onChange: (checked) => handleToolToggle(tool.name, checked)
5002
+ }
5003
+ )
5004
+ ] }, tool.name)) : /* @__PURE__ */ jsxs22("div", { className: "no-tools", children: [
5005
+ /* @__PURE__ */ jsx29("div", { children: "\u6682\u65E0\u53EF\u7528\u5DE5\u5177" }),
5006
+ /* @__PURE__ */ jsxs22("div", { className: "no-tools-hint", children: [
5007
+ "\u5DE5\u5177\u9700\u8981\u5728\u521B\u5EFA Electron Bridge \u65F6\u901A\u8FC7 ",
5008
+ /* @__PURE__ */ jsx29("code", { children: "tools" }),
5009
+ " \u53C2\u6570\u6CE8\u5165"
5010
+ ] })
5011
+ ] }) })
5012
+ ] })
5013
+ ] }),
4211
5014
  currentSection === "indexing" && /* @__PURE__ */ jsx29(IndexingSettings, {})
4212
5015
  ] })
4213
5016
  ] })
@@ -4215,7 +5018,7 @@ function SettingsPanel({ visible, config, onClose, onChange }) {
4215
5018
  }
4216
5019
 
4217
5020
  // src/components/ChatPanel.tsx
4218
- import { jsx as jsx30, jsxs as jsxs22 } from "react/jsx-runtime";
5021
+ import { jsx as jsx30, jsxs as jsxs23 } from "react/jsx-runtime";
4219
5022
  var ChatPanel = forwardRef8(({
4220
5023
  adapter,
4221
5024
  defaultModel = "anthropic/claude-opus-4.5",
@@ -4226,13 +5029,15 @@ var ChatPanel = forwardRef8(({
4226
5029
  onToolComplete,
4227
5030
  className = "",
4228
5031
  welcomeConfig,
4229
- toolRenderers = {},
5032
+ partRenderers = {},
4230
5033
  stepsExpandedType = "auto"
4231
5034
  }, ref) => {
4232
- const messagesRef = useRef11(null);
4233
- const inputRef = useRef11(null);
5035
+ const messagesRef = useRef12(null);
5036
+ const inputRef = useRef12(null);
4234
5037
  const [shouldAutoScroll, setShouldAutoScroll] = useState19(true);
4235
- const SCROLL_THRESHOLD = 100;
5038
+ const SCROLL_THRESHOLD = 25;
5039
+ const lastScrollTopRef = useRef12(0);
5040
+ const isProgrammaticScrollRef = useRef12(false);
4236
5041
  const [settingsPanelVisible, setSettingsPanelVisible] = useState19(false);
4237
5042
  const [models, setModels] = useState19(propModels || []);
4238
5043
  const [confirmDialog, setConfirmDialog] = useState19({
@@ -4244,7 +5049,7 @@ var ChatPanel = forwardRef8(({
4244
5049
  onConfirm: () => {
4245
5050
  }
4246
5051
  });
4247
- const showConfirm = useCallback15((options) => {
5052
+ const showConfirm = useCallback16((options) => {
4248
5053
  setConfirmDialog({
4249
5054
  visible: true,
4250
5055
  title: options.title || "\u786E\u8BA4",
@@ -4255,7 +5060,7 @@ var ChatPanel = forwardRef8(({
4255
5060
  });
4256
5061
  }, []);
4257
5062
  const [toast, setToast] = useState19({ visible: false, message: "", type: "info" });
4258
- const showToast = useCallback15((message, type = "info") => {
5063
+ const showToast = useCallback16((message, type = "info") => {
4259
5064
  setToast({ visible: true, message, type });
4260
5065
  }, []);
4261
5066
  const {
@@ -4279,19 +5084,27 @@ var ChatPanel = forwardRef8(({
4279
5084
  cancelRequest,
4280
5085
  copyMessage,
4281
5086
  regenerateMessage,
5087
+ resendFromIndex,
4282
5088
  setMode,
4283
5089
  setModel,
4284
5090
  setWebSearch,
4285
5091
  setThinking,
4286
5092
  setWorkingDirectory,
4287
5093
  autoRunConfig,
4288
- saveAutoRunConfig
5094
+ saveAutoRunConfig,
5095
+ // 工具管理
5096
+ allTools,
5097
+ enabledTools,
5098
+ saveEnabledTools
4289
5099
  } = useChat({
4290
5100
  adapter,
4291
5101
  defaultModel,
4292
5102
  defaultMode,
4293
5103
  onToolComplete
4294
5104
  });
5105
+ const handleCancelToolCall = useCallback16((_toolCallId) => {
5106
+ cancelRequest();
5107
+ }, [cancelRequest]);
4295
5108
  useImperativeHandle8(ref, () => ({
4296
5109
  setInputText: (text) => {
4297
5110
  inputRef.current?.setText(text);
@@ -4307,61 +5120,84 @@ var ChatPanel = forwardRef8(({
4307
5120
  },
4308
5121
  setCwd: setWorkingDirectory
4309
5122
  }), [sendMessage, setWorkingDirectory]);
4310
- useEffect16(() => {
5123
+ useEffect17(() => {
4311
5124
  loadSessions();
4312
5125
  }, [loadSessions]);
4313
- useEffect16(() => {
5126
+ useEffect17(() => {
4314
5127
  adapter.getModels().then(setModels).catch((err) => console.warn("\u83B7\u53D6\u6A21\u578B\u5217\u8868\u5931\u8D25:", err));
4315
5128
  }, [adapter]);
4316
- const isNearBottom = useCallback15(() => {
5129
+ const isNearBottom = useCallback16(() => {
4317
5130
  if (!messagesRef.current) return true;
4318
5131
  const { scrollTop, scrollHeight, clientHeight } = messagesRef.current;
4319
5132
  return scrollHeight - scrollTop - clientHeight < SCROLL_THRESHOLD;
4320
5133
  }, []);
4321
- const handleScroll = useCallback15(() => {
4322
- setShouldAutoScroll(isNearBottom());
5134
+ const handleScroll = useCallback16(() => {
5135
+ if (!messagesRef.current) return;
5136
+ if (isProgrammaticScrollRef.current) return;
5137
+ const { scrollTop } = messagesRef.current;
5138
+ const isScrollingUp = scrollTop < lastScrollTopRef.current;
5139
+ lastScrollTopRef.current = scrollTop;
5140
+ if (isScrollingUp) {
5141
+ setShouldAutoScroll(false);
5142
+ return;
5143
+ }
5144
+ if (isNearBottom()) {
5145
+ setShouldAutoScroll(true);
5146
+ }
4323
5147
  }, [isNearBottom]);
4324
- const scrollToBottom = useCallback15((force = false) => {
5148
+ const scrollToBottom = useCallback16((force = false) => {
4325
5149
  if (messagesRef.current && (force || shouldAutoScroll)) {
5150
+ isProgrammaticScrollRef.current = true;
4326
5151
  messagesRef.current.scrollTop = messagesRef.current.scrollHeight;
4327
- setShouldAutoScroll(true);
5152
+ lastScrollTopRef.current = messagesRef.current.scrollTop;
5153
+ if (force) {
5154
+ setShouldAutoScroll(true);
5155
+ }
5156
+ requestAnimationFrame(() => {
5157
+ isProgrammaticScrollRef.current = false;
5158
+ });
4328
5159
  }
4329
5160
  }, [shouldAutoScroll]);
4330
- useEffect16(() => {
5161
+ useEffect17(() => {
4331
5162
  scrollToBottom();
4332
5163
  }, [messages, scrollToBottom]);
4333
- const prevIsLoadingRef = useRef11(isLoading);
4334
- useEffect16(() => {
5164
+ const prevIsLoadingRef = useRef12(isLoading);
5165
+ useEffect17(() => {
4335
5166
  if (isLoading && !prevIsLoadingRef.current) {
4336
5167
  scrollToBottom(true);
4337
5168
  }
4338
5169
  prevIsLoadingRef.current = isLoading;
4339
5170
  }, [isLoading, scrollToBottom]);
4340
- const handleSend = useCallback15((text) => {
4341
- sendMessage(text);
5171
+ const handleSend = useCallback16((text, images) => {
5172
+ const imageUrls = images?.map((img) => `data:${img.mimeType};base64,${img.base64}`);
5173
+ sendMessage(text, imageUrls);
4342
5174
  }, [sendMessage]);
4343
- const handleAtContext = useCallback15(() => {
5175
+ const handleAtContext = useCallback16(() => {
4344
5176
  console.log("@ \u4E0A\u4E0B\u6587");
4345
5177
  }, []);
4346
- const handleQuickAction = useCallback15(
5178
+ const handleQuickAction = useCallback16(
4347
5179
  (text) => {
4348
5180
  sendMessage(text);
4349
5181
  },
4350
5182
  [sendMessage]
4351
5183
  );
4352
- const handleResend = useCallback15(
4353
- (_index, text) => {
4354
- sendMessage(text);
5184
+ const handleResend = useCallback16(
5185
+ (index, text) => {
5186
+ resendFromIndex(index, text);
4355
5187
  },
4356
- [sendMessage]
5188
+ [resendFromIndex]
4357
5189
  );
4358
- const handleClose = useCallback15(() => {
5190
+ const handleNewSession = useCallback16(async () => {
5191
+ await createNewSession();
5192
+ inputRef.current?.clear();
5193
+ }, [createNewSession]);
5194
+ const handleClose = useCallback16(() => {
4359
5195
  onClose?.();
4360
5196
  }, [onClose]);
4361
- const handleSettings = useCallback15(() => {
5197
+ const handleSettings = useCallback16(() => {
4362
5198
  setSettingsPanelVisible(true);
4363
5199
  }, []);
4364
- const handleSaveSettings = useCallback15(async (config) => {
5200
+ const handleSaveSettings = useCallback16(async (config) => {
4365
5201
  try {
4366
5202
  await saveAutoRunConfig(config);
4367
5203
  } catch (error) {
@@ -4369,7 +5205,15 @@ var ChatPanel = forwardRef8(({
4369
5205
  showToast("\u4FDD\u5B58\u8BBE\u7F6E\u5931\u8D25", "error");
4370
5206
  }
4371
5207
  }, [saveAutoRunConfig, showToast]);
4372
- const handleClearAll = useCallback15(() => {
5208
+ const handleUpdateEnabledTools = useCallback16(async (tools) => {
5209
+ try {
5210
+ await saveEnabledTools(tools);
5211
+ } catch (error) {
5212
+ console.error("\u4FDD\u5B58\u5DE5\u5177\u5F00\u5173\u5931\u8D25:", error);
5213
+ showToast("\u4FDD\u5B58\u5DE5\u5177\u5F00\u5173\u5931\u8D25", "error");
5214
+ }
5215
+ }, [saveEnabledTools, showToast]);
5216
+ const handleClearAll = useCallback16(() => {
4373
5217
  showConfirm({
4374
5218
  title: "\u6E05\u7A7A\u6240\u6709\u5BF9\u8BDD",
4375
5219
  message: "\u786E\u5B9A\u8981\u6E05\u7A7A\u6240\u6709\u5BF9\u8BDD\u5417\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002",
@@ -4378,10 +5222,10 @@ var ChatPanel = forwardRef8(({
4378
5222
  onConfirm: () => clearAllSessions()
4379
5223
  });
4380
5224
  }, [showConfirm, clearAllSessions]);
4381
- const handleCloseOthers = useCallback15(async () => {
5225
+ const handleCloseOthers = useCallback16(async () => {
4382
5226
  await hideOtherSessions();
4383
5227
  }, [hideOtherSessions]);
4384
- const handleExport = useCallback15(() => {
5228
+ const handleExport = useCallback16(() => {
4385
5229
  const data = exportCurrentSession();
4386
5230
  if (!data) {
4387
5231
  showToast("\u5F53\u524D\u4F1A\u8BDD\u6CA1\u6709\u5185\u5BB9\u53EF\u5BFC\u51FA", "warning");
@@ -4399,7 +5243,7 @@ var ChatPanel = forwardRef8(({
4399
5243
  document.body.removeChild(a);
4400
5244
  URL.revokeObjectURL(url);
4401
5245
  }, [exportCurrentSession, sessions, currentSessionId, showToast]);
4402
- const handleCopyId = useCallback15(async () => {
5246
+ const handleCopyId = useCallback16(async () => {
4403
5247
  if (!currentSessionId) return;
4404
5248
  try {
4405
5249
  await navigator.clipboard.writeText(currentSessionId);
@@ -4408,7 +5252,7 @@ var ChatPanel = forwardRef8(({
4408
5252
  console.error("\u590D\u5236\u5931\u8D25:", error);
4409
5253
  }
4410
5254
  }, [currentSessionId, showToast]);
4411
- const handleFeedback = useCallback15(() => {
5255
+ const handleFeedback = useCallback16(() => {
4412
5256
  console.log("\u53CD\u9988");
4413
5257
  }, []);
4414
5258
  const inputContextValue = useMemo14(
@@ -4430,7 +5274,7 @@ var ChatPanel = forwardRef8(({
4430
5274
  }),
4431
5275
  [mode, model, models, webSearch, thinking, isLoading, adapter, setMode, setModel, setWebSearch, setThinking]
4432
5276
  );
4433
- return /* @__PURE__ */ jsx30(ChatInputProvider, { value: inputContextValue, children: /* @__PURE__ */ jsx30(RenderersProvider, { blockRenderers: {}, toolRenderers, children: /* @__PURE__ */ jsxs22("div", { className: `chat-panel ${className}`.trim(), children: [
5277
+ return /* @__PURE__ */ jsx30(ChatInputProvider, { value: inputContextValue, children: /* @__PURE__ */ jsx30(PartRenderersProvider, { partRenderers, children: /* @__PURE__ */ jsxs23("div", { className: `chat-panel ${className}`.trim(), children: [
4434
5278
  /* @__PURE__ */ jsx30(
4435
5279
  ConfirmDialog,
4436
5280
  {
@@ -4460,6 +5304,9 @@ var ChatPanel = forwardRef8(({
4460
5304
  {
4461
5305
  visible: settingsPanelVisible,
4462
5306
  config: autoRunConfig,
5307
+ allTools,
5308
+ enabledTools,
5309
+ onUpdateEnabledTools: handleUpdateEnabledTools,
4463
5310
  onChange: handleSaveSettings,
4464
5311
  onClose: () => setSettingsPanelVisible(false)
4465
5312
  }
@@ -4470,7 +5317,7 @@ var ChatPanel = forwardRef8(({
4470
5317
  sessions,
4471
5318
  currentSessionId,
4472
5319
  showClose: !!onClose,
4473
- onNewSession: createNewSession,
5320
+ onNewSession: handleNewSession,
4474
5321
  onSwitchSession: switchSession,
4475
5322
  onDeleteSession: deleteSession,
4476
5323
  onHideSession: hideSession,
@@ -4483,7 +5330,7 @@ var ChatPanel = forwardRef8(({
4483
5330
  onSettings: handleSettings
4484
5331
  }
4485
5332
  ),
4486
- /* @__PURE__ */ jsxs22("div", { className: "messages-wrapper", children: [
5333
+ /* @__PURE__ */ jsxs23("div", { className: "messages-wrapper", children: [
4487
5334
  /* @__PURE__ */ jsx30("div", { ref: messagesRef, className: "messages-container chat-scrollbar", onScroll: handleScroll, children: messages.length === 0 ? /* @__PURE__ */ jsx30(WelcomeMessage, { config: welcomeConfig, onQuickAction: handleQuickAction }) : messages.map((msg, index) => /* @__PURE__ */ jsx30(
4488
5335
  MessageBubble,
4489
5336
  {
@@ -4497,6 +5344,7 @@ var ChatPanel = forwardRef8(({
4497
5344
  timestamp: msg.timestamp,
4498
5345
  stepsExpandedType,
4499
5346
  adapter,
5347
+ onCancelToolCall: handleCancelToolCall,
4500
5348
  autoRunConfig,
4501
5349
  onSaveConfig: saveAutoRunConfig,
4502
5350
  onCopy: () => copyMessage(msg.id),
@@ -4511,7 +5359,7 @@ var ChatPanel = forwardRef8(({
4511
5359
  className: "scroll-to-bottom-btn",
4512
5360
  onClick: () => scrollToBottom(true),
4513
5361
  title: "\u6EDA\u52A8\u5230\u5E95\u90E8",
4514
- children: /* @__PURE__ */ jsx30(Icon22, { icon: "lucide:arrow-down", width: 16 })
5362
+ children: /* @__PURE__ */ jsx30(Icon21, { icon: "lucide:arrow-down", width: 16 })
4515
5363
  }
4516
5364
  )
4517
5365
  ] }),
@@ -4538,251 +5386,32 @@ var ChatPanel = forwardRef8(({
4538
5386
  });
4539
5387
  ChatPanel.displayName = "ChatPanel";
4540
5388
 
4541
- // src/components/message/ContentRenderer.tsx
4542
- import { useMemo as useMemo16, useContext as useContext3 } from "react";
4543
- import { parseContent } from "@huyooo/ai-chat-shared";
4544
-
4545
- // src/components/message/blocks/TextBlock.tsx
4546
- import { jsx as jsx31 } from "react/jsx-runtime";
4547
- var TextBlock = ({ block }) => {
4548
- return /* @__PURE__ */ jsx31("div", { className: "text-block", children: block.content });
4549
- };
4550
-
4551
- // src/components/message/blocks/CodeBlock.tsx
4552
- import { useState as useState20, useCallback as useCallback16, useMemo as useMemo15 } from "react";
4553
- import { Icon as Icon23 } from "@iconify/react";
4554
- import { highlightCode, getLanguageDisplayName } from "@huyooo/ai-chat-shared";
4555
- import { jsx as jsx32, jsxs as jsxs23 } from "react/jsx-runtime";
4556
- var CodeBlock = ({ block, onCopy }) => {
4557
- const [copied, setCopied] = useState20(false);
4558
- const languageDisplay = useMemo15(
4559
- () => getLanguageDisplayName(block.language),
4560
- [block.language]
4561
- );
4562
- const highlightedCode = useMemo15(
4563
- () => highlightCode(block.content, block.language),
4564
- [block.content, block.language]
4565
- );
4566
- const handleCopy = useCallback16(async () => {
4567
- try {
4568
- await navigator.clipboard.writeText(block.content);
4569
- setCopied(true);
4570
- onCopy?.(block.content);
4571
- setTimeout(() => setCopied(false), 2e3);
4572
- } catch (err) {
4573
- console.error("\u590D\u5236\u5931\u8D25:", err);
4574
- }
4575
- }, [block.content, onCopy]);
4576
- return /* @__PURE__ */ jsxs23("div", { className: "code-block", children: [
4577
- /* @__PURE__ */ jsxs23("div", { className: "code-header", children: [
4578
- /* @__PURE__ */ jsx32("span", { className: "code-language", children: languageDisplay }),
4579
- /* @__PURE__ */ jsx32("div", { className: "code-actions", children: /* @__PURE__ */ jsx32("button", { className: "code-action-btn", title: "\u590D\u5236\u4EE3\u7801", onClick: handleCopy, children: /* @__PURE__ */ jsx32(Icon23, { icon: copied ? "lucide:check" : "lucide:copy", width: 14 }) }) })
4580
- ] }),
4581
- /* @__PURE__ */ jsx32("pre", { className: "code-content", children: /* @__PURE__ */ jsx32("code", { dangerouslySetInnerHTML: { __html: highlightedCode } }) })
4582
- ] });
4583
- };
4584
-
4585
- // src/components/message/ContentRenderer.tsx
4586
- import { jsx as jsx33 } from "react/jsx-runtime";
4587
- var ContentRenderer = ({
4588
- content,
4589
- blocks: preBlocks,
4590
- onCodeCopy
4591
- }) => {
4592
- const customRenderers = useContext3(BlockRenderersContext);
4593
- const blocks = useMemo16(() => {
4594
- if (preBlocks?.length) {
4595
- return preBlocks;
4596
- }
4597
- return parseContent(content);
4598
- }, [content, preBlocks]);
4599
- if (!blocks.length) return null;
4600
- return /* @__PURE__ */ jsx33("div", { className: "content-renderer", children: blocks.map((block) => {
4601
- const CustomRenderer = customRenderers[block.type];
4602
- if (CustomRenderer) {
4603
- return /* @__PURE__ */ jsx33(CustomRenderer, { block }, block.id);
4604
- }
4605
- switch (block.type) {
4606
- case "text":
4607
- return /* @__PURE__ */ jsx33(TextBlock, { block }, block.id);
4608
- case "code":
4609
- return /* @__PURE__ */ jsx33(CodeBlock, { block, onCopy: onCodeCopy }, block.id);
4610
- default:
4611
- return null;
4612
- }
4613
- }) });
4614
- };
4615
-
4616
- // src/components/message/ToolResultRenderer.tsx
4617
- import { useContext as useContext4, useMemo as useMemo20 } from "react";
4618
-
4619
- // src/components/message/tool-results/DefaultToolResult.tsx
4620
- import { useMemo as useMemo17 } from "react";
4621
- import { jsx as jsx34 } from "react/jsx-runtime";
4622
- var DefaultToolResult = ({ toolResult }) => {
4623
- const formattedResult = useMemo17(() => {
4624
- if (typeof toolResult === "string") {
4625
- return toolResult;
4626
- }
4627
- try {
4628
- return JSON.stringify(toolResult, null, 2);
4629
- } catch {
4630
- return String(toolResult);
4631
- }
4632
- }, [toolResult]);
4633
- return /* @__PURE__ */ jsx34("div", { className: "default-tool-result", children: /* @__PURE__ */ jsx34("pre", { className: "result-content", children: formattedResult }) });
4634
- };
4635
-
4636
- // src/components/message/tool-results/WeatherCard.tsx
4637
- import { useMemo as useMemo18 } from "react";
4638
- import { Icon as Icon24 } from "@iconify/react";
4639
- import { jsx as jsx35, jsxs as jsxs24 } from "react/jsx-runtime";
4640
- var WeatherCard = ({ toolResult }) => {
4641
- const weather = useMemo18(() => {
4642
- if (typeof toolResult === "object" && toolResult !== null) {
4643
- return toolResult;
4644
- }
4645
- return {
4646
- city: "\u672A\u77E5",
4647
- temperature: 0,
4648
- condition: "\u672A\u77E5"
4649
- };
4650
- }, [toolResult]);
4651
- return /* @__PURE__ */ jsxs24("div", { className: "weather-card", children: [
4652
- /* @__PURE__ */ jsxs24("div", { className: "weather-header", children: [
4653
- /* @__PURE__ */ jsx35(Icon24, { icon: "lucide:cloud-sun", width: 24, className: "weather-icon" }),
4654
- /* @__PURE__ */ jsx35("div", { className: "weather-location", children: weather.city })
4655
- ] }),
4656
- /* @__PURE__ */ jsxs24("div", { className: "weather-main", children: [
4657
- /* @__PURE__ */ jsxs24("div", { className: "weather-temp", children: [
4658
- weather.temperature,
4659
- "\xB0"
4660
- ] }),
4661
- /* @__PURE__ */ jsx35("div", { className: "weather-condition", children: weather.condition })
4662
- ] }),
4663
- (weather.humidity || weather.wind) && /* @__PURE__ */ jsxs24("div", { className: "weather-details", children: [
4664
- weather.humidity && /* @__PURE__ */ jsxs24("div", { className: "weather-detail", children: [
4665
- /* @__PURE__ */ jsx35(Icon24, { icon: "lucide:droplets", width: 14 }),
4666
- /* @__PURE__ */ jsxs24("span", { children: [
4667
- weather.humidity,
4668
- "%"
4669
- ] })
4670
- ] }),
4671
- weather.wind && /* @__PURE__ */ jsxs24("div", { className: "weather-detail", children: [
4672
- /* @__PURE__ */ jsx35(Icon24, { icon: "lucide:wind", width: 14 }),
4673
- /* @__PURE__ */ jsx35("span", { children: weather.wind })
4674
- ] })
4675
- ] }),
4676
- weather.forecast?.length ? /* @__PURE__ */ jsx35("div", { className: "weather-forecast", children: weather.forecast.map((item) => /* @__PURE__ */ jsxs24("div", { className: "forecast-item", children: [
4677
- /* @__PURE__ */ jsx35("div", { className: "forecast-date", children: item.date }),
4678
- /* @__PURE__ */ jsxs24("div", { className: "forecast-temp", children: [
4679
- item.low,
4680
- "\xB0 / ",
4681
- item.high,
4682
- "\xB0"
4683
- ] }),
4684
- /* @__PURE__ */ jsx35("div", { className: "forecast-condition", children: item.condition })
4685
- ] }, item.date)) }) : null
4686
- ] });
4687
- };
4688
-
4689
- // src/components/message/tool-results/SearchResults.tsx
4690
- import { useMemo as useMemo19, useCallback as useCallback17 } from "react";
4691
- import { Icon as Icon25 } from "@iconify/react";
4692
- import { jsx as jsx36, jsxs as jsxs25 } from "react/jsx-runtime";
4693
- var SearchResults = ({ toolResult }) => {
4694
- const results = useMemo19(() => {
4695
- if (Array.isArray(toolResult)) {
4696
- return toolResult;
4697
- }
4698
- if (typeof toolResult === "object" && toolResult !== null && "results" in toolResult) {
4699
- return toolResult.results;
4700
- }
4701
- return [];
4702
- }, [toolResult]);
4703
- const getDomain = useCallback17((url) => {
4704
- try {
4705
- return new URL(url).hostname;
4706
- } catch {
4707
- return url;
4708
- }
4709
- }, []);
4710
- const openExternal = useCallback17((url) => {
4711
- const bridge = window.aiChatBridge;
4712
- if (bridge?.openExternal) {
4713
- bridge.openExternal(url);
4714
- } else {
4715
- window.open(url, "_blank");
4716
- }
4717
- }, []);
4718
- return /* @__PURE__ */ jsxs25("div", { className: "search-results-card", children: [
4719
- /* @__PURE__ */ jsxs25("div", { className: "search-header", children: [
4720
- /* @__PURE__ */ jsx36(Icon25, { icon: "lucide:search", width: 16 }),
4721
- /* @__PURE__ */ jsx36("span", { children: "\u641C\u7D22\u7ED3\u679C" }),
4722
- /* @__PURE__ */ jsxs25("span", { className: "search-count", children: [
4723
- results.length,
4724
- " \u6761"
4725
- ] })
4726
- ] }),
4727
- /* @__PURE__ */ jsx36("div", { className: "search-list", children: results.map((item, index) => /* @__PURE__ */ jsxs25(
4728
- "a",
4729
- {
4730
- href: item.url,
4731
- target: "_blank",
4732
- rel: "noopener noreferrer",
4733
- className: "search-item",
4734
- onClick: (e) => {
4735
- e.preventDefault();
4736
- openExternal(item.url);
4737
- },
4738
- children: [
4739
- /* @__PURE__ */ jsx36("div", { className: "item-title", children: item.title }),
4740
- item.snippet && /* @__PURE__ */ jsx36("div", { className: "item-snippet", children: item.snippet }),
4741
- /* @__PURE__ */ jsx36("div", { className: "item-url", children: getDomain(item.url) })
4742
- ]
4743
- },
4744
- index
4745
- )) })
4746
- ] });
4747
- };
4748
-
4749
- // src/components/message/ToolResultRenderer.tsx
4750
- import { jsx as jsx37 } from "react/jsx-runtime";
4751
- var ToolResultRenderer = (props) => {
4752
- const customRenderers = useContext4(ToolRenderersContext);
4753
- const Component = useMemo20(() => {
4754
- return customRenderers[props.toolName] || DefaultToolResult;
4755
- }, [customRenderers, props.toolName]);
4756
- return /* @__PURE__ */ jsx37(Component, { ...props });
4757
- };
4758
-
4759
5389
  // src/index.ts
4760
- import { parseContent as parseContent2, highlightCode as highlightCode2, getLanguageDisplayName as getLanguageDisplayName2, renderMarkdown as renderMarkdown2 } from "@huyooo/ai-chat-shared";
5390
+ import { parseContent, highlightCode as highlightCode3, getLanguageDisplayName, renderMarkdown as renderMarkdown3 } from "@huyooo/ai-chat-shared";
4761
5391
  export {
4762
- BlockRenderersContext,
4763
5392
  ChatHeader,
4764
5393
  ChatInput,
4765
5394
  ChatInputProvider,
4766
5395
  ChatPanel,
4767
- CodeBlock,
4768
5396
  ConfirmDialog,
4769
- ContentRenderer,
4770
- DefaultToolResult,
5397
+ ErrorPart as ErrorPartComponent,
5398
+ ImagePart as ImagePartComponent,
4771
5399
  MessageBubble,
4772
- RenderersProvider,
4773
- SearchResults,
4774
- TextBlock,
5400
+ PartRenderersContext,
5401
+ PartRenderersProvider,
5402
+ PartsRenderer,
5403
+ SearchPart as SearchPartComponent,
5404
+ TextPart as TextPartComponent,
5405
+ ThinkingPart as ThinkingPartComponent,
4775
5406
  Toast,
4776
- ToolRenderersContext,
4777
- ToolResultRenderer,
4778
- WeatherCard,
5407
+ ToolCallPart as ToolCallPartComponent,
4779
5408
  WelcomeMessage,
4780
5409
  defaultWelcomeConfig,
4781
- getLanguageDisplayName2 as getLanguageDisplayName,
5410
+ getLanguageDisplayName,
4782
5411
  getMessageText,
4783
- highlightCode2 as highlightCode,
4784
- parseContent2 as parseContent,
4785
- renderMarkdown2 as renderMarkdown,
5412
+ highlightCode3 as highlightCode,
5413
+ parseContent,
5414
+ renderMarkdown3 as renderMarkdown,
4786
5415
  useChat,
4787
5416
  useChatInputContext
4788
5417
  };