Skip to content

Commit a0978cc

Browse files
authored
Merge pull request #231 from MAX-TAB/main
修复了新版UI导致程序崩溃的问题
2 parents af21fb6 + 4aff65b commit a0978cc

7 files changed

Lines changed: 94 additions & 68 deletions

File tree

api_utils/app.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030
_initialize_page_logic,
3131
_close_page_logic,
3232
load_excluded_models,
33-
_handle_initial_model_state_and_storage
33+
_handle_initial_model_state_and_storage,
34+
enable_temporary_chat_mode
3435
)
3536

3637
import stream
@@ -141,6 +142,7 @@ async def _initialize_browser_and_page():
141142
server.page_instance, server.is_page_ready = await _initialize_page_logic(server.browser_instance)
142143
if server.is_page_ready:
143144
await _handle_initial_model_state_and_storage(server.page_instance)
145+
await enable_temporary_chat_mode(server.page_instance)
144146
server.logger.info("Page initialized successfully.")
145147
else:
146148
server.logger.error("Page initialization failed.")

browser_utils/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# --- browser_utils/__init__.py ---
22
# 浏览器操作工具模块
3-
from .initialization import _initialize_page_logic, _close_page_logic, signal_camoufox_shutdown
3+
from .initialization import _initialize_page_logic, _close_page_logic, signal_camoufox_shutdown, enable_temporary_chat_mode
44
from .operations import (
55
_handle_model_list_response,
66
detect_and_extract_page_error,
@@ -28,6 +28,7 @@
2828
'_initialize_page_logic',
2929
'_close_page_logic',
3030
'signal_camoufox_shutdown',
31+
'enable_temporary_chat_mode',
3132

3233
# 页面操作相关
3334
'_handle_model_list_response',

browser_utils/initialization.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,7 @@ async def _initialize_page_logic(browser: AsyncBrowser):
416416
await expect_async(found_page.locator(INPUT_SELECTOR)).to_be_visible(timeout=10000)
417417
logger.info("-> ✅ 核心输入区域可见。")
418418

419-
model_name_locator = found_page.locator('mat-select[data-test-ms-model-selector] .model-option-content span')
419+
model_name_locator = found_page.locator('[data-test-id="model-name"]')
420420
try:
421421
model_name_on_page = await model_name_locator.first.inner_text(timeout=5000)
422422
logger.info(f"-> 🤖 页面检测到的当前模型: {model_name_on_page}")
@@ -596,4 +596,34 @@ async def _handle_auth_file_save_auto(temp_context):
596596
logger.info(f" 自动保存认证状态成功: {auth_save_path}")
597597
except Exception as save_state_err:
598598
logger.error(f" ❌ 自动保存认证状态失败: {save_state_err}", exc_info=True)
599-
print(f" ❌ 自动保存认证状态失败: {save_state_err}", flush=True)
599+
print(f" ❌ 自动保存认证状态失败: {save_state_err}", flush=True)
600+
601+
async def enable_temporary_chat_mode(page: AsyncPage):
602+
"""
603+
检查并启用 AI Studio 界面的“临时聊天”模式。
604+
这是一个独立的UI操作,应该在页面完全稳定后调用。
605+
"""
606+
try:
607+
logger.info("-> (UI Op) 正在检查并启用 '临时聊天' 模式...")
608+
609+
incognito_button_locator = page.locator('button[aria-label="Temporary chat toggle"]')
610+
611+
await incognito_button_locator.wait_for(state="visible", timeout=10000)
612+
613+
button_classes = await incognito_button_locator.get_attribute("class")
614+
615+
if button_classes and 'ms-button-active' in button_classes:
616+
logger.info("-> (UI Op) '临时聊天' 模式已激活。")
617+
else:
618+
logger.info("-> (UI Op) '临时聊天' 模式未激活,正在点击...")
619+
await incognito_button_locator.click(timeout=5000, force=True)
620+
await asyncio.sleep(1)
621+
622+
updated_classes = await incognito_button_locator.get_attribute("class")
623+
if updated_classes and 'ms-button-active' in updated_classes:
624+
logger.info("✅ (UI Op) '临时聊天' 模式已成功启用。")
625+
else:
626+
logger.warning("⚠️ (UI Op) 点击后 '临时聊天' 模式状态验证失败。")
627+
628+
except Exception as e:
629+
logger.warning(f"⚠️ (UI Op) 启用 '临时聊天' 模式时出错: {e}")

browser_utils/model_management.py

Lines changed: 47 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -274,27 +274,49 @@ async def switch_ai_studio_model(page: AsyncPage, model_id: str, req_id: str) ->
274274
if m_obj.get("id") == model_id:
275275
expected_display_name_for_target_id = m_obj.get("display_name")
276276
break
277+
278+
try:
279+
model_name_locator = page.locator('[data-test-id="model-name"]')
280+
actual_displayed_model_id_on_page_raw = await model_name_locator.first.inner_text(timeout=5000)
281+
actual_displayed_model_id_on_page = actual_displayed_model_id_on_page_raw.strip()
282+
283+
target_model_id = model_id
284+
285+
if actual_displayed_model_id_on_page == target_model_id:
286+
page_display_match = True
287+
logger.info(f"[{req_id}] ✅ 页面显示模型ID ('{actual_displayed_model_id_on_page}') 与期望ID ('{target_model_id}') 一致。")
288+
else:
289+
page_display_match = False
290+
logger.error(f"[{req_id}] ❌ 页面显示模型ID ('{actual_displayed_model_id_on_page}') 与期望ID ('{target_model_id}') 不一致。")
277291

278-
if not expected_display_name_for_target_id:
279-
logger.warning(f"[{req_id}] 无法在parsed_model_list中找到目标ID '{model_id}' 的显示名称,跳过页面显示名称验证。这可能不准确。")
280-
page_display_match = True
281-
else:
292+
except Exception as e_disp:
293+
page_display_match = False # 读取失败则认为不匹配
294+
logger.warning(f"[{req_id}] 读取页面显示的当前模型ID时出错: {e_disp}。将无法验证页面显示。")
295+
296+
if page_display_match:
282297
try:
283-
model_name_locator = page.locator('mat-select[data-test-ms-model-selector] .model-option-content span')
284-
actual_displayed_model_name_on_page_raw = await model_name_locator.first.inner_text(timeout=5000)
285-
actual_displayed_model_name_on_page = actual_displayed_model_name_on_page_raw.strip()
286-
normalized_actual_display = actual_displayed_model_name_on_page.lower()
287-
normalized_expected_display = expected_display_name_for_target_id.strip().lower()
298+
logger.info(f"[{req_id}] 模型切换成功,重新启用 '临时聊天' 模式...")
299+
incognito_button_locator = page.locator('button[aria-label="Temporary chat toggle"]')
288300

289-
if normalized_actual_display == normalized_expected_display:
290-
page_display_match = True
291-
logger.info(f"[{req_id}] ✅ 页面显示模型 ('{actual_displayed_model_name_on_page}') 与期望 ('{expected_display_name_for_target_id}') 一致。")
301+
await incognito_button_locator.wait_for(state="visible", timeout=5000)
302+
303+
button_classes = await incognito_button_locator.get_attribute("class")
304+
305+
if button_classes and 'ms-button-active' in button_classes:
306+
logger.info(f"[{req_id}] '临时聊天' 模式已处于激活状态。")
292307
else:
293-
logger.error(f"[{req_id}] ❌ 页面显示模型 ('{actual_displayed_model_name_on_page}') 与期望 ('{expected_display_name_for_target_id}') 不一致。(Raw page: '{actual_displayed_model_name_on_page_raw}')")
294-
except Exception as e_disp:
295-
logger.warning(f"[{req_id}] 读取页面显示的当前模型名称时出错: {e_disp}。将无法验证页面显示。")
296-
297-
if page_display_match:
308+
logger.info(f"[{req_id}] '临时聊天' 模式未激活,正在点击以开启...")
309+
await incognito_button_locator.click(timeout=3000)
310+
await asyncio.sleep(0.5)
311+
312+
updated_classes = await incognito_button_locator.get_attribute("class")
313+
if updated_classes and 'ms-button-active' in updated_classes:
314+
logger.info(f"[{req_id}] ✅ '临时聊天' 模式已成功重新启用。")
315+
else:
316+
logger.warning(f"[{req_id}] ⚠️ 点击后 '临时聊天' 模式状态验证失败,可能未成功重新开启。")
317+
318+
except Exception as e:
319+
logger.warning(f"[{req_id}] ⚠️ 模型切换后重新启用 '临时聊天' 模式失败: {e}")
298320
return True
299321
else:
300322
logger.error(f"[{req_id}] ❌ 模型切换失败,因为页面显示的模型与期望不符 (即使localStorage可能已更改)。")
@@ -306,7 +328,7 @@ async def switch_ai_studio_model(page: AsyncPage, model_id: str, req_id: str) ->
306328
current_displayed_name_for_revert_stripped = "无法读取"
307329

308330
try:
309-
model_name_locator_revert = page.locator('mat-select[data-test-ms-model-selector] .model-option-content span')
331+
model_name_locator_revert = page.locator('[data-test-id="model-name"]')
310332
current_displayed_name_for_revert_raw = await model_name_locator_revert.first.inner_text(timeout=5000)
311333
current_displayed_name_for_revert_stripped = current_displayed_name_for_revert_raw.strip()
312334
logger.info(f"[{req_id}] 恢复:页面当前显示的模型名称 (原始: '{current_displayed_name_for_revert_raw}', 清理后: '{current_displayed_name_for_revert_stripped}')")
@@ -324,17 +346,9 @@ async def switch_ai_studio_model(page: AsyncPage, model_id: str, req_id: str) ->
324346
return False
325347

326348
model_id_to_revert_to = None
327-
if parsed_model_list and current_displayed_name_for_revert_stripped != "无法读取":
328-
normalized_current_display_for_revert = current_displayed_name_for_revert_stripped.lower()
329-
for m_obj in parsed_model_list:
330-
parsed_list_display_name = m_obj.get("display_name", "").strip().lower()
331-
if parsed_list_display_name == normalized_current_display_for_revert:
332-
model_id_to_revert_to = m_obj.get("id")
333-
logger.info(f"[{req_id}] 恢复:页面显示名称 '{current_displayed_name_for_revert_stripped}' 对应模型ID: {model_id_to_revert_to}")
334-
break
335-
336-
if not model_id_to_revert_to:
337-
logger.warning(f"[{req_id}] 恢复:无法在 parsed_model_list 中找到与页面显示名称 '{current_displayed_name_for_revert_stripped}' 匹配的模型ID。")
349+
if current_displayed_name_for_revert_stripped != "无法读取":
350+
model_id_to_revert_to = current_displayed_name_for_revert_stripped
351+
logger.info(f"[{req_id}] 恢复:页面当前显示的ID是 '{model_id_to_revert_to}',将直接用于恢复。")
338352
else:
339353
if current_displayed_name_for_revert_stripped == "无法读取":
340354
logger.warning(f"[{req_id}] 恢复:因无法读取页面显示名称,故不能从 parsed_model_list 转换ID。")
@@ -529,7 +543,7 @@ async def _set_model_from_page_display(page: AsyncPage, set_storage: bool = Fals
529543

530544
try:
531545
logger.info(" 尝试从页面显示元素读取当前模型名称...")
532-
model_name_locator = page.locator('mat-select[data-test-ms-model-selector] .model-option-content span')
546+
model_name_locator = page.locator('[data-test-id="model-name"]')
533547
displayed_model_name_from_page_raw = await model_name_locator.first.inner_text(timeout=7000)
534548
displayed_model_name = displayed_model_name_from_page_raw.strip()
535549
logger.info(f" 页面当前显示模型名称 (原始: '{displayed_model_name_from_page_raw}', 清理后: '{displayed_model_name}')")
@@ -542,19 +556,10 @@ async def _set_model_from_page_display(page: AsyncPage, set_storage: bool = Fals
542556
except asyncio.TimeoutError:
543557
logger.warning(" 等待模型列表超时,可能无法准确转换显示名称为ID。")
544558

545-
if parsed_model_list:
546-
for model_obj in parsed_model_list:
547-
if model_obj.get("display_name") and model_obj.get("display_name").strip() == displayed_model_name:
548-
found_model_id_from_display = model_obj.get("id")
549-
logger.info(f" 显示名称 '{displayed_model_name}' 对应模型 ID: {found_model_id_from_display}")
550-
break
551-
552-
if not found_model_id_from_display:
553-
logger.warning(f" 未在已知模型列表中找到与显示名称 '{displayed_model_name}' 匹配的 ID。")
554-
else:
555-
logger.warning(" 模型列表尚不可用,无法将显示名称转换为ID。")
559+
found_model_id_from_display = displayed_model_name
560+
logger.info(f" 页面显示的直接是模型ID: '{found_model_id_from_display}'")
556561

557-
new_model_value = found_model_id_from_display if found_model_id_from_display else displayed_model_name
562+
new_model_value = found_model_id_from_display
558563
if server.current_ai_studio_model_id != new_model_value:
559564
server.current_ai_studio_model_id = new_model_value
560565
logger.info(f" 全局 current_ai_studio_model_id 已更新为: {server.current_ai_studio_model_id}")

browser_utils/page_controller.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
)
2424
from models import ClientDisconnectedError
2525
from .operations import save_error_snapshot, _wait_for_response_completion, _get_final_response_content
26+
from .initialization import enable_temporary_chat_mode
2627

2728
class PageController:
2829
"""封装了与AI Studio页面交互的所有操作。"""
@@ -572,6 +573,8 @@ async def clear_chat_history(self, check_client_disconnected: Callable):
572573
if can_attempt_clear:
573574
await self._execute_chat_clear(clear_chat_button_locator, confirm_button_locator, overlay_locator, check_client_disconnected)
574575
await self._verify_chat_cleared(check_client_disconnected)
576+
self.logger.info(f"[{self.req_id}] 聊天已清空,重新启用 '临时聊天' 模式...")
577+
await enable_temporary_chat_mode(self.page)
575578

576579
except Exception as e_clear:
577580
self.logger.error(f"[{self.req_id}] 清空聊天过程中发生错误: {e_clear}")

config/selectors.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010

1111
# --- 按钮选择器 ---
1212
SUBMIT_BUTTON_SELECTOR = 'button[aria-label="Run"].run-button'
13-
CLEAR_CHAT_BUTTON_SELECTOR = 'button[data-test-clear="outside"][aria-label="Clear chat"]'
14-
CLEAR_CHAT_CONFIRM_BUTTON_SELECTOR = 'button.ms-button-primary:has-text("Continue")'
15-
UPLOAD_BUTTON_SELECTOR = 'button[aria-label="Upload File"]'
13+
CLEAR_CHAT_BUTTON_SELECTOR = 'button[data-test-clear="outside"][aria-label="New chat"]'
14+
CLEAR_CHAT_CONFIRM_BUTTON_SELECTOR = 'button.ms-button-primary:has-text("Discard and continue")'
15+
UPLOAD_BUTTON_SELECTOR = 'button[aria-label^="Insert assets"]'
1616

1717
# --- 响应相关选择器 ---
1818
RESPONSE_CONTAINER_SELECTOR = 'ms-chat-turn .chat-turn-container.model'

deprecated_javascript_version/server.cjs

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -989,47 +989,32 @@ async function processQueue() {
989989
console.log(`[${reqId}] 检测到可能是新对话 (消息数: ${messages.length}),尝试清空聊天记录...`);
990990
try {
991991
const clearButton = page.locator(CLEAR_CHAT_BUTTON_SELECTOR);
992-
console.log(`[${reqId}] - 查找并点击"Clear chat"按钮...`);
992+
console.log(`[${reqId}] - 查找并点击"Clear chat" (New chat) 按钮...`);
993993
await clearButton.waitFor({ state: 'visible', timeout: 7000 });
994994
await clearButton.click({ timeout: 5000 });
995-
console.log(`[${reqId}] - "Clear chat"按钮已点击。`);
995+
console.log(`[${reqId}] - "Clear chat"按钮已点击。新版UI无确认步骤,开始验证清空效果...`);
996996

997-
console.log(`[${reqId}] - 等待确认对话框及"Continue"按钮出现...`);
998-
const confirmButton = page.locator(CLEAR_CHAT_CONFIRM_BUTTON_SELECTOR);
999-
await confirmButton.waitFor({ state: 'visible', timeout: 5000 });
1000-
1001-
console.log(`[${reqId}] - 点击"Continue"按钮...`);
1002-
await confirmButton.click({ timeout: 5000 });
1003-
console.log(`[${reqId}] - "Continue"按钮已点击。开始验证清空效果...`);
1004-
1005-
// --- 新增:验证清空效果 ---
1006997
const checkStartTime = Date.now();
1007998
let cleared = false;
1008999
while (Date.now() - checkStartTime < CLEAR_CHAT_VERIFY_TIMEOUT_MS) {
1009-
// 定位所有 AI 回复容器
10101000
const modelTurns = page.locator(RESPONSE_CONTAINER_SELECTOR);
10111001
const count = await modelTurns.count();
10121002
if (count === 0) {
10131003
console.log(`[${reqId}] ✅ 验证成功: 页面上未找到之前的 AI 回复元素 (耗时 ${Date.now() - checkStartTime}ms)。`);
10141004
cleared = true;
1015-
break; // 验证成功,退出循环
1005+
break;
10161006
}
1017-
// 稍微等待后再次检查
10181007
await page.waitForTimeout(CLEAR_CHAT_VERIFY_INTERVAL_MS);
10191008
}
10201009

10211010
if (!cleared) {
1022-
// 如果超时后仍然找到 AI 回复元素
10231011
console.warn(`[${reqId}] ⚠️ 验证超时: 在 ${CLEAR_CHAT_VERIFY_TIMEOUT_MS}ms 内仍能检测到之前的 AI 回复元素。上下文可能未完全清空。`);
1024-
// 保存快照以供调试
10251012
await saveErrorSnapshot(`clear_chat_verify_fail_${reqId}`);
10261013
}
1027-
// --- 结束:验证清空效果 ---
1028-
10291014
} catch (clearChatError) {
10301015
console.warn(`[${reqId}] ⚠️ 清空聊天记录或验证时出错: ${clearChatError.message.split('\n')[0]}. 将继续执行请求,但上下文可能未被清除。`);
10311016
if (clearChatError.message.includes('selector')) {
1032-
console.warn(` (请仔细检查选择器是否仍然有效: CLEAR_CHAT_BUTTON_SELECTOR='${CLEAR_CHAT_BUTTON_SELECTOR}', CLEAR_CHAT_CONFIRM_BUTTON_SELECTOR='${CLEAR_CHAT_CONFIRM_BUTTON_SELECTOR}')`);
1017+
console.warn(` (请仔细检查选择器是否仍然有效: CLEAR_CHAT_BUTTON_SELECTOR='${CLEAR_CHAT_BUTTON_SELECTOR}')`);
10331018
}
10341019
await saveErrorSnapshot(`clear_chat_fail_or_verify_${reqId}`);
10351020
}

0 commit comments

Comments
 (0)