@huyooo/ai-chat-bridge-electron 0.1.6 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,71 +17,162 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/main/index.ts
21
31
  var main_exports = {};
22
32
  __export(main_exports, {
23
- AVAILABLE_MODELS: () => import_ai_chat_core.AVAILABLE_MODELS,
33
+ MODELS: () => import_ai_chat_core.MODELS,
24
34
  createElectronBridge: () => createElectronBridge
25
35
  });
26
36
  module.exports = __toCommonJS(main_exports);
27
37
  var import_electron = require("electron");
38
+ var fs = __toESM(require("fs"), 1);
39
+ var path = __toESM(require("path"), 1);
28
40
  var import_ai_chat_core = require("@huyooo/ai-chat-core");
41
+ var import_electron2 = require("@huyooo/ai-search/bridge/electron");
29
42
  var import_ai_chat_storage = require("@huyooo/ai-chat-storage");
30
43
  async function createElectronBridge(options) {
31
44
  const {
32
45
  channelPrefix = "ai-chat",
33
- storagePath,
46
+ dataDir,
34
47
  defaultContext = {},
35
48
  ...agentConfig
36
49
  } = options;
37
- const agent = new import_ai_chat_core.HybridAgent(agentConfig);
50
+ const resolvedStoragePath = path.join(dataDir, "db.sqlite");
51
+ (0, import_electron2.createSearchElectronBridge)({
52
+ ipcMain: import_electron.ipcMain,
53
+ channelPrefix
54
+ }).init();
55
+ const pendingApprovals = /* @__PURE__ */ new Map();
56
+ const toolApprovalCallback = async (toolCall) => {
57
+ console.log("[Main] \u5DE5\u5177\u6279\u51C6\u56DE\u8C03\u88AB\u8C03\u7528:", toolCall.name);
58
+ const currentWebContents = global.currentWebContents;
59
+ if (!currentWebContents) {
60
+ console.log("[Main] \u8B66\u544A: \u6CA1\u6709 webContents\uFF0C\u9ED8\u8BA4\u6279\u51C6");
61
+ return true;
62
+ }
63
+ return new Promise((resolve2, reject) => {
64
+ pendingApprovals.set(toolCall.id, {
65
+ resolve: resolve2,
66
+ reject,
67
+ webContents: currentWebContents
68
+ });
69
+ console.log("[Main] \u53D1\u9001\u5DE5\u5177\u6279\u51C6\u8BF7\u6C42\u5230\u524D\u7AEF:", toolCall.name);
70
+ currentWebContents.send(`${channelPrefix}:toolApprovalRequest`, {
71
+ id: toolCall.id,
72
+ name: toolCall.name,
73
+ args: toolCall.args
74
+ });
75
+ });
76
+ };
38
77
  const storage = await (0, import_ai_chat_storage.createStorage)({
39
78
  type: "sqlite",
40
- sqlitePath: storagePath || (0, import_ai_chat_storage.getDefaultStoragePath)()
79
+ sqlitePath: resolvedStoragePath
41
80
  });
42
81
  const getContext = () => defaultContext;
82
+ const getAutoRunConfig = async () => {
83
+ try {
84
+ const configJson = await storage.getUserSetting("autoRunConfig", getContext());
85
+ if (configJson) {
86
+ return JSON.parse(configJson);
87
+ }
88
+ } catch (error) {
89
+ console.error("[Main] \u83B7\u53D6 autoRunConfig \u5931\u8D25:", error);
90
+ }
91
+ return void 0;
92
+ };
93
+ const agent = new import_ai_chat_core.HybridAgent({
94
+ ...agentConfig,
95
+ // tools 直接传递 ToolConfigItem[],HybridAgent 会在 asyncInit 中解析
96
+ tools: options.tools,
97
+ onToolApprovalRequest: toolApprovalCallback,
98
+ getAutoRunConfig
99
+ });
43
100
  import_electron.ipcMain.handle(`${channelPrefix}:models`, () => {
44
- return import_ai_chat_core.AVAILABLE_MODELS;
101
+ return import_ai_chat_core.MODELS;
45
102
  });
46
103
  import_electron.ipcMain.handle(`${channelPrefix}:send`, async (event, params) => {
47
104
  const webContents = event.sender;
48
105
  const { message, images, options: options2 = {}, sessionId } = params;
106
+ global.currentWebContents = webContents;
49
107
  console.log("[AI-Chat] \u6536\u5230\u6D88\u606F:", { message, options: options2, images: images?.length || 0, sessionId });
108
+ console.log("[AI-Chat] autoRunConfig:", JSON.stringify(options2.autoRunConfig, null, 2));
50
109
  try {
51
- for await (const progress of agent.chat(message, options2, images)) {
110
+ for await (const chatEvent of agent.chat(message, options2, images)) {
52
111
  console.log(
53
- "[AI-Chat] \u8FDB\u5EA6:",
54
- progress.type,
55
- progress.type === "text_delta" ? progress.data.slice(0, 20) : progress.type === "thinking" ? JSON.stringify(progress.data) : progress.type === "search_result" ? `\u641C\u7D22\u5B8C\u6210 ${progress.data.results?.length || 0} \u6761` : JSON.stringify(progress.data).slice(0, 100)
112
+ "[AI-Chat] \u4E8B\u4EF6:",
113
+ chatEvent.type,
114
+ 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)
56
115
  );
57
116
  if (!webContents.isDestroyed()) {
58
- webContents.send(`${channelPrefix}:progress`, progress);
117
+ webContents.send(`${channelPrefix}:progress`, { ...chatEvent, sessionId });
59
118
  }
60
119
  }
61
120
  console.log("[AI-Chat] \u5B8C\u6210");
62
121
  } catch (error) {
63
122
  console.error("[AI-Chat] \u9519\u8BEF:", error);
123
+ if (error instanceof Error) {
124
+ console.error("[AI-Chat] \u9519\u8BEF\u8BE6\u60C5:", {
125
+ message: error.message,
126
+ stack: error.stack,
127
+ name: error.name
128
+ });
129
+ }
64
130
  if (!webContents.isDestroyed()) {
131
+ const errorData = error instanceof Error ? {
132
+ category: "api",
133
+ message: error.message || String(error),
134
+ cause: error.stack
135
+ } : {
136
+ category: "api",
137
+ message: String(error)
138
+ };
65
139
  webContents.send(`${channelPrefix}:progress`, {
66
140
  type: "error",
67
- data: String(error)
141
+ data: errorData,
142
+ sessionId
68
143
  });
69
144
  }
145
+ } finally {
146
+ delete global.currentWebContents;
147
+ }
148
+ });
149
+ import_electron.ipcMain.handle(`${channelPrefix}:toolApprovalResponse`, (_event, params) => {
150
+ const { id, approved } = params;
151
+ const pending = pendingApprovals.get(id);
152
+ if (pending) {
153
+ pendingApprovals.delete(id);
154
+ pending.resolve(approved);
70
155
  }
71
156
  });
72
157
  import_electron.ipcMain.handle(`${channelPrefix}:cancel`, () => {
73
158
  agent.abort();
74
159
  });
75
- import_electron.ipcMain.handle(`${channelPrefix}:clearAgentHistory`, () => {
76
- agent.clearHistory();
160
+ import_electron.ipcMain.handle(`${channelPrefix}:settings:get`, async (_event, key) => {
161
+ return storage.getUserSetting(key, getContext());
77
162
  });
78
- import_electron.ipcMain.handle(`${channelPrefix}:agentHistory`, () => {
79
- return agent.getHistory();
163
+ import_electron.ipcMain.handle(`${channelPrefix}:settings:set`, async (_event, key, value) => {
164
+ await storage.setUserSetting(key, value, getContext());
165
+ return { success: true };
166
+ });
167
+ import_electron.ipcMain.handle(`${channelPrefix}:settings:getAll`, async () => {
168
+ return storage.getUserSettings(getContext());
80
169
  });
81
- import_electron.ipcMain.handle(`${channelPrefix}:setWorkingDir`, (_event, dir) => {
82
- agent.setWorkingDir(dir);
170
+ import_electron.ipcMain.handle(`${channelPrefix}:settings:delete`, async (_event, key) => {
171
+ await storage.deleteUserSetting(key, getContext());
172
+ return { success: true };
173
+ });
174
+ import_electron.ipcMain.handle(`${channelPrefix}:setCwd`, (_event, dir) => {
175
+ agent.setCwd(dir);
83
176
  });
84
177
  import_electron.ipcMain.handle(`${channelPrefix}:config`, () => {
85
178
  return agent.getConfig();
@@ -94,8 +187,11 @@ async function createElectronBridge(options) {
94
187
  const input = {
95
188
  id: params.id || crypto.randomUUID(),
96
189
  title: params.title || "\u65B0\u5BF9\u8BDD",
97
- model: params.model || "doubao-seed-1-6-251015",
98
- mode: params.mode || "agent"
190
+ model: params.model || import_ai_chat_core.DEFAULT_MODEL,
191
+ mode: params.mode || "agent",
192
+ webSearchEnabled: params.webSearchEnabled ?? true,
193
+ thinkingEnabled: params.thinkingEnabled ?? true,
194
+ hidden: params.hidden ?? false
99
195
  };
100
196
  return storage.createSession(input, getContext());
101
197
  });
@@ -112,17 +208,26 @@ async function createElectronBridge(options) {
112
208
  });
113
209
  import_electron.ipcMain.handle(`${channelPrefix}:messages:save`, async (_event, params) => {
114
210
  const input = {
115
- id: crypto.randomUUID(),
211
+ id: params.id || crypto.randomUUID(),
116
212
  sessionId: params.sessionId,
117
213
  role: params.role,
118
214
  content: params.content,
119
- thinking: params.thinking || null,
120
- toolCalls: params.toolCalls || null,
121
- searchResults: params.searchResults || null,
215
+ model: params.model || null,
216
+ mode: params.mode || null,
217
+ webSearchEnabled: params.webSearchEnabled ?? null,
218
+ thinkingEnabled: params.thinkingEnabled ?? null,
219
+ steps: params.steps || null,
122
220
  operationIds: params.operationIds || null
123
221
  };
124
222
  return storage.saveMessage(input, getContext());
125
223
  });
224
+ import_electron.ipcMain.handle(`${channelPrefix}:messages:update`, async (_event, params) => {
225
+ await storage.updateMessage(params.id, {
226
+ content: params.content,
227
+ steps: params.steps
228
+ }, getContext());
229
+ return { success: true };
230
+ });
126
231
  import_electron.ipcMain.handle(`${channelPrefix}:messages:deleteAfter`, async (_event, sessionId, timestamp) => {
127
232
  await storage.deleteMessagesAfter(sessionId, new Date(timestamp), getContext());
128
233
  return { success: true };
@@ -136,10 +241,124 @@ async function createElectronBridge(options) {
136
241
  import_electron.ipcMain.handle(`${channelPrefix}:trash:restore`, async (_event, id) => {
137
242
  return storage.restoreFromTrash?.(id, getContext());
138
243
  });
244
+ import_electron.ipcMain.handle(`${channelPrefix}:openExternal`, async (_event, url) => {
245
+ return import_electron.shell.openExternal(url);
246
+ });
247
+ import_electron.ipcMain.handle(`${channelPrefix}:fs:listDir`, async (_event, dirPath) => {
248
+ try {
249
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
250
+ const files = [];
251
+ for (const entry of entries) {
252
+ if (entry.name.startsWith(".")) continue;
253
+ const fullPath = path.join(dirPath, entry.name);
254
+ try {
255
+ const stats = fs.statSync(fullPath);
256
+ files.push({
257
+ name: entry.name,
258
+ path: fullPath,
259
+ isDirectory: entry.isDirectory(),
260
+ size: stats.size,
261
+ modifiedAt: stats.mtime,
262
+ extension: entry.isDirectory() ? "" : path.extname(entry.name).toLowerCase()
263
+ });
264
+ } catch {
265
+ }
266
+ }
267
+ return files.sort((a, b) => {
268
+ if (a.isDirectory && !b.isDirectory) return -1;
269
+ if (!a.isDirectory && b.isDirectory) return 1;
270
+ return a.name.localeCompare(b.name);
271
+ });
272
+ } catch (error) {
273
+ console.error("[AI-Chat] \u5217\u51FA\u76EE\u5F55\u5931\u8D25:", error);
274
+ return [];
275
+ }
276
+ });
277
+ import_electron.ipcMain.handle(`${channelPrefix}:fs:exists`, async (_event, filePath) => {
278
+ return fs.existsSync(filePath);
279
+ });
280
+ import_electron.ipcMain.handle(`${channelPrefix}:fs:stat`, async (_event, filePath) => {
281
+ try {
282
+ const stats = fs.statSync(filePath);
283
+ return {
284
+ name: path.basename(filePath),
285
+ path: filePath,
286
+ isDirectory: stats.isDirectory(),
287
+ size: stats.size,
288
+ modifiedAt: stats.mtime,
289
+ extension: stats.isDirectory() ? "" : path.extname(filePath).toLowerCase()
290
+ };
291
+ } catch {
292
+ return null;
293
+ }
294
+ });
295
+ import_electron.ipcMain.handle(`${channelPrefix}:fs:readFile`, async (_event, filePath) => {
296
+ try {
297
+ return fs.readFileSync(filePath, "utf-8");
298
+ } catch {
299
+ return null;
300
+ }
301
+ });
302
+ import_electron.ipcMain.handle(`${channelPrefix}:fs:readFileBase64`, async (_event, filePath) => {
303
+ try {
304
+ const buffer = fs.readFileSync(filePath);
305
+ return buffer.toString("base64");
306
+ } catch {
307
+ return null;
308
+ }
309
+ });
310
+ import_electron.ipcMain.handle(`${channelPrefix}:fs:homeDir`, async () => {
311
+ return process.env.HOME || process.env.USERPROFILE || "/";
312
+ });
313
+ import_electron.ipcMain.handle(`${channelPrefix}:fs:resolvePath`, async (_event, inputPath) => {
314
+ if (inputPath.startsWith("~")) {
315
+ const homeDir = process.env.HOME || process.env.USERPROFILE || "/";
316
+ return path.join(homeDir, inputPath.slice(1));
317
+ }
318
+ return path.resolve(inputPath);
319
+ });
320
+ import_electron.ipcMain.handle(`${channelPrefix}:fs:parentDir`, async (_event, dirPath) => {
321
+ return path.dirname(dirPath);
322
+ });
323
+ const activeWatchers = /* @__PURE__ */ new Map();
324
+ import_electron.ipcMain.handle(`${channelPrefix}:fs:watchDir`, async (event, dirPath) => {
325
+ const webContents = event.sender;
326
+ if (activeWatchers.has(dirPath)) {
327
+ activeWatchers.get(dirPath)?.close();
328
+ activeWatchers.delete(dirPath);
329
+ }
330
+ try {
331
+ const watcher = fs.watch(dirPath, { persistent: false }, (eventType, filename) => {
332
+ if (!webContents.isDestroyed()) {
333
+ webContents.send(`${channelPrefix}:fs:dirChange`, {
334
+ dirPath,
335
+ eventType,
336
+ filename
337
+ });
338
+ }
339
+ });
340
+ watcher.on("error", (error) => {
341
+ console.error("[AI-Chat] Watch error:", error);
342
+ activeWatchers.delete(dirPath);
343
+ });
344
+ activeWatchers.set(dirPath, watcher);
345
+ return true;
346
+ } catch (error) {
347
+ console.error("[AI-Chat] Failed to watch directory:", error);
348
+ return false;
349
+ }
350
+ });
351
+ import_electron.ipcMain.handle(`${channelPrefix}:fs:unwatchDir`, async (_event, dirPath) => {
352
+ const watcher = activeWatchers.get(dirPath);
353
+ if (watcher) {
354
+ watcher.close();
355
+ activeWatchers.delete(dirPath);
356
+ }
357
+ });
139
358
  return { agent, storage };
140
359
  }
141
360
  // Annotate the CommonJS export names for ESM import in node:
142
361
  0 && (module.exports = {
143
- AVAILABLE_MODELS,
362
+ MODELS,
144
363
  createElectronBridge
145
364
  });
@@ -1,5 +1,5 @@
1
- import { AgentConfig, HybridAgent } from '@huyooo/ai-chat-core';
2
- export { AVAILABLE_MODELS, AgentConfig, ChatMode, ChatOptions, ChatProgress, ModelProvider } from '@huyooo/ai-chat-core';
1
+ import { AgentConfig, ToolConfigItem, HybridAgent } from '@huyooo/ai-chat-core';
2
+ export { AgentConfig, ChatEvent, ChatMode, ChatOptions, MODELS, ProviderType } from '@huyooo/ai-chat-core';
3
3
  import { StorageContext, StorageAdapter } from '@huyooo/ai-chat-storage';
4
4
  export { MessageRecord, SessionRecord, StorageAdapter, StorageContext } from '@huyooo/ai-chat-storage';
5
5
 
@@ -10,13 +10,33 @@ export { MessageRecord, SessionRecord, StorageAdapter, StorageContext } from '@h
10
10
  * 集成 SQLite 存储
11
11
  */
12
12
 
13
- interface ElectronBridgeOptions extends AgentConfig {
13
+ /** 文件信息 */
14
+ interface FileInfo {
15
+ name: string;
16
+ path: string;
17
+ isDirectory: boolean;
18
+ size: number;
19
+ modifiedAt: Date;
20
+ extension: string;
21
+ }
22
+
23
+ interface ElectronBridgeOptions extends Omit<AgentConfig, 'tools'> {
14
24
  /** IPC channel 前缀 */
15
25
  channelPrefix?: string;
16
- /** SQLite 数据库路径 */
17
- storagePath?: string;
26
+ /**
27
+ * 统一数据目录(必填)
28
+ * 所有数据都存储在此目录下:
29
+ * - db.sqlite: 对话历史
30
+ * - search-data/: 搜索索引(由宿主 tools 注入 searchPlugin 时使用)
31
+ */
32
+ dataDir: string;
18
33
  /** 默认租户上下文 */
19
34
  defaultContext?: StorageContext;
35
+ /**
36
+ * 工具列表(Vite 插件风格)
37
+ * 支持:ToolPlugin、Promise<ToolPlugin>
38
+ */
39
+ tools?: ToolConfigItem[];
20
40
  }
21
41
  /**
22
42
  * 创建 Electron IPC 桥接
@@ -38,4 +58,4 @@ declare function createElectronBridge(options: ElectronBridgeOptions): Promise<{
38
58
  storage: StorageAdapter;
39
59
  }>;
40
60
 
41
- export { type ElectronBridgeOptions, createElectronBridge };
61
+ export { type ElectronBridgeOptions, type FileInfo, createElectronBridge };
@@ -1,5 +1,5 @@
1
- import { AgentConfig, HybridAgent } from '@huyooo/ai-chat-core';
2
- export { AVAILABLE_MODELS, AgentConfig, ChatMode, ChatOptions, ChatProgress, ModelProvider } from '@huyooo/ai-chat-core';
1
+ import { AgentConfig, ToolConfigItem, HybridAgent } from '@huyooo/ai-chat-core';
2
+ export { AgentConfig, ChatEvent, ChatMode, ChatOptions, MODELS, ProviderType } from '@huyooo/ai-chat-core';
3
3
  import { StorageContext, StorageAdapter } from '@huyooo/ai-chat-storage';
4
4
  export { MessageRecord, SessionRecord, StorageAdapter, StorageContext } from '@huyooo/ai-chat-storage';
5
5
 
@@ -10,13 +10,33 @@ export { MessageRecord, SessionRecord, StorageAdapter, StorageContext } from '@h
10
10
  * 集成 SQLite 存储
11
11
  */
12
12
 
13
- interface ElectronBridgeOptions extends AgentConfig {
13
+ /** 文件信息 */
14
+ interface FileInfo {
15
+ name: string;
16
+ path: string;
17
+ isDirectory: boolean;
18
+ size: number;
19
+ modifiedAt: Date;
20
+ extension: string;
21
+ }
22
+
23
+ interface ElectronBridgeOptions extends Omit<AgentConfig, 'tools'> {
14
24
  /** IPC channel 前缀 */
15
25
  channelPrefix?: string;
16
- /** SQLite 数据库路径 */
17
- storagePath?: string;
26
+ /**
27
+ * 统一数据目录(必填)
28
+ * 所有数据都存储在此目录下:
29
+ * - db.sqlite: 对话历史
30
+ * - search-data/: 搜索索引(由宿主 tools 注入 searchPlugin 时使用)
31
+ */
32
+ dataDir: string;
18
33
  /** 默认租户上下文 */
19
34
  defaultContext?: StorageContext;
35
+ /**
36
+ * 工具列表(Vite 插件风格)
37
+ * 支持:ToolPlugin、Promise<ToolPlugin>
38
+ */
39
+ tools?: ToolConfigItem[];
20
40
  }
21
41
  /**
22
42
  * 创建 Electron IPC 桥接
@@ -38,4 +58,4 @@ declare function createElectronBridge(options: ElectronBridgeOptions): Promise<{
38
58
  storage: StorageAdapter;
39
59
  }>;
40
60
 
41
- export { type ElectronBridgeOptions, createElectronBridge };
61
+ export { type ElectronBridgeOptions, type FileInfo, createElectronBridge };