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