@@ -812,15 +812,25 @@ async def _upload_images_via_file_input(self, images: List[Dict[str, str]], chec
812812 return False
813813
814814 await self ._check_disconnect (check_client_disconnected , '上传图片前' )
815+
816+ '''
817+ # 旧批量上传逻辑(AI Studio新前端不支援multiple属性)
815818 menu_opened = await self._robust_click_insert_assets(check_client_disconnected)
816819 if not menu_opened:
817820 self.logger.warning(f"[{self.req_id}] 未能打开菜单,尝试直接查找 input...")
818821
819- file_input , matched_input = await get_first_visible_locator (self .page , HIDDEN_FILE_INPUT_SELECTORS + ['input[type="file"]' ], timeout = 2000 )
822+ file_input = None
823+ for selector in HIDDEN_FILE_INPUT_SELECTORS:
824+ loc = self.page.locator(selector)
825+ if await loc.count() > 0:
826+ file_input = loc.first
827+ break
820828 if not file_input:
821- file_input = self .page .locator ('input[type="file"]' ).first
829+ loc = self.page.locator('input[type="file"]')
830+ if await loc.count() > 0:
831+ file_input = loc.first
822832
823- if await file_input . count () == 0 :
833+ if not file_input:
824834 self.logger.warning(f"[{self.req_id}] 未找到文件输入框")
825835 asyncio.create_task(self._cleanup_temp_files(temp_files))
826836 return False
@@ -835,26 +845,36 @@ async def _upload_images_via_file_input(self, images: List[Dict[str, str]], chec
835845 return True
836846 except Exception as batch_err:
837847 self.logger.warning(f"[{self.req_id}] 批量上传失败: {batch_err},尝试逐个上传...")
848+ '''
838849
839850 uploaded_count = 0
840851 for idx , tf_path in enumerate (temp_files ):
841852 await self ._check_disconnect (check_client_disconnected , f'上传图片 { idx + 1 } /{ len (temp_files )} ' )
842853
843- if idx > 0 :
844- menu_opened = await self ._robust_click_insert_assets (check_client_disconnected )
845- if not menu_opened :
846- continue
847- file_input , _ = await get_first_visible_locator (self .page , HIDDEN_FILE_INPUT_SELECTORS + ['input[type="file"]' ], timeout = 2000 )
848- if not file_input :
849- file_input = self .page .locator ('input[type="file"]' ).first
854+ menu_opened = await self ._robust_click_insert_assets (check_client_disconnected )
855+ if not menu_opened :
856+ continue
857+
858+ file_input = None
859+ for selector in HIDDEN_FILE_INPUT_SELECTORS :
860+ loc = self .page .locator (selector )
861+ if await loc .count () > 0 :
862+ file_input = loc .first
863+ break
864+ if not file_input :
865+ loc = self .page .locator ('input[type="file"]' )
866+ if await loc .count () > 0 :
867+ file_input = loc .first
868+
869+ if not file_input :
870+ self .logger .warning (f"[{ self .req_id } ] 第{ idx + 1 } 张图片:未找到文件输入框" )
871+ continue
850872
851873 try :
852874 self .logger .info (f"[{ self .req_id } ] 上传图片 { idx + 1 } /{ len (temp_files )} ..." )
853875 await file_input .set_input_files (tf_path )
854876 uploaded_count += 1
855- await asyncio .sleep (DELAY_AFTER_FILL )
856- await self .page .keyboard .press ('Escape' )
857- await asyncio .sleep (SLEEP_TICK )
877+ await asyncio .sleep (SLEEP_IMAGE_UPLOAD )
858878 except Exception as single_err :
859879 self .logger .warning (f"[{ self .req_id } ] 单张上传失败 { idx + 1 } : { single_err } " )
860880
@@ -879,78 +899,82 @@ async def _cleanup_temp_files(self, file_paths: List[str]):
879899 except Exception :
880900 pass
881901
882- async def _paste_images_via_event (self , images : List [Dict [str , str ]], target_locator : Locator ) :
902+ async def _paste_images_via_event (self , images : List [Dict [str , str ]], target_locator : Locator , check_client_disconnected : Callable ) -> bool :
883903 self .logger .info (f"[{ self .req_id } ] (备选) 正在通过虚拟粘贴事件上传 { len (images )} 张图片..." )
904+ expected_count = len (images )
884905
885906 try :
886- await target_locator .focus ()
887- await target_locator .click ()
888- except Exception :
889- pass
907+ await self ._check_disconnect (check_client_disconnected , '虚拟粘贴' )
908+
909+ await self .page .evaluate ('document.querySelector("ms-prompt-box textarea")?.focus()' )
890910
891- script = """
892- async (images) => {
893- try {
894- const dataTransfer = new DataTransfer();
895-
896- for (let i = 0; i < images.length; i++) {
897- const img = images[i];
898- try {
899- const byteCharacters = atob(img.data);
900- const byteNumbers = new Array(byteCharacters.length);
901- for (let j = 0; j < byteCharacters.length; j++) {
902- byteNumbers[j] = byteCharacters.charCodeAt(j);
911+ script = """
912+ async (images) => {
913+ try {
914+ const dataTransfer = new DataTransfer();
915+ for (let i = 0; i < images.length; i++) {
916+ const img = images[i];
917+ try {
918+ const byteCharacters = atob(img.data);
919+ const byteNumbers = new Array(byteCharacters.length);
920+ for (let j = 0; j < byteCharacters.length; j++) {
921+ byteNumbers[j] = byteCharacters.charCodeAt(j);
922+ }
923+ const byteArray = new Uint8Array(byteNumbers);
924+ const blob = new Blob([byteArray], { type: img.mime });
925+ const ext = img.mime.split('/')[1] || 'png';
926+ const filename = `pasted_image_${i + 1}.${ext}`;
927+ const file = new File([blob], filename, { type: img.mime, lastModified: Date.now() });
928+ dataTransfer.items.add(file);
929+ } catch (err) {
930+ console.error(`[Paste] 处理图片 ${i + 1} 失败:`, err);
903931 }
904- const byteArray = new Uint8Array(byteNumbers);
905- const blob = new Blob([byteArray], { type: img.mime });
906-
907- const ext = img.mime.split('/')[1] || 'png';
908- const filename = `pasted_image_${i + 1}.${ext}`;
909- const file = new File([blob], filename, { type: img.mime, lastModified: Date.now() });
910-
911- dataTransfer.items.add(file);
912- } catch (err) {
913- console.error(`[Paste] 处理图片 ${i + 1} 失败:`, err);
914932 }
933+ if (dataTransfer.files.length === 0) {
934+ return { success: false, error: "没有文件被添加到 DataTransfer" };
935+ }
936+ const pasteEvent = new ClipboardEvent('paste', { bubbles: true, cancelable: true });
937+ Object.defineProperty(pasteEvent, 'clipboardData', { value: dataTransfer, writable: false, configurable: true });
938+ const textarea = document.querySelector('ms-prompt-box textarea');
939+ if (textarea) { textarea.focus(); textarea.dispatchEvent(pasteEvent); }
940+ document.dispatchEvent(pasteEvent);
941+ return { success: true, count: dataTransfer.files.length };
942+ } catch (err) {
943+ return { success: false, error: err.message };
915944 }
916-
917- if (dataTransfer.files.length === 0) {
918- return { success: false, error: "没有文件被添加到 DataTransfer" };
919- }
920-
921- const pasteEvent = new ClipboardEvent('paste', {
922- bubbles: true,
923- cancelable: true,
924- });
925-
926- Object.defineProperty(pasteEvent, 'clipboardData', {
927- value: dataTransfer,
928- writable: false,
929- configurable: true
930- });
931-
932- const textarea = document.querySelector('ms-prompt-box textarea');
933- if (textarea) {
934- textarea.focus();
935- textarea.dispatchEvent(pasteEvent);
936- }
937-
938- document.dispatchEvent(pasteEvent);
939-
940- console.log(`[Paste] 已触发粘贴事件,包含 ${dataTransfer.files.length} 个文件`);
941- return { success: true, count: dataTransfer.files.length };
942- } catch (err) {
943- console.error('[Paste] 模拟粘贴失败:', err);
944- return { success: false, error: err.message };
945945 }
946- }
947- """
948- result = await self .page .evaluate (script , images )
949- if not result or not result .get ('success' ):
950- error_message = result .get ('error' , '未知错误' )
951- self .logger .error (f"[{ self .req_id } ] 虚拟粘贴图片失败: { error_message } " )
952- else :
953- self .logger .info (f"[{ self .req_id } ] ✅ 虚拟粘贴事件已触发 ({ result .get ('count' , 0 )} 个文件)" )
946+ """
947+ result = await self .page .evaluate (script , images )
948+ if not result or not result .get ('success' ):
949+ self .logger .error (f"[{ self .req_id } ] 虚拟粘贴触发失败: { result .get ('error' , '未知' )} " )
950+ return False
951+
952+ self .logger .info (f"[{ self .req_id } ] 虚拟粘贴事件已触发" )
953+ await asyncio .sleep (SLEEP_IMAGE_UPLOAD )
954+
955+ uploaded_images = 0
956+ for selector in ['ms-prompt-box img' , '.prompt-input img' , 'img[src*="blob:"]' ]:
957+ try :
958+ locator = self .page .locator (selector )
959+ count = await locator .count ()
960+ if count >= expected_count :
961+ uploaded_images = count
962+ break
963+ except Exception :
964+ pass
965+
966+ if uploaded_images >= expected_count :
967+ self .logger .info (f"[{ self .req_id } ] ✅ 虚拟粘贴成功,检测到 { uploaded_images } 张图片" )
968+ return True
969+ else :
970+ self .logger .warning (f"[{ self .req_id } ] 虚拟粘贴验证失败: 检测到{ uploaded_images } /{ expected_count } 张" )
971+ return False
972+
973+ except Exception as e :
974+ if isinstance (e , ClientDisconnectedError ):
975+ raise
976+ self .logger .error (f"[{ self .req_id } ] ❌ 虚拟粘贴异常: { e } " )
977+ return False
954978
955979
956980 async def submit_prompt (self , prompt : str , image_list : List , check_client_disconnected : Callable ):
@@ -996,7 +1020,7 @@ async def submit_prompt(self, prompt: str, image_list: List, check_client_discon
9961020
9971021 if not upload_success :
9981022 self .logger .info (f"[{ self .req_id } ] 回退到虚拟粘贴模式..." )
999- await self ._paste_images_via_event (processed_images , prompt_textarea_locator )
1023+ await self ._paste_images_via_event (processed_images , prompt_textarea_locator , check_client_disconnected )
10001024
10011025 await asyncio .sleep (SLEEP_LONG )
10021026
0 commit comments