|
5 | 5 | import re |
6 | 6 | import os |
7 | 7 | from playwright.async_api import Page as AsyncPage, expect as expect_async, TimeoutError, Locator |
8 | | -from config import TEMPERATURE_INPUT_SELECTOR, MAX_OUTPUT_TOKENS_SELECTOR, STOP_SEQUENCE_INPUT_SELECTOR, MAT_CHIP_REMOVE_BUTTON_SELECTOR, TOP_P_INPUT_SELECTOR, SUBMIT_BUTTON_SELECTOR, OVERLAY_SELECTOR, PROMPT_TEXTAREA_SELECTOR, RESPONSE_CONTAINER_SELECTOR, RESPONSE_TEXT_SELECTOR, EDIT_MESSAGE_BUTTON_SELECTOR, USE_URL_CONTEXT_SELECTOR, UPLOAD_BUTTON_SELECTOR, INSERT_BUTTON_SELECTOR, THINKING_MODE_TOGGLE_SELECTOR, SET_THINKING_BUDGET_TOGGLE_SELECTOR, THINKING_BUDGET_INPUT_SELECTOR, GROUNDING_WITH_GOOGLE_SEARCH_TOGGLE_SELECTOR, ZERO_STATE_SELECTOR, SYSTEM_INSTRUCTIONS_BUTTON_SELECTOR, SYSTEM_INSTRUCTIONS_TEXTAREA_SELECTOR, SKIP_PREFERENCE_VOTE_BUTTON_SELECTOR, CLICK_TIMEOUT_MS, WAIT_FOR_ELEMENT_TIMEOUT_MS, CLEAR_CHAT_VERIFY_TIMEOUT_MS, DEFAULT_TEMPERATURE, DEFAULT_MAX_OUTPUT_TOKENS, DEFAULT_STOP_SEQUENCES, DEFAULT_TOP_P, ENABLE_URL_CONTEXT, ENABLE_THINKING_BUDGET, DEFAULT_THINKING_BUDGET, ENABLE_GOOGLE_SEARCH, THINKING_LEVEL_SELECT_SELECTOR, THINKING_LEVEL_OPTION_HIGH_SELECTOR, THINKING_LEVEL_OPTION_LOW_SELECTOR, DEFAULT_THINKING_LEVEL |
| 8 | +from config import TEMPERATURE_INPUT_SELECTOR, MAX_OUTPUT_TOKENS_SELECTOR, STOP_SEQUENCE_INPUT_SELECTOR, MAT_CHIP_REMOVE_BUTTON_SELECTOR, TOP_P_INPUT_SELECTOR, SUBMIT_BUTTON_SELECTOR, OVERLAY_SELECTOR, PROMPT_TEXTAREA_SELECTOR, RESPONSE_CONTAINER_SELECTOR, RESPONSE_TEXT_SELECTOR, EDIT_MESSAGE_BUTTON_SELECTOR, USE_URL_CONTEXT_SELECTOR, UPLOAD_BUTTON_SELECTOR, INSERT_BUTTON_SELECTOR, THINKING_MODE_TOGGLE_SELECTOR, SET_THINKING_BUDGET_TOGGLE_SELECTOR, THINKING_BUDGET_INPUT_SELECTOR, GROUNDING_WITH_GOOGLE_SEARCH_TOGGLE_SELECTOR, ZERO_STATE_SELECTOR, SYSTEM_INSTRUCTIONS_BUTTON_SELECTOR, SYSTEM_INSTRUCTIONS_TEXTAREA_SELECTOR, SKIP_PREFERENCE_VOTE_BUTTON_SELECTOR, CLICK_TIMEOUT_MS, WAIT_FOR_ELEMENT_TIMEOUT_MS, CLEAR_CHAT_VERIFY_TIMEOUT_MS, DEFAULT_TEMPERATURE, DEFAULT_MAX_OUTPUT_TOKENS, DEFAULT_STOP_SEQUENCES, DEFAULT_TOP_P, ENABLE_URL_CONTEXT, ENABLE_THINKING_BUDGET, DEFAULT_THINKING_BUDGET, ENABLE_GOOGLE_SEARCH, THINKING_LEVEL_SELECT_SELECTOR, THINKING_LEVEL_OPTION_HIGH_SELECTOR, THINKING_LEVEL_OPTION_LOW_SELECTOR, DEFAULT_THINKING_LEVEL, ADVANCED_SETTINGS_EXPANDER_SELECTOR |
9 | 9 | from models import ClientDisconnectedError, ElementClickError |
10 | 10 | from .operations import save_error_snapshot, _wait_for_response_completion, _get_final_response_content, click_element |
11 | 11 | from .thinking_normalizer import parse_reasoning_param, describe_config |
@@ -54,6 +54,8 @@ async def adjust_parameters(self, request_params: Dict[str, Any], page_params_ca |
54 | 54 | stop_to_set = request_params.get('stop', DEFAULT_STOP_SEQUENCES) |
55 | 55 | top_p_to_set = request_params.get('top_p', DEFAULT_TOP_P) |
56 | 56 |
|
| 57 | + await self._ensure_advanced_settings_expanded(check_client_disconnected) |
| 58 | + |
57 | 59 | async def handle_tools_panel(): |
58 | 60 | await self._ensure_tools_panel_expanded(check_client_disconnected) |
59 | 61 | if ENABLE_URL_CONTEXT: |
@@ -354,6 +356,60 @@ async def _adjust_google_search(self, request_params: Dict[str, Any], check_clie |
354 | 356 | await asyncio.sleep(0.3) |
355 | 357 | self.logger.error(f"[{self.req_id}] ❌ Google Search 設定失敗,已重試 {max_retries} 次") |
356 | 358 |
|
| 359 | + |
| 360 | + async def _ensure_advanced_settings_expanded(self, check_client_disconnected: Callable): |
| 361 | + max_retries = 3 |
| 362 | + expander_locator = self.page.locator(ADVANCED_SETTINGS_EXPANDER_SELECTOR) |
| 363 | + |
| 364 | + async def is_expanded() -> bool: |
| 365 | + try: |
| 366 | + grandparent = expander_locator.locator('xpath=../..') |
| 367 | + class_str = await grandparent.get_attribute('class', timeout=2000) |
| 368 | + return class_str and 'expanded' in class_str.split() |
| 369 | + except Exception: |
| 370 | + return False |
| 371 | + |
| 372 | + for attempt in range(1, max_retries + 1): |
| 373 | + try: |
| 374 | + await self._check_disconnect(check_client_disconnected, f'高级设置展开 - 尝试 {attempt}') |
| 375 | + |
| 376 | + if await expander_locator.count() == 0: |
| 377 | + self.logger.info(f'[{self.req_id}] 高级设置展开按钮不存在,可能已是新版布局,跳过。') |
| 378 | + return |
| 379 | + |
| 380 | + if await is_expanded(): |
| 381 | + self.logger.info(f'[{self.req_id}] ✅ 高级设置面板已展开。') |
| 382 | + return |
| 383 | + |
| 384 | + self.logger.info(f'[{self.req_id}] 🔧 (尝试 {attempt}/{max_retries}) 正在展开高级设置面板...') |
| 385 | + |
| 386 | + try: |
| 387 | + await click_element(self.page, expander_locator, '高级设置展开按钮', self.req_id) |
| 388 | + except ElementClickError as e: |
| 389 | + self.logger.warning(f'[{self.req_id}] 高级设置展开按钮点击失败: {e}') |
| 390 | + if attempt < max_retries: |
| 391 | + await asyncio.sleep(0.3) |
| 392 | + continue |
| 393 | + |
| 394 | + await asyncio.sleep(0.3) |
| 395 | + |
| 396 | + if await is_expanded(): |
| 397 | + self.logger.info(f'[{self.req_id}] ✅ 高级设置面板已展开。') |
| 398 | + return |
| 399 | + |
| 400 | + self.logger.warning(f'[{self.req_id}] 高级设置展开验证失败 (尝试 {attempt})') |
| 401 | + if attempt < max_retries: |
| 402 | + await asyncio.sleep(0.3) |
| 403 | + |
| 404 | + except Exception as e: |
| 405 | + if isinstance(e, ClientDisconnectedError): |
| 406 | + raise |
| 407 | + self.logger.warning(f'[{self.req_id}] 展开高级设置失败 (尝试 {attempt}): {e}') |
| 408 | + if attempt < max_retries: |
| 409 | + await asyncio.sleep(0.3) |
| 410 | + |
| 411 | + self.logger.error(f'[{self.req_id}] ❌ 高级设置展开失败,已重试 {max_retries} 次') |
| 412 | + |
357 | 413 | async def _ensure_tools_panel_expanded(self, check_client_disconnected: Callable): |
358 | 414 | max_retries = 3 |
359 | 415 | for attempt in range(1, max_retries + 1): |
@@ -646,64 +702,56 @@ async def _verify_chat_cleared(self, check_client_disconnected: Callable): |
646 | 702 | self.logger.warning(f'[{self.req_id}] 警告: 清空聊天验证失败,但将继续执行。后续操作可能会受影响。') |
647 | 703 |
|
648 | 704 | async def _robust_click_insert_assets(self, check_client_disconnected: Callable) -> bool: |
649 | | - self.logger.info(f"[{self.req_id}] 开始寻找并点击 'Insert assets' 按钮...") |
| 705 | + self.logger.info(f"[{self.req_id}] 开始寻找并点击媒体添加按钮...") |
650 | 706 |
|
651 | 707 | trigger_selectors = [ |
652 | | - 'button[aria-label*="Insert assets"]', |
| 708 | + INSERT_BUTTON_SELECTOR, |
| 709 | + 'button[aria-label*="Insert"]', |
653 | 710 | 'button[iconname="add_circle"]', |
654 | | - '.ms-button-icon[iconname="add_circle"]' |
| 711 | + 'button[iconname="note_add"]' |
655 | 712 | ] |
656 | 713 |
|
657 | 714 | trigger_btn = None |
658 | 715 | for sel in trigger_selectors: |
659 | 716 | if await self.page.locator(sel).count() > 0: |
660 | 717 | trigger_btn = self.page.locator(sel).first |
| 718 | + self.logger.info(f"[{self.req_id}] 找到媒体按钮: {sel}") |
661 | 719 | break |
662 | 720 |
|
663 | 721 | if not trigger_btn: |
664 | | - self.logger.warning(f"[{self.req_id}] 未找到 'Insert assets' 按钮。") |
665 | | - return False |
666 | | - |
667 | | - async def is_menu_open(): |
668 | | - try: |
669 | | - count = await self.page.locator('button[aria-label="Upload File"]').count() |
670 | | - if count > 0 and await self.page.locator('button[aria-label="Upload File"]').first.is_visible(): |
671 | | - return True |
672 | | - except Exception: |
673 | | - pass |
| 722 | + self.logger.warning(f"[{self.req_id}] 未找到媒体添加按钮。") |
674 | 723 | return False |
675 | 724 |
|
| 725 | + upload_menu_locator = self.page.locator(UPLOAD_BUTTON_SELECTOR) |
| 726 | + |
676 | 727 | max_attempts = 3 |
677 | | - for attempt in range(max_attempts): |
678 | | - await self._check_disconnect(check_client_disconnected, f'点击Insert Assets - 尝试 {attempt+1}') |
| 728 | + for attempt in range(1, max_attempts + 1): |
| 729 | + await self._check_disconnect(check_client_disconnected, f'点击媒体按钮 - 尝试 {attempt}') |
| 730 | + |
| 731 | + self.logger.info(f"[{self.req_id}] (尝试 {attempt}/{max_attempts}) 点击媒体添加按钮...") |
679 | 732 |
|
680 | | - self.logger.info(f"[{self.req_id}] (尝试 {attempt+1}) 发送 'click' 事件到 Insert Assets 按钮...") |
681 | 733 | try: |
682 | | - await trigger_btn.dispatch_event('click') |
683 | | - except Exception as e: |
684 | | - self.logger.warning(f"[{self.req_id}] dispatch_event 失败: {e}") |
| 734 | + await click_element(self.page, trigger_btn, '媒体添加按钮', self.req_id) |
| 735 | + except ElementClickError as e: |
| 736 | + self.logger.warning(f"[{self.req_id}] 媒体按钮点击失败: {e}") |
| 737 | + if attempt < max_attempts: |
| 738 | + await asyncio.sleep(0.5) |
| 739 | + continue |
685 | 740 |
|
686 | 741 | for _ in range(10): |
687 | | - if await is_menu_open(): |
688 | | - self.logger.info(f"[{self.req_id}] 'Upload File' 菜单项已检测到开启。") |
689 | | - return True |
| 742 | + try: |
| 743 | + if await upload_menu_locator.count() > 0 and await upload_menu_locator.first.is_visible(): |
| 744 | + self.logger.info(f"[{self.req_id}] ✅ 'Upload file' 菜单项已检测到开启。") |
| 745 | + return True |
| 746 | + except Exception: |
| 747 | + pass |
690 | 748 | await asyncio.sleep(0.2) |
691 | 749 |
|
692 | | - self.logger.info(f"[{self.req_id}] (尝试 {attempt+1}) 菜单未开,尝试 JS click...") |
693 | | - try: |
694 | | - await trigger_btn.evaluate('e => e.click()') |
695 | | - except Exception as e: |
696 | | - self.logger.warning(f"[{self.req_id}] JS click 失败: {e}") |
697 | | - |
698 | | - for _ in range(5): |
699 | | - if await is_menu_open(): |
700 | | - self.logger.info(f"[{self.req_id}] 'Upload File' 菜单项已检测到开启 (JS click 后)。") |
701 | | - return True |
702 | | - await asyncio.sleep(0.2) |
703 | | - |
704 | | - self.logger.warning(f"[{self.req_id}] (尝试 {attempt+1}) 菜单仍未开启。") |
| 750 | + self.logger.warning(f"[{self.req_id}] (尝试 {attempt}/{max_attempts}) 菜单仍未开启。") |
| 751 | + if attempt < max_attempts: |
| 752 | + await asyncio.sleep(0.3) |
705 | 753 |
|
706 | | - self.logger.error(f"[{self.req_id}] 多次尝试后仍无法打开 'Insert assets' 菜单。") |
| 754 | + self.logger.error(f"[{self.req_id}] 多次尝试后仍无法打开媒体菜单。") |
707 | 755 | return False |
708 | 756 |
|
709 | 757 | async def _upload_images_via_file_input(self, images: List[Dict[str, str]], check_client_disconnected: Callable) -> bool: |
|
0 commit comments