inspiration-agent 0.0.1
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 +368 -0
- package/dist/.env.example +53 -0
- package/dist/README.md +368 -0
- package/dist/agent/ai.agent.js +712 -0
- package/dist/channel/feishu-connection.js +238 -0
- package/dist/channel/feishu.service.js +222 -0
- package/dist/cli.js +659 -0
- package/dist/config/system.prompt.txt +312 -0
- package/dist/index.js +176 -0
- package/dist/model/base-llm.service.js +320 -0
- package/dist/model/deepseek.service.js +18 -0
- package/dist/model/kimi.service.js +19 -0
- package/dist/model/minimax.service.js +35 -0
- package/dist/model/zhipu.service.js +26 -0
- package/dist/services/database.service.js +697 -0
- package/dist/services/memory.manager.js +486 -0
- package/dist/services/message.queue.js +157 -0
- package/dist/tools/browser.tools.js +814 -0
- package/dist/tools/command.tools.js +361 -0
- package/dist/tools/file.tools.js +222 -0
- package/dist/tools/memory.tools.js +393 -0
- package/dist/tools/scheduler.tools.js +559 -0
- package/dist/tools/search.tools.js +208 -0
- package/dist/utils/logger.js +52 -0
- package/dist/utils/markdown-renderer.js +207 -0
- package/package.json +51 -0
|
@@ -0,0 +1,814 @@
|
|
|
1
|
+
const puppeteer = require("puppeteer");
|
|
2
|
+
const logger = require("../utils/logger");
|
|
3
|
+
const sharp = require("sharp");
|
|
4
|
+
const fs = require("fs").promises;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 浏览器自动化工具类
|
|
8
|
+
* 提供基于 Puppeteer 的浏览器自动化功能
|
|
9
|
+
*/
|
|
10
|
+
class BrowserTools {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.browser = null;
|
|
13
|
+
this.page = null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 启动浏览器
|
|
18
|
+
* @param {Object} args - 参数对象
|
|
19
|
+
* @param {boolean} args.headless - 是否无头模式,默认 true
|
|
20
|
+
* @returns {Promise<Object>} 操作结果
|
|
21
|
+
*/
|
|
22
|
+
async launchBrowser(args) {
|
|
23
|
+
try {
|
|
24
|
+
const { headless = true } = args;
|
|
25
|
+
|
|
26
|
+
logger.info(`\n========== 启动浏览器 ==========`);
|
|
27
|
+
logger.info(`无头模式: ${headless}`);
|
|
28
|
+
logger.info(`================================\n`);
|
|
29
|
+
|
|
30
|
+
if (this.browser) {
|
|
31
|
+
logger.info(`\n========== 浏览器已在运行 ==========\n`);
|
|
32
|
+
return {
|
|
33
|
+
success: true,
|
|
34
|
+
headless: headless,
|
|
35
|
+
message: "浏览器已在运行",
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
this.browser = await puppeteer.launch({
|
|
40
|
+
headless: headless ? "new" : false,
|
|
41
|
+
args: [
|
|
42
|
+
"--no-sandbox",
|
|
43
|
+
"--disable-setuid-sandbox",
|
|
44
|
+
"--disable-blink-features=AutomationControlled",
|
|
45
|
+
"--disable-dev-shm-usage",
|
|
46
|
+
"--disable-gpu",
|
|
47
|
+
"--no-first-run",
|
|
48
|
+
"--no-default-browser-check",
|
|
49
|
+
"--disable-background-timer-throttling",
|
|
50
|
+
"--disable-backgrounding-occluded-windows",
|
|
51
|
+
"--disable-renderer-backgrounding",
|
|
52
|
+
"--disable-features=TranslateUI",
|
|
53
|
+
"--disable-ipc-flooding-protection",
|
|
54
|
+
"--window-size=1920,1080",
|
|
55
|
+
],
|
|
56
|
+
executablePath: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
57
|
+
ignoreDefaultArgs: ["--enable-automation"],
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
this.page = await this.browser.newPage();
|
|
61
|
+
|
|
62
|
+
await this.page.setUserAgent(
|
|
63
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
await this.page.setViewport({ width: 1920, height: 1080 });
|
|
67
|
+
|
|
68
|
+
await this.page.evaluateOnNewDocument(() => {
|
|
69
|
+
Object.defineProperty(navigator, "webdriver", {
|
|
70
|
+
get: () => false,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
Object.defineProperty(navigator, "plugins", {
|
|
74
|
+
get: () => [1, 2, 3, 4, 5],
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
Object.defineProperty(navigator, "languages", {
|
|
78
|
+
get: () => ["zh-CN", "zh", "en"],
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
Object.defineProperty(navigator, "platform", {
|
|
82
|
+
get: () => "MacIntel",
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
window.chrome = {
|
|
86
|
+
runtime: {},
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
Object.defineProperty(navigator, "permissions", {
|
|
90
|
+
get: () => ({
|
|
91
|
+
query: () => Promise.resolve({ state: "granted" }),
|
|
92
|
+
}),
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const originalQuery = window.navigator.permissions.query;
|
|
96
|
+
window.navigator.permissions.query = (parameters) =>
|
|
97
|
+
parameters.name === "notifications"
|
|
98
|
+
? Promise.resolve({ state: Notification.permission })
|
|
99
|
+
: originalQuery(parameters);
|
|
100
|
+
|
|
101
|
+
Object.defineProperty(navigator, "hardwareConcurrency", {
|
|
102
|
+
get: () => 8,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
Object.defineProperty(navigator, "deviceMemory", {
|
|
106
|
+
get: () => 8,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
Object.defineProperty(navigator, "maxTouchPoints", {
|
|
110
|
+
get: () => 0,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
window.outerHeight = window.innerHeight;
|
|
114
|
+
window.outerWidth = window.innerWidth;
|
|
115
|
+
|
|
116
|
+
const originalGetContext = HTMLCanvasElement.prototype.getContext;
|
|
117
|
+
HTMLCanvasElement.prototype.getContext = function (type) {
|
|
118
|
+
const context = originalGetContext.call(this, type);
|
|
119
|
+
if (type === "2d") {
|
|
120
|
+
const originalGetImageData = context.getImageData;
|
|
121
|
+
context.getImageData = function () {
|
|
122
|
+
return originalGetImageData.apply(this, arguments);
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
return context;
|
|
126
|
+
};
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
logger.info(`\n========== 浏览器启动成功 ==========`);
|
|
130
|
+
logger.info(`====================================\n`);
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
success: true,
|
|
134
|
+
headless: headless,
|
|
135
|
+
message: "浏览器启动成功",
|
|
136
|
+
};
|
|
137
|
+
} catch (error) {
|
|
138
|
+
logger.info(`\n========== 浏览器启动失败 ==========`);
|
|
139
|
+
logger.info(`错误: ${error.message}`);
|
|
140
|
+
logger.info(`====================================\n`);
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
success: false,
|
|
144
|
+
error: error.message,
|
|
145
|
+
message: `浏览器启动失败: ${error.message}`,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* 确保浏览器已启动
|
|
152
|
+
* 如果浏览器未启动,自动启动
|
|
153
|
+
* @returns {Promise<boolean>} 是否成功启动
|
|
154
|
+
*/
|
|
155
|
+
async ensureBrowser() {
|
|
156
|
+
if (!this.browser) {
|
|
157
|
+
const result = await this.launchBrowser({});
|
|
158
|
+
return result.success;
|
|
159
|
+
}
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* 导航到指定 URL
|
|
165
|
+
* @param {Object} args - 参数对象
|
|
166
|
+
* @param {string} args.url - 目标 URL
|
|
167
|
+
* @param {number} args.timeout - 超时时间(毫秒),默认 30000
|
|
168
|
+
* @returns {Promise<Object>} 操作结果
|
|
169
|
+
*/
|
|
170
|
+
async navigateTo(args) {
|
|
171
|
+
try {
|
|
172
|
+
const { url, timeout = 30000 } = args;
|
|
173
|
+
|
|
174
|
+
await this.ensureBrowser();
|
|
175
|
+
|
|
176
|
+
if (!url) {
|
|
177
|
+
return {
|
|
178
|
+
success: false,
|
|
179
|
+
error: "缺少 url 参数",
|
|
180
|
+
message: "导航失败: 缺少 URL",
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
logger.info(`\n========== 导航到页面 ==========`);
|
|
185
|
+
logger.info(`URL: ${url}`);
|
|
186
|
+
logger.info(`超时: ${timeout}ms`);
|
|
187
|
+
logger.info(`================================\n`);
|
|
188
|
+
|
|
189
|
+
await this.page.goto(url, { waitUntil: "networkidle2", timeout });
|
|
190
|
+
|
|
191
|
+
const pageTitle = await this.page.title();
|
|
192
|
+
const pageUrl = this.page.url();
|
|
193
|
+
|
|
194
|
+
logger.info(`\n========== 导航成功 ==========`);
|
|
195
|
+
logger.info(`页面标题: ${pageTitle}`);
|
|
196
|
+
logger.info(`页面 URL: ${pageUrl}`);
|
|
197
|
+
logger.info(`=============================\n`);
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
success: true,
|
|
201
|
+
url: pageUrl,
|
|
202
|
+
title: pageTitle,
|
|
203
|
+
message: `成功导航到: ${url}`,
|
|
204
|
+
};
|
|
205
|
+
} catch (error) {
|
|
206
|
+
logger.info(`\n========== 导航失败 ==========`);
|
|
207
|
+
logger.info(`URL: ${args.url}`);
|
|
208
|
+
logger.info(`错误: ${error.message}`);
|
|
209
|
+
logger.info(`=============================\n`);
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
success: false,
|
|
213
|
+
error: error.message,
|
|
214
|
+
message: `导航失败: ${error.message}`,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* 点击页面元素
|
|
221
|
+
* @param {Object} args - 参数对象
|
|
222
|
+
* @param {string} args.selector - CSS 选择器
|
|
223
|
+
* @param {number} args.timeout - 等待超时时间(毫秒),默认 5000
|
|
224
|
+
* @returns {Promise<Object>} 操作结果
|
|
225
|
+
*/
|
|
226
|
+
async clickElement(args) {
|
|
227
|
+
try {
|
|
228
|
+
const { selector, timeout = 5000 } = args;
|
|
229
|
+
|
|
230
|
+
await this.ensureBrowser();
|
|
231
|
+
|
|
232
|
+
if (!selector) {
|
|
233
|
+
return {
|
|
234
|
+
success: false,
|
|
235
|
+
error: "缺少 selector 参数",
|
|
236
|
+
message: "点击失败: 缺少选择器",
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
logger.info(`\n========== 点击元素 ==========`);
|
|
241
|
+
logger.info(`选择器: ${selector}`);
|
|
242
|
+
logger.info(`超时: ${timeout}ms`);
|
|
243
|
+
logger.info(`=============================\n`);
|
|
244
|
+
|
|
245
|
+
await this.page.waitForSelector(selector, { timeout });
|
|
246
|
+
await this.page.click(selector);
|
|
247
|
+
|
|
248
|
+
logger.info(`\n========== 点击成功 ==========\n`);
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
success: true,
|
|
252
|
+
selector: selector,
|
|
253
|
+
message: `成功点击元素: ${selector}`,
|
|
254
|
+
};
|
|
255
|
+
} catch (error) {
|
|
256
|
+
logger.info(`\n========== 点击失败 ==========`);
|
|
257
|
+
logger.info(`选择器: ${args.selector}`);
|
|
258
|
+
logger.info(`错误: ${error.message}`);
|
|
259
|
+
logger.info(`=============================\n`);
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
success: false,
|
|
263
|
+
error: error.message,
|
|
264
|
+
message: `点击失败: ${error.message}`,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* 填写表单输入
|
|
271
|
+
* @param {Object} args - 参数对象
|
|
272
|
+
* @param {string} args.selector - CSS 选择器
|
|
273
|
+
* @param {string} args.value - 要输入的值
|
|
274
|
+
* @param {number} args.timeout - 等待超时时间(毫秒),默认 5000
|
|
275
|
+
* @returns {Promise<Object>} 操作结果
|
|
276
|
+
*/
|
|
277
|
+
async fillInput(args) {
|
|
278
|
+
try {
|
|
279
|
+
const { selector, value, timeout = 5000 } = args;
|
|
280
|
+
|
|
281
|
+
await this.ensureBrowser();
|
|
282
|
+
|
|
283
|
+
if (!selector || value === undefined) {
|
|
284
|
+
return {
|
|
285
|
+
success: false,
|
|
286
|
+
error: "缺少 selector 或 value 参数",
|
|
287
|
+
message: "填写失败: 缺少必要参数",
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
logger.info(`\n========== 填写输入框 ==========`);
|
|
292
|
+
logger.info(`选择器: ${selector}`);
|
|
293
|
+
logger.info(`值: ${value}`);
|
|
294
|
+
logger.info(`超时: ${timeout}ms`);
|
|
295
|
+
logger.info(`================================\n`);
|
|
296
|
+
|
|
297
|
+
await this.page.waitForSelector(selector, { timeout });
|
|
298
|
+
await this.page.type(selector, value);
|
|
299
|
+
|
|
300
|
+
logger.info(`\n========== 填写成功 ==========\n`);
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
success: true,
|
|
304
|
+
selector: selector,
|
|
305
|
+
value: value,
|
|
306
|
+
message: `成功填写输入框: ${selector}`,
|
|
307
|
+
};
|
|
308
|
+
} catch (error) {
|
|
309
|
+
logger.info(`\n========== 填写失败 ==========`);
|
|
310
|
+
logger.info(`选择器: ${args.selector}`);
|
|
311
|
+
logger.info(`错误: ${error.message}`);
|
|
312
|
+
logger.info(`=============================\n`);
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
success: false,
|
|
316
|
+
error: error.message,
|
|
317
|
+
message: `填写失败: ${error.message}`,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* 获取页面内容
|
|
324
|
+
* @param {Object} args - 参数对象
|
|
325
|
+
* @param {string} args.selector - CSS 选择器,可选,默认获取整个页面
|
|
326
|
+
* @returns {Promise<Object>} 操作结果
|
|
327
|
+
*/
|
|
328
|
+
async getPageContent(args) {
|
|
329
|
+
try {
|
|
330
|
+
const { selector } = args;
|
|
331
|
+
|
|
332
|
+
await this.ensureBrowser();
|
|
333
|
+
|
|
334
|
+
logger.info(`\n========== 获取页面内容 ==========`);
|
|
335
|
+
logger.info(`选择器: ${selector || "整个页面"}`);
|
|
336
|
+
logger.info(`==================================\n`);
|
|
337
|
+
|
|
338
|
+
let content;
|
|
339
|
+
if (selector) {
|
|
340
|
+
const element = await this.page.$(selector);
|
|
341
|
+
if (!element) {
|
|
342
|
+
return {
|
|
343
|
+
success: false,
|
|
344
|
+
error: "元素未找到",
|
|
345
|
+
message: `未找到元素: ${selector}`,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
content = await this.page.evaluate((el) => el.textContent, element);
|
|
349
|
+
} else {
|
|
350
|
+
content = await this.page.evaluate(() => document.body.textContent);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const title = await this.page.title();
|
|
354
|
+
const url = this.page.url();
|
|
355
|
+
|
|
356
|
+
logger.info(`\n========== 获取成功 ==========`);
|
|
357
|
+
logger.info(`内容长度: ${content.length} 字符`);
|
|
358
|
+
logger.info(`=============================\n`);
|
|
359
|
+
|
|
360
|
+
return {
|
|
361
|
+
success: true,
|
|
362
|
+
url: url,
|
|
363
|
+
title: title,
|
|
364
|
+
content: content,
|
|
365
|
+
selector: selector,
|
|
366
|
+
message: `成功获取页面内容`,
|
|
367
|
+
};
|
|
368
|
+
} catch (error) {
|
|
369
|
+
logger.info(`\n========== 获取失败 ==========`);
|
|
370
|
+
logger.info(`错误: ${error.message}`);
|
|
371
|
+
logger.info(`=============================\n`);
|
|
372
|
+
|
|
373
|
+
return {
|
|
374
|
+
success: false,
|
|
375
|
+
error: error.message,
|
|
376
|
+
message: `获取页面内容失败: ${error.message}`,
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* 截取页面截图
|
|
383
|
+
* @param {Object} args - 参数对象
|
|
384
|
+
* @param {string} args.path - 保存路径,可选
|
|
385
|
+
* @param {string} args.selector - CSS 选择器,可选,截取指定元素
|
|
386
|
+
* @param {number} args.quality - 压缩质量(1-100),默认 80
|
|
387
|
+
* @param {number} args.maxWidth - 最大宽度(像素),可选
|
|
388
|
+
* @param {number} args.maxHeight - 最大高度(像素),可选
|
|
389
|
+
* @returns {Promise<Object>} 操作结果
|
|
390
|
+
*/
|
|
391
|
+
async takeScreenshot(args) {
|
|
392
|
+
try {
|
|
393
|
+
const { path, selector, quality = 80, maxWidth, maxHeight } = args;
|
|
394
|
+
|
|
395
|
+
await this.ensureBrowser();
|
|
396
|
+
|
|
397
|
+
logger.info(`\n========== 截图 ==========`);
|
|
398
|
+
logger.info(`路径: ${path || "不保存"}`);
|
|
399
|
+
logger.info(`选择器: ${selector || "整个页面"}`);
|
|
400
|
+
logger.info(`压缩质量: ${quality}`);
|
|
401
|
+
logger.info(`最大尺寸: ${maxWidth ? maxWidth + "x" + maxHeight : "不限制"}`);
|
|
402
|
+
logger.info(`===========================\n`);
|
|
403
|
+
|
|
404
|
+
let screenshot;
|
|
405
|
+
if (selector) {
|
|
406
|
+
const element = await this.page.$(selector);
|
|
407
|
+
if (!element) {
|
|
408
|
+
return {
|
|
409
|
+
success: false,
|
|
410
|
+
error: "元素未找到",
|
|
411
|
+
message: `未找到元素: ${selector}`,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
screenshot = await element.screenshot({ encoding: "base64" });
|
|
415
|
+
} else {
|
|
416
|
+
screenshot = await this.page.screenshot({ encoding: "base64", fullPage: true });
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (path) {
|
|
420
|
+
const tempPath = path.replace(/(\.[^.]+)$/, "_temp$1");
|
|
421
|
+
await this.page.screenshot({ path: tempPath, fullPage: !selector });
|
|
422
|
+
|
|
423
|
+
let sharpInstance = sharp(tempPath);
|
|
424
|
+
|
|
425
|
+
if (maxWidth || maxHeight) {
|
|
426
|
+
sharpInstance = sharpInstance.resize(maxWidth || null, maxHeight || null, {
|
|
427
|
+
fit: "inside",
|
|
428
|
+
withoutEnlargement: true,
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const ext = path.split(".").pop().toLowerCase();
|
|
433
|
+
if (ext === "jpg" || ext === "jpeg") {
|
|
434
|
+
sharpInstance = sharpInstance.jpeg({ quality });
|
|
435
|
+
} else if (ext === "png") {
|
|
436
|
+
sharpInstance = sharpInstance.png({ quality: Math.round(quality / 10) });
|
|
437
|
+
} else if (ext === "webp") {
|
|
438
|
+
sharpInstance = sharpInstance.webp({ quality });
|
|
439
|
+
} else {
|
|
440
|
+
sharpInstance = sharpInstance.jpeg({ quality });
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
await sharpInstance.toFile(path);
|
|
444
|
+
|
|
445
|
+
const originalStats = await fs.stat(tempPath);
|
|
446
|
+
const compressedStats = await fs.stat(path);
|
|
447
|
+
await fs.unlink(tempPath);
|
|
448
|
+
|
|
449
|
+
logger.info(`\n========== 压缩完成 ==========`);
|
|
450
|
+
logger.info(`原始大小: ${originalStats.size} 字节`);
|
|
451
|
+
logger.info(`压缩后: ${compressedStats.size} 字节`);
|
|
452
|
+
logger.info(
|
|
453
|
+
`压缩率: ${((1 - compressedStats.size / originalStats.size) * 100).toFixed(2)}%`
|
|
454
|
+
);
|
|
455
|
+
logger.info(`=============================\n`);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
logger.info(`\n========== 截图成功 ==========\n`);
|
|
459
|
+
|
|
460
|
+
return {
|
|
461
|
+
success: true,
|
|
462
|
+
path: path,
|
|
463
|
+
screenshot: screenshot,
|
|
464
|
+
selector: selector,
|
|
465
|
+
message: `成功截图${path ? `,保存到: ${path}` : ""}`,
|
|
466
|
+
};
|
|
467
|
+
} catch (error) {
|
|
468
|
+
logger.info(`\n========== 截图失败 ==========`);
|
|
469
|
+
logger.info(`错误: ${error.message}`);
|
|
470
|
+
logger.info(`=============================\n`);
|
|
471
|
+
|
|
472
|
+
return {
|
|
473
|
+
success: false,
|
|
474
|
+
error: error.message,
|
|
475
|
+
message: `截图失败: ${error.message}`,
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* 等待元素出现
|
|
482
|
+
* @param {Object} args - 参数对象
|
|
483
|
+
* @param {string} args.selector - CSS 选择器
|
|
484
|
+
* @param {number} args.timeout - 超时时间(毫秒),默认 30000
|
|
485
|
+
* @returns {Promise<Object>} 操作结果
|
|
486
|
+
*/
|
|
487
|
+
async waitForElement(args) {
|
|
488
|
+
try {
|
|
489
|
+
const { selector, timeout = 30000 } = args;
|
|
490
|
+
|
|
491
|
+
await this.ensureBrowser();
|
|
492
|
+
|
|
493
|
+
if (!selector) {
|
|
494
|
+
return {
|
|
495
|
+
success: false,
|
|
496
|
+
error: "缺少 selector 参数",
|
|
497
|
+
message: "等待失败: 缺少选择器",
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
logger.info(`\n========== 等待元素 ==========`);
|
|
502
|
+
logger.info(`选择器: ${selector}`);
|
|
503
|
+
logger.info(`超时: ${timeout}ms`);
|
|
504
|
+
logger.info(`===========================\n`);
|
|
505
|
+
|
|
506
|
+
await this.page.waitForSelector(selector, { timeout });
|
|
507
|
+
|
|
508
|
+
logger.info(`\n========== 元素已出现 ==========\n`);
|
|
509
|
+
|
|
510
|
+
return {
|
|
511
|
+
success: true,
|
|
512
|
+
selector: selector,
|
|
513
|
+
message: `元素已出现: ${selector}`,
|
|
514
|
+
};
|
|
515
|
+
} catch (error) {
|
|
516
|
+
logger.info(`\n========== 等待超时 ==========`);
|
|
517
|
+
logger.info(`选择器: ${args.selector}`);
|
|
518
|
+
logger.info(`错误: ${error.message}`);
|
|
519
|
+
logger.info(`=============================\n`);
|
|
520
|
+
|
|
521
|
+
return {
|
|
522
|
+
success: false,
|
|
523
|
+
error: error.message,
|
|
524
|
+
message: `等待元素超时: ${error.message}`,
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* 执行 JavaScript 代码
|
|
531
|
+
* @param {Object} args - 参数对象
|
|
532
|
+
* @param {string} args.script - JavaScript 代码
|
|
533
|
+
* @returns {Promise<Object>} 操作结果
|
|
534
|
+
*/
|
|
535
|
+
async executeScript(args) {
|
|
536
|
+
try {
|
|
537
|
+
const { script } = args;
|
|
538
|
+
|
|
539
|
+
await this.ensureBrowser();
|
|
540
|
+
|
|
541
|
+
if (!script) {
|
|
542
|
+
return {
|
|
543
|
+
success: false,
|
|
544
|
+
error: "缺少 script 参数",
|
|
545
|
+
message: "执行失败: 缺少脚本",
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
logger.info(`\n========== 执行脚本 ==========`);
|
|
550
|
+
logger.info(`脚本: ${script.substring(0, 100)}...`);
|
|
551
|
+
logger.info(`=============================\n`);
|
|
552
|
+
|
|
553
|
+
const result = await this.page.evaluate(script);
|
|
554
|
+
|
|
555
|
+
logger.info(`\n========== 执行成功 ==========\n`);
|
|
556
|
+
|
|
557
|
+
return {
|
|
558
|
+
success: true,
|
|
559
|
+
result: result,
|
|
560
|
+
message: "脚本执行成功",
|
|
561
|
+
};
|
|
562
|
+
} catch (error) {
|
|
563
|
+
logger.info(`\n========== 执行失败 ==========`);
|
|
564
|
+
logger.info(`错误: ${error.message}`);
|
|
565
|
+
logger.info(`=============================\n`);
|
|
566
|
+
|
|
567
|
+
return {
|
|
568
|
+
success: false,
|
|
569
|
+
error: error.message,
|
|
570
|
+
message: `脚本执行失败: ${error.message}`,
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* 关闭浏览器
|
|
577
|
+
* @param {Object} args - 参数对象(未使用)
|
|
578
|
+
* @returns {Promise<Object>} 操作结果
|
|
579
|
+
*/
|
|
580
|
+
async closeBrowser(args) {
|
|
581
|
+
try {
|
|
582
|
+
if (!this.browser) {
|
|
583
|
+
return {
|
|
584
|
+
success: false,
|
|
585
|
+
error: "浏览器未启动",
|
|
586
|
+
message: "浏览器未运行",
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
logger.info(`\n========== 关闭浏览器 ==========\n`);
|
|
591
|
+
|
|
592
|
+
await this.browser.close();
|
|
593
|
+
this.browser = null;
|
|
594
|
+
this.page = null;
|
|
595
|
+
|
|
596
|
+
logger.info(`\n========== 浏览器已关闭 ==========\n`);
|
|
597
|
+
|
|
598
|
+
return {
|
|
599
|
+
success: true,
|
|
600
|
+
message: "浏览器已关闭",
|
|
601
|
+
};
|
|
602
|
+
} catch (error) {
|
|
603
|
+
logger.info(`\n========== 关闭失败 ==========`);
|
|
604
|
+
logger.info(`错误: ${error.message}`);
|
|
605
|
+
logger.info(`=============================\n`);
|
|
606
|
+
|
|
607
|
+
return {
|
|
608
|
+
success: false,
|
|
609
|
+
error: error.message,
|
|
610
|
+
message: `关闭浏览器失败: ${error.message}`,
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* 获取函数定义
|
|
617
|
+
* 返回所有浏览器工具的函数定义,用于 OpenAI 函数调用格式
|
|
618
|
+
* @returns {Array<Object>} 函数定义数组
|
|
619
|
+
*/
|
|
620
|
+
getFunctionDefinitions() {
|
|
621
|
+
return [
|
|
622
|
+
{
|
|
623
|
+
type: "function",
|
|
624
|
+
function: {
|
|
625
|
+
name: "launch_browser",
|
|
626
|
+
description: "启动浏览器实例(如果已启动则复用现有实例),支持多轮对话保持浏览器状态",
|
|
627
|
+
parameters: {
|
|
628
|
+
type: "object",
|
|
629
|
+
properties: {
|
|
630
|
+
headless: {
|
|
631
|
+
type: "boolean",
|
|
632
|
+
description: "是否使用无头模式(不显示浏览器界面),默认 true",
|
|
633
|
+
default: true,
|
|
634
|
+
},
|
|
635
|
+
},
|
|
636
|
+
},
|
|
637
|
+
},
|
|
638
|
+
},
|
|
639
|
+
{
|
|
640
|
+
type: "function",
|
|
641
|
+
function: {
|
|
642
|
+
name: "navigate_to",
|
|
643
|
+
description: "导航到指定的 URL(如果浏览器未启动会自动启动)",
|
|
644
|
+
parameters: {
|
|
645
|
+
type: "object",
|
|
646
|
+
properties: {
|
|
647
|
+
url: {
|
|
648
|
+
type: "string",
|
|
649
|
+
description: "要访问的网页 URL,必须以 http:// 或 https:// 开头",
|
|
650
|
+
},
|
|
651
|
+
timeout: {
|
|
652
|
+
type: "integer",
|
|
653
|
+
description: "页面加载超时时间(毫秒),默认 30000",
|
|
654
|
+
default: 30000,
|
|
655
|
+
},
|
|
656
|
+
},
|
|
657
|
+
required: ["url"],
|
|
658
|
+
},
|
|
659
|
+
},
|
|
660
|
+
},
|
|
661
|
+
{
|
|
662
|
+
type: "function",
|
|
663
|
+
function: {
|
|
664
|
+
name: "click_element",
|
|
665
|
+
description: "点击页面上的元素(如果浏览器未启动会自动启动)",
|
|
666
|
+
parameters: {
|
|
667
|
+
type: "object",
|
|
668
|
+
properties: {
|
|
669
|
+
selector: {
|
|
670
|
+
type: "string",
|
|
671
|
+
description:
|
|
672
|
+
"CSS 选择器,如 '#submit-btn', '.login-button', 'input[type=\"submit\"]'",
|
|
673
|
+
},
|
|
674
|
+
timeout: {
|
|
675
|
+
type: "integer",
|
|
676
|
+
description: "等待元素出现的超时时间(毫秒),默认 5000",
|
|
677
|
+
default: 5000,
|
|
678
|
+
},
|
|
679
|
+
},
|
|
680
|
+
required: ["selector"],
|
|
681
|
+
},
|
|
682
|
+
},
|
|
683
|
+
},
|
|
684
|
+
{
|
|
685
|
+
type: "function",
|
|
686
|
+
function: {
|
|
687
|
+
name: "fill_input",
|
|
688
|
+
description: "在输入框中填写内容(如果浏览器未启动会自动启动)",
|
|
689
|
+
parameters: {
|
|
690
|
+
type: "object",
|
|
691
|
+
properties: {
|
|
692
|
+
selector: {
|
|
693
|
+
type: "string",
|
|
694
|
+
description: "CSS 选择器,如 '#username', 'input[name=\"email\"]'",
|
|
695
|
+
},
|
|
696
|
+
value: {
|
|
697
|
+
type: "string",
|
|
698
|
+
description: "要输入的文本内容",
|
|
699
|
+
},
|
|
700
|
+
timeout: {
|
|
701
|
+
type: "integer",
|
|
702
|
+
description: "等待输入框出现的超时时间(毫秒),默认 5000",
|
|
703
|
+
default: 5000,
|
|
704
|
+
},
|
|
705
|
+
},
|
|
706
|
+
required: ["selector", "value"],
|
|
707
|
+
},
|
|
708
|
+
},
|
|
709
|
+
},
|
|
710
|
+
{
|
|
711
|
+
type: "function",
|
|
712
|
+
function: {
|
|
713
|
+
name: "get_page_content",
|
|
714
|
+
description: "获取页面文本内容(如果浏览器未启动会自动启动)",
|
|
715
|
+
parameters: {
|
|
716
|
+
type: "object",
|
|
717
|
+
properties: {
|
|
718
|
+
selector: {
|
|
719
|
+
type: "string",
|
|
720
|
+
description: "CSS 选择器,可选,如果不提供则获取整个页面的内容",
|
|
721
|
+
},
|
|
722
|
+
},
|
|
723
|
+
},
|
|
724
|
+
},
|
|
725
|
+
},
|
|
726
|
+
// {
|
|
727
|
+
// type: "function",
|
|
728
|
+
// function: {
|
|
729
|
+
// name: "take_screenshot",
|
|
730
|
+
// description: "截取页面或元素的截图,支持图片压缩和尺寸调整(如果浏览器未启动会自动启动)",
|
|
731
|
+
// parameters: {
|
|
732
|
+
// type: "object",
|
|
733
|
+
// properties: {
|
|
734
|
+
// path: {
|
|
735
|
+
// type: "string",
|
|
736
|
+
// description: "截图保存路径,如 'screenshot.png',可选",
|
|
737
|
+
// },
|
|
738
|
+
// selector: {
|
|
739
|
+
// type: "string",
|
|
740
|
+
// description: "CSS 选择器,可选,截取指定元素,不提供则截取整个页面",
|
|
741
|
+
// },
|
|
742
|
+
// quality: {
|
|
743
|
+
// type: "integer",
|
|
744
|
+
// description: "压缩质量(1-100),默认 80,数值越小压缩率越高",
|
|
745
|
+
// default: 80,
|
|
746
|
+
// },
|
|
747
|
+
// maxWidth: {
|
|
748
|
+
// type: "integer",
|
|
749
|
+
// description: "最大宽度(像素),可选,超过此宽度会等比缩放",
|
|
750
|
+
// },
|
|
751
|
+
// maxHeight: {
|
|
752
|
+
// type: "integer",
|
|
753
|
+
// description: "最大高度(像素),可选,超过此高度会等比缩放",
|
|
754
|
+
// },
|
|
755
|
+
// },
|
|
756
|
+
// },
|
|
757
|
+
// },
|
|
758
|
+
// },
|
|
759
|
+
{
|
|
760
|
+
type: "function",
|
|
761
|
+
function: {
|
|
762
|
+
name: "wait_for_element",
|
|
763
|
+
description: "等待页面元素出现(如果浏览器未启动会自动启动)",
|
|
764
|
+
parameters: {
|
|
765
|
+
type: "object",
|
|
766
|
+
properties: {
|
|
767
|
+
selector: {
|
|
768
|
+
type: "string",
|
|
769
|
+
description: "CSS 选择器,如 '#loading', '.content'",
|
|
770
|
+
},
|
|
771
|
+
timeout: {
|
|
772
|
+
type: "integer",
|
|
773
|
+
description: "等待超时时间(毫秒),默认 30000",
|
|
774
|
+
default: 30000,
|
|
775
|
+
},
|
|
776
|
+
},
|
|
777
|
+
required: ["selector"],
|
|
778
|
+
},
|
|
779
|
+
},
|
|
780
|
+
},
|
|
781
|
+
{
|
|
782
|
+
type: "function",
|
|
783
|
+
function: {
|
|
784
|
+
name: "execute_script",
|
|
785
|
+
description: "在页面中执行 JavaScript 代码(如果浏览器未启动会自动启动)",
|
|
786
|
+
parameters: {
|
|
787
|
+
type: "object",
|
|
788
|
+
properties: {
|
|
789
|
+
script: {
|
|
790
|
+
type: "string",
|
|
791
|
+
description:
|
|
792
|
+
"要执行的 JavaScript 代码,如 'document.title' 或 'window.scrollTo(0, 1000)'",
|
|
793
|
+
},
|
|
794
|
+
},
|
|
795
|
+
required: ["script"],
|
|
796
|
+
},
|
|
797
|
+
},
|
|
798
|
+
},
|
|
799
|
+
{
|
|
800
|
+
type: "function",
|
|
801
|
+
function: {
|
|
802
|
+
name: "close_browser",
|
|
803
|
+
description: "关闭浏览器实例",
|
|
804
|
+
parameters: {
|
|
805
|
+
type: "object",
|
|
806
|
+
properties: {},
|
|
807
|
+
},
|
|
808
|
+
},
|
|
809
|
+
},
|
|
810
|
+
];
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
module.exports = BrowserTools;
|