yiyan-browser-agent 1.4.5 → 1.4.7
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/LICENSE +1 -1
- package/README.md +241 -153
- package/package.json +25 -30
- package/src/agent.js +63 -0
- package/src/browser.js +589 -0
- package/src/calibrate.js +178 -0
- package/src/config.js +68 -0
- package/src/index.js +115 -0
- package/src/logger.js +118 -0
- package/src/parser.js +272 -0
- package/src/postinstall.js +26 -0
- package/src/prompt.js +188 -0
- package/src/tools.js +547 -0
- package/dist/cli.d.ts +0 -27
- package/dist/cli.js +0 -857
- package/dist/cli.js.map +0 -1
- package/dist/index.d.ts +0 -73
- package/dist/index.js +0 -666
- package/dist/index.js.map +0 -1
- package/dist/postinstall.d.ts +0 -2
- package/dist/postinstall.js +0 -70
- package/dist/postinstall.js.map +0 -1
- package/dist/types-BhQ78DYf.d.ts +0 -39
package/dist/index.js
DELETED
|
@@ -1,666 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __export = (target, all) => {
|
|
9
|
-
for (var name in all)
|
|
10
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
-
};
|
|
12
|
-
var __copyProps = (to, from, except, desc) => {
|
|
13
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
-
for (let key of __getOwnPropNames(from))
|
|
15
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
-
}
|
|
18
|
-
return to;
|
|
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
|
-
));
|
|
28
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
-
|
|
30
|
-
// src/index.ts
|
|
31
|
-
var src_exports = {};
|
|
32
|
-
__export(src_exports, {
|
|
33
|
-
CONFIG_FILE_PATH: () => CONFIG_FILE_PATH,
|
|
34
|
-
DEFAULT_OPTIONS: () => DEFAULT_OPTIONS,
|
|
35
|
-
DEFAULT_SESSION_DIR: () => DEFAULT_SESSION_DIR2,
|
|
36
|
-
YIYAN_CHAT_URL: () => YIYAN_CHAT_URL,
|
|
37
|
-
YiyanAgent: () => YiyanAgent,
|
|
38
|
-
YiyanAgentError: () => YiyanAgentError,
|
|
39
|
-
readConfig: () => readConfig,
|
|
40
|
-
saveConfig: () => saveConfig
|
|
41
|
-
});
|
|
42
|
-
module.exports = __toCommonJS(src_exports);
|
|
43
|
-
|
|
44
|
-
// src/agent.ts
|
|
45
|
-
var import_path = __toESM(require("path"));
|
|
46
|
-
var import_os = __toESM(require("os"));
|
|
47
|
-
|
|
48
|
-
// src/types.ts
|
|
49
|
-
var YiyanAgentError = class extends Error {
|
|
50
|
-
constructor(type, message) {
|
|
51
|
-
super(message);
|
|
52
|
-
this.type = type;
|
|
53
|
-
this.name = "YiyanAgentError";
|
|
54
|
-
}
|
|
55
|
-
type;
|
|
56
|
-
};
|
|
57
|
-
var DEFAULT_OPTIONS = {
|
|
58
|
-
timeout: 12e4,
|
|
59
|
-
retryCount: 3,
|
|
60
|
-
profileDir: "",
|
|
61
|
-
chromePath: ""
|
|
62
|
-
};
|
|
63
|
-
var YIYAN_CHAT_URL = "https://yiyan.baidu.com/";
|
|
64
|
-
|
|
65
|
-
// src/browser.ts
|
|
66
|
-
var import_playwright = require("playwright");
|
|
67
|
-
var import_fs = __toESM(require("fs"));
|
|
68
|
-
var SELECTORS = {
|
|
69
|
-
// 输入框选择器(按优先级排序)
|
|
70
|
-
chatInput: [
|
|
71
|
-
"#chat-input",
|
|
72
|
-
'textarea[placeholder*="\u8F93\u5165"]',
|
|
73
|
-
"textarea[placeholder]",
|
|
74
|
-
"textarea",
|
|
75
|
-
'[contenteditable="true"][role="textbox"]',
|
|
76
|
-
'[contenteditable="true"]'
|
|
77
|
-
],
|
|
78
|
-
// 发送按钮选择器
|
|
79
|
-
sendButton: [
|
|
80
|
-
'button[aria-label*="\u53D1\u9001"]',
|
|
81
|
-
'button[aria-label*="Send"]',
|
|
82
|
-
'[data-testid="send-button"]',
|
|
83
|
-
'button[type="submit"]',
|
|
84
|
-
'[class*="send-btn"]',
|
|
85
|
-
'[class*="sendBtn"]',
|
|
86
|
-
'[class*="send-button"]'
|
|
87
|
-
],
|
|
88
|
-
// 响应容器选择器
|
|
89
|
-
responseContainer: [
|
|
90
|
-
'[class*="answerBox"]',
|
|
91
|
-
'[class*="response"]',
|
|
92
|
-
'[class*="ai-message"]',
|
|
93
|
-
'[class*="assistant"]',
|
|
94
|
-
'[class*="markdown"]',
|
|
95
|
-
'[class*="message-content"]',
|
|
96
|
-
'[class*="chat-message"]'
|
|
97
|
-
],
|
|
98
|
-
// 停止生成按钮
|
|
99
|
-
stopButton: [
|
|
100
|
-
'button[aria-label*="\u505C\u6B62"]',
|
|
101
|
-
'button[aria-label*="Stop"]',
|
|
102
|
-
'[class*="stop-btn"]',
|
|
103
|
-
'[class*="stopBtn"]',
|
|
104
|
-
'[class*="stop-gen"]'
|
|
105
|
-
]
|
|
106
|
-
};
|
|
107
|
-
function log(verbose, msg) {
|
|
108
|
-
if (verbose) {
|
|
109
|
-
process.stderr.write(`[yiyan-browser-agent] ${msg}
|
|
110
|
-
`);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
async function launchBrowser(options) {
|
|
114
|
-
const { sessionDir, headless, timeout = 3e4, verbose = false } = options;
|
|
115
|
-
process.stderr.write("[yiyan-browser-agent] \u542F\u52A8 Chromium \u6D4F\u89C8\u5668...\n");
|
|
116
|
-
try {
|
|
117
|
-
if (!import_fs.default.existsSync(sessionDir)) {
|
|
118
|
-
import_fs.default.mkdirSync(sessionDir, { recursive: true });
|
|
119
|
-
}
|
|
120
|
-
const context = await import_playwright.chromium.launchPersistentContext(sessionDir, {
|
|
121
|
-
headless,
|
|
122
|
-
viewport: { width: 1280, height: 900 },
|
|
123
|
-
userAgent: [
|
|
124
|
-
"Mozilla/5.0 (X11; Linux x86_64)",
|
|
125
|
-
"AppleWebKit/537.36 (KHTML, like Gecko)",
|
|
126
|
-
"Chrome/124.0.0.0 Safari/537.36"
|
|
127
|
-
].join(" "),
|
|
128
|
-
args: [
|
|
129
|
-
"--disable-blink-features=AutomationControlled",
|
|
130
|
-
"--no-first-run",
|
|
131
|
-
"--no-default-browser-check",
|
|
132
|
-
"--no-sandbox",
|
|
133
|
-
"--disable-setuid-sandbox",
|
|
134
|
-
"--disable-dev-shm-usage"
|
|
135
|
-
],
|
|
136
|
-
ignoreDefaultArgs: ["--enable-automation"]
|
|
137
|
-
});
|
|
138
|
-
context.setDefaultTimeout(timeout);
|
|
139
|
-
const pages = context.pages();
|
|
140
|
-
const page = pages.length > 0 ? pages[0] : await context.newPage();
|
|
141
|
-
await page.addInitScript(() => {
|
|
142
|
-
Object.defineProperty(navigator, "webdriver", { get: () => false });
|
|
143
|
-
});
|
|
144
|
-
process.stderr.write("[yiyan-browser-agent] \u6D4F\u89C8\u5668\u542F\u52A8\u5B8C\u6210\n");
|
|
145
|
-
return { context, page };
|
|
146
|
-
} catch (error) {
|
|
147
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
148
|
-
process.stderr.write(`[yiyan-browser-agent] \u2717 \u6D4F\u89C8\u5668\u542F\u52A8\u5931\u8D25: ${errorMsg}
|
|
149
|
-
`);
|
|
150
|
-
process.stderr.write("[yiyan-browser-agent] \u63D0\u793A: Ubuntu \u9700\u8981\u5148\u8FD0\u884C: npx playwright install-deps chromium\n");
|
|
151
|
-
throw new YiyanAgentError(
|
|
152
|
-
"BROWSER_LAUNCH",
|
|
153
|
-
`Failed to launch browser: ${errorMsg}`
|
|
154
|
-
);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
async function closeBrowser(context, verbose) {
|
|
158
|
-
process.stderr.write("[yiyan-browser-agent] \u5173\u95ED\u6D4F\u89C8\u5668...\n");
|
|
159
|
-
try {
|
|
160
|
-
await context.close();
|
|
161
|
-
process.stderr.write("[yiyan-browser-agent] \u6D4F\u89C8\u5668\u5DF2\u5173\u95ED\n");
|
|
162
|
-
} catch {
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
async function navigateToYiyan(page, verbose = false) {
|
|
166
|
-
process.stderr.write("[yiyan-browser-agent] \u5BFC\u822A\u5230\u6587\u5FC3\u4E00\u8A00...\n");
|
|
167
|
-
try {
|
|
168
|
-
await page.goto(YIYAN_CHAT_URL, { waitUntil: "domcontentloaded", timeout: 3e4 });
|
|
169
|
-
await page.waitForTimeout(1500);
|
|
170
|
-
process.stderr.write("[yiyan-browser-agent] \u9875\u9762\u52A0\u8F7D\u5B8C\u6210\n");
|
|
171
|
-
} catch (err) {
|
|
172
|
-
process.stderr.write(`[yiyan-browser-agent] \u5BFC\u822A\u8B66\u544A: ${err instanceof Error ? err.message : String(err)}
|
|
173
|
-
`);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
async function checkLoggedIn(page, verbose = false) {
|
|
177
|
-
await page.waitForTimeout(2e3);
|
|
178
|
-
const isLoggedIn = await page.evaluate(() => {
|
|
179
|
-
const url = window.location.href;
|
|
180
|
-
const bodyText = document.body?.innerText || "";
|
|
181
|
-
if (url.includes("/auth") || url.includes("/login") || url.includes("/sign")) return false;
|
|
182
|
-
if (bodyText.includes("\u672A\u767B\u5F55") || bodyText.includes("\u8BF7\u767B\u5F55")) return false;
|
|
183
|
-
if (document.querySelector('input[type="password"]')) return false;
|
|
184
|
-
const loginBtn = document.querySelector("button");
|
|
185
|
-
if (loginBtn && loginBtn.textContent?.trim() === "\u767B\u5F55") return false;
|
|
186
|
-
return true;
|
|
187
|
-
});
|
|
188
|
-
process.stderr.write(isLoggedIn ? "[yiyan-browser-agent] \u2713 \u5DF2\u68C0\u6D4B\u5230\u767B\u5F55\u72B6\u6001\n" : "[yiyan-browser-agent] \u26A0 \u672A\u767B\u5F55\n");
|
|
189
|
-
return isLoggedIn;
|
|
190
|
-
}
|
|
191
|
-
async function checkCaptcha(page, verbose = false) {
|
|
192
|
-
const hasCaptcha = await page.evaluate(() => {
|
|
193
|
-
const bodyText = document.body.innerText || "";
|
|
194
|
-
const indicators = ["\u9A8C\u8BC1\u7801", "\u8BF7\u5B8C\u6210\u9A8C\u8BC1", "\u5B89\u5168\u9A8C\u8BC1", "\u6ED1\u52A8\u9A8C\u8BC1", "captcha"];
|
|
195
|
-
for (const kw of indicators) {
|
|
196
|
-
if (bodyText.includes(kw)) return true;
|
|
197
|
-
}
|
|
198
|
-
const dialogs = document.querySelectorAll('[role="dialog"], [class*="captcha"], [class*="verify"]');
|
|
199
|
-
return dialogs.length > 0;
|
|
200
|
-
});
|
|
201
|
-
if (hasCaptcha) log(verbose, "\u26A0 \u68C0\u6D4B\u5230\u9A8C\u8BC1\u7801\uFF01");
|
|
202
|
-
return hasCaptcha;
|
|
203
|
-
}
|
|
204
|
-
async function waitForUserAction(page, reason, verbose = false) {
|
|
205
|
-
const messages = {
|
|
206
|
-
captcha: "\u68C0\u6D4B\u5230\u9A8C\u8BC1\u7801\uFF0C\u8BF7\u5728\u6D4F\u89C8\u5668\u4E2D\u624B\u52A8\u5B8C\u6210\u9A8C\u8BC1",
|
|
207
|
-
login: "\u68C0\u6D4B\u5230\u672A\u767B\u5F55\uFF0C\u8BF7\u5728\u6D4F\u89C8\u5668\u4E2D\u624B\u52A8\u767B\u5F55",
|
|
208
|
-
"no-reply": "AI \u672A\u56DE\u590D\uFF0C\u8BF7\u68C0\u67E5\u6D4F\u89C8\u5668\u662F\u5426\u9700\u8981\u624B\u52A8\u64CD\u4F5C"
|
|
209
|
-
};
|
|
210
|
-
process.stderr.write(`
|
|
211
|
-
[yiyan-browser-agent] \u26A0 ${messages[reason]}
|
|
212
|
-
`);
|
|
213
|
-
process.stderr.write("[yiyan-browser-agent] \u7B49\u5F85\u60A8\u64CD\u4F5C\u5B8C\u6210...\uFF08\u64CD\u4F5C\u5B8C\u6210\u540E\u4F1A\u81EA\u52A8\u7EE7\u7EED\uFF09\n\n");
|
|
214
|
-
const maxWait = 18e4;
|
|
215
|
-
const start = Date.now();
|
|
216
|
-
while (Date.now() - start < maxWait) {
|
|
217
|
-
await page.waitForTimeout(2e3);
|
|
218
|
-
if (reason === "captcha" && !await checkCaptcha(page, false)) {
|
|
219
|
-
log(verbose, "\u2713 \u9A8C\u8BC1\u7801\u5DF2\u901A\u8FC7\uFF01");
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
if (reason === "login" && await checkLoggedIn(page, false)) {
|
|
223
|
-
log(verbose, "\u2713 \u767B\u5F55\u6210\u529F\uFF01");
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
if (reason === "no-reply" && await getMessageCount(page) > 0) {
|
|
227
|
-
log(verbose, "\u2713 AI \u5DF2\u5F00\u59CB\u56DE\u590D\uFF01");
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
log(verbose, "\u26A0 \u7B49\u5F85\u7528\u6237\u64CD\u4F5C\u8D85\u65F6\uFF083\u5206\u949F\uFF09");
|
|
232
|
-
}
|
|
233
|
-
async function sendMessage(page, message, verbose = false, headful = false) {
|
|
234
|
-
const input = await findInput(page, verbose);
|
|
235
|
-
if (!input) {
|
|
236
|
-
throw new YiyanAgentError("NETWORK", "Cannot find the Yiyan chat input box");
|
|
237
|
-
}
|
|
238
|
-
await input.click({ force: true });
|
|
239
|
-
await page.waitForTimeout(200);
|
|
240
|
-
await page.keyboard.press("Control+a");
|
|
241
|
-
await page.waitForTimeout(100);
|
|
242
|
-
const isTextarea = await input.evaluate((el) => el.tagName.toLowerCase() === "textarea");
|
|
243
|
-
if (isTextarea) {
|
|
244
|
-
await input.fill(message);
|
|
245
|
-
} else {
|
|
246
|
-
await input.evaluate((el, content) => {
|
|
247
|
-
el.focus();
|
|
248
|
-
document.execCommand("selectAll", false, null);
|
|
249
|
-
document.execCommand("delete", false, null);
|
|
250
|
-
document.execCommand("insertText", false, content);
|
|
251
|
-
el.dispatchEvent(new InputEvent("input", { bubbles: true, data: content }));
|
|
252
|
-
}, message);
|
|
253
|
-
}
|
|
254
|
-
await page.waitForTimeout(400);
|
|
255
|
-
log(verbose, `\u5DF2\u8F93\u5165\u95EE\u9898: "${message.substring(0, 50)}..."`);
|
|
256
|
-
const sent = await clickSendButton(page);
|
|
257
|
-
if (!sent) {
|
|
258
|
-
await page.keyboard.press("Enter");
|
|
259
|
-
}
|
|
260
|
-
await page.waitForTimeout(500);
|
|
261
|
-
if (await checkCaptcha(page, verbose)) {
|
|
262
|
-
if (headful) {
|
|
263
|
-
await waitForUserAction(page, "captcha", verbose);
|
|
264
|
-
} else {
|
|
265
|
-
throw new YiyanAgentError("CAPTCHA", "Captcha detected. Use --headful to solve it.");
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
async function findInput(page, verbose = false) {
|
|
270
|
-
for (const sel of SELECTORS.chatInput) {
|
|
271
|
-
try {
|
|
272
|
-
const el = await page.waitForSelector(sel, { timeout: 4e3, state: "visible" });
|
|
273
|
-
if (el) {
|
|
274
|
-
log(verbose, `\u2713 \u8F93\u5165\u6846: ${sel}`);
|
|
275
|
-
return el;
|
|
276
|
-
}
|
|
277
|
-
} catch {
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
return null;
|
|
281
|
-
}
|
|
282
|
-
async function clickSendButton(page) {
|
|
283
|
-
for (const sel of SELECTORS.sendButton) {
|
|
284
|
-
try {
|
|
285
|
-
const el = await page.$(sel);
|
|
286
|
-
if (el && await el.isVisible() && await el.isEnabled()) {
|
|
287
|
-
await el.click();
|
|
288
|
-
return true;
|
|
289
|
-
}
|
|
290
|
-
} catch {
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
return false;
|
|
294
|
-
}
|
|
295
|
-
async function waitForReply(page, timeout, verbose = false, headful = false) {
|
|
296
|
-
const maxWait = Math.min(timeout, 12e4);
|
|
297
|
-
const stableDelay = 2500;
|
|
298
|
-
const start = Date.now();
|
|
299
|
-
const initialCount = await getMessageCount(page);
|
|
300
|
-
let appeared = false;
|
|
301
|
-
while (Date.now() - start < 12e3) {
|
|
302
|
-
const count = await getMessageCount(page);
|
|
303
|
-
if (count > initialCount) {
|
|
304
|
-
appeared = true;
|
|
305
|
-
log(verbose, "\u2713 AI \u56DE\u590D\u5DF2\u5F00\u59CB\u751F\u6210");
|
|
306
|
-
break;
|
|
307
|
-
}
|
|
308
|
-
await page.waitForTimeout(400);
|
|
309
|
-
}
|
|
310
|
-
if (!appeared) {
|
|
311
|
-
log(verbose, "\u56DE\u590D\u53EF\u80FD\u5EF6\u8FDF\uFF0C\u7EE7\u7EED\u7B49\u5F85...");
|
|
312
|
-
}
|
|
313
|
-
let lastText = "";
|
|
314
|
-
let stableStart = null;
|
|
315
|
-
while (Date.now() - start < maxWait) {
|
|
316
|
-
const text = await extractLastMessage(page);
|
|
317
|
-
if (text !== lastText) {
|
|
318
|
-
lastText = text;
|
|
319
|
-
stableStart = null;
|
|
320
|
-
} else if (text.length > 0) {
|
|
321
|
-
if (!stableStart) stableStart = Date.now();
|
|
322
|
-
else if (Date.now() - stableStart >= stableDelay) {
|
|
323
|
-
if (!await isGenerating(page)) {
|
|
324
|
-
log(verbose, `\u2713 AI \u56DE\u590D\u5DF2\u5B8C\u6210\uFF08${text.length}\u5B57\uFF09`);
|
|
325
|
-
break;
|
|
326
|
-
}
|
|
327
|
-
stableStart = null;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
await page.waitForTimeout(500);
|
|
331
|
-
}
|
|
332
|
-
if (lastText.length === 0) {
|
|
333
|
-
if (await checkCaptcha(page, verbose)) {
|
|
334
|
-
if (headful) {
|
|
335
|
-
await waitForUserAction(page, "captcha", verbose);
|
|
336
|
-
} else {
|
|
337
|
-
throw new YiyanAgentError("CAPTCHA", "Captcha detected.");
|
|
338
|
-
}
|
|
339
|
-
} else if (headful) {
|
|
340
|
-
await waitForUserAction(page, "no-reply", verbose);
|
|
341
|
-
} else {
|
|
342
|
-
throw new YiyanAgentError("TIMEOUT", "AI reply timeout.");
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
await page.waitForTimeout(1e3);
|
|
346
|
-
}
|
|
347
|
-
async function getMessageCount(page) {
|
|
348
|
-
return await page.evaluate(() => {
|
|
349
|
-
for (const sel of [
|
|
350
|
-
'[class*="answerBox"]',
|
|
351
|
-
'[class*="assistant"]',
|
|
352
|
-
'[class*="ai-message"]',
|
|
353
|
-
'[class*="response"]'
|
|
354
|
-
]) {
|
|
355
|
-
const els = document.querySelectorAll(sel);
|
|
356
|
-
if (els.length > 0) return els.length;
|
|
357
|
-
}
|
|
358
|
-
return document.querySelectorAll('[class*="message"]').length;
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
|
-
async function isGenerating(page) {
|
|
362
|
-
return await page.evaluate(() => {
|
|
363
|
-
for (const sel of ['button[aria-label*="\u505C\u6B62"]', '[class*="stop"]', '[class*="generating"]']) {
|
|
364
|
-
const el = document.querySelector(sel);
|
|
365
|
-
if (el) {
|
|
366
|
-
const s = window.getComputedStyle(el);
|
|
367
|
-
if (s.display !== "none" && s.visibility !== "hidden") return true;
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
for (const sel of ['[class*="loading"]', '[class*="typing"]', '[class*="spinner"]']) {
|
|
371
|
-
const el = document.querySelector(sel);
|
|
372
|
-
if (el) {
|
|
373
|
-
const s = window.getComputedStyle(el);
|
|
374
|
-
if (s.display !== "none" && s.visibility !== "hidden") return true;
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
return false;
|
|
378
|
-
});
|
|
379
|
-
}
|
|
380
|
-
async function extractLastMessage(page) {
|
|
381
|
-
return await page.evaluate(() => {
|
|
382
|
-
function getFullText(el) {
|
|
383
|
-
if (!el) return "";
|
|
384
|
-
let result = "";
|
|
385
|
-
function walk(node) {
|
|
386
|
-
if (node.nodeType === Node.TEXT_NODE) {
|
|
387
|
-
result += node.textContent || "";
|
|
388
|
-
return;
|
|
389
|
-
}
|
|
390
|
-
if (node.nodeType !== Node.ELEMENT_NODE) return;
|
|
391
|
-
const tag = node.tagName.toLowerCase();
|
|
392
|
-
if (tag === "pre") {
|
|
393
|
-
const codeEl = node.querySelector("code");
|
|
394
|
-
if (codeEl) {
|
|
395
|
-
const cls = codeEl.className || "";
|
|
396
|
-
const lang = (cls.match(/language-(\S+)/) || [])[1] || "";
|
|
397
|
-
result += "\n```" + lang + "\n" + (codeEl.textContent || "") + "\n```\n";
|
|
398
|
-
} else {
|
|
399
|
-
result += "\n```\n" + (node.textContent || "") + "\n```\n";
|
|
400
|
-
}
|
|
401
|
-
return;
|
|
402
|
-
}
|
|
403
|
-
for (const child of node.childNodes) walk(child);
|
|
404
|
-
if (["p", "div", "li", "br", "h1", "h2", "h3", "h4", "h5", "h6"].includes(tag)) {
|
|
405
|
-
result += "\n";
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
walk(el);
|
|
409
|
-
return result.trim();
|
|
410
|
-
}
|
|
411
|
-
for (const sel of SELECTORS.responseContainer) {
|
|
412
|
-
const els = document.querySelectorAll(sel);
|
|
413
|
-
if (els.length > 0) {
|
|
414
|
-
const text = getFullText(els[els.length - 1]);
|
|
415
|
-
if (text.length > 10) return text;
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
const mdEls = document.querySelectorAll('[class*="markdown"], [class*="prose"]');
|
|
419
|
-
if (mdEls.length > 0) {
|
|
420
|
-
const text = getFullText(mdEls[mdEls.length - 1]);
|
|
421
|
-
if (text.length > 10) return text;
|
|
422
|
-
}
|
|
423
|
-
return "";
|
|
424
|
-
});
|
|
425
|
-
}
|
|
426
|
-
async function extractReply(page, question, verbose = false) {
|
|
427
|
-
log(verbose, "\u63D0\u53D6 AI \u56DE\u590D...");
|
|
428
|
-
const text = await extractLastMessage(page);
|
|
429
|
-
if (text.length > 0) {
|
|
430
|
-
const cleaned = text.replace(/^参考\d+个网页\s*/, "").replace(/^深度思考已完成\s*/, "").replace(/^思考完成[::]\s*/, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
431
|
-
log(verbose, `\u63D0\u53D6\u6210\u529F: ${cleaned.length} \u5B57\u7B26`);
|
|
432
|
-
return cleaned;
|
|
433
|
-
}
|
|
434
|
-
throw new YiyanAgentError("TIMEOUT", "Failed to extract reply");
|
|
435
|
-
}
|
|
436
|
-
async function dumpDebugInfo(page) {
|
|
437
|
-
const info = await page.evaluate(() => {
|
|
438
|
-
const classFreq = {};
|
|
439
|
-
document.querySelectorAll("*").forEach((el) => {
|
|
440
|
-
el.classList.forEach((c) => {
|
|
441
|
-
if (c.match(/message|chat|input|send|stop|markdown|content|assistant|user|bot|answer/i)) {
|
|
442
|
-
classFreq[c] = (classFreq[c] || 0) + 1;
|
|
443
|
-
}
|
|
444
|
-
});
|
|
445
|
-
});
|
|
446
|
-
const inputs = Array.from(document.querySelectorAll("textarea, [contenteditable]")).map((e) => ({
|
|
447
|
-
tag: e.tagName,
|
|
448
|
-
id: e.id || null,
|
|
449
|
-
class: (e.className?.toString() || "").substring(0, 80),
|
|
450
|
-
placeholder: e.placeholder || null,
|
|
451
|
-
editable: e.isContentEditable,
|
|
452
|
-
visible: e.offsetParent !== null
|
|
453
|
-
}));
|
|
454
|
-
return {
|
|
455
|
-
url: window.location.href,
|
|
456
|
-
title: document.title,
|
|
457
|
-
classes: Object.entries(classFreq).sort((a, b) => b[1] - a[1]).slice(0, 40),
|
|
458
|
-
inputs
|
|
459
|
-
};
|
|
460
|
-
});
|
|
461
|
-
console.log("\n" + "\u2550".repeat(60));
|
|
462
|
-
console.log(" DOM DEBUG INFO");
|
|
463
|
-
console.log("\u2550".repeat(60));
|
|
464
|
-
console.log("URL :", info.url);
|
|
465
|
-
console.log("Title :", info.title);
|
|
466
|
-
console.log("\nInput elements:");
|
|
467
|
-
info.inputs.forEach((i) => console.log(" ", JSON.stringify(i)));
|
|
468
|
-
console.log("\nMatching CSS classes (by frequency):");
|
|
469
|
-
info.classes.forEach(([cls, count]) => console.log(` ${String(count).padStart(3)}x .${cls}`));
|
|
470
|
-
console.log("\u2550".repeat(60) + "\n");
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
// src/agent.ts
|
|
474
|
-
var DEFAULT_SESSION_DIR = import_path.default.join(import_os.default.homedir(), ".yiyan-browser-agent", "session");
|
|
475
|
-
var YiyanAgent = class {
|
|
476
|
-
options;
|
|
477
|
-
sessionDir;
|
|
478
|
-
verbose;
|
|
479
|
-
_context = null;
|
|
480
|
-
// 保存当前 context 以便关闭
|
|
481
|
-
constructor(options) {
|
|
482
|
-
this.options = {
|
|
483
|
-
...DEFAULT_OPTIONS,
|
|
484
|
-
...options
|
|
485
|
-
};
|
|
486
|
-
this.sessionDir = this.options.profileDir || DEFAULT_SESSION_DIR;
|
|
487
|
-
this.verbose = options?.verbose ?? false;
|
|
488
|
-
const fs3 = require("fs");
|
|
489
|
-
fs3.mkdirSync(this.sessionDir, { recursive: true });
|
|
490
|
-
}
|
|
491
|
-
/**
|
|
492
|
-
* 关闭浏览器(如果正在运行)
|
|
493
|
-
*/
|
|
494
|
-
async close() {
|
|
495
|
-
if (this._context) {
|
|
496
|
-
try {
|
|
497
|
-
await this._context.close();
|
|
498
|
-
} catch {
|
|
499
|
-
}
|
|
500
|
-
this._context = null;
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
/**
|
|
504
|
-
* 发送问题并获取答案
|
|
505
|
-
* @param question 问题文本
|
|
506
|
-
* @param headful 是否使用有头浏览器(可见窗口),默认为 false(headless)
|
|
507
|
-
*/
|
|
508
|
-
async ask(question, headful = false) {
|
|
509
|
-
return this.executeAsk(question, headful);
|
|
510
|
-
}
|
|
511
|
-
/**
|
|
512
|
-
* 执行单次问答
|
|
513
|
-
*/
|
|
514
|
-
async executeAsk(question, headful) {
|
|
515
|
-
const { context, page } = await launchBrowser({
|
|
516
|
-
sessionDir: this.sessionDir,
|
|
517
|
-
headless: !headful,
|
|
518
|
-
timeout: this.options.timeout,
|
|
519
|
-
verbose: this.verbose
|
|
520
|
-
});
|
|
521
|
-
try {
|
|
522
|
-
await navigateToYiyan(page, this.verbose);
|
|
523
|
-
const isLoggedIn = await checkLoggedIn(page, this.verbose);
|
|
524
|
-
if (!isLoggedIn) {
|
|
525
|
-
if (headful) {
|
|
526
|
-
await waitForUserAction(page, "login", this.verbose);
|
|
527
|
-
} else {
|
|
528
|
-
throw new YiyanAgentError(
|
|
529
|
-
"CAPTCHA",
|
|
530
|
-
'Not logged in. Please run "yiyan-browser-agent login" first, or use headed mode.'
|
|
531
|
-
);
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
await sendMessage(page, question, this.verbose, headful);
|
|
535
|
-
await waitForReply(page, this.options.timeout, this.verbose, headful);
|
|
536
|
-
const reply = await extractReply(page, question, this.verbose);
|
|
537
|
-
return reply;
|
|
538
|
-
} finally {
|
|
539
|
-
await closeBrowser(context, this.verbose);
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
/**
|
|
543
|
-
* 登录文心一言(打开有头浏览器让用户手动登录)
|
|
544
|
-
* 登录成功后,后续的 ask 调用将自动使用登录状态
|
|
545
|
-
*/
|
|
546
|
-
async login() {
|
|
547
|
-
const { context, page } = await launchBrowser({
|
|
548
|
-
sessionDir: this.sessionDir,
|
|
549
|
-
headless: false,
|
|
550
|
-
timeout: 6e4,
|
|
551
|
-
verbose: this.verbose
|
|
552
|
-
});
|
|
553
|
-
try {
|
|
554
|
-
await navigateToYiyan(page, this.verbose);
|
|
555
|
-
process.stderr.write("\n[yiyan-browser-agent] \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\n");
|
|
556
|
-
process.stderr.write("[yiyan-browser-agent] \u2551 \u{1F510} LOGIN REQUIRED \u2551\n");
|
|
557
|
-
process.stderr.write("[yiyan-browser-agent] \u2551 \u2551\n");
|
|
558
|
-
process.stderr.write("[yiyan-browser-agent] \u2551 1. Log in to Yiyan in the browser window \u2551\n");
|
|
559
|
-
process.stderr.write("[yiyan-browser-agent] \u2551 2. Return here and press ENTER to continue \u2551\n");
|
|
560
|
-
process.stderr.write("[yiyan-browser-agent] \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D\n\n");
|
|
561
|
-
await this.waitForEnter();
|
|
562
|
-
process.stderr.write("[yiyan-browser-agent] \u2713 Login complete, saving session...\n");
|
|
563
|
-
await new Promise((resolve) => setTimeout(resolve, 3e3));
|
|
564
|
-
} finally {
|
|
565
|
-
await closeBrowser(context, this.verbose);
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
/**
|
|
569
|
-
* 等待用户按 Enter
|
|
570
|
-
*/
|
|
571
|
-
async waitForEnter() {
|
|
572
|
-
const readline = await import("readline");
|
|
573
|
-
return new Promise((resolve) => {
|
|
574
|
-
const rl = readline.createInterface({
|
|
575
|
-
input: process.stdin,
|
|
576
|
-
output: process.stderr
|
|
577
|
-
});
|
|
578
|
-
rl.question("Press ENTER to continue...", () => {
|
|
579
|
-
rl.close();
|
|
580
|
-
resolve();
|
|
581
|
-
});
|
|
582
|
-
});
|
|
583
|
-
}
|
|
584
|
-
/**
|
|
585
|
-
* 清除保存的 session
|
|
586
|
-
*/
|
|
587
|
-
async reset() {
|
|
588
|
-
const fs3 = await import("fs/promises");
|
|
589
|
-
try {
|
|
590
|
-
await fs3.rm(this.sessionDir, { recursive: true, force: true });
|
|
591
|
-
} catch {
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
/**
|
|
595
|
-
* 检查状态
|
|
596
|
-
*/
|
|
597
|
-
status() {
|
|
598
|
-
const fs3 = require("fs");
|
|
599
|
-
return {
|
|
600
|
-
loggedIn: fs3.existsSync(this.sessionDir),
|
|
601
|
-
sessionPath: this.sessionDir
|
|
602
|
-
};
|
|
603
|
-
}
|
|
604
|
-
/**
|
|
605
|
-
* Debug 模式:启动浏览器并输出 DOM 信息
|
|
606
|
-
*/
|
|
607
|
-
async debug() {
|
|
608
|
-
const { context, page } = await launchBrowser({
|
|
609
|
-
sessionDir: this.sessionDir,
|
|
610
|
-
headless: false,
|
|
611
|
-
timeout: this.options.timeout,
|
|
612
|
-
verbose: true
|
|
613
|
-
});
|
|
614
|
-
try {
|
|
615
|
-
await navigateToYiyan(page, true);
|
|
616
|
-
await page.waitForTimeout(3e3);
|
|
617
|
-
await dumpDebugInfo(page);
|
|
618
|
-
process.stderr.write("\n[yiyan-browser-agent] \u6D4F\u89C8\u5668\u4FDD\u6301\u6253\u5F00\uFF0C\u53EF\u624B\u52A8\u6D4B\u8BD5\u3002\n");
|
|
619
|
-
process.stderr.write("[yiyan-browser-agent] \u5173\u95ED\u6D4F\u89C8\u5668\u7A97\u53E3\u7ED3\u675F debug \u6A21\u5F0F...\n");
|
|
620
|
-
await new Promise((resolve) => {
|
|
621
|
-
context.on("close", () => resolve());
|
|
622
|
-
});
|
|
623
|
-
} finally {
|
|
624
|
-
try {
|
|
625
|
-
await context.close();
|
|
626
|
-
} catch {
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
};
|
|
631
|
-
|
|
632
|
-
// src/profile.ts
|
|
633
|
-
var import_path2 = __toESM(require("path"));
|
|
634
|
-
var import_os2 = __toESM(require("os"));
|
|
635
|
-
var import_fs2 = __toESM(require("fs"));
|
|
636
|
-
var CONFIG_FILE_PATH = import_path2.default.join(import_os2.default.homedir(), ".yiyan-browser-agent", "config.json");
|
|
637
|
-
var DEFAULT_SESSION_DIR2 = import_path2.default.join(import_os2.default.homedir(), ".yiyan-browser-agent", "session");
|
|
638
|
-
function readConfig() {
|
|
639
|
-
try {
|
|
640
|
-
if (import_fs2.default.existsSync(CONFIG_FILE_PATH)) {
|
|
641
|
-
const content = import_fs2.default.readFileSync(CONFIG_FILE_PATH, "utf-8");
|
|
642
|
-
return JSON.parse(content);
|
|
643
|
-
}
|
|
644
|
-
} catch {
|
|
645
|
-
}
|
|
646
|
-
return {};
|
|
647
|
-
}
|
|
648
|
-
function saveConfig(config) {
|
|
649
|
-
const configDir = import_path2.default.dirname(CONFIG_FILE_PATH);
|
|
650
|
-
if (!import_fs2.default.existsSync(configDir)) {
|
|
651
|
-
import_fs2.default.mkdirSync(configDir, { recursive: true });
|
|
652
|
-
}
|
|
653
|
-
import_fs2.default.writeFileSync(CONFIG_FILE_PATH, JSON.stringify(config, null, 2));
|
|
654
|
-
}
|
|
655
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
656
|
-
0 && (module.exports = {
|
|
657
|
-
CONFIG_FILE_PATH,
|
|
658
|
-
DEFAULT_OPTIONS,
|
|
659
|
-
DEFAULT_SESSION_DIR,
|
|
660
|
-
YIYAN_CHAT_URL,
|
|
661
|
-
YiyanAgent,
|
|
662
|
-
YiyanAgentError,
|
|
663
|
-
readConfig,
|
|
664
|
-
saveConfig
|
|
665
|
-
});
|
|
666
|
-
//# sourceMappingURL=index.js.map
|