@huyooo/ai-chat-bridge-electron 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main/index.cjs +243 -23
- package/dist/main/index.d.cts +31 -5
- package/dist/main/index.d.ts +31 -5
- package/dist/main/index.js +235 -24
- package/dist/preload/index.cjs +58 -4
- package/dist/preload/index.d.cts +152 -19
- package/dist/preload/index.d.ts +152 -19
- package/dist/preload/index.js +58 -4
- package/dist/renderer/index.cjs +90 -17
- package/dist/renderer/index.d.cts +282 -46
- package/dist/renderer/index.d.ts +282 -46
- package/dist/renderer/index.js +90 -17
- package/package.json +9 -4
- package/src/main/index.ts +611 -0
- package/src/preload/index.ts +457 -0
- package/src/renderer/index.ts +507 -0
package/dist/main/index.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
// src/main/index.ts
|
|
2
|
-
import { ipcMain } from "electron";
|
|
2
|
+
import { ipcMain, shell } from "electron";
|
|
3
|
+
import * as fs from "fs";
|
|
4
|
+
import * as path from "path";
|
|
3
5
|
import {
|
|
4
6
|
HybridAgent,
|
|
5
|
-
|
|
7
|
+
MODELS,
|
|
8
|
+
DEFAULT_MODEL
|
|
6
9
|
} from "@huyooo/ai-chat-core";
|
|
10
|
+
import { createSearchElectronBridge } from "@huyooo/ai-search/bridge/electron";
|
|
7
11
|
import {
|
|
8
12
|
createStorage,
|
|
9
13
|
getDefaultStoragePath
|
|
@@ -11,56 +15,137 @@ import {
|
|
|
11
15
|
async function createElectronBridge(options) {
|
|
12
16
|
const {
|
|
13
17
|
channelPrefix = "ai-chat",
|
|
18
|
+
dataDir,
|
|
14
19
|
storagePath,
|
|
15
20
|
defaultContext = {},
|
|
16
21
|
...agentConfig
|
|
17
22
|
} = options;
|
|
18
|
-
const
|
|
23
|
+
const resolvedStoragePath = storagePath || (dataDir ? `${dataDir}/db.sqlite` : void 0);
|
|
24
|
+
createSearchElectronBridge({
|
|
25
|
+
ipcMain,
|
|
26
|
+
channelPrefix
|
|
27
|
+
}).init();
|
|
28
|
+
const pendingApprovals = /* @__PURE__ */ new Map();
|
|
29
|
+
const toolApprovalCallback = async (toolCall) => {
|
|
30
|
+
console.log("[Main] \u5DE5\u5177\u6279\u51C6\u56DE\u8C03\u88AB\u8C03\u7528:", toolCall.name);
|
|
31
|
+
const currentWebContents = global.currentWebContents;
|
|
32
|
+
if (!currentWebContents) {
|
|
33
|
+
console.log("[Main] \u8B66\u544A: \u6CA1\u6709 webContents\uFF0C\u9ED8\u8BA4\u6279\u51C6");
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
return new Promise((resolve2, reject) => {
|
|
37
|
+
pendingApprovals.set(toolCall.id, {
|
|
38
|
+
resolve: resolve2,
|
|
39
|
+
reject,
|
|
40
|
+
webContents: currentWebContents
|
|
41
|
+
});
|
|
42
|
+
console.log("[Main] \u53D1\u9001\u5DE5\u5177\u6279\u51C6\u8BF7\u6C42\u5230\u524D\u7AEF:", toolCall.name);
|
|
43
|
+
currentWebContents.send(`${channelPrefix}:toolApprovalRequest`, {
|
|
44
|
+
id: toolCall.id,
|
|
45
|
+
name: toolCall.name,
|
|
46
|
+
args: toolCall.args
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
};
|
|
19
50
|
const storage = await createStorage({
|
|
20
51
|
type: "sqlite",
|
|
21
|
-
sqlitePath:
|
|
52
|
+
sqlitePath: resolvedStoragePath || getDefaultStoragePath()
|
|
22
53
|
});
|
|
23
54
|
const getContext = () => defaultContext;
|
|
55
|
+
const getAutoRunConfig = async () => {
|
|
56
|
+
try {
|
|
57
|
+
const configJson = await storage.getUserSetting("autoRunConfig", getContext());
|
|
58
|
+
if (configJson) {
|
|
59
|
+
return JSON.parse(configJson);
|
|
60
|
+
}
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error("[Main] \u83B7\u53D6 autoRunConfig \u5931\u8D25:", error);
|
|
63
|
+
}
|
|
64
|
+
return void 0;
|
|
65
|
+
};
|
|
66
|
+
const agent = new HybridAgent({
|
|
67
|
+
...agentConfig,
|
|
68
|
+
// tools 直接传递 ToolConfigItem[],HybridAgent 会在 asyncInit 中解析
|
|
69
|
+
tools: options.tools,
|
|
70
|
+
onToolApprovalRequest: toolApprovalCallback,
|
|
71
|
+
getAutoRunConfig
|
|
72
|
+
});
|
|
24
73
|
ipcMain.handle(`${channelPrefix}:models`, () => {
|
|
25
|
-
return
|
|
74
|
+
return MODELS;
|
|
26
75
|
});
|
|
27
76
|
ipcMain.handle(`${channelPrefix}:send`, async (event, params) => {
|
|
28
77
|
const webContents = event.sender;
|
|
29
78
|
const { message, images, options: options2 = {}, sessionId } = params;
|
|
79
|
+
global.currentWebContents = webContents;
|
|
30
80
|
console.log("[AI-Chat] \u6536\u5230\u6D88\u606F:", { message, options: options2, images: images?.length || 0, sessionId });
|
|
81
|
+
console.log("[AI-Chat] autoRunConfig:", JSON.stringify(options2.autoRunConfig, null, 2));
|
|
31
82
|
try {
|
|
32
|
-
for await (const
|
|
83
|
+
for await (const chatEvent of agent.chat(message, options2, images)) {
|
|
33
84
|
console.log(
|
|
34
|
-
"[AI-Chat] \
|
|
35
|
-
|
|
36
|
-
|
|
85
|
+
"[AI-Chat] \u4E8B\u4EF6:",
|
|
86
|
+
chatEvent.type,
|
|
87
|
+
chatEvent.type === "text_delta" ? chatEvent.data.content.slice(0, 20) : chatEvent.type === "thinking_delta" ? chatEvent.data.content.slice(0, 20) : chatEvent.type === "search_result" ? `\u641C\u7D22\u5B8C\u6210 ${chatEvent.data.results?.length || 0} \u6761` : JSON.stringify(chatEvent.data).slice(0, 100)
|
|
37
88
|
);
|
|
38
89
|
if (!webContents.isDestroyed()) {
|
|
39
|
-
webContents.send(`${channelPrefix}:progress`,
|
|
90
|
+
webContents.send(`${channelPrefix}:progress`, { ...chatEvent, sessionId });
|
|
40
91
|
}
|
|
41
92
|
}
|
|
42
93
|
console.log("[AI-Chat] \u5B8C\u6210");
|
|
43
94
|
} catch (error) {
|
|
44
95
|
console.error("[AI-Chat] \u9519\u8BEF:", error);
|
|
96
|
+
if (error instanceof Error) {
|
|
97
|
+
console.error("[AI-Chat] \u9519\u8BEF\u8BE6\u60C5:", {
|
|
98
|
+
message: error.message,
|
|
99
|
+
stack: error.stack,
|
|
100
|
+
name: error.name
|
|
101
|
+
});
|
|
102
|
+
}
|
|
45
103
|
if (!webContents.isDestroyed()) {
|
|
104
|
+
const errorData = error instanceof Error ? {
|
|
105
|
+
category: "api",
|
|
106
|
+
message: error.message || String(error),
|
|
107
|
+
cause: error.stack
|
|
108
|
+
} : {
|
|
109
|
+
category: "api",
|
|
110
|
+
message: String(error)
|
|
111
|
+
};
|
|
46
112
|
webContents.send(`${channelPrefix}:progress`, {
|
|
47
113
|
type: "error",
|
|
48
|
-
data:
|
|
114
|
+
data: errorData,
|
|
115
|
+
sessionId
|
|
49
116
|
});
|
|
50
117
|
}
|
|
118
|
+
} finally {
|
|
119
|
+
delete global.currentWebContents;
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
ipcMain.handle(`${channelPrefix}:toolApprovalResponse`, (_event, params) => {
|
|
123
|
+
const { id, approved } = params;
|
|
124
|
+
const pending = pendingApprovals.get(id);
|
|
125
|
+
if (pending) {
|
|
126
|
+
pendingApprovals.delete(id);
|
|
127
|
+
pending.resolve(approved);
|
|
51
128
|
}
|
|
52
129
|
});
|
|
53
130
|
ipcMain.handle(`${channelPrefix}:cancel`, () => {
|
|
54
131
|
agent.abort();
|
|
55
132
|
});
|
|
56
|
-
ipcMain.handle(`${channelPrefix}:
|
|
57
|
-
|
|
133
|
+
ipcMain.handle(`${channelPrefix}:settings:get`, async (_event, key) => {
|
|
134
|
+
return storage.getUserSetting(key, getContext());
|
|
58
135
|
});
|
|
59
|
-
ipcMain.handle(`${channelPrefix}:
|
|
60
|
-
|
|
136
|
+
ipcMain.handle(`${channelPrefix}:settings:set`, async (_event, key, value) => {
|
|
137
|
+
await storage.setUserSetting(key, value, getContext());
|
|
138
|
+
return { success: true };
|
|
139
|
+
});
|
|
140
|
+
ipcMain.handle(`${channelPrefix}:settings:getAll`, async () => {
|
|
141
|
+
return storage.getUserSettings(getContext());
|
|
61
142
|
});
|
|
62
|
-
ipcMain.handle(`${channelPrefix}:
|
|
63
|
-
|
|
143
|
+
ipcMain.handle(`${channelPrefix}:settings:delete`, async (_event, key) => {
|
|
144
|
+
await storage.deleteUserSetting(key, getContext());
|
|
145
|
+
return { success: true };
|
|
146
|
+
});
|
|
147
|
+
ipcMain.handle(`${channelPrefix}:setCwd`, (_event, dir) => {
|
|
148
|
+
agent.setCwd(dir);
|
|
64
149
|
});
|
|
65
150
|
ipcMain.handle(`${channelPrefix}:config`, () => {
|
|
66
151
|
return agent.getConfig();
|
|
@@ -75,8 +160,11 @@ async function createElectronBridge(options) {
|
|
|
75
160
|
const input = {
|
|
76
161
|
id: params.id || crypto.randomUUID(),
|
|
77
162
|
title: params.title || "\u65B0\u5BF9\u8BDD",
|
|
78
|
-
model: params.model ||
|
|
79
|
-
mode: params.mode || "agent"
|
|
163
|
+
model: params.model || DEFAULT_MODEL,
|
|
164
|
+
mode: params.mode || "agent",
|
|
165
|
+
webSearchEnabled: params.webSearchEnabled ?? true,
|
|
166
|
+
thinkingEnabled: params.thinkingEnabled ?? true,
|
|
167
|
+
hidden: params.hidden ?? false
|
|
80
168
|
};
|
|
81
169
|
return storage.createSession(input, getContext());
|
|
82
170
|
});
|
|
@@ -93,17 +181,26 @@ async function createElectronBridge(options) {
|
|
|
93
181
|
});
|
|
94
182
|
ipcMain.handle(`${channelPrefix}:messages:save`, async (_event, params) => {
|
|
95
183
|
const input = {
|
|
96
|
-
id: crypto.randomUUID(),
|
|
184
|
+
id: params.id || crypto.randomUUID(),
|
|
97
185
|
sessionId: params.sessionId,
|
|
98
186
|
role: params.role,
|
|
99
187
|
content: params.content,
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
188
|
+
model: params.model || null,
|
|
189
|
+
mode: params.mode || null,
|
|
190
|
+
webSearchEnabled: params.webSearchEnabled ?? null,
|
|
191
|
+
thinkingEnabled: params.thinkingEnabled ?? null,
|
|
192
|
+
steps: params.steps || null,
|
|
103
193
|
operationIds: params.operationIds || null
|
|
104
194
|
};
|
|
105
195
|
return storage.saveMessage(input, getContext());
|
|
106
196
|
});
|
|
197
|
+
ipcMain.handle(`${channelPrefix}:messages:update`, async (_event, params) => {
|
|
198
|
+
await storage.updateMessage(params.id, {
|
|
199
|
+
content: params.content,
|
|
200
|
+
steps: params.steps
|
|
201
|
+
}, getContext());
|
|
202
|
+
return { success: true };
|
|
203
|
+
});
|
|
107
204
|
ipcMain.handle(`${channelPrefix}:messages:deleteAfter`, async (_event, sessionId, timestamp) => {
|
|
108
205
|
await storage.deleteMessagesAfter(sessionId, new Date(timestamp), getContext());
|
|
109
206
|
return { success: true };
|
|
@@ -117,9 +214,123 @@ async function createElectronBridge(options) {
|
|
|
117
214
|
ipcMain.handle(`${channelPrefix}:trash:restore`, async (_event, id) => {
|
|
118
215
|
return storage.restoreFromTrash?.(id, getContext());
|
|
119
216
|
});
|
|
217
|
+
ipcMain.handle(`${channelPrefix}:openExternal`, async (_event, url) => {
|
|
218
|
+
return shell.openExternal(url);
|
|
219
|
+
});
|
|
220
|
+
ipcMain.handle(`${channelPrefix}:fs:listDir`, async (_event, dirPath) => {
|
|
221
|
+
try {
|
|
222
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
223
|
+
const files = [];
|
|
224
|
+
for (const entry of entries) {
|
|
225
|
+
if (entry.name.startsWith(".")) continue;
|
|
226
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
227
|
+
try {
|
|
228
|
+
const stats = fs.statSync(fullPath);
|
|
229
|
+
files.push({
|
|
230
|
+
name: entry.name,
|
|
231
|
+
path: fullPath,
|
|
232
|
+
isDirectory: entry.isDirectory(),
|
|
233
|
+
size: stats.size,
|
|
234
|
+
modifiedAt: stats.mtime,
|
|
235
|
+
extension: entry.isDirectory() ? "" : path.extname(entry.name).toLowerCase()
|
|
236
|
+
});
|
|
237
|
+
} catch {
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return files.sort((a, b) => {
|
|
241
|
+
if (a.isDirectory && !b.isDirectory) return -1;
|
|
242
|
+
if (!a.isDirectory && b.isDirectory) return 1;
|
|
243
|
+
return a.name.localeCompare(b.name);
|
|
244
|
+
});
|
|
245
|
+
} catch (error) {
|
|
246
|
+
console.error("[AI-Chat] \u5217\u51FA\u76EE\u5F55\u5931\u8D25:", error);
|
|
247
|
+
return [];
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
ipcMain.handle(`${channelPrefix}:fs:exists`, async (_event, filePath) => {
|
|
251
|
+
return fs.existsSync(filePath);
|
|
252
|
+
});
|
|
253
|
+
ipcMain.handle(`${channelPrefix}:fs:stat`, async (_event, filePath) => {
|
|
254
|
+
try {
|
|
255
|
+
const stats = fs.statSync(filePath);
|
|
256
|
+
return {
|
|
257
|
+
name: path.basename(filePath),
|
|
258
|
+
path: filePath,
|
|
259
|
+
isDirectory: stats.isDirectory(),
|
|
260
|
+
size: stats.size,
|
|
261
|
+
modifiedAt: stats.mtime,
|
|
262
|
+
extension: stats.isDirectory() ? "" : path.extname(filePath).toLowerCase()
|
|
263
|
+
};
|
|
264
|
+
} catch {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
ipcMain.handle(`${channelPrefix}:fs:readFile`, async (_event, filePath) => {
|
|
269
|
+
try {
|
|
270
|
+
return fs.readFileSync(filePath, "utf-8");
|
|
271
|
+
} catch {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
ipcMain.handle(`${channelPrefix}:fs:readFileBase64`, async (_event, filePath) => {
|
|
276
|
+
try {
|
|
277
|
+
const buffer = fs.readFileSync(filePath);
|
|
278
|
+
return buffer.toString("base64");
|
|
279
|
+
} catch {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
ipcMain.handle(`${channelPrefix}:fs:homeDir`, async () => {
|
|
284
|
+
return process.env.HOME || process.env.USERPROFILE || "/";
|
|
285
|
+
});
|
|
286
|
+
ipcMain.handle(`${channelPrefix}:fs:resolvePath`, async (_event, inputPath) => {
|
|
287
|
+
if (inputPath.startsWith("~")) {
|
|
288
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || "/";
|
|
289
|
+
return path.join(homeDir, inputPath.slice(1));
|
|
290
|
+
}
|
|
291
|
+
return path.resolve(inputPath);
|
|
292
|
+
});
|
|
293
|
+
ipcMain.handle(`${channelPrefix}:fs:parentDir`, async (_event, dirPath) => {
|
|
294
|
+
return path.dirname(dirPath);
|
|
295
|
+
});
|
|
296
|
+
const activeWatchers = /* @__PURE__ */ new Map();
|
|
297
|
+
ipcMain.handle(`${channelPrefix}:fs:watchDir`, async (event, dirPath) => {
|
|
298
|
+
const webContents = event.sender;
|
|
299
|
+
if (activeWatchers.has(dirPath)) {
|
|
300
|
+
activeWatchers.get(dirPath)?.close();
|
|
301
|
+
activeWatchers.delete(dirPath);
|
|
302
|
+
}
|
|
303
|
+
try {
|
|
304
|
+
const watcher = fs.watch(dirPath, { persistent: false }, (eventType, filename) => {
|
|
305
|
+
if (!webContents.isDestroyed()) {
|
|
306
|
+
webContents.send(`${channelPrefix}:fs:dirChange`, {
|
|
307
|
+
dirPath,
|
|
308
|
+
eventType,
|
|
309
|
+
filename
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
watcher.on("error", (error) => {
|
|
314
|
+
console.error("[AI-Chat] Watch error:", error);
|
|
315
|
+
activeWatchers.delete(dirPath);
|
|
316
|
+
});
|
|
317
|
+
activeWatchers.set(dirPath, watcher);
|
|
318
|
+
return true;
|
|
319
|
+
} catch (error) {
|
|
320
|
+
console.error("[AI-Chat] Failed to watch directory:", error);
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
ipcMain.handle(`${channelPrefix}:fs:unwatchDir`, async (_event, dirPath) => {
|
|
325
|
+
const watcher = activeWatchers.get(dirPath);
|
|
326
|
+
if (watcher) {
|
|
327
|
+
watcher.close();
|
|
328
|
+
activeWatchers.delete(dirPath);
|
|
329
|
+
}
|
|
330
|
+
});
|
|
120
331
|
return { agent, storage };
|
|
121
332
|
}
|
|
122
333
|
export {
|
|
123
|
-
|
|
334
|
+
MODELS,
|
|
124
335
|
createElectronBridge
|
|
125
336
|
};
|
package/dist/preload/index.cjs
CHANGED
|
@@ -31,9 +31,8 @@ function exposeElectronBridge(options = {}) {
|
|
|
31
31
|
getModels: () => import_electron.ipcRenderer.invoke(`${channelPrefix}:models`),
|
|
32
32
|
send: (params) => import_electron.ipcRenderer.invoke(`${channelPrefix}:send`, params),
|
|
33
33
|
cancel: () => import_electron.ipcRenderer.invoke(`${channelPrefix}:cancel`),
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
setWorkingDir: (dir) => import_electron.ipcRenderer.invoke(`${channelPrefix}:setWorkingDir`, dir),
|
|
34
|
+
// 无状态架构:历史通过 ChatOptions.history 传入
|
|
35
|
+
setCwd: (dir) => import_electron.ipcRenderer.invoke(`${channelPrefix}:setCwd`, dir),
|
|
37
36
|
getConfig: () => import_electron.ipcRenderer.invoke(`${channelPrefix}:config`),
|
|
38
37
|
onProgress: (callback) => {
|
|
39
38
|
const handler = (_event, progress) => {
|
|
@@ -44,6 +43,16 @@ function exposeElectronBridge(options = {}) {
|
|
|
44
43
|
import_electron.ipcRenderer.removeListener(`${channelPrefix}:progress`, handler);
|
|
45
44
|
};
|
|
46
45
|
},
|
|
46
|
+
onToolApprovalRequest: (callback) => {
|
|
47
|
+
const handler = (_event, request) => {
|
|
48
|
+
callback(request);
|
|
49
|
+
};
|
|
50
|
+
import_electron.ipcRenderer.on(`${channelPrefix}:toolApprovalRequest`, handler);
|
|
51
|
+
return () => {
|
|
52
|
+
import_electron.ipcRenderer.removeListener(`${channelPrefix}:toolApprovalRequest`, handler);
|
|
53
|
+
};
|
|
54
|
+
},
|
|
55
|
+
respondToolApproval: (id, approved) => import_electron.ipcRenderer.invoke(`${channelPrefix}:toolApprovalResponse`, { id, approved }),
|
|
47
56
|
// ============ Sessions API ============
|
|
48
57
|
getSessions: () => import_electron.ipcRenderer.invoke(`${channelPrefix}:sessions:list`),
|
|
49
58
|
getSession: (id) => import_electron.ipcRenderer.invoke(`${channelPrefix}:sessions:get`, id),
|
|
@@ -53,12 +62,57 @@ function exposeElectronBridge(options = {}) {
|
|
|
53
62
|
// ============ Messages API ============
|
|
54
63
|
getMessages: (sessionId) => import_electron.ipcRenderer.invoke(`${channelPrefix}:messages:list`, sessionId),
|
|
55
64
|
saveMessage: (params) => import_electron.ipcRenderer.invoke(`${channelPrefix}:messages:save`, params),
|
|
65
|
+
updateMessage: (params) => import_electron.ipcRenderer.invoke(`${channelPrefix}:messages:update`, params),
|
|
56
66
|
deleteMessagesAfter: (sessionId, timestamp) => import_electron.ipcRenderer.invoke(`${channelPrefix}:messages:deleteAfter`, sessionId, timestamp),
|
|
57
67
|
// ============ Operations API ============
|
|
58
68
|
getOperations: (sessionId) => import_electron.ipcRenderer.invoke(`${channelPrefix}:operations:list`, sessionId),
|
|
59
69
|
// ============ Trash API ============
|
|
60
70
|
getTrashItems: () => import_electron.ipcRenderer.invoke(`${channelPrefix}:trash:list`),
|
|
61
|
-
restoreFromTrash: (id) => import_electron.ipcRenderer.invoke(`${channelPrefix}:trash:restore`, id)
|
|
71
|
+
restoreFromTrash: (id) => import_electron.ipcRenderer.invoke(`${channelPrefix}:trash:restore`, id),
|
|
72
|
+
// ============ System API ============
|
|
73
|
+
openExternal: (url) => import_electron.ipcRenderer.invoke(`${channelPrefix}:openExternal`, url),
|
|
74
|
+
// ============ File System API ============
|
|
75
|
+
listDir: (dirPath) => import_electron.ipcRenderer.invoke(`${channelPrefix}:fs:listDir`, dirPath),
|
|
76
|
+
exists: (filePath) => import_electron.ipcRenderer.invoke(`${channelPrefix}:fs:exists`, filePath),
|
|
77
|
+
stat: (filePath) => import_electron.ipcRenderer.invoke(`${channelPrefix}:fs:stat`, filePath),
|
|
78
|
+
readFile: (filePath) => import_electron.ipcRenderer.invoke(`${channelPrefix}:fs:readFile`, filePath),
|
|
79
|
+
readFileBase64: (filePath) => import_electron.ipcRenderer.invoke(`${channelPrefix}:fs:readFileBase64`, filePath),
|
|
80
|
+
homeDir: () => import_electron.ipcRenderer.invoke(`${channelPrefix}:fs:homeDir`),
|
|
81
|
+
resolvePath: (inputPath) => import_electron.ipcRenderer.invoke(`${channelPrefix}:fs:resolvePath`, inputPath),
|
|
82
|
+
parentDir: (dirPath) => import_electron.ipcRenderer.invoke(`${channelPrefix}:fs:parentDir`, dirPath),
|
|
83
|
+
watchDir: (dirPath) => import_electron.ipcRenderer.invoke(`${channelPrefix}:fs:watchDir`, dirPath),
|
|
84
|
+
unwatchDir: (dirPath) => import_electron.ipcRenderer.invoke(`${channelPrefix}:fs:unwatchDir`, dirPath),
|
|
85
|
+
onDirChange: (callback) => {
|
|
86
|
+
const handler = (_event, data) => {
|
|
87
|
+
callback(data);
|
|
88
|
+
};
|
|
89
|
+
import_electron.ipcRenderer.on(`${channelPrefix}:fs:dirChange`, handler);
|
|
90
|
+
return () => {
|
|
91
|
+
import_electron.ipcRenderer.removeListener(`${channelPrefix}:fs:dirChange`, handler);
|
|
92
|
+
};
|
|
93
|
+
},
|
|
94
|
+
// ============ Settings API ============
|
|
95
|
+
getSetting: (key) => import_electron.ipcRenderer.invoke(`${channelPrefix}:settings:get`, key),
|
|
96
|
+
setSetting: (key, value) => import_electron.ipcRenderer.invoke(`${channelPrefix}:settings:set`, key, value),
|
|
97
|
+
getAllSettings: () => import_electron.ipcRenderer.invoke(`${channelPrefix}:settings:getAll`),
|
|
98
|
+
deleteSetting: (key) => import_electron.ipcRenderer.invoke(`${channelPrefix}:settings:delete`, key),
|
|
99
|
+
// ============ Index API ============
|
|
100
|
+
getIndexStats: () => import_electron.ipcRenderer.invoke(`${channelPrefix}:index:getStats`),
|
|
101
|
+
getIndexStatus: () => import_electron.ipcRenderer.invoke(`${channelPrefix}:index:status`),
|
|
102
|
+
syncIndex: () => import_electron.ipcRenderer.invoke(`${channelPrefix}:index:sync`),
|
|
103
|
+
cancelIndex: () => import_electron.ipcRenderer.invoke(`${channelPrefix}:index:cancel`),
|
|
104
|
+
deleteIndex: () => import_electron.ipcRenderer.invoke(`${channelPrefix}:index:delete`),
|
|
105
|
+
registerIndexListener: () => import_electron.ipcRenderer.invoke(`${channelPrefix}:index:registerListener`),
|
|
106
|
+
unregisterIndexListener: () => import_electron.ipcRenderer.invoke(`${channelPrefix}:index:unregisterListener`),
|
|
107
|
+
onIndexProgress: (callback) => {
|
|
108
|
+
const handler = (_event, progress) => {
|
|
109
|
+
callback(progress);
|
|
110
|
+
};
|
|
111
|
+
import_electron.ipcRenderer.on(`${channelPrefix}:index:progress`, handler);
|
|
112
|
+
return () => {
|
|
113
|
+
import_electron.ipcRenderer.removeListener(`${channelPrefix}:index:progress`, handler);
|
|
114
|
+
};
|
|
115
|
+
}
|
|
62
116
|
};
|
|
63
117
|
import_electron.contextBridge.exposeInMainWorld(exposeName, bridge);
|
|
64
118
|
}
|
package/dist/preload/index.d.cts
CHANGED
|
@@ -18,13 +18,15 @@ interface SendMessageParams {
|
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
20
|
/** 模型配置 */
|
|
21
|
-
interface
|
|
22
|
-
|
|
23
|
-
model: string;
|
|
21
|
+
interface ModelOption {
|
|
22
|
+
modelId: string;
|
|
24
23
|
displayName: string;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
/** 分组名称(由后端决定,前端只负责渲染,必填) */
|
|
25
|
+
group: string;
|
|
26
|
+
/** 是否来自 OpenRouter(保留用于兼容,后续可能移除) */
|
|
27
|
+
isOpenRouter?: boolean;
|
|
28
|
+
/** 提供商名称(保留用于兼容,后续可能移除) */
|
|
29
|
+
provider?: string;
|
|
28
30
|
}
|
|
29
31
|
/** 会话记录 */
|
|
30
32
|
interface SessionRecord {
|
|
@@ -34,6 +36,10 @@ interface SessionRecord {
|
|
|
34
36
|
title: string;
|
|
35
37
|
model: string;
|
|
36
38
|
mode: 'agent' | 'plan' | 'ask';
|
|
39
|
+
webSearchEnabled: boolean;
|
|
40
|
+
thinkingEnabled: boolean;
|
|
41
|
+
/** 是否在 tab 栏隐藏(关闭但不删除) */
|
|
42
|
+
hidden: boolean;
|
|
37
43
|
createdAt: Date;
|
|
38
44
|
updatedAt: Date;
|
|
39
45
|
}
|
|
@@ -43,9 +49,16 @@ interface MessageRecord {
|
|
|
43
49
|
sessionId: string;
|
|
44
50
|
role: 'user' | 'assistant';
|
|
45
51
|
content: string;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
/** 生成此消息时使用的模型 */
|
|
53
|
+
model?: string | null;
|
|
54
|
+
/** 生成此消息时使用的模式 (ask/agent) */
|
|
55
|
+
mode?: string | null;
|
|
56
|
+
/** 生成此消息时是否启用 web 搜索 */
|
|
57
|
+
webSearchEnabled?: boolean | null;
|
|
58
|
+
/** 生成此消息时是否启用深度思考 */
|
|
59
|
+
thinkingEnabled?: boolean | null;
|
|
60
|
+
/** 执行步骤列表 JSON */
|
|
61
|
+
steps?: string | null;
|
|
49
62
|
operationIds?: string | null;
|
|
50
63
|
timestamp: Date;
|
|
51
64
|
}
|
|
@@ -69,19 +82,24 @@ interface TrashRecord {
|
|
|
69
82
|
deletedAt: Date;
|
|
70
83
|
autoDeleteAt: Date;
|
|
71
84
|
}
|
|
85
|
+
/** 文件信息 */
|
|
86
|
+
interface FileInfo {
|
|
87
|
+
name: string;
|
|
88
|
+
path: string;
|
|
89
|
+
isDirectory: boolean;
|
|
90
|
+
size: number;
|
|
91
|
+
modifiedAt: Date;
|
|
92
|
+
extension: string;
|
|
93
|
+
}
|
|
72
94
|
interface AiChatBridge {
|
|
73
95
|
/** 获取可用模型 */
|
|
74
|
-
getModels(): Promise<
|
|
96
|
+
getModels(): Promise<ModelOption[]>;
|
|
75
97
|
/** 发送消息 */
|
|
76
98
|
send(params: SendMessageParams): Promise<void>;
|
|
77
99
|
/** 取消当前请求 */
|
|
78
100
|
cancel(): Promise<void>;
|
|
79
|
-
/**
|
|
80
|
-
|
|
81
|
-
/** 获取 Agent 内存历史 */
|
|
82
|
-
getAgentHistory(): Promise<unknown[]>;
|
|
83
|
-
/** 设置工作目录 */
|
|
84
|
-
setWorkingDir(dir: string): Promise<void>;
|
|
101
|
+
/** 设置当前工作目录 */
|
|
102
|
+
setCwd(dir: string): Promise<void>;
|
|
85
103
|
/** 获取配置 */
|
|
86
104
|
getConfig(): Promise<unknown>;
|
|
87
105
|
/** 监听进度事件 */
|
|
@@ -95,12 +113,18 @@ interface AiChatBridge {
|
|
|
95
113
|
title?: string;
|
|
96
114
|
model?: string;
|
|
97
115
|
mode?: string;
|
|
116
|
+
webSearchEnabled?: boolean;
|
|
117
|
+
thinkingEnabled?: boolean;
|
|
118
|
+
hidden?: boolean;
|
|
98
119
|
}): Promise<SessionRecord>;
|
|
99
120
|
/** 更新会话 */
|
|
100
121
|
updateSession(id: string, data: {
|
|
101
122
|
title?: string;
|
|
102
123
|
model?: string;
|
|
103
124
|
mode?: string;
|
|
125
|
+
webSearchEnabled?: boolean;
|
|
126
|
+
thinkingEnabled?: boolean;
|
|
127
|
+
hidden?: boolean;
|
|
104
128
|
}): Promise<SessionRecord | null>;
|
|
105
129
|
/** 删除会话 */
|
|
106
130
|
deleteSession(id: string): Promise<{
|
|
@@ -110,14 +134,31 @@ interface AiChatBridge {
|
|
|
110
134
|
getMessages(sessionId: string): Promise<MessageRecord[]>;
|
|
111
135
|
/** 保存消息 */
|
|
112
136
|
saveMessage(params: {
|
|
137
|
+
/** 消息 ID(可选,如果不传则自动生成) */
|
|
138
|
+
id?: string;
|
|
113
139
|
sessionId: string;
|
|
114
140
|
role: 'user' | 'assistant';
|
|
115
141
|
content: string;
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
142
|
+
/** 生成此消息时使用的模型 */
|
|
143
|
+
model?: string;
|
|
144
|
+
/** 生成此消息时使用的模式 (ask/agent) */
|
|
145
|
+
mode?: string;
|
|
146
|
+
/** 生成此消息时是否启用 web 搜索 */
|
|
147
|
+
webSearchEnabled?: boolean;
|
|
148
|
+
/** 生成此消息时是否启用深度思考 */
|
|
149
|
+
thinkingEnabled?: boolean;
|
|
150
|
+
/** 执行步骤列表 JSON */
|
|
151
|
+
steps?: string;
|
|
119
152
|
operationIds?: string;
|
|
120
153
|
}): Promise<MessageRecord>;
|
|
154
|
+
/** 更新消息(增量保存) */
|
|
155
|
+
updateMessage(params: {
|
|
156
|
+
id: string;
|
|
157
|
+
content?: string;
|
|
158
|
+
steps?: string;
|
|
159
|
+
}): Promise<{
|
|
160
|
+
success: boolean;
|
|
161
|
+
}>;
|
|
121
162
|
/** 删除指定时间之后的消息(用于分叉) */
|
|
122
163
|
deleteMessagesAfter(sessionId: string, timestamp: number): Promise<{
|
|
123
164
|
success: boolean;
|
|
@@ -128,6 +169,98 @@ interface AiChatBridge {
|
|
|
128
169
|
getTrashItems(): Promise<TrashRecord[]>;
|
|
129
170
|
/** 恢复回收站项目 */
|
|
130
171
|
restoreFromTrash(id: string): Promise<TrashRecord | undefined>;
|
|
172
|
+
/** 在系统默认浏览器中打开链接 */
|
|
173
|
+
openExternal(url: string): Promise<void>;
|
|
174
|
+
/** 列出目录内容 */
|
|
175
|
+
listDir(dirPath: string): Promise<FileInfo[]>;
|
|
176
|
+
/** 检查路径是否存在 */
|
|
177
|
+
exists(filePath: string): Promise<boolean>;
|
|
178
|
+
/** 获取文件信息 */
|
|
179
|
+
stat(filePath: string): Promise<FileInfo | null>;
|
|
180
|
+
/** 读取文件内容(文本) */
|
|
181
|
+
readFile(filePath: string): Promise<string | null>;
|
|
182
|
+
/** 读取文件为 base64 */
|
|
183
|
+
readFileBase64(filePath: string): Promise<string | null>;
|
|
184
|
+
/** 获取用户主目录 */
|
|
185
|
+
homeDir(): Promise<string>;
|
|
186
|
+
/** 解析路径(处理 ~) */
|
|
187
|
+
resolvePath(inputPath: string): Promise<string>;
|
|
188
|
+
/** 获取父目录 */
|
|
189
|
+
parentDir(dirPath: string): Promise<string>;
|
|
190
|
+
/** 监听目录变化 */
|
|
191
|
+
watchDir(dirPath: string): Promise<boolean>;
|
|
192
|
+
/** 停止监听目录 */
|
|
193
|
+
unwatchDir(dirPath: string): Promise<void>;
|
|
194
|
+
/** 监听目录变化事件 */
|
|
195
|
+
onDirChange(callback: (data: {
|
|
196
|
+
dirPath: string;
|
|
197
|
+
eventType: string;
|
|
198
|
+
filename: string | null;
|
|
199
|
+
}) => void): () => void;
|
|
200
|
+
/** 获取用户设置 */
|
|
201
|
+
getSetting(key: string): Promise<string | null>;
|
|
202
|
+
/** 设置用户设置 */
|
|
203
|
+
setSetting(key: string, value: string): Promise<{
|
|
204
|
+
success: boolean;
|
|
205
|
+
}>;
|
|
206
|
+
/** 获取所有用户设置 */
|
|
207
|
+
getAllSettings(): Promise<Record<string, string>>;
|
|
208
|
+
/** 删除用户设置 */
|
|
209
|
+
deleteSetting(key: string): Promise<{
|
|
210
|
+
success: boolean;
|
|
211
|
+
}>;
|
|
212
|
+
/** 获取索引统计信息 */
|
|
213
|
+
getIndexStats(): Promise<{
|
|
214
|
+
totalDocuments: number;
|
|
215
|
+
indexSize: number;
|
|
216
|
+
lastUpdated: string | null;
|
|
217
|
+
}>;
|
|
218
|
+
/** 检查索引状态 */
|
|
219
|
+
getIndexStatus(): Promise<{
|
|
220
|
+
isIndexing: boolean;
|
|
221
|
+
lastProgress: {
|
|
222
|
+
indexed: number;
|
|
223
|
+
total: number;
|
|
224
|
+
currentFile?: string;
|
|
225
|
+
stage: string;
|
|
226
|
+
} | null;
|
|
227
|
+
}>;
|
|
228
|
+
/** 同步索引(重新索引工作空间) */
|
|
229
|
+
syncIndex(): Promise<{
|
|
230
|
+
success: boolean;
|
|
231
|
+
}>;
|
|
232
|
+
/** 取消索引 */
|
|
233
|
+
cancelIndex(): Promise<{
|
|
234
|
+
success: boolean;
|
|
235
|
+
}>;
|
|
236
|
+
/** 删除索引 */
|
|
237
|
+
deleteIndex(): Promise<{
|
|
238
|
+
success: boolean;
|
|
239
|
+
}>;
|
|
240
|
+
/** 注册索引进度监听器 */
|
|
241
|
+
registerIndexListener(): Promise<{
|
|
242
|
+
success: boolean;
|
|
243
|
+
}>;
|
|
244
|
+
/** 注销索引进度监听器 */
|
|
245
|
+
unregisterIndexListener(): Promise<{
|
|
246
|
+
success: boolean;
|
|
247
|
+
}>;
|
|
248
|
+
/** 监听索引进度 */
|
|
249
|
+
onIndexProgress(callback: (progress: {
|
|
250
|
+
indexed: number;
|
|
251
|
+
total: number;
|
|
252
|
+
currentFile?: string;
|
|
253
|
+
stage: string;
|
|
254
|
+
error?: string;
|
|
255
|
+
}) => void): () => void;
|
|
256
|
+
/** 监听工具批准请求(manual 模式) */
|
|
257
|
+
onToolApprovalRequest(callback: (request: {
|
|
258
|
+
id: string;
|
|
259
|
+
name: string;
|
|
260
|
+
args: Record<string, unknown>;
|
|
261
|
+
}) => void): () => void;
|
|
262
|
+
/** 响应工具批准请求 */
|
|
263
|
+
respondToolApproval(id: string, approved: boolean): Promise<void>;
|
|
131
264
|
}
|
|
132
265
|
interface ExposeOptions {
|
|
133
266
|
/** IPC channel 前缀 */
|