diff --git a/CHANGELOG.md b/CHANGELOG.md index 04fabdf..6e1c460 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [3.5.9] - 2026-03-25 + +### 🐛 Fixed +- **适配器** + - 尝试修复同实例多窗口时出现点击超时的问题 + - 修复 Gemini 文本适配器不选择模型的问题 + - 修正 Gemini 文本适配器的模型 ID + ## [3.5.8] - 2026-03-22 ### ✨ Added diff --git a/src/backend/adapter/gemini_text.js b/src/backend/adapter/gemini_text.js index 2daa3b4..9f5e38c 100644 --- a/src/backend/adapter/gemini_text.js +++ b/src/backend/adapter/gemini_text.js @@ -87,8 +87,8 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) { await page.keyboard.press('Enter'); await sleep(300, 500); - // 获取所有 menuitemradio 选项的文本 - const menuItemsLocator = page.getByRole('menuitemradio'); + // 获取所有 menuitem 选项的文本 + const menuItemsLocator = page.getByRole('menuitem'); const menuItemsCount = await menuItemsLocator.count(); if (menuItemsCount === 0) { @@ -107,16 +107,16 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) { if (hasPro) { // 有 Pro 选项的情况 - if (modelId === 'gemini-3-pro' || modelId === 'gemini-exp-1206') { + if (modelId === 'gemini-3.1-pro') { targetPrefix = 'Pro'; - } else if (modelId === 'gemini-3-flash' || modelId === 'gemini-2.0-flash-exp') { + } else if (modelId === 'gemini-3.1-flash-thinking') { targetPrefix = 'Thinking'; } else { targetPrefix = 'Fast'; } } else { // 没有 Pro 选项的情况 - if (modelId === 'gemini-3-pro' || modelId === 'gemini-exp-1206') { + if (modelId === 'gemini-3.1-pro' || modelId === 'gemini-3.1-flash-thinking') { targetPrefix = 'Thinking'; } else { targetPrefix = 'Fast'; @@ -214,10 +214,9 @@ export const manifest = { }, models: [ - { id: 'gemini-2.0-flash-exp', imagePolicy: 'optional', type: 'text' }, - { id: 'gemini-exp-1206', imagePolicy: 'optional', type: 'text' }, - { id: 'gemini-3-pro', imagePolicy: 'optional', type: 'text' }, - { id: 'gemini-3-flash', imagePolicy: 'optional', type: 'text' } + { id: 'gemini-3.1-flash', imagePolicy: 'optional', type: 'text' }, + { id: 'gemini-3.1-flash-thinking', imagePolicy: 'optional', type: 'text' }, + { id: 'gemini-3.1-pro', imagePolicy: 'optional', type: 'text' } ], navigationHandlers: [], diff --git a/src/backend/pool/PoolManager.js b/src/backend/pool/PoolManager.js index 8f51da3..ca8e737 100644 --- a/src/backend/pool/PoolManager.js +++ b/src/backend/pool/PoolManager.js @@ -108,6 +108,8 @@ export class PoolManager { // 建立共享关系:设置所有者引用,并添加到所有者的共享列表 worker._browserOwner = existing.ownerWorker; existing.ownerWorker._sharedWorkers.push(worker); + // 共享同一把浏览器互斥锁 + worker._browserMutex = existing.ownerWorker._browserMutex; } else { await worker.init(); browserMap.set(worker.userDataDir, { diff --git a/src/backend/pool/Worker.js b/src/backend/pool/Worker.js index 88cd714..300a39a 100644 --- a/src/backend/pool/Worker.js +++ b/src/backend/pool/Worker.js @@ -8,6 +8,7 @@ import { logger } from '../../utils/logger.js'; import { initBrowserBase, createCursor } from '../engine/launcher.js'; import { registry } from '../registry.js'; import { tryGotoWithCheck } from '../utils/page.js'; +import { AsyncMutex } from '../../utils/asyncMutex.js'; /** * Worker 类 - 封装单个浏览器实例 @@ -40,6 +41,9 @@ export class Worker { this._isBrowserOwner = false; // 是否是浏览器的所有者(负责重启) this._browserOwner = null; // 如果是共享者,指向所有者 Worker this._sharedWorkers = []; // 如果是所有者,保存共享该浏览器的 Worker 列表 + + // 浏览器操作互斥锁(同一浏览器实例的 Worker 共享同一把锁) + this._browserMutex = new AsyncMutex(); } /** @@ -478,12 +482,18 @@ export class Worker { userDataDir: this.userDataDir }; + // 获取浏览器互斥锁(防止同一浏览器实例的多个 Worker 并发操作鼠标) + const releaseLock = await this._browserMutex.acquire(); + logger.debug('工作池', `[${this.name}] 已获取浏览器锁`, meta); + this.busyCount++; try { // 传递原始 modelId,由适配器自己解析 return await adapter.generate(subContext, prompt, paths, modelId, meta); } finally { this.busyCount--; + releaseLock(); + logger.debug('工作池', `[${this.name}] 已释放浏览器锁`, meta); } } diff --git a/src/utils/asyncMutex.js b/src/utils/asyncMutex.js new file mode 100644 index 0000000..4baa5d9 --- /dev/null +++ b/src/utils/asyncMutex.js @@ -0,0 +1,34 @@ +/** + * @fileoverview 轻量异步互斥锁 + * @description 保证同一时刻只有一个 async 任务持有锁,用于防止同一浏览器实例的多个 Worker 并发操作鼠标。 + */ + +export class AsyncMutex { + constructor() { + this._queue = []; + this._locked = false; + } + + /** + * 获取锁,返回释放函数 + * @returns {Promise<() => void>} + */ + acquire() { + return new Promise(resolve => { + const tryAcquire = () => { + if (!this._locked) { + this._locked = true; + resolve(() => { + this._locked = false; + if (this._queue.length > 0) { + this._queue.shift()(); + } + }); + } else { + this._queue.push(tryAcquire); + } + }; + tryAcquire(); + }); + } +}