返回提交历史
Modified
g4f/Provider/needs_auth/Gemini.py
+18
-26
Modified
g4f/Provider/needs_auth/OpenaiChat.py
+30
-25
Modified
g4f/api/__init__.py
+4
-4
Modified
g4f/client/__init__.py
+21
-16
Modified
g4f/gui/server/api.py
+4
-2
Modified
g4f/image.py
+17
-10
Modified
g4f/requests/raise_for_status.py
+4
-2
XFEstudio/gpt4free
Improve image generation in OpenaiChat and Gemini
78c20c08
代码差异
7 个文件
+98
-85
@@ -76,9 +76,8 @@ class Gemini(AsyncGeneratorProvider):
76
76
page = await browser.get(f"{cls.url}/app")
77
77
await page.select("div.ql-editor.textarea", 240)
78
78
cookies = {}
79
for c in await page.browser.cookies.get_all():
80
if c.domain.endswith(".google.com"):
81
cookies[c.name] = c.value
79
for c in await page.send(nodriver.cdp.network.get_cookies([cls.url])):
80
cookies[c.name] = c.value
82
81
await page.close()
83
82
cls._cookies = cookies
84
83
@@ -92,7 +91,6 @@ class Gemini(AsyncGeneratorProvider):
92
91
connector: BaseConnector = None,
93
92
image: ImageType = None,
94
93
image_name: str = None,
95
response_format: str = None,
96
94
return_conversation: bool = False,
97
95
conversation: Conversation = None,
98
96
language: str = "en",
@@ -113,7 +111,7 @@ class Gemini(AsyncGeneratorProvider):
113
111
async for chunk in cls.nodriver_login(proxy):
114
112
yield chunk
115
113
except Exception as e:
116
raise MissingAuthError('Missing "__Secure-1PSID" cookie', e)
114
raise MissingAuthError('Missing or invalid "__Secure-1PSID" cookie', e)
117
115
if not cls._snlm0e:
118
116
if cls._cookies is None or "__Secure-1PSID" not in cls._cookies:
119
117
raise MissingAuthError('Missing "__Secure-1PSID" cookie')
@@ -153,7 +151,7 @@ class Gemini(AsyncGeneratorProvider):
153
151
) as response:
154
152
await raise_for_status(response)
155
153
image_prompt = response_part = None
156
last_content_len = 0
154
last_content = ""
157
155
async for line in response.content:
158
156
try:
159
157
try:
@@ -171,32 +169,26 @@ class Gemini(AsyncGeneratorProvider):
171
169
yield Conversation(response_part[1][0], response_part[1][1], response_part[4][0][0])
172
170
content = response_part[4][0][1][0]
173
171
except (ValueError, KeyError, TypeError, IndexError) as e:
174
print(f"{cls.__name__}:{e.__class__.__name__}:{e}")
172
debug.log(f"{cls.__name__}:{e.__class__.__name__}:{e}")
175
173
continue
176
174
match = re.search(r'\[Imagen of (.*?)\]', content)
177
175
if match:
178
176
image_prompt = match.group(1)
179
177
content = content.replace(match.group(0), '')
180
yield content[last_content_len:]
181
last_content_len = len(content)
182
if image_prompt:
183
try:
184
images = [image[0][3][3] for image in response_part[4][0][12][7][0]]
185
if response_format == "b64_json":
178
pattern = r"http://googleusercontent.com/image_generation_content/\d+"
179
content = re.sub(pattern, "", content)
180
if last_content and content.startswith(last_content):
181
yield content[len(last_content):]
182
else:
183
yield content
184
last_content = content
185
if image_prompt:
186
try:
187
images = [image[0][3][3] for image in response_part[4][0][12][7][0]]
188
image_prompt = image_prompt.replace("a fake image", "")
186
189
yield ImageResponse(images, image_prompt, {"cookies": cls._cookies})
187
else:
188
resolved_images = []
189
preview = []
190
for image in images:
191
async with client.get(image, allow_redirects=False) as fetch:
192
image = fetch.headers["location"]
193
async with client.get(image, allow_redirects=False) as fetch:
194
image = fetch.headers["location"]
195
resolved_images.append(image)
196
preview.append(image.replace('=s512', '=s200'))
197
yield ImageResponse(resolved_images, image_prompt, {"orginal_links": images, "preview": preview})
198
except TypeError:
199
pass
190
except TypeError:
191
pass
200
192
201
193
@classmethod
202
194
async def synthesize(cls, params: dict, proxy: str = None) -> AsyncIterator[bytes]:
@@ -184,7 +184,6 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
184
184
"content": {"content_type": "text", "parts": [message["content"]]},
185
185
"id": str(uuid.uuid4()),
186
186
"create_time": int(time.time()),
187
"id": str(uuid.uuid4()),
188
187
"metadata": {"serialization_metadata": {"custom_symbol_offsets": []}, "system_hints": system_hints},
189
188
} for message in messages]
190
189
@@ -295,8 +294,6 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
295
294
Raises:
296
295
RuntimeError: If an error occurs during processing.
297
296
"""
298
if model == cls.default_image_model:
299
model = cls.default_model
300
297
if cls.needs_auth:
301
298
await cls.login(proxy)
302
299
@@ -308,9 +305,9 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
308
305
if not cls.needs_auth:
309
306
cls._create_request_args(cookies)
310
307
RequestConfig.proof_token = get_config(cls._headers.get("user-agent"))
311
async with session.get(cls.url, headers=INIT_HEADERS) as response:
312
cls._update_request_args(session)
313
await raise_for_status(response)
308
async with session.get(cls.url, headers=INIT_HEADERS) as response:
309
cls._update_request_args(session)
310
await raise_for_status(response)
314
311
try:
315
312
image_request = await cls.upload_image(session, cls._headers, image, image_name) if image else None
316
313
except Exception as e:
@@ -318,6 +315,8 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
318
315
debug.log("OpenaiChat: Upload image failed")
319
316
debug.log(f"{e.__class__.__name__}: {e}")
320
317
model = cls.get_model(model)
318
if model == cls.default_image_model:
319
model = cls.default_vision_model
321
320
if conversation is None:
322
321
conversation = Conversation(conversation_id, str(uuid.uuid4()) if parent_id is None else parent_id)
323
322
else:
@@ -363,13 +362,22 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
363
362
"messages": None,
364
363
"parent_message_id": conversation.message_id,
365
364
"model": model,
366
"paragen_cot_summary_display_override": "allow",
365
"timezone_offset_min":-60,
366
"timezone":"Europe/Berlin",
367
367
"history_and_training_disabled": history_disabled and not auto_continue and not return_conversation,
368
"conversation_mode": {"kind":"primary_assistant"},
368
"conversation_mode":{"kind":"primary_assistant","plugin_ids":None},
369
"force_paragen":False,
370
"force_paragen_model_slug":"",
371
"force_rate_limit":False,
372
"reset_rate_limits":False,
369
373
"websocket_request_id": str(uuid.uuid4()),
370
"supported_encodings": ["v1"],
371
"supports_buffering": True,
372
"system_hints": ["search"] if web_search else None
374
"system_hints": ["search"] if web_search else None,
375
"supported_encodings":["v1"],
376
"conversation_origin":None,
377
"client_contextual_info":{"is_dark_mode":False,"time_since_loaded":14,"page_height":578,"page_width":1850,"pixel_ratio":1,"screen_height":1080,"screen_width":1920},
378
"paragen_stream_type_override":None,
379
"paragen_cot_summary_display_override":"allow",
380
"supports_buffering":True
373
381
}
374
382
if conversation.conversation_id is not None:
375
383
data["conversation_id"] = conversation.conversation_id
@@ -408,7 +416,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
408
416
async for line in response.iter_lines():
409
417
async for chunk in cls.iter_messages_line(session, line, conversation):
410
418
yield chunk
411
if not history_disabled:
419
if not history_disabled and RequestConfig.access_token is not None:
412
420
yield SynthesizeData(cls.__name__, {
413
421
"conversation_id": conversation.conversation_id,
414
422
"message_id": conversation.message_id,
@@ -493,9 +501,12 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
493
501
await get_request_config(proxy)
494
502
cls._create_request_args(RequestConfig.cookies, RequestConfig.headers)
495
503
cls._set_api_key(RequestConfig.access_token)
504
if RequestConfig.proof_token is None:
505
RequestConfig.proof_token = get_config(cls._headers.get("user-agent"))
496
506
except NoValidHarFileError:
497
507
if has_nodriver:
498
await cls.nodriver_auth(proxy)
508
if RequestConfig.access_token is None:
509
await cls.nodriver_auth(proxy)
499
510
else:
500
511
raise
501
512
@@ -527,23 +538,17 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
527
538
await page.send(nodriver.cdp.network.enable())
528
539
page.add_handler(nodriver.cdp.network.RequestWillBeSent, on_request)
529
540
page = await browser.get(cls.url)
530
try:
531
if RequestConfig.access_request_id is not None:
532
body = await page.send(get_response_body(RequestConfig.access_request_id))
533
if isinstance(body, tuple) and body:
534
body = body[0]
535
if body:
536
match = re.search(r'"accessToken":"(.*?)"', body)
537
if match:
538
RequestConfig.access_token = match.group(1)
539
except KeyError:
540
pass
541
body = await page.evaluate("JSON.stringify(window.__remixContext)")
542
if body:
543
match = re.search(r'"accessToken":"(.*?)"', body)
544
if match:
545
RequestConfig.access_token = match.group(1)
541
546
for c in await page.send(nodriver.cdp.network.get_cookies([cls.url])):
542
547
RequestConfig.cookies[c.name] = c.value
543
548
user_agent = await page.evaluate("window.navigator.userAgent")
544
549
await page.select("#prompt-textarea", 240)
545
550
while True:
546
if RequestConfig.proof_token:
551
if RequestConfig.access_token:
547
552
break
548
553
await asyncio.sleep(1)
549
554
await page.close()
@@ -112,7 +112,7 @@ class ImageGenerationConfig(BaseModel):
112
112
prompt: str
113
113
model: Optional[str] = None
114
114
provider: Optional[str] = None
115
response_format: str = "url"
115
response_format: Optional[str] = None
116
116
api_key: Optional[str] = None
117
117
proxy: Optional[str] = None
118
118
@@ -370,9 +370,9 @@ class Api:
370
370
model=config.model,
371
371
provider=AppConfig.image_provider if config.provider is None else config.provider,
372
372
**filter_none(
373
response_format = config.response_format,
374
api_key = config.api_key,
375
proxy = config.proxy
373
response_format=config.response_format,
374
api_key=config.api_key,
375
proxy=config.proxy
376
376
)
377
377
)
378
378
for image in response.data:
@@ -292,6 +292,7 @@ class Images:
292
292
if proxy is None:
293
293
proxy = self.client.proxy
294
294
295
e = None
295
296
response = None
296
297
if isinstance(provider_handler, IterListProvider):
297
298
for provider in provider_handler.providers:
@@ -300,7 +301,7 @@ class Images:
300
301
if response is not None:
301
302
provider_name = provider.__name__
302
303
break
303
except (MissingAuthError, NoValidHarFileError) as e:
304
except Exception as e:
304
305
debug.log(f"Image provider {provider.__name__}: {e}")
305
306
else:
306
307
response = await self._generate_image_response(provider_handler, provider_name, model, prompt, **kwargs)
@@ -314,6 +315,8 @@ class Images:
314
315
provider_name
315
316
)
316
317
if response is None:
318
if e is not None:
319
raise e
317
320
raise NoImageResponseError(f"No image response from {provider_name}")
318
321
raise NoImageResponseError(f"Unexpected response type: {type(response)}")
319
322
@@ -362,7 +365,7 @@ class Images:
362
365
image: ImageType,
363
366
model: str = None,
364
367
provider: Optional[ProviderType] = None,
365
response_format: str = "url",
368
response_format: Optional[str] = None,
366
369
**kwargs
367
370
) -> ImagesResponse:
368
371
return asyncio.run(self.async_create_variation(
@@ -374,7 +377,7 @@ class Images:
374
377
image: ImageType,
375
378
model: Optional[str] = None,
376
379
provider: Optional[ProviderType] = None,
377
response_format: str = "url",
380
response_format: Optional[str] = None,
378
381
proxy: Optional[str] = None,
379
382
**kwargs
380
383
) -> ImagesResponse:
@@ -384,6 +387,7 @@ class Images:
384
387
proxy = self.client.proxy
385
388
prompt = "create a variation of this image"
386
389
390
e = None
387
391
response = None
388
392
if isinstance(provider_handler, IterListProvider):
389
393
# File pointer can be read only once, so we need to convert it to bytes
@@ -394,7 +398,7 @@ class Images:
394
398
if response is not None:
395
399
provider_name = provider.__name__
396
400
break
397
except (MissingAuthError, NoValidHarFileError) as e:
401
except Exception as e:
398
402
debug.log(f"Image provider {provider.__name__}: {e}")
399
403
else:
400
404
response = await self._generate_image_response(provider_handler, provider_name, model, prompt, image=image, **kwargs)
@@ -402,10 +406,11 @@ class Images:
402
406
if isinstance(response, ImageResponse):
403
407
return await self._process_image_response(response, response_format, proxy, model, provider_name)
404
408
if response is None:
409
if e is not None:
410
raise e
405
411
raise NoImageResponseError(f"No image response from {provider_name}")
406
412
raise NoImageResponseError(f"Unexpected response type: {type(response)}")
407
413
408
409
414
async def _process_image_response(
410
415
self,
411
416
response: ImageResponse,
@@ -414,21 +419,21 @@ class Images:
414
419
model: Optional[str] = None,
415
420
provider: Optional[str] = None
416
421
) -> ImagesResponse:
422
last_provider = get_last_provider(True)
417
423
if response_format == "url":
418
424
# Return original URLs without saving locally
419
425
images = [Image.construct(url=image, revised_prompt=response.alt) for image in response.get_list()]
420
elif response_format == "b64_json":
421
images = await copy_images(response.get_list(), response.options.get("cookies"), proxy)
422
async def process_image_item(image_file: str) -> Image:
423
with open(os.path.join(images_dir, os.path.basename(image_file)), "rb") as file:
424
image_data = base64.b64encode(file.read()).decode()
425
return Image.construct(b64_json=image_data, revised_prompt=response.alt)
426
images = await asyncio.gather(*[process_image_item(image) for image in images])
427
426
else:
428
427
# Save locally for None (default) case
429
images = await copy_images(response.get_list(), response.options.get("cookies"), proxy)
430
images = [Image.construct(url=f"/images/{os.path.basename(image)}", revised_prompt=response.alt) for image in images]
431
last_provider = get_last_provider(True)
428
images = await copy_images(response.get_list(), response.get("cookies"), proxy)
429
if response_format == "b64_json":
430
async def process_image_item(image_file: str) -> Image:
431
with open(os.path.join(images_dir, os.path.basename(image_file)), "rb") as file:
432
image_data = base64.b64encode(file.read()).decode()
433
return Image.construct(b64_json=image_data, revised_prompt=response.alt)
434
images = await asyncio.gather(*[process_image_item(image) for image in images])
435
else:
436
images = [Image.construct(url=f"/images/{os.path.basename(image)}", revised_prompt=response.alt) for image in images]
432
437
return ImagesResponse.construct(
433
438
created=int(time.time()),
434
439
data=images,
@@ -529,7 +534,7 @@ class AsyncImages(Images):
529
534
image: ImageType,
530
535
model: str = None,
531
536
provider: ProviderType = None,
532
response_format: str = "url",
537
response_format: Optional[str] = None,
533
538
**kwargs
534
539
) -> ImagesResponse:
535
540
return await self.async_create_variation(
@@ -110,8 +110,10 @@ class Api:
110
110
def _create_response_stream(self, kwargs: dict, conversation_id: str, provider: str, download_images: bool = True) -> Iterator:
111
111
def log_handler(text: str):
112
112
debug.logs.append(text)
113
print(text)
113
if debug.logging:
114
print(text)
114
115
debug.log_handler = log_handler
116
proxy = os.environ.get("G4F_PROXY")
115
117
try:
116
118
result = ChatCompletion.create(**kwargs)
117
119
first = True
@@ -139,7 +141,7 @@ class Api:
139
141
elif isinstance(chunk, ImageResponse):
140
142
images = chunk
141
143
if download_images:
142
images = asyncio.run(copy_images(chunk.get_list(), chunk.options.get("cookies")))
144
images = asyncio.run(copy_images(chunk.get_list(), chunk.get("cookies"), proxy))
143
145
images = ImageResponse(images, chunk.alt)
144
146
yield self._format_json("content", str(images))
145
147
elif isinstance(chunk, SynthesizeData):
@@ -7,8 +7,7 @@ import uuid
7
7
from io import BytesIO
8
8
import base64
9
9
import asyncio
10
from aiohttp import ClientSession
11
10
from aiohttp import ClientSession, ClientError
12
11
try:
13
12
from PIL.Image import open as open_image, new as new_image
14
13
from PIL.Image import FLIP_LEFT_RIGHT, ROTATE_180, ROTATE_270, ROTATE_90
@@ -20,6 +19,7 @@ from .typing import ImageType, Union, Image, Optional, Cookies
20
19
from .errors import MissingRequirementsError
21
20
from .providers.response import ResponseType
22
21
from .requests.aiohttp import get_connector
22
from . import debug
23
23
24
24
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'webp', 'svg'}
25
25
@@ -277,12 +277,14 @@ def ensure_images_dir():
277
277
if not os.path.exists(images_dir):
278
278
os.makedirs(images_dir)
279
279
280
async def copy_images(images: list[str], cookies: Optional[Cookies] = None, proxy: Optional[str] = None):
280
async def copy_images(
281
images: list[str],
282
cookies: Optional[Cookies] = None,
283
proxy: Optional[str] = None
284
):
281
285
ensure_images_dir()
282
286
async with ClientSession(
283
connector=get_connector(
284
proxy=os.environ.get("G4F_PROXY") if proxy is None else proxy
285
),
287
connector=get_connector(proxy=proxy),
286
288
cookies=cookies
287
289
) as session:
288
290
async def copy_image(image: str) -> str:
@@ -291,10 +293,15 @@ async def copy_images(images: list[str], cookies: Optional[Cookies] = None, prox
291
293
with open(target, "wb") as f:
292
294
f.write(extract_data_uri(image))
293
295
else:
294
async with session.get(image) as response:
295
with open(target, "wb") as f:
296
async for chunk in response.content.iter_chunked(4096):
297
f.write(chunk)
296
try:
297
async with session.get(image) as response:
298
response.raise_for_status()
299
with open(target, "wb") as f:
300
async for chunk in response.content.iter_chunked(4096):
301
f.write(chunk)
302
except ClientError as e:
303
debug.log(f"copy_images failed: {e.__class__.__name__}: {e}")
304
return image
298
305
with open(target, "rb") as f:
299
306
extension = is_accepted_format(f.read(12)).split("/")[-1]
300
307
extension = "jpg" if extension == "jpeg" else extension
@@ -18,7 +18,7 @@ def is_cloudflare(text: str) -> bool:
18
18
return '<div id="cf-please-wait">' in text or "<title>Just a moment...</title>" in text
19
19
20
20
def is_openai(text: str) -> bool:
21
return "<p>Unable to load site</p>" in text
21
return "<p>Unable to load site</p>" in text or 'id="challenge-error-text"' in text
22
22
23
23
async def raise_for_status_async(response: Union[StreamResponse, ClientResponse], message: str = None):
24
24
if response.status in (429, 402):
@@ -27,8 +27,10 @@ async def raise_for_status_async(response: Union[StreamResponse, ClientResponse]
27
27
if response.status == 403 and is_cloudflare(message):
28
28
raise CloudflareError(f"Response {response.status}: Cloudflare detected")
29
29
elif response.status == 403 and is_openai(message):
30
raise ResponseStatusError(f"Response {response.status}: Bot are detected")
30
raise ResponseStatusError(f"Response {response.status}: OpenAI Bot detected")
31
31
elif not response.ok:
32
if "<html>" in message:
33
message = "HTML content"
32
34
raise ResponseStatusError(f"Response {response.status}: {message}")
33
35
34
36
def raise_for_status(response: Union[Response, StreamResponse, ClientResponse, RequestsResponse], message: str = None):