diff --git a/src/services/flow_current.py b/src/services/flow_current.py index f3429902..9b411cb6 100644 --- a/src/services/flow_current.py +++ b/src/services/flow_current.py @@ -311,17 +311,15 @@ async def upload_image( context, base64.b64encode(image_bytes).decode("ascii"), mime_type, - True, + 1, None, None, None, - False, - filename, - self._map_frontend_image_aspect_ratio(aspect_ratio), - None, - None, None, + filename, None, + str(uuid.uuid4()).upper(), + str(uuid.uuid4()).upper(), ], google_cookies=google_cookies, token_id=token_id, diff --git a/src/services/flow_frontend.py b/src/services/flow_frontend.py index bf3345cf..0e9c41fc 100644 --- a/src/services/flow_frontend.py +++ b/src/services/flow_frontend.py @@ -189,29 +189,17 @@ def _build_frontend_image_generation_argument( reference_media_ids = [item for item in reference_media_ids if item] client_context = self._frontend_project_context(project_id, recaptcha_token) seed = random.randint(1, 2147483647) - generation_settings = json.dumps( - { - "imageModelKey": model_name, - "aspectRatio": self._map_frontend_image_aspect_ratio(aspect_ratio), - "seed": seed, - "count": 1, - "structuredPrompt": {"parts": [{"text": prompt}]}, - "referenceImageMediaIds": reference_media_ids, - }, - ensure_ascii=False, - separators=(",", ":"), - ) request: List[Any] = [ None, None, - [[1, item] for item in reference_media_ids] or None, + [[media_id, None, None, None, 1] for media_id in reference_media_ids] or None, seed, self._map_frontend_image_aspect_ratio(aspect_ratio), model_name, None, client_context, [[[prompt]]], - generation_settings if reference_media_ids else None, + None, None, None, None, diff --git a/tests/test_flow_client_upload.py b/tests/test_flow_client_upload.py index c2a03d93..de9f988e 100644 --- a/tests/test_flow_client_upload.py +++ b/tests/test_flow_client_upload.py @@ -40,6 +40,32 @@ async def test_upload_uses_current_frontend_rpc(self): self.assertEqual(request["argument"][0][10], ["captcha-token", 1]) self.assertEqual(request["argument"][2], "image/jpeg") + async def test_upload_argument_matches_current_frontend_envelope(self): + client = FlowClient(proxy_manager=None) + client._get_recaptcha_token = AsyncMock(return_value=("captcha-token", None)) + client._call_flow_frontend_rpc = AsyncMock( + return_value=[[MEDIA_ID, PROJECT_ID, f"https://flow-content.google/image/{MEDIA_ID}?Signature=1"]] + ) + client._make_request = AsyncMock() + + await client.upload_image( + at="frontend-cookie", + image_bytes=JPEG_BYTES, + aspect_ratio="IMAGE_ASPECT_RATIO_LANDSCAPE", + project_id=PROJECT_ID, + google_cookies="SID=session-cookie", + ) + + argument = client._call_flow_frontend_rpc.await_args.kwargs["argument"] + self.assertEqual(len(argument), 12) + # slot 3 is an integer enum; sending a bool makes the RPC reject with code=[3] + self.assertEqual(argument[3], 1) + self.assertIsInstance(argument[3], int) + self.assertIsNone(argument[7]) + self.assertIsNone(argument[9]) + for slot in (10, 11): + self.assertRegex(argument[slot], r"^[0-9A-F]{8}(-[0-9A-F]{4}){3}-[0-9A-F]{12}$") + async def test_upload_requires_project_id(self): client = FlowClient(proxy_manager=None) diff --git a/tests/test_flow_image_generation_args.py b/tests/test_flow_image_generation_args.py new file mode 100644 index 00000000..93b7e40e --- /dev/null +++ b/tests/test_flow_image_generation_args.py @@ -0,0 +1,48 @@ +import unittest + +from src.services.flow_client import FlowClient + + +PROJECT_ID = "01234567-89ab-cdef-0123-456789abcdef" +MEDIA_ID = "11234567-89ab-cdef-0123-456789abcdef" + + +class FlowClientImageGenerationArgumentTests(unittest.TestCase): + def setUp(self): + self.client = FlowClient(proxy_manager=None) + + def test_reference_images_use_media_envelope(self): + argument = self.client._build_frontend_image_generation_argument( + project_id=PROJECT_ID, + prompt="换成针织开衫", + model_name="NARWHAL", + aspect_ratio="IMAGE_ASPECT_RATIO_LANDSCAPE", + recaptcha_token="recaptcha-token", + session_id="session-id", + image_inputs=[{"name": MEDIA_ID}], + ) + + request = argument[1][0] + # the reference slot is a 5-field envelope, not [1, mediaId] + self.assertEqual(request[2], [[MEDIA_ID, None, None, None, 1]]) + # slot 9 stays null; a serialized settings blob here is rejected + self.assertIsNone(request[9]) + self.assertEqual(request[8], [[["换成针织开衫"]]]) + + def test_text_only_generation_omits_reference_slot(self): + argument = self.client._build_frontend_image_generation_argument( + project_id=PROJECT_ID, + prompt="一只红苹果", + model_name="NARWHAL", + aspect_ratio="IMAGE_ASPECT_RATIO_LANDSCAPE", + recaptcha_token="recaptcha-token", + session_id="session-id", + ) + + request = argument[1][0] + self.assertIsNone(request[2]) + self.assertIsNone(request[9]) + + +if __name__ == "__main__": + unittest.main()