mirror of
https://github.com/foxhui/WebAI2API.git
synced 2026-06-16 21:03:59 +08:00
fix: 尝试修复元素无法点击的问题
This commit is contained in:
@@ -86,23 +86,20 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
|
||||
await page.keyboard.press('Enter');
|
||||
await sleep(300, 500);
|
||||
|
||||
// 获取所有 menuitemradio 选项
|
||||
const menuItems = await page.getByRole('menuitemradio').all();
|
||||
// 获取所有 menuitemradio 选项的文本
|
||||
const menuItemsLocator = page.getByRole('menuitemradio');
|
||||
const menuItemsCount = await menuItemsLocator.count();
|
||||
|
||||
if (menuItems.length === 0) {
|
||||
if (menuItemsCount === 0) {
|
||||
logger.warn('适配器', '未找到模型选项,使用默认模型', meta);
|
||||
} else {
|
||||
// 获取所有选项的文本(去除前后空白)
|
||||
const itemTexts = [];
|
||||
for (const item of menuItems) {
|
||||
const text = await item.textContent();
|
||||
itemTexts.push((text || '').trim());
|
||||
}
|
||||
const itemTexts = await menuItemsLocator.allTextContents();
|
||||
|
||||
logger.debug('适配器', `可用模型选项: [${itemTexts.join('], [')}]`, meta);
|
||||
logger.debug('适配器', `可用模型选项: [${itemTexts.map(t => t.trim()).join('], [')}]`, meta);
|
||||
|
||||
// 判断是否有 Pro 选项
|
||||
const hasPro = itemTexts.some(text => text.startsWith('Pro'));
|
||||
const hasPro = itemTexts.some(text => text.trim().startsWith('Pro'));
|
||||
|
||||
// 确定要选择的目标选项文本前缀
|
||||
let targetPrefix = null;
|
||||
@@ -127,18 +124,14 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
|
||||
|
||||
logger.debug('适配器', `目标模型前缀: "${targetPrefix}"`, meta);
|
||||
|
||||
// 查找并点击对应的选项
|
||||
let found = false;
|
||||
for (let i = 0; i < menuItems.length; i++) {
|
||||
if (itemTexts[i].startsWith(targetPrefix)) {
|
||||
await safeClick(page, menuItems[i], { bias: 'button' });
|
||||
logger.info('适配器', `已选择模型: "${itemTexts[i]}"`, meta);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 使用 locator 直接定位目标选项(避免缓存元素引用导致 detached 错误)
|
||||
const targetItem = menuItemsLocator.filter({ hasText: new RegExp(`^\\s*${targetPrefix}`) }).first();
|
||||
|
||||
if (!found) {
|
||||
if (await targetItem.count() > 0) {
|
||||
const selectedText = (await targetItem.textContent() || '').trim();
|
||||
await safeClick(page, targetItem, { bias: 'button' });
|
||||
logger.info('适配器', `已选择模型: "${selectedText}"`, meta);
|
||||
} else {
|
||||
logger.warn('适配器', `未找到匹配的模型选项 (${targetPrefix}),使用默认模型`, meta);
|
||||
// 按 Escape 关闭菜单
|
||||
await page.keyboard.press('Escape');
|
||||
|
||||
+26
-13
@@ -241,37 +241,50 @@ export async function safeClick(page, target, options = {}) {
|
||||
? baseTimeout + Math.ceil(50000 / cursorSpeed)
|
||||
: baseTimeout;
|
||||
|
||||
const doClick = async () => {
|
||||
let el;
|
||||
|
||||
// 判断输入类型
|
||||
logger.debug('浏览器', `[safeClick] 开始查找: ${selector}`);
|
||||
// 元素定位函数(可重复调用以获取新鲜的 ElementHandle)
|
||||
const resolveElement = async () => {
|
||||
if (typeof target === 'string') {
|
||||
// CSS selector
|
||||
el = await page.$(target);
|
||||
const el = await page.$(target);
|
||||
if (!el) throw new Error(`未找到: ${target}`);
|
||||
return el;
|
||||
} else if (typeof target.elementHandle === 'function') {
|
||||
// Locator (来自 page.getByRole, page.getByText 等)
|
||||
el = await target.elementHandle();
|
||||
const el = await target.elementHandle();
|
||||
if (!el) throw new Error(`Locator 未匹配到元素`);
|
||||
return el;
|
||||
} else {
|
||||
// ElementHandle
|
||||
el = target;
|
||||
if (!el || !el.asElement()) throw new Error(`Element handle invalid`);
|
||||
if (!target || !target.asElement()) throw new Error(`Element handle invalid`);
|
||||
return target;
|
||||
}
|
||||
};
|
||||
|
||||
const doClick = async () => {
|
||||
// 1. 首次获取元素(用于滚动和等待稳定)
|
||||
logger.debug('浏览器', `[safeClick] 开始查找: ${selector}`);
|
||||
let el = await resolveElement();
|
||||
logger.debug('浏览器', `[safeClick] 已找到元素`);
|
||||
|
||||
// 确保元素在可视区域内
|
||||
// 2. 确保元素在可视区域内
|
||||
logger.debug('浏览器', `[safeClick] 滚动到可视区域...`);
|
||||
await el.scrollIntoViewIfNeeded().catch(() => { });
|
||||
|
||||
// 如果开启了布局稳定等待,等待元素位置稳定
|
||||
// 3. 如果开启了布局稳定等待,等待元素位置稳定
|
||||
if (waitStable) {
|
||||
logger.debug('浏览器', `[safeClick] 等待元素稳定...`);
|
||||
await waitForElementStable(el);
|
||||
logger.debug('浏览器', `[safeClick] 元素已稳定`);
|
||||
|
||||
// 4. 重新获取元素引用(防止等待期间 DOM 变化导致 detached 错误)
|
||||
// 仅对 Locator 类型重新获取,ElementHandle 无法刷新
|
||||
if (typeof target.elementHandle === 'function') {
|
||||
logger.debug('浏览器', `[safeClick] 重新获取元素引用...`);
|
||||
el = await resolveElement();
|
||||
}
|
||||
}
|
||||
// 使用自维护 ghost-cursor 拟人鼠标轨迹 (仅当 humanizeCursor=true)
|
||||
|
||||
// 5. 使用自维护 ghost-cursor 拟人鼠标轨迹 (仅当 humanizeCursor=true)
|
||||
if (useGhostCursor) {
|
||||
const box = await el.boundingBox();
|
||||
logger.debug('浏览器', `[safeClick] boundingBox: ${JSON.stringify(box)}`);
|
||||
@@ -289,7 +302,7 @@ export async function safeClick(page, target, options = {}) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用原生点击 (humanizeCursor=false 或 "camou")
|
||||
// 6. 使用原生点击 (humanizeCursor=false 或 "camou")
|
||||
const mode = page?._humanizeCursorMode;
|
||||
logger.debug('浏览器', `[safeClick] humanizeCursor=${mode} 使用原生点击`);
|
||||
// force: true 跳过可操作性检查(遮挡检测等),避免在复杂页面卡住
|
||||
|
||||
Reference in New Issue
Block a user