@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.
- package/README.md +99 -84
- package/dist/KaTeX_AMS-Regular-CYEKBG2K.woff +0 -0
- package/dist/KaTeX_AMS-Regular-JKX5W2C4.ttf +0 -0
- package/dist/KaTeX_AMS-Regular-U6PRYMIZ.woff2 +0 -0
- package/dist/KaTeX_Caligraphic-Bold-5QL5CMTE.woff2 +0 -0
- package/dist/KaTeX_Caligraphic-Bold-WZ3QSGD3.woff +0 -0
- package/dist/KaTeX_Caligraphic-Bold-ZTS3R3HK.ttf +0 -0
- package/dist/KaTeX_Caligraphic-Regular-3LKEU76G.woff +0 -0
- package/dist/KaTeX_Caligraphic-Regular-A7XRTZ5Q.ttf +0 -0
- package/dist/KaTeX_Caligraphic-Regular-KX5MEWCF.woff2 +0 -0
- package/dist/KaTeX_Fraktur-Bold-2QVFK6NQ.woff2 +0 -0
- package/dist/KaTeX_Fraktur-Bold-T4SWXBMT.woff +0 -0
- package/dist/KaTeX_Fraktur-Bold-WGHVTYOR.ttf +0 -0
- package/dist/KaTeX_Fraktur-Regular-2PEIFJSJ.woff2 +0 -0
- package/dist/KaTeX_Fraktur-Regular-5U4OPH2X.ttf +0 -0
- package/dist/KaTeX_Fraktur-Regular-PQMHCIK6.woff +0 -0
- package/dist/KaTeX_Main-Bold-2GA4IZIN.woff +0 -0
- package/dist/KaTeX_Main-Bold-W5FBVCZM.ttf +0 -0
- package/dist/KaTeX_Main-Bold-YP5VVQRP.woff2 +0 -0
- package/dist/KaTeX_Main-BoldItalic-4P4C7HJH.woff +0 -0
- package/dist/KaTeX_Main-BoldItalic-N4V3DX7S.woff2 +0 -0
- package/dist/KaTeX_Main-BoldItalic-ODMLBJJQ.ttf +0 -0
- package/dist/KaTeX_Main-Italic-I43T2HSR.ttf +0 -0
- package/dist/KaTeX_Main-Italic-RELBIK7M.woff2 +0 -0
- package/dist/KaTeX_Main-Italic-SASNQFN2.woff +0 -0
- package/dist/KaTeX_Main-Regular-ARRPAO67.woff2 +0 -0
- package/dist/KaTeX_Main-Regular-P5I74A2A.woff +0 -0
- package/dist/KaTeX_Main-Regular-W74P5G27.ttf +0 -0
- package/dist/KaTeX_Math-BoldItalic-6EBV3DK5.woff +0 -0
- package/dist/KaTeX_Math-BoldItalic-K4WTGH3J.woff2 +0 -0
- package/dist/KaTeX_Math-BoldItalic-VB447A4D.ttf +0 -0
- package/dist/KaTeX_Math-Italic-6KGCHLFN.woff2 +0 -0
- package/dist/KaTeX_Math-Italic-KKK3USB2.woff +0 -0
- package/dist/KaTeX_Math-Italic-SON4MRCA.ttf +0 -0
- package/dist/KaTeX_SansSerif-Bold-RRNVJFFW.woff2 +0 -0
- package/dist/KaTeX_SansSerif-Bold-STQ6RXC7.ttf +0 -0
- package/dist/KaTeX_SansSerif-Bold-X5M5EMOD.woff +0 -0
- package/dist/KaTeX_SansSerif-Italic-HMPFTM52.woff2 +0 -0
- package/dist/KaTeX_SansSerif-Italic-PSN4QKYX.woff +0 -0
- package/dist/KaTeX_SansSerif-Italic-WTBAZBGY.ttf +0 -0
- package/dist/KaTeX_SansSerif-Regular-2TL3USAE.ttf +0 -0
- package/dist/KaTeX_SansSerif-Regular-OQCII6EP.woff +0 -0
- package/dist/KaTeX_SansSerif-Regular-XIQ62X4E.woff2 +0 -0
- package/dist/KaTeX_Script-Regular-72OLXYNA.ttf +0 -0
- package/dist/KaTeX_Script-Regular-A5IFOEBS.woff +0 -0
- package/dist/KaTeX_Script-Regular-APUWIHLP.woff2 +0 -0
- package/dist/KaTeX_Size1-Regular-4HRHTS65.woff +0 -0
- package/dist/KaTeX_Size1-Regular-5LRUTBFT.woff2 +0 -0
- package/dist/KaTeX_Size1-Regular-7K6AASVL.ttf +0 -0
- package/dist/KaTeX_Size2-Regular-222HN3GT.ttf +0 -0
- package/dist/KaTeX_Size2-Regular-K5ZHAIS6.woff +0 -0
- package/dist/KaTeX_Size2-Regular-LELKET5D.woff2 +0 -0
- package/dist/KaTeX_Size3-Regular-TLFPAHDE.woff +0 -0
- package/dist/KaTeX_Size3-Regular-UFCO6WCA.ttf +0 -0
- package/dist/KaTeX_Size3-Regular-WQRQ47UD.woff2 +0 -0
- package/dist/KaTeX_Size4-Regular-7PGNVPQK.ttf +0 -0
- package/dist/KaTeX_Size4-Regular-CDMV7U5C.woff2 +0 -0
- package/dist/KaTeX_Size4-Regular-PKMWZHNC.woff +0 -0
- package/dist/KaTeX_Typewriter-Regular-3F5K6SQ6.ttf +0 -0
- package/dist/KaTeX_Typewriter-Regular-MJMFSK64.woff +0 -0
- package/dist/KaTeX_Typewriter-Regular-VBYJ4NRC.woff2 +0 -0
- package/dist/index.css +2156 -603
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +126 -92
- package/dist/index.js +1605 -976
- package/dist/index.js.map +1 -1
- package/dist/style.css +130 -0
- package/package.json +3 -3
- package/src/components/ChatPanel.tsx +82 -19
- package/src/components/common/SettingsPanel.css +81 -0
- package/src/components/common/SettingsPanel.tsx +96 -1
- package/src/components/input/ChatInput.css +0 -1
- package/src/components/input/ChatInput.tsx +48 -26
- package/src/components/input/DropdownSelector.css +66 -0
- package/src/components/input/DropdownSelector.tsx +157 -19
- package/src/components/message/MessageBubble.css +5 -2
- package/src/components/message/MessageBubble.tsx +44 -35
- package/src/components/message/PartsRenderer.css +8 -0
- package/src/components/message/PartsRenderer.tsx +137 -83
- package/src/components/message/parts/CollapsibleCard.css +4 -2
- package/src/components/message/parts/CollapsibleCard.tsx +4 -1
- package/src/components/message/parts/ImagePart.css +0 -1
- package/src/components/message/parts/TextPart.css +574 -5
- package/src/components/message/parts/TextPart.tsx +201 -8
- package/src/components/message/parts/ToolCallPart.css +139 -115
- package/src/components/message/parts/ToolCallPart.tsx +138 -134
- package/src/components/message/parts/ToolResultPart.css +0 -1
- package/src/components/message/parts/index.ts +3 -1
- package/src/components/message/parts/visual-predicate.ts +43 -0
- package/src/components/message/parts/visual-render.ts +19 -0
- package/src/components/message/parts/visual.ts +12 -0
- package/src/context/RenderersContext.tsx +19 -25
- package/src/hooks/useChat.ts +567 -79
- package/src/hooks/useImageUpload.ts +104 -12
- package/src/hooks/useVoiceInput.ts +17 -0
- package/src/index.ts +19 -16
- package/src/styles.css +130 -0
- package/src/types/index.ts +52 -68
- package/src/components/message/ContentRenderer.tsx +0 -63
- package/src/components/message/ToolResultRenderer.tsx +0 -21
- package/src/components/message/blocks/CodeBlock.tsx +0 -60
- package/src/components/message/blocks/TextBlock.tsx +0 -15
- package/src/components/message/blocks/blocks.css +0 -141
- package/src/components/message/blocks/index.ts +0 -6
- package/src/components/message/parts/ToolResultPart.tsx +0 -96
- package/src/components/message/tool-results/DefaultToolResult.tsx +0 -26
- package/src/components/message/tool-results/SearchResults.tsx +0 -69
- package/src/components/message/tool-results/WeatherCard.tsx +0 -63
- package/src/components/message/tool-results/index.ts +0 -7
- 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
|
|
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
|
-
|
|
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
|
-
|
|
177
|
-
|
|
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(
|
|
305
|
+
const savedMessages = await adapter.getMessages(firstVisible.id);
|
|
180
306
|
state.messages = savedMessages.map(convertToMessage);
|
|
181
307
|
forceUpdate();
|
|
182
308
|
}
|
|
183
|
-
setModeState(
|
|
184
|
-
setModelState(
|
|
185
|
-
setWebSearchState(
|
|
186
|
-
setThinkingState(
|
|
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
|
-
|
|
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.
|
|
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"
|
|
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.
|
|
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"
|
|
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
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
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 === "
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
840
|
+
promptMessage,
|
|
654
841
|
{
|
|
655
842
|
mode: sendMode,
|
|
656
843
|
model: sendModel,
|
|
657
844
|
enableWebSearch: sendWebSearch,
|
|
658
845
|
thinkingMode: sendThinking ? "enabled" : "disabled",
|
|
659
|
-
|
|
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
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
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
|
-
|
|
680
|
-
|
|
681
|
-
|
|
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
|
|
883
|
+
await saveMessageToDb(sessionId, assistantMsgIndex, assistantMsgId);
|
|
691
884
|
state.abortController = null;
|
|
885
|
+
state.activeAssistantMessageId = null;
|
|
692
886
|
forceUpdate();
|
|
693
887
|
}
|
|
694
|
-
}, [adapter, getSessionState,
|
|
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
|
|
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
|
|
1095
|
+
const userIndex = messageIndex - 1;
|
|
1096
|
+
const userMsg = state.messages[userIndex];
|
|
737
1097
|
const userText = extractTextContent(userMsg.parts);
|
|
738
|
-
|
|
739
|
-
forceUpdate();
|
|
740
|
-
sendMessage(userText, userMsg.images);
|
|
1098
|
+
void resendFromIndex(userIndex, userText);
|
|
741
1099
|
}
|
|
742
|
-
}, [
|
|
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
|
|
835
|
-
var
|
|
836
|
-
|
|
837
|
-
blockRenderers = {},
|
|
838
|
-
toolRenderers = {},
|
|
1188
|
+
var PartRenderersContext = createContext2({});
|
|
1189
|
+
var PartRenderersProvider = ({
|
|
1190
|
+
partRenderers = {},
|
|
839
1191
|
children
|
|
840
1192
|
}) => {
|
|
841
|
-
return /* @__PURE__ */ jsx2(
|
|
1193
|
+
return /* @__PURE__ */ jsx2(PartRenderersContext.Provider, { value: partRenderers, children });
|
|
842
1194
|
};
|
|
843
1195
|
|
|
844
1196
|
// src/components/ChatPanel.tsx
|
|
845
|
-
import { useEffect as
|
|
846
|
-
import { Icon as
|
|
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
|
|
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(
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1247
|
-
|
|
1248
|
-
"
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
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
|
|
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] =
|
|
1271
|
-
|
|
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
|
|
1292
|
-
import { jsx as jsx8, jsxs as
|
|
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] =
|
|
1304
|
-
|
|
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__ */
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
1355
|
-
const
|
|
1356
|
-
const
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1962
|
+
const selectOption = useCallback4(
|
|
1395
1963
|
(optValue) => {
|
|
1396
1964
|
onSelect?.(optValue);
|
|
1397
1965
|
setMenuOpen(false);
|
|
1398
1966
|
},
|
|
1399
1967
|
[onSelect]
|
|
1400
1968
|
);
|
|
1401
|
-
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
1431
|
-
"
|
|
2036
|
+
groupItems.map((opt) => /* @__PURE__ */ jsx9(
|
|
2037
|
+
"div",
|
|
1432
2038
|
{
|
|
1433
|
-
className:
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
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__ */
|
|
1446
|
-
"
|
|
2062
|
+
sortedOptions.map((opt) => /* @__PURE__ */ jsx9(
|
|
2063
|
+
"div",
|
|
1447
2064
|
{
|
|
1448
|
-
className:
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
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
|
|
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] =
|
|
1478
|
-
const handleCopy =
|
|
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 {
|
|
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
|
|
2203
|
+
const argsType = useMemo4(() => {
|
|
1551
2204
|
if (name === "execute_command" && args?.command) {
|
|
1552
|
-
|
|
1553
|
-
const parts = cmd.trim().split(/\s+/);
|
|
1554
|
-
return parts.slice(0, 3).join(" ") + (parts.length > 3 ? "..." : "");
|
|
2205
|
+
return "command";
|
|
1555
2206
|
}
|
|
1556
|
-
|
|
2207
|
+
if (args && Object.keys(args).length > 0) {
|
|
2208
|
+
return "json";
|
|
2209
|
+
}
|
|
2210
|
+
return "text";
|
|
1557
2211
|
}, [name, args]);
|
|
1558
|
-
const
|
|
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
|
|
1572
|
-
if (
|
|
1573
|
-
|
|
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
|
-
|
|
1580
|
-
|
|
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
|
-
|
|
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 =
|
|
2252
|
+
const handleModeChange = useCallback6(async (value) => {
|
|
1603
2253
|
if (!onSaveConfig || !autoRunConfig) return;
|
|
1604
|
-
|
|
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 =
|
|
1614
|
-
|
|
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 =
|
|
1622
|
-
|
|
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 =
|
|
2262
|
+
const handleCancel = useCallback6(() => {
|
|
2263
|
+
if (onCancelToolCall) {
|
|
2264
|
+
onCancelToolCall(id);
|
|
2265
|
+
return;
|
|
2266
|
+
}
|
|
1630
2267
|
adapter?.cancel?.();
|
|
1631
|
-
}, [adapter]);
|
|
1632
|
-
const
|
|
1633
|
-
|
|
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__ */
|
|
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:
|
|
1650
|
-
children:
|
|
1651
|
-
/* @__PURE__ */
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
/* @__PURE__ */
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
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("
|
|
2347
|
+
/* @__PURE__ */ jsx11("pre", { className: "output-body chat-scrollbar", children: /* @__PURE__ */ jsx11("code", { children: activeOutputText || "(\u65E0\u8F93\u51FA)" }) })
|
|
1664
2348
|
] }),
|
|
1665
|
-
/* @__PURE__ */
|
|
1666
|
-
/* @__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: "
|
|
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__ */
|
|
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
|
-
/*
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
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
|
|
1767
|
-
import { jsx as
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
1798
|
-
/* @__PURE__ */
|
|
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
|
|
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__ */
|
|
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__ */
|
|
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
|
|
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
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
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
|
|
1922
|
-
import { Icon as
|
|
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
|
|
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
|
|
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
|
|
1969
|
-
import { Icon as
|
|
1970
|
-
import { jsx as
|
|
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 =
|
|
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
|
-
|
|
2625
|
+
useEffect8(() => {
|
|
1990
2626
|
onUpdateCount(filteredEntries.length);
|
|
1991
2627
|
}, [filteredEntries.length, onUpdateCount]);
|
|
1992
|
-
const loadDir =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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__ */
|
|
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__ */
|
|
2053
|
-
/* @__PURE__ */
|
|
2054
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
2064
|
-
/* @__PURE__ */
|
|
2065
|
-
/* @__PURE__ */
|
|
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
|
|
2077
|
-
import { jsx as
|
|
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__ */
|
|
2086
|
-
/* @__PURE__ */
|
|
2087
|
-
/* @__PURE__ */
|
|
2088
|
-
/* @__PURE__ */
|
|
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
|
|
2096
|
-
import { jsx as
|
|
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__ */
|
|
2105
|
-
/* @__PURE__ */
|
|
2106
|
-
/* @__PURE__ */
|
|
2107
|
-
/* @__PURE__ */
|
|
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
|
|
2115
|
-
import { jsx as
|
|
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__ */
|
|
2124
|
-
/* @__PURE__ */
|
|
2125
|
-
/* @__PURE__ */
|
|
2126
|
-
/* @__PURE__ */
|
|
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
|
|
2134
|
-
import { jsx as
|
|
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__ */
|
|
2143
|
-
/* @__PURE__ */
|
|
2144
|
-
/* @__PURE__ */
|
|
2145
|
-
/* @__PURE__ */
|
|
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
|
|
2153
|
-
import { jsx as
|
|
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__ */
|
|
2162
|
-
/* @__PURE__ */
|
|
2163
|
-
/* @__PURE__ */
|
|
2164
|
-
/* @__PURE__ */
|
|
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
|
|
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 =
|
|
2192
|
-
const bodyRef =
|
|
2193
|
-
const viewRef =
|
|
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 =
|
|
2203
|
-
const updateDropdownPosition =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
2942
|
+
const selectPath = useCallback8((path) => {
|
|
2307
2943
|
pushRecent(path);
|
|
2308
2944
|
onSelect(path);
|
|
2309
2945
|
}, [pushRecent, onSelect]);
|
|
2310
|
-
const handleScroll =
|
|
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 =
|
|
2956
|
+
const handleSetActiveKey = useCallback8((key) => {
|
|
2321
2957
|
if (isScrolling) return;
|
|
2322
2958
|
setActiveKey(key);
|
|
2323
2959
|
}, [isScrolling]);
|
|
2324
|
-
const handleSetViewActiveIndex =
|
|
2960
|
+
const handleSetViewActiveIndex = useCallback8((index) => {
|
|
2325
2961
|
if (isScrolling) return;
|
|
2326
2962
|
setViewActiveIndex(index);
|
|
2327
2963
|
}, [isScrolling]);
|
|
2328
|
-
const scrollToActive =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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__ */
|
|
3103
|
+
return /* @__PURE__ */ jsx21(AtFilesView, { ref: viewRef, adapter, initialDir, ...commonProps });
|
|
2468
3104
|
case "docs":
|
|
2469
|
-
return /* @__PURE__ */
|
|
3105
|
+
return /* @__PURE__ */ jsx21(AtDocsView, { ref: viewRef, ...commonProps });
|
|
2470
3106
|
case "terminals":
|
|
2471
|
-
return /* @__PURE__ */
|
|
3107
|
+
return /* @__PURE__ */ jsx21(AtTerminalsView, { ref: viewRef, ...commonProps });
|
|
2472
3108
|
case "chats":
|
|
2473
|
-
return /* @__PURE__ */
|
|
3109
|
+
return /* @__PURE__ */ jsx21(AtChatsView, { ref: viewRef, ...commonProps });
|
|
2474
3110
|
case "branch":
|
|
2475
|
-
return /* @__PURE__ */
|
|
3111
|
+
return /* @__PURE__ */ jsx21(AtBranchView, { ref: viewRef, ...commonProps });
|
|
2476
3112
|
case "browser":
|
|
2477
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
2486
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
2518
|
-
/* @__PURE__ */
|
|
2519
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
2532
|
-
/* @__PURE__ */
|
|
2533
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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
|
|
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
|
|
2554
|
-
import { jsx as
|
|
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
|
-
|
|
3199
|
+
useEffect10(() => {
|
|
2564
3200
|
if (visible) {
|
|
2565
3201
|
setCurrentIndex(initialIndex);
|
|
2566
3202
|
}
|
|
2567
3203
|
}, [visible, initialIndex]);
|
|
2568
|
-
|
|
3204
|
+
useEffect10(() => {
|
|
2569
3205
|
onIndexChange?.(currentIndex);
|
|
2570
3206
|
}, [currentIndex, onIndexChange]);
|
|
2571
|
-
const prev =
|
|
3207
|
+
const prev = useCallback9(() => {
|
|
2572
3208
|
if (currentIndex > 0) {
|
|
2573
3209
|
setCurrentIndex(currentIndex - 1);
|
|
2574
3210
|
}
|
|
2575
3211
|
}, [currentIndex]);
|
|
2576
|
-
const next =
|
|
3212
|
+
const next = useCallback9(() => {
|
|
2577
3213
|
if (currentIndex < images.length - 1) {
|
|
2578
3214
|
setCurrentIndex(currentIndex + 1);
|
|
2579
3215
|
}
|
|
2580
3216
|
}, [currentIndex, images.length]);
|
|
2581
|
-
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
3252
|
+
children: /* @__PURE__ */ jsx22(Icon15, { icon: "lucide:chevron-left", width: 20 })
|
|
2617
3253
|
}
|
|
2618
3254
|
),
|
|
2619
|
-
/* @__PURE__ */
|
|
2620
|
-
images.length > 1 && /* @__PURE__ */
|
|
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__ */
|
|
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
|
|
3274
|
+
import { useState as useState14, useCallback as useCallback10, useMemo as useMemo8 } from "react";
|
|
2639
3275
|
function useImageUpload(options = {}) {
|
|
2640
|
-
const {
|
|
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
|
|
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
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
3398
|
+
const handleDragLeave = useCallback10(() => {
|
|
2728
3399
|
setIsDragOver(false);
|
|
2729
3400
|
}, []);
|
|
2730
|
-
const handleDrop =
|
|
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 =
|
|
3413
|
+
const openPreview = useCallback10((index) => {
|
|
2743
3414
|
setPreviewIndex(index);
|
|
2744
3415
|
setPreviewVisible(true);
|
|
2745
3416
|
}, []);
|
|
2746
|
-
const closePreview =
|
|
3417
|
+
const closePreview = useCallback10(() => {
|
|
2747
3418
|
setPreviewVisible(false);
|
|
2748
3419
|
}, []);
|
|
2749
|
-
const removeImage =
|
|
3420
|
+
const removeImage = useCallback10((index) => {
|
|
2750
3421
|
setImages((prev) => prev.filter((_, i) => i !== index));
|
|
2751
3422
|
}, []);
|
|
2752
|
-
const clearImages =
|
|
3423
|
+
const clearImages = useCallback10(() => {
|
|
2753
3424
|
setImages([]);
|
|
2754
3425
|
}, []);
|
|
2755
|
-
const addImages =
|
|
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
|
|
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
|
|
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 =
|
|
2902
|
-
const currentTextRef =
|
|
2903
|
-
const finalTextRef =
|
|
2904
|
-
const mediaStreamRef =
|
|
2905
|
-
const audioContextRef =
|
|
2906
|
-
const workletCleanupRef =
|
|
2907
|
-
const sourceRef =
|
|
2908
|
-
const audioBufferRef =
|
|
2909
|
-
const sendTimerRef =
|
|
2910
|
-
const cleanupFnsRef =
|
|
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
|
-
|
|
3617
|
+
useEffect11(() => {
|
|
2913
3618
|
statusRef.current = status;
|
|
2914
3619
|
}, [status]);
|
|
2915
|
-
|
|
3620
|
+
useEffect11(() => {
|
|
2916
3621
|
currentTextRef.current = currentText;
|
|
2917
3622
|
}, [currentText]);
|
|
2918
|
-
|
|
3623
|
+
useEffect11(() => {
|
|
2919
3624
|
finalTextRef.current = finalText;
|
|
2920
3625
|
}, [finalText]);
|
|
2921
|
-
const setStatusSafe =
|
|
3626
|
+
const setStatusSafe = useCallback11((next) => {
|
|
2922
3627
|
statusRef.current = next;
|
|
2923
3628
|
setStatus(next);
|
|
2924
3629
|
}, []);
|
|
2925
|
-
const cleanup =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
3841
|
+
const prefixRef = useRef9("");
|
|
3137
3842
|
const isVoiceActive = useMemo10(
|
|
3138
3843
|
() => voiceInput.status === "connecting" || voiceInput.status === "recording",
|
|
3139
3844
|
[voiceInput.status]
|
|
3140
3845
|
);
|
|
3141
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
3222
|
-
const inputRef =
|
|
3223
|
-
const containerRef =
|
|
3224
|
-
const atSelectorRef =
|
|
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 =
|
|
3227
|
-
const pendingCaretRef =
|
|
3228
|
-
const pendingFocusRef =
|
|
3229
|
-
const prevValueRef =
|
|
3230
|
-
const triggerImageUpload =
|
|
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
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
|
3383
|
-
onSend?.(text,
|
|
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 =
|
|
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
|
-
|
|
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__ */
|
|
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__ */
|
|
3433
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
4165
|
+
children: /* @__PURE__ */ jsx23(Icon16, { icon: "lucide:x", width: 10 })
|
|
3452
4166
|
}
|
|
3453
4167
|
)
|
|
3454
4168
|
] }, i)) }),
|
|
3455
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
4222
|
+
/* @__PURE__ */ jsx23(
|
|
3509
4223
|
DropdownSelector,
|
|
3510
4224
|
{
|
|
3511
4225
|
value: model,
|
|
3512
|
-
|
|
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__ */
|
|
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__ */
|
|
4238
|
+
children: /* @__PURE__ */ jsx23(Icon16, { icon: "lucide:sparkles", width: 18 })
|
|
3525
4239
|
}
|
|
3526
4240
|
),
|
|
3527
|
-
/* @__PURE__ */
|
|
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__ */
|
|
4247
|
+
children: /* @__PURE__ */ jsx23(Icon16, { icon: "lucide:globe", width: 18 })
|
|
3534
4248
|
}
|
|
3535
4249
|
),
|
|
3536
|
-
/* @__PURE__ */
|
|
3537
|
-
/* @__PURE__ */
|
|
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__ */
|
|
3559
|
-
adapter && /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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
|
|
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 === "
|
|
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:
|
|
4370
|
+
return { type: "text", text: waitingText };
|
|
3655
4371
|
}
|
|
3656
|
-
const
|
|
3657
|
-
|
|
3658
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3674
|
-
|
|
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: "
|
|
3680
|
-
}, [loading, parts]);
|
|
3681
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
3700
|
-
images && images.length > 0 && /* @__PURE__ */
|
|
3701
|
-
formattedTime && /* @__PURE__ */
|
|
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__ */
|
|
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:
|
|
3717
|
-
/* @__PURE__ */
|
|
3718
|
-
/* @__PURE__ */
|
|
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__ */
|
|
3724
|
-
mode && /* @__PURE__ */
|
|
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__ */
|
|
3728
|
-
/* @__PURE__ */
|
|
3729
|
-
/* @__PURE__ */
|
|
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
|
|
4454
|
+
import { useEffect as useEffect14 } from "react";
|
|
3738
4455
|
import { createPortal as createPortal3 } from "react-dom";
|
|
3739
|
-
import { Icon as
|
|
3740
|
-
import { jsx as
|
|
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
|
-
|
|
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
|
-
|
|
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__ */
|
|
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__ */
|
|
3780
|
-
/* @__PURE__ */
|
|
4496
|
+
/* @__PURE__ */ jsx25(Icon18, { icon: iconMap[type], className: `icon ${type}`, width: 18 }),
|
|
4497
|
+
/* @__PURE__ */ jsx25("span", { className: "title", children: title })
|
|
3781
4498
|
] }),
|
|
3782
|
-
/* @__PURE__ */
|
|
4499
|
+
/* @__PURE__ */ jsx25("div", { className: "confirm-dialog-content", children: message }),
|
|
3783
4500
|
/* @__PURE__ */ jsxs19("div", { className: "confirm-dialog-footer", children: [
|
|
3784
|
-
/* @__PURE__ */
|
|
3785
|
-
/* @__PURE__ */
|
|
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
|
|
4510
|
+
import { useEffect as useEffect15 } from "react";
|
|
3794
4511
|
import { createPortal as createPortal4 } from "react-dom";
|
|
3795
|
-
import { jsx as
|
|
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
|
-
|
|
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__ */
|
|
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
|
|
3820
|
-
import { Icon as
|
|
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
|
|
3824
|
-
import { Icon as
|
|
3825
|
-
import { jsx as jsx28, jsxs as
|
|
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 =
|
|
3838
|
-
const formatSize =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
3884
|
-
const setupProgressListener =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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__ */
|
|
4043
|
-
/* @__PURE__ */ jsx28("div", { className: "setting-item indexing-section", children: /* @__PURE__ */
|
|
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__ */
|
|
4047
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
4792
|
+
] }) : /* @__PURE__ */ jsxs21("span", { children: [
|
|
4060
4793
|
progressPercentage,
|
|
4061
4794
|
"% (",
|
|
4062
4795
|
progressIndexed,
|
|
4063
4796
|
"/",
|
|
4064
4797
|
progressTotal,
|
|
4065
4798
|
")"
|
|
4066
|
-
] }) : /* @__PURE__ */
|
|
4799
|
+
] }) : /* @__PURE__ */ jsxs21("span", { children: [
|
|
4067
4800
|
indexedFiles.toLocaleString(),
|
|
4068
4801
|
" \u4E2A\u6587\u4EF6"
|
|
4069
4802
|
] }) })
|
|
4070
4803
|
] }),
|
|
4071
|
-
!isIndexing && indexedFiles > 0 && /* @__PURE__ */
|
|
4072
|
-
/* @__PURE__ */
|
|
4073
|
-
/* @__PURE__ */ jsx28(
|
|
4074
|
-
/* @__PURE__ */
|
|
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__ */
|
|
4080
|
-
/* @__PURE__ */ jsx28(
|
|
4081
|
-
/* @__PURE__ */
|
|
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__ */
|
|
4088
|
-
/* @__PURE__ */ jsx28(
|
|
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__ */
|
|
4092
|
-
!isIndexing ? /* @__PURE__ */
|
|
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(
|
|
4831
|
+
/* @__PURE__ */ jsx28(Icon19, { icon: "lucide:refresh-cw", width: 16 }),
|
|
4099
4832
|
/* @__PURE__ */ jsx28("span", { children: "\u540C\u6B65" })
|
|
4100
4833
|
]
|
|
4101
4834
|
}
|
|
4102
|
-
) : /* @__PURE__ */
|
|
4835
|
+
) : /* @__PURE__ */ jsxs21(
|
|
4103
4836
|
"button",
|
|
4104
4837
|
{
|
|
4105
4838
|
className: "edit-btn",
|
|
4106
4839
|
onClick: handleCancelIndex,
|
|
4107
4840
|
children: [
|
|
4108
|
-
/* @__PURE__ */ jsx28(
|
|
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__ */
|
|
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(
|
|
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
|
|
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 =
|
|
4895
|
+
const updateConfig = useCallback15((key, value) => {
|
|
4163
4896
|
onChange({ ...config, [key]: value });
|
|
4164
4897
|
}, [config, onChange]);
|
|
4165
|
-
const handleModeChange =
|
|
4898
|
+
const handleModeChange = useCallback15((value) => {
|
|
4166
4899
|
updateConfig("mode", value);
|
|
4167
4900
|
}, [updateConfig]);
|
|
4168
|
-
const handleOverlayClick =
|
|
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__ */
|
|
4175
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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(
|
|
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__ */
|
|
4191
|
-
/* @__PURE__ */
|
|
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(
|
|
4957
|
+
/* @__PURE__ */ jsx29("button", { className: "close-btn", onClick: onClose, children: /* @__PURE__ */ jsx29(Icon20, { icon: "lucide:x", width: 20 }) })
|
|
4194
4958
|
] }),
|
|
4195
|
-
/* @__PURE__ */
|
|
4196
|
-
currentSection === "agent" && /* @__PURE__ */
|
|
4197
|
-
/* @__PURE__ */
|
|
4198
|
-
/* @__PURE__ */
|
|
4199
|
-
|
|
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__ */
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
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
|
|
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
|
-
|
|
5032
|
+
partRenderers = {},
|
|
4230
5033
|
stepsExpandedType = "auto"
|
|
4231
5034
|
}, ref) => {
|
|
4232
|
-
const messagesRef =
|
|
4233
|
-
const inputRef =
|
|
5035
|
+
const messagesRef = useRef12(null);
|
|
5036
|
+
const inputRef = useRef12(null);
|
|
4234
5037
|
const [shouldAutoScroll, setShouldAutoScroll] = useState19(true);
|
|
4235
|
-
const SCROLL_THRESHOLD =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
5123
|
+
useEffect17(() => {
|
|
4311
5124
|
loadSessions();
|
|
4312
5125
|
}, [loadSessions]);
|
|
4313
|
-
|
|
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 =
|
|
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 =
|
|
4322
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
5161
|
+
useEffect17(() => {
|
|
4331
5162
|
scrollToBottom();
|
|
4332
5163
|
}, [messages, scrollToBottom]);
|
|
4333
|
-
const prevIsLoadingRef =
|
|
4334
|
-
|
|
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 =
|
|
4341
|
-
|
|
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 =
|
|
5175
|
+
const handleAtContext = useCallback16(() => {
|
|
4344
5176
|
console.log("@ \u4E0A\u4E0B\u6587");
|
|
4345
5177
|
}, []);
|
|
4346
|
-
const handleQuickAction =
|
|
5178
|
+
const handleQuickAction = useCallback16(
|
|
4347
5179
|
(text) => {
|
|
4348
5180
|
sendMessage(text);
|
|
4349
5181
|
},
|
|
4350
5182
|
[sendMessage]
|
|
4351
5183
|
);
|
|
4352
|
-
const handleResend =
|
|
4353
|
-
(
|
|
4354
|
-
|
|
5184
|
+
const handleResend = useCallback16(
|
|
5185
|
+
(index, text) => {
|
|
5186
|
+
resendFromIndex(index, text);
|
|
4355
5187
|
},
|
|
4356
|
-
[
|
|
5188
|
+
[resendFromIndex]
|
|
4357
5189
|
);
|
|
4358
|
-
const
|
|
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 =
|
|
5197
|
+
const handleSettings = useCallback16(() => {
|
|
4362
5198
|
setSettingsPanelVisible(true);
|
|
4363
5199
|
}, []);
|
|
4364
|
-
const handleSaveSettings =
|
|
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
|
|
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 =
|
|
5225
|
+
const handleCloseOthers = useCallback16(async () => {
|
|
4382
5226
|
await hideOtherSessions();
|
|
4383
5227
|
}, [hideOtherSessions]);
|
|
4384
|
-
const handleExport =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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:
|
|
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__ */
|
|
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(
|
|
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
|
|
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
|
-
|
|
4770
|
-
|
|
5397
|
+
ErrorPart as ErrorPartComponent,
|
|
5398
|
+
ImagePart as ImagePartComponent,
|
|
4771
5399
|
MessageBubble,
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
5400
|
+
PartRenderersContext,
|
|
5401
|
+
PartRenderersProvider,
|
|
5402
|
+
PartsRenderer,
|
|
5403
|
+
SearchPart as SearchPartComponent,
|
|
5404
|
+
TextPart as TextPartComponent,
|
|
5405
|
+
ThinkingPart as ThinkingPartComponent,
|
|
4775
5406
|
Toast,
|
|
4776
|
-
|
|
4777
|
-
ToolResultRenderer,
|
|
4778
|
-
WeatherCard,
|
|
5407
|
+
ToolCallPart as ToolCallPartComponent,
|
|
4779
5408
|
WelcomeMessage,
|
|
4780
5409
|
defaultWelcomeConfig,
|
|
4781
|
-
|
|
5410
|
+
getLanguageDisplayName,
|
|
4782
5411
|
getMessageText,
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
5412
|
+
highlightCode3 as highlightCode,
|
|
5413
|
+
parseContent,
|
|
5414
|
+
renderMarkdown3 as renderMarkdown,
|
|
4786
5415
|
useChat,
|
|
4787
5416
|
useChatInputContext
|
|
4788
5417
|
};
|