Skip to content

Commit ef2b7bd

Browse files
committed
fix: 所有selector逻辑统一
1 parent 69d3626 commit ef2b7bd

File tree

2 files changed

+102
-67
lines changed

2 files changed

+102
-67
lines changed

src/browser/page_controller.py

Lines changed: 98 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,16 @@ async def set_system_instructions(self, system_prompt: str, check_client_disconn
8686
await sys_prompt_textarea.fill(system_prompt, timeout=5000)
8787
await expect_async(sys_prompt_textarea).to_have_value(system_prompt, timeout=5000)
8888
self.logger.info(f'[{self.req_id}] 系统指令已成功填充并验证。')
89+
for close_attempt in range(1, 4):
90+
try:
91+
await self.page.keyboard.press("Escape")
92+
await asyncio.sleep(0.2)
93+
if not await sys_prompt_textarea.is_visible():
94+
self.logger.info(f'[{self.req_id}] ✅ 系统指令面板已关闭。')
95+
break
96+
self.logger.warning(f"[{self.req_id}] 系统指令面板关闭验证失败 (嘗試 {close_attempt})")
97+
except Exception:
98+
pass
8999
except Exception as e:
90100
self.logger.error(f'[{self.req_id}] 设置系统指令时出错: {e}')
91101
if isinstance(e, ClientDisconnectedError):
@@ -136,10 +146,6 @@ def _is_gemini3_pro_series(self, model_id: Optional[str]) -> bool:
136146
mid = (model_id or "").lower()
137147
return ("gemini-3" in mid) and ("pro" in mid)
138148

139-
def _has_main_reasoning_switch(self, model_id: Optional[str]) -> bool:
140-
"""判斷模型是否擁有主開關(Flash 系列)"""
141-
mid = (model_id or "").lower()
142-
return "flash" in mid
143149

144150
async def _check_level_dropdown_exists(self) -> bool:
145151
"""檢查等級下拉選單是否存在"""
@@ -150,12 +156,11 @@ async def _check_level_dropdown_exists(self) -> bool:
150156
return False
151157

152158
def _determine_level_from_effort(self, reasoning_effort: Any) -> Optional[str]:
153-
"""根據 reasoning_effort 決定等級(high/low)"""
154159
if isinstance(reasoning_effort, str):
155160
rs = reasoning_effort.strip().lower()
156161
if rs == "low":
157162
return "low"
158-
if rs in ["high", "none", "-1"]:
163+
if rs in ["high", "medium", "none", "-1"]:
159164
return "high"
160165
try:
161166
return "high" if int(rs) >= 8000 else "low"
@@ -177,7 +182,6 @@ def _apply_model_budget_cap(self, value: int, model_id: Optional[str]) -> int:
177182
return value
178183

179184
async def _select_thinking_level(self, level: str, check_client_disconnected: Callable):
180-
"""設定推理等級(Gemini 3 Pro 專用),包含重試邏輯"""
181185
target_selector = (
182186
THINKING_LEVEL_OPTION_HIGH_SELECTOR if level == "high"
183187
else THINKING_LEVEL_OPTION_LOW_SELECTOR
@@ -202,40 +206,51 @@ async def _select_thinking_level(self, level: str, check_client_disconnected: Ca
202206
pass
203207
raise Exception(f"等級選項 {level} 不存在")
204208
await click_element(self.page, option, f"Thinking Level {level}", self.req_id)
205-
await asyncio.sleep(0.2)
206-
self.logger.info(f"[{self.req_id}] ✓ 推理等級已設定為 {level}")
207-
return
209+
await asyncio.sleep(0.3)
210+
current_text = await trigger.inner_text(timeout=2000)
211+
if level.lower() in current_text.lower():
212+
self.logger.info(f"[{self.req_id}] ✓ 推理等級已設定為 {level}")
213+
return
214+
self.logger.warning(f"[{self.req_id}] 等級驗證失敗 (嘗試 {attempt}): 當前顯示 '{current_text}'")
215+
if attempt < max_retries:
216+
await asyncio.sleep(0.3)
208217
except Exception as e:
209218
if isinstance(e, ClientDisconnectedError):
210219
raise
211220
self.logger.warning(f"[{self.req_id}] 設定等級失敗 (嘗試 {attempt}): {e}")
212221
if attempt < max_retries:
213222
await asyncio.sleep(0.5)
214-
else:
215-
raise
223+
self.logger.error(f"[{self.req_id}] ❌ 推理等級設定失敗,已重試 {max_retries} 次")
224+
raise Exception(f"推理等級 {level} 設定失敗")
216225

217226
async def _set_budget_value(self, token_budget: int, check_client_disconnected: Callable):
218-
"""設定預算數值"""
219227
budget_input = self.page.locator(THINKING_BUDGET_INPUT_SELECTOR)
220-
try:
221-
await expect_async(budget_input).to_be_visible(timeout=5000)
222-
await self._check_disconnect(check_client_disconnected, '預算輸入框可見後')
223-
self.logger.info(f"[{self.req_id}] 設定推理預算為: {token_budget} tokens")
224-
await budget_input.fill(str(token_budget), timeout=5000)
225-
await self._check_disconnect(check_client_disconnected, '預算填充後')
226-
await asyncio.sleep(0.1)
227-
actual_val = await budget_input.input_value(timeout=3000)
228-
if int(actual_val) == token_budget:
229-
self.logger.info(f"[{self.req_id}] ✓ 預算已更新為 {actual_val}")
230-
else:
231-
self.logger.warning(f"[{self.req_id}] 預算驗證失敗,實際: {actual_val},預期: {token_budget}")
232-
except Exception as e:
233-
self.logger.error(f"[{self.req_id}] 設定預算時發生錯誤: {e}")
234-
if isinstance(e, ClientDisconnectedError):
235-
raise
228+
max_retries = 3
229+
for attempt in range(1, max_retries + 1):
230+
try:
231+
self.logger.info(f"[{self.req_id}] (嘗試 {attempt}/{max_retries}) 設定推理預算為: {token_budget} tokens")
232+
await expect_async(budget_input).to_be_visible(timeout=5000)
233+
await self._check_disconnect(check_client_disconnected, '預算輸入框可見後')
234+
await budget_input.fill(str(token_budget), timeout=5000)
235+
await self._check_disconnect(check_client_disconnected, '預算填充後')
236+
await asyncio.sleep(0.2)
237+
actual_val = await budget_input.input_value(timeout=3000)
238+
if int(actual_val) == token_budget:
239+
self.logger.info(f"[{self.req_id}] ✓ 預算已更新為 {actual_val}")
240+
return True
241+
self.logger.warning(f"[{self.req_id}] 預算驗證失敗 (嘗試 {attempt}): 實際 {actual_val}, 預期 {token_budget}")
242+
if attempt < max_retries:
243+
await asyncio.sleep(0.3)
244+
except Exception as e:
245+
if isinstance(e, ClientDisconnectedError):
246+
raise
247+
self.logger.warning(f"[{self.req_id}] 設定預算失敗 (嘗試 {attempt}): {e}")
248+
if attempt < max_retries:
249+
await asyncio.sleep(0.3)
250+
self.logger.error(f"[{self.req_id}] ❌ 預算設定失敗,已重試 {max_retries} 次")
251+
return False
236252

237253
async def _handle_thinking_budget(self, request_params: Dict[str, Any], model_id_to_use: Optional[str], check_client_disconnected: Callable):
238-
"""處理推理模式與預算的完整邏輯"""
239254
reasoning_effort = request_params.get('reasoning_effort')
240255
cfg = parse_reasoning_param(reasoning_effort)
241256
self.logger.info(f"[{self.req_id}] 推理配置: {describe_config(cfg)}")
@@ -245,12 +260,9 @@ async def _handle_thinking_budget(self, request_params: Dict[str, Any], model_id
245260
return
246261

247262
try:
248-
uses_level = self._is_gemini3_pro_series(model_id_to_use) and await self._check_level_dropdown_exists()
249-
has_switch = self._has_main_reasoning_switch(model_id_to_use)
263+
await self._control_thinking_mode_toggle(should_be_checked=True, check_client_disconnected=check_client_disconnected)
250264

251-
if has_switch:
252-
self.logger.info(f"[{self.req_id}] 控制主開關: 啟用")
253-
await self._control_thinking_mode_toggle(should_be_checked=True, check_client_disconnected=check_client_disconnected)
265+
uses_level = self._is_gemini3_pro_series(model_id_to_use) and await self._check_level_dropdown_exists()
254266

255267
if uses_level:
256268
level = self._determine_level_from_effort(reasoning_effort) or DEFAULT_THINKING_LEVEL
@@ -332,41 +344,63 @@ async def _adjust_google_search(self, request_params: Dict[str, Any], check_clie
332344
self.logger.error(f"[{self.req_id}] ❌ Google Search 設定失敗,已重試 {max_retries} 次")
333345

334346
async def _ensure_tools_panel_expanded(self, check_client_disconnected: Callable):
335-
try:
336-
collapse_tools_locator = self.page.locator('button[aria-label="Expand or collapse tools"]')
337-
await expect_async(collapse_tools_locator).to_be_visible(timeout=5000)
338-
grandparent_locator = collapse_tools_locator.locator('xpath=../..')
339-
class_string = await grandparent_locator.get_attribute('class', timeout=3000)
340-
if class_string and 'expanded' not in class_string.split():
341-
self.logger.info(f'[{self.req_id}] 🔧 正在展开工具面板...')
347+
max_retries = 3
348+
for attempt in range(1, max_retries + 1):
349+
try:
350+
collapse_tools_locator = self.page.locator('button[aria-label="Expand or collapse tools"]')
351+
await expect_async(collapse_tools_locator).to_be_visible(timeout=5000)
352+
grandparent_locator = collapse_tools_locator.locator('xpath=../..')
353+
class_string = await grandparent_locator.get_attribute('class', timeout=3000)
354+
if class_string and 'expanded' in class_string.split():
355+
self.logger.info(f'[{self.req_id}] ✅ 工具面板已展开。')
356+
return
357+
self.logger.info(f'[{self.req_id}] 🔧 (嘗試 {attempt}/{max_retries}) 正在展开工具面板...')
342358
await click_element(self.page, collapse_tools_locator, 'Expand/Collapse Tools Button', self.req_id)
343359
await self._check_disconnect(check_client_disconnected, '展开工具面板后')
344-
await expect_async(grandparent_locator).to_have_class(re.compile('.*expanded.*'), timeout=5000)
345-
self.logger.info(f'[{self.req_id}] ✅ 工具面板已展开。')
346-
else:
347-
self.logger.info(f'[{self.req_id}] ✅ 工具面板已展开。')
348-
except Exception as e:
349-
self.logger.error(f'[{self.req_id}] 展开工具面板时发生错误: {e}')
350-
if isinstance(e, ClientDisconnectedError):
351-
raise
360+
await asyncio.sleep(0.3)
361+
new_class = await grandparent_locator.get_attribute('class', timeout=3000)
362+
if new_class and 'expanded' in new_class.split():
363+
self.logger.info(f'[{self.req_id}] ✅ 工具面板已展开。')
364+
return
365+
self.logger.warning(f"[{self.req_id}] 工具面板展开验证失败 (嘗試 {attempt})")
366+
if attempt < max_retries:
367+
await asyncio.sleep(0.3)
368+
except Exception as e:
369+
if isinstance(e, ClientDisconnectedError):
370+
raise
371+
self.logger.warning(f'[{self.req_id}] 展开工具面板失败 (嘗試 {attempt}): {e}')
372+
if attempt < max_retries:
373+
await asyncio.sleep(0.3)
374+
self.logger.error(f'[{self.req_id}] ❌ 工具面板展开失败,已重试 {max_retries} 次')
352375

353376
async def _open_url_content(self, check_client_disconnected: Callable):
354-
try:
355-
self.logger.info(f'[{self.req_id}] 检查并启用 URL Context 开关...')
356-
use_url_content_selector = self.page.locator(USE_URL_CONTEXT_SELECTOR)
357-
await expect_async(use_url_content_selector).to_be_visible(timeout=5000)
358-
is_checked = await use_url_content_selector.get_attribute('aria-checked')
359-
if 'false' == is_checked:
360-
self.logger.info(f'[{self.req_id}] URL Context 开关未开启,正在点击以开启...')
377+
max_retries = 3
378+
for attempt in range(1, max_retries + 1):
379+
try:
380+
self.logger.info(f'[{self.req_id}] (嘗試 {attempt}/{max_retries}) 检查并启用 URL Context 开关...')
381+
use_url_content_selector = self.page.locator(USE_URL_CONTEXT_SELECTOR)
382+
await expect_async(use_url_content_selector).to_be_visible(timeout=5000)
383+
is_checked = await use_url_content_selector.get_attribute('aria-checked')
384+
if is_checked == 'true':
385+
self.logger.info(f'[{self.req_id}] ✅ URL Context 开关已处于开启状态。')
386+
return
361387
await click_element(self.page, use_url_content_selector, 'URL Context Toggle', self.req_id)
362388
await self._check_disconnect(check_client_disconnected, '点击URLCONTEXT后')
363-
self.logger.info(f'[{self.req_id}] URL Context 开关已点击。')
364-
else:
365-
self.logger.info(f'[{self.req_id}] URL Context 开关已处于开启状态。')
366-
except Exception as e:
367-
self.logger.error(f'[{self.req_id}] 操作 USE_URL_CONTEXT_SELECTOR 时发生错误:{e}。')
368-
if isinstance(e, ClientDisconnectedError):
369-
raise
389+
await asyncio.sleep(0.3)
390+
new_state = await use_url_content_selector.get_attribute('aria-checked')
391+
if new_state == 'true':
392+
self.logger.info(f'[{self.req_id}] ✅ URL Context 开关已开启。')
393+
return
394+
self.logger.warning(f"[{self.req_id}] URL Context 验证失败 (嘗試 {attempt}): '{new_state}'")
395+
if attempt < max_retries:
396+
await asyncio.sleep(0.3)
397+
except Exception as e:
398+
if isinstance(e, ClientDisconnectedError):
399+
raise
400+
self.logger.warning(f'[{self.req_id}] URL Context 操作失败 (嘗試 {attempt}): {e}')
401+
if attempt < max_retries:
402+
await asyncio.sleep(0.3)
403+
self.logger.error(f'[{self.req_id}] ❌ URL Context 设定失败,已重试 {max_retries} 次')
370404

371405
async def _control_thinking_budget_toggle(self, should_be_checked: bool, check_client_disconnected: Callable) -> bool:
372406
toggle_selector = SET_THINKING_BUDGET_TOGGLE_SELECTOR

src/browser/thinking_normalizer.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,12 @@ def parse_reasoning_param(effort: Optional[Union[int, str]]) -> ReasoningConfig:
3838
budget_tokens=None,
3939
raw_input=effort,
4040
)
41-
if val in ["high", "low", "medium"]:
41+
level_map = {"low": 4096, "medium": 8192, "high": 16384}
42+
if val in level_map:
4243
return ReasoningConfig(
4344
enable_reasoning=True,
44-
use_budget_limit=False,
45-
budget_tokens=None,
45+
use_budget_limit=True,
46+
budget_tokens=level_map[val],
4647
raw_input=effort,
4748
)
4849
elif effort == -1:

0 commit comments

Comments
 (0)