XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet
公开
关注 0 Fork 0 Star 1
返回提交历史

XFEstudio/gpt4free

Support upload image in gui Add image upload to OpenaiChat Add image response to OpenaiChat Improve ChatGPT Plus Support Remove unused requirements

ea8d6b84
Heiner Lohaus <hlohaus@users.noreply.github.com>
提交于

代码差异

20 个文件 +610 -441
Modified etc/testing/test_chat_completion.py +5 -6
@@ -7,10 +7,9 @@ import g4f, asyncio
7 7
8 8 print("create:", end=" ", flush=True)
9 9 for response in g4f.ChatCompletion.create(
10 model=g4f.models.gpt_4_32k_0613,
11 provider=g4f.Provider.Aivvm,
10 model=g4f.models.default,
11 provider=g4f.Provider.Bing,
12 12 messages=[{"role": "user", "content": "write a poem about a tree"}],
13 temperature=0.1,
14 13 stream=True
15 14 ):
16 15 print(response, end="", flush=True)
@@ -18,10 +17,10 @@ print()
18 17
19 18 async def run_async():
20 19 response = await g4f.ChatCompletion.create_async(
21 model=g4f.models.gpt_35_turbo_16k_0613,
22 provider=g4f.Provider.GptGod,
20 model=g4f.models.default,
21 provider=g4f.Provider.Bing,
23 22 messages=[{"role": "user", "content": "hello!"}],
24 23 )
25 24 print("create_async:", response)
26 25
27 # asyncio.run(run_async())
26 asyncio.run(run_async())
Modified g4f/Provider/Bing.py +5 -13
@@ -8,11 +8,10 @@ import time
8 8 from urllib import parse
9 9 from aiohttp import ClientSession, ClientTimeout
10 10
11 from ..typing import AsyncResult, Messages
11 from ..typing import AsyncResult, Messages, ImageType
12 12 from .base_provider import AsyncGeneratorProvider
13 from ..webdriver import get_browser, get_driver_cookies
14 13 from .bing.upload_image import upload_image
15 from .bing.create_images import create_images, format_images_markdown, wait_for_login
14 from .bing.create_images import create_images, format_images_markdown
16 15 from .bing.conversation import Conversation, create_conversation, delete_conversation
17 16
18 17 class Tones():
@@ -34,7 +33,7 @@ class Bing(AsyncGeneratorProvider):
34 33 timeout: int = 900,
35 34 cookies: dict = None,
36 35 tone: str = Tones.balanced,
37 image: str = None,
36 image: ImageType = None,
38 37 web_search: bool = False,
39 38 **kwargs
40 39 ) -> AsyncResult:
@@ -247,7 +246,7 @@ def create_message(
247 246 async def stream_generate(
248 247 prompt: str,
249 248 tone: str,
250 image: str = None,
249 image: ImageType = None,
251 250 context: str = None,
252 251 proxy: str = None,
253 252 cookies: dict = None,
@@ -315,14 +314,7 @@ async def stream_generate(
315 314 result = response['item']['result']
316 315 if result.get('error'):
317 316 if result["value"] == "CaptchaChallenge":
318 driver = get_browser(proxy=proxy)
319 try:
320 wait_for_login(driver)
321 cookies = get_driver_cookies(driver)
322 finally:
323 driver.quit()
324 async for chunk in stream_generate(prompt, tone, image, context, proxy, cookies, web_search, gpt4_turbo, timeout):
325 yield chunk
317 raise Exception(f"{result['value']}: Use other cookies or/and ip address")
326 318 else:
327 319 raise Exception(f"{result['value']}: {result['message']}")
328 320 return
Modified g4f/Provider/base_provider.py +3 -4
@@ -7,7 +7,7 @@ from concurrent.futures import ThreadPoolExecutor
7 7 from abc import abstractmethod
8 8 from inspect import signature, Parameter
9 9 from .helper import get_event_loop, get_cookies, format_prompt
10 from ..typing import CreateResult, AsyncResult, Messages, Union
10 from ..typing import CreateResult, AsyncResult, Messages
11 11 from ..base_provider import BaseProvider
12 12
13 13 if sys.version_info < (3, 10):
@@ -77,8 +77,7 @@ class AbstractProvider(BaseProvider):
77 77 continue
78 78 if args:
79 79 args += ", "
80 args += "\n"
81 args += " " + name
80 args += "\n " + name
82 81 if name != "model" and param.annotation is not Parameter.empty:
83 82 args += f": {get_type_name(param.annotation)}"
84 83 if param.default == "":
@@ -156,7 +155,7 @@ class AsyncGeneratorProvider(AsyncProvider):
156 155 messages,
157 156 stream=False,
158 157 **kwargs
159 )
158 ) if not isinstance(chunk, Exception)
160 159 ])
161 160
162 161 @staticmethod
Modified g4f/Provider/bing/conversation.py +5 -2
@@ -10,7 +10,10 @@ class Conversation():
10 10 async def create_conversation(session: ClientSession, proxy: str = None) -> Conversation:
11 11 url = 'https://www.bing.com/turing/conversation/create?bundleVersion=1.1199.4'
12 12 async with session.get(url, proxy=proxy) as response:
13 data = await response.json()
13 try:
14 data = await response.json()
15 except:
16 raise RuntimeError(f"Response: {await response.text()}")
14 17
15 18 conversationId = data.get('conversationId')
16 19 clientId = data.get('clientId')
@@ -26,7 +29,7 @@ async def list_conversations(session: ClientSession) -> list:
26 29 response = await response.json()
27 30 return response["chats"]
28 31
29 async def delete_conversation(session: ClientSession, conversation: Conversation, proxy: str = None) -> list:
32 async def delete_conversation(session: ClientSession, conversation: Conversation, proxy: str = None) -> bool:
30 33 url = "https://sydney.bing.com/sydney/DeleteSingleConversation"
31 34 json = {
32 35 "conversationId": conversation.conversationId,
Modified g4f/Provider/bing/create_images.py +7 -11
@@ -9,6 +9,7 @@ from ..create_images import CreateImagesProvider
9 9 from ..helper import get_cookies, get_event_loop
10 10 from ...webdriver import WebDriver, get_driver_cookies, get_browser
11 11 from ...base_provider import ProviderType
12 from ...image import format_images_markdown
12 13
13 14 BING_URL = "https://www.bing.com"
14 15
@@ -23,6 +24,7 @@ def wait_for_login(driver: WebDriver, timeout: int = 1200) -> None:
23 24 raise RuntimeError("Timeout error")
24 25 value = driver.get_cookie("_U")
25 26 if value:
27 time.sleep(1)
26 28 return
27 29 time.sleep(0.5)
28 30
@@ -62,7 +64,8 @@ async def create_images(session: ClientSession, prompt: str, proxy: str = None,
62 64 errors = [
63 65 "this prompt is being reviewed",
64 66 "this prompt has been blocked",
65 "we're working hard to offer image creator in more languages"
67 "we're working hard to offer image creator in more languages",
68 "we can't create your images right now"
66 69 ]
67 70 text = (await response.text()).lower()
68 71 for error in errors:
@@ -72,7 +75,7 @@ async def create_images(session: ClientSession, prompt: str, proxy: str = None,
72 75 url = f"{BING_URL}/images/create?q={url_encoded_prompt}&rt=3&FORM=GENCRE"
73 76 async with session.post(url, allow_redirects=False, proxy=proxy, timeout=timeout) as response:
74 77 if response.status != 302:
75 raise RuntimeError(f"Create images failed. Status Code: {response.status}")
78 raise RuntimeError(f"Create images failed. Code: {response.status}")
76 79
77 80 redirect_url = response.headers["Location"].replace("&nfy=1", "")
78 81 redirect_url = f"{BING_URL}{redirect_url}"
@@ -84,10 +87,10 @@ async def create_images(session: ClientSession, prompt: str, proxy: str = None,
84 87 start_time = time.time()
85 88 while True:
86 89 if time.time() - start_time > timeout:
87 raise RuntimeError(f"Timeout error after {timeout} seconds")
90 raise RuntimeError(f"Timeout error after {timeout} sec")
88 91 async with session.get(polling_url) as response:
89 92 if response.status != 200:
90 raise RuntimeError(f"Polling images faild. Status Code: {response.status}")
93 raise RuntimeError(f"Polling images faild. Code: {response.status}")
91 94 text = await response.text()
92 95 if not text:
93 96 await asyncio.sleep(1)
@@ -119,13 +122,6 @@ def read_images(text: str) -> list:
119 122 raise RuntimeError("No images found")
120 123 return images
121 124
122 def format_images_markdown(images: list, prompt: str) -> str:
123 images = [f"[![#{idx+1} {prompt}]({image}?w=200&h=200)]({image})" for idx, image in enumerate(images)]
124 images = "\n".join(images)
125 start_flag = "<!-- generated images start -->\n"
126 end_flag = "<!-- generated images end -->\n"
127 return f"\n{start_flag}{images}\n{end_flag}\n"
128
129 125 async def create_images_markdown(cookies: dict, prompt: str, proxy: str = None) -> str:
130 126 session = create_session(cookies)
131 127 try:
Modified g4f/Provider/bing/upload_image.py +47 -119
@@ -3,70 +3,59 @@ from __future__ import annotations
3 3 import string
4 4 import random
5 5 import json
6 import re
7 import io
8 import base64
9 6 import numpy as np
10 from PIL import Image
7 from ...typing import ImageType
11 8 from aiohttp import ClientSession
9 from ...image import to_image, process_image, to_base64
10
11 image_config = {
12 "maxImagePixels": 360000,
13 "imageCompressionRate": 0.7,
14 "enableFaceBlurDebug": 0,
15 }
12 16
13 17 async def upload_image(
14 18 session: ClientSession,
15 image: str,
19 image: ImageType,
16 20 tone: str,
17 21 proxy: str = None
18 ):
19 try:
20 image_config = {
21 "maxImagePixels": 360000,
22 "imageCompressionRate": 0.7,
23 "enableFaceBlurDebug": 0,
24 }
25 is_data_uri_an_image(image)
26 img_binary_data = extract_data_uri(image)
27 is_accepted_format(img_binary_data)
28 img = Image.open(io.BytesIO(img_binary_data))
29 width, height = img.size
30 max_image_pixels = image_config['maxImagePixels']
31 if max_image_pixels / (width * height) < 1:
32 new_width = int(width * np.sqrt(max_image_pixels / (width * height)))
33 new_height = int(height * np.sqrt(max_image_pixels / (width * height)))
34 else:
35 new_width = width
36 new_height = height
37 try:
38 orientation = get_orientation(img)
39 except Exception:
40 orientation = None
41 new_img = process_image(orientation, img, new_width, new_height)
42 new_img_binary_data = compress_image_to_base64(new_img, image_config['imageCompressionRate'])
43 data, boundary = build_image_upload_api_payload(new_img_binary_data, tone)
44 headers = session.headers.copy()
45 headers["content-type"] = f'multipart/form-data; boundary={boundary}'
46 headers["referer"] = 'https://www.bing.com/search?q=Bing+AI&showconv=1&FORM=hpcodx'
47 headers["origin"] = 'https://www.bing.com'
48 async with session.post("https://www.bing.com/images/kblob", data=data, headers=headers, proxy=proxy) as response:
49 if response.status != 200:
50 raise RuntimeError("Failed to upload image.")
51 image_info = await response.json()
52 if not image_info.get('blobId'):
53 raise RuntimeError("Failed to parse image info.")
54 result = {'bcid': image_info.get('blobId', "")}
55 result['blurredBcid'] = image_info.get('processedBlobId', "")
56 if result['blurredBcid'] != "":
57 result["imageUrl"] = "https://www.bing.com/images/blob?bcid=" + result['blurredBcid']
58 elif result['bcid'] != "":
59 result["imageUrl"] = "https://www.bing.com/images/blob?bcid=" + result['bcid']
60 result['originalImageUrl'] = (
61 "https://www.bing.com/images/blob?bcid="
62 + result['blurredBcid']
63 if image_config["enableFaceBlurDebug"]
64 else "https://www.bing.com/images/blob?bcid="
65 + result['bcid']
66 )
67 return result
68 except Exception as e:
69 raise RuntimeError(f"Upload image failed: {e}")
22 ) -> dict:
23 image = to_image(image)
24 width, height = image.size
25 max_image_pixels = image_config['maxImagePixels']
26 if max_image_pixels / (width * height) < 1:
27 new_width = int(width * np.sqrt(max_image_pixels / (width * height)))
28 new_height = int(height * np.sqrt(max_image_pixels / (width * height)))
29 else:
30 new_width = width
31 new_height = height
32 new_img = process_image(image, new_width, new_height)
33 new_img_binary_data = to_base64(new_img, image_config['imageCompressionRate'])
34 data, boundary = build_image_upload_api_payload(new_img_binary_data, tone)
35 headers = session.headers.copy()
36 headers["content-type"] = f'multipart/form-data; boundary={boundary}'
37 headers["referer"] = 'https://www.bing.com/search?q=Bing+AI&showconv=1&FORM=hpcodx'
38 headers["origin"] = 'https://www.bing.com'
39 async with session.post("https://www.bing.com/images/kblob", data=data, headers=headers, proxy=proxy) as response:
40 if response.status != 200:
41 raise RuntimeError("Failed to upload image.")
42 image_info = await response.json()
43 if not image_info.get('blobId'):
44 raise RuntimeError("Failed to parse image info.")
45 result = {'bcid': image_info.get('blobId', "")}
46 result['blurredBcid'] = image_info.get('processedBlobId', "")
47 if result['blurredBcid'] != "":
48 result["imageUrl"] = "https://www.bing.com/images/blob?bcid=" + result['blurredBcid']
49 elif result['bcid'] != "":
50 result["imageUrl"] = "https://www.bing.com/images/blob?bcid=" + result['bcid']
51 result['originalImageUrl'] = (
52 "https://www.bing.com/images/blob?bcid="
53 + result['blurredBcid']
54 if image_config["enableFaceBlurDebug"]
55 else "https://www.bing.com/images/blob?bcid="
56 + result['bcid']
57 )
58 return result
70 59
71 60
72 61 def build_image_upload_api_payload(image_bin: str, tone: str):
@@ -98,65 +87,4 @@ def build_image_upload_api_payload(image_bin: str, tone: str):
98 87 + boundary
99 88 + "--\r\n"
100 89 )
101 return data, boundary
102
103 def is_data_uri_an_image(data_uri: str):
104 # Check if the data URI starts with 'data:image' and contains an image format (e.g., jpeg, png, gif)
105 if not re.match(r'data:image/(\w+);base64,', data_uri):
106 raise ValueError("Invalid data URI image.")
107 # Extract the image format from the data URI
108 image_format = re.match(r'data:image/(\w+);base64,', data_uri).group(1)
109 # Check if the image format is one of the allowed formats (jpg, jpeg, png, gif)
110 if image_format.lower() not in ['jpeg', 'jpg', 'png', 'gif']:
111 raise ValueError("Invalid image format (from mime file type).")
112
113 def is_accepted_format(binary_data: bytes) -> bool:
114 if binary_data.startswith(b'\xFF\xD8\xFF'):
115 pass # It's a JPEG image
116 elif binary_data.startswith(b'\x89PNG\r\n\x1a\n'):
117 pass # It's a PNG image
118 elif binary_data.startswith(b'GIF87a') or binary_data.startswith(b'GIF89a'):
119 pass # It's a GIF image
120 elif binary_data.startswith(b'\x89JFIF') or binary_data.startswith(b'JFIF\x00'):
121 pass # It's a JPEG image
122 elif binary_data.startswith(b'\xFF\xD8'):
123 pass # It's a JPEG image
124 elif binary_data.startswith(b'RIFF') and binary_data[8:12] == b'WEBP':
125 pass # It's a WebP image
126 else:
127 raise ValueError("Invalid image format (from magic code).")
128
129 def extract_data_uri(data_uri: str) -> bytes:
130 data = data_uri.split(",")[1]
131 data = base64.b64decode(data)
132 return data
133
134 def get_orientation(data: bytes) -> int:
135 if data[:2] != b'\xFF\xD8':
136 raise Exception('NotJpeg')
137 with Image.open(data) as img:
138 exif_data = img._getexif()
139 if exif_data is not None:
140 orientation = exif_data.get(274) # 274 corresponds to the orientation tag in EXIF
141 if orientation is not None:
142 return orientation
143
144 def process_image(orientation: int, img: Image.Image, new_width: int, new_height: int) -> Image.Image:
145 # Initialize the canvas
146 new_img = Image.new("RGB", (new_width, new_height), color="#FFFFFF")
147 if orientation:
148 if orientation > 4:
149 img = img.transpose(Image.FLIP_LEFT_RIGHT)
150 if orientation in [3, 4]:
151 img = img.transpose(Image.ROTATE_180)
152 if orientation in [5, 6]:
153 img = img.transpose(Image.ROTATE_270)
154 if orientation in [7, 8]:
155 img = img.transpose(Image.ROTATE_90)
156 new_img.paste(img, (0, 0))
157 return new_img
158
159 def compress_image_to_base64(image: Image.Image, compression_rate: float) -> str:
160 output_buffer = io.BytesIO()
161 image.save(output_buffer, format="JPEG", quality=int(compression_rate * 100))
162 return base64.b64encode(output_buffer.getvalue()).decode('utf-8')
90 return data, boundary
Modified g4f/Provider/create_images.py +7 -3
@@ -2,6 +2,7 @@ from __future__ import annotations
2 2
3 3 import re
4 4 import asyncio
5 from .. import debug
5 6 from ..typing import CreateResult, Messages
6 7 from ..base_provider import BaseProvider, ProviderType
7 8
@@ -26,12 +27,11 @@ class CreateImagesProvider(BaseProvider):
26 27 self.create_images = create_images
27 28 self.create_images_async = create_async
28 29 self.system_message = system_message
30 self.include_placeholder = include_placeholder
29 31 self.__name__ = provider.__name__
32 self.url = provider.url
30 33 self.working = provider.working
31 34 self.supports_stream = provider.supports_stream
32 self.include_placeholder = include_placeholder
33 if hasattr(provider, "url"):
34 self.url = provider.url
35 35
36 36 def create_completion(
37 37 self,
@@ -54,6 +54,8 @@ class CreateImagesProvider(BaseProvider):
54 54 yield start
55 55 if self.include_placeholder:
56 56 yield placeholder
57 if debug.logging:
58 print(f"Create images with prompt: {prompt}")
57 59 yield from self.create_images(prompt)
58 60 if append:
59 61 yield append
@@ -76,6 +78,8 @@ class CreateImagesProvider(BaseProvider):
76 78 placeholders = []
77 79 for placeholder, prompt in matches:
78 80 if placeholder not in placeholders:
81 if debug.logging:
82 print(f"Create images with prompt: {prompt}")
79 83 results.append(self.create_images_async(prompt))
80 84 placeholders.append(placeholder)
81 85 results = await asyncio.gather(*results)
Modified g4f/Provider/needs_auth/OpenaiChat.py +242 -102
@@ -2,17 +2,18 @@ from __future__ import annotations
2 2
3 3 import uuid, json, asyncio, os
4 4 from py_arkose_generator.arkose import get_values_for_request
5 from asyncstdlib.itertools import tee
6 5 from async_property import async_cached_property
7 6 from selenium.webdriver.common.by import By
8 7 from selenium.webdriver.support.ui import WebDriverWait
9 8 from selenium.webdriver.support import expected_conditions as EC
10 9
11 10 from ..base_provider import AsyncGeneratorProvider
12 from ..helper import get_event_loop, format_prompt, get_cookies
13 from ...webdriver import get_browser
11 from ..helper import format_prompt, get_cookies
12 from ...webdriver import get_browser, get_driver_cookies
14 13 from ...typing import AsyncResult, Messages
15 14 from ...requests import StreamSession
15 from ...image import to_image, to_bytes, ImageType, ImageResponse
16 from ... import debug
16 17
17 18 models = {
18 19 "gpt-3.5": "text-davinci-002-render-sha",
@@ -28,6 +29,7 @@ class OpenaiChat(AsyncGeneratorProvider):
28 29 supports_gpt_35_turbo = True
29 30 supports_gpt_4 = True
30 31 _cookies: dict = {}
32 _default_model: str = None
31 33
32 34 @classmethod
33 35 async def create(
@@ -39,6 +41,7 @@ class OpenaiChat(AsyncGeneratorProvider):
39 41 action: str = "next",
40 42 conversation_id: str = None,
41 43 parent_id: str = None,
44 image: ImageType = None,
42 45 **kwargs
43 46 ) -> Response:
44 47 if prompt:
@@ -53,16 +56,120 @@ class OpenaiChat(AsyncGeneratorProvider):
53 56 action=action,
54 57 conversation_id=conversation_id,
55 58 parent_id=parent_id,
59 image=image,
56 60 response_fields=True,
57 61 **kwargs
58 62 )
59 63 return Response(
60 64 generator,
61 await anext(generator),
62 65 action,
63 66 messages,
64 67 kwargs
65 68 )
69
70 @classmethod
71 async def upload_image(
72 cls,
73 session: StreamSession,
74 headers: dict,
75 image: ImageType
76 ) -> ImageResponse:
77 image = to_image(image)
78 extension = image.format.lower()
79 data_bytes = to_bytes(image)
80 data = {
81 "file_name": f"{image.width}x{image.height}.{extension}",
82 "file_size": len(data_bytes),
83 "use_case": "multimodal"
84 }
85 async with session.post(f"{cls.url}/backend-api/files", json=data, headers=headers) as response:
86 response.raise_for_status()
87 image_data = {
88 **data,
89 **await response.json(),
90 "mime_type": f"image/{extension}",
91 "extension": extension,
92 "height": image.height,
93 "width": image.width
94 }
95 async with session.put(
96 image_data["upload_url"],
97 data=data_bytes,
98 headers={
99 "Content-Type": image_data["mime_type"],
100 "x-ms-blob-type": "BlockBlob"
101 }
102 ) as response:
103 response.raise_for_status()
104 async with session.post(
105 f"{cls.url}/backend-api/files/{image_data['file_id']}/uploaded",
106 json={},
107 headers=headers
108 ) as response:
109 response.raise_for_status()
110 download_url = (await response.json())["download_url"]
111 return ImageResponse(download_url, image_data["file_name"], image_data)
112
113 @classmethod
114 async def get_default_model(cls, session: StreamSession, headers: dict):
115 if cls._default_model:
116 model = cls._default_model
117 else:
118 async with session.get(f"{cls.url}/backend-api/models", headers=headers) as response:
119 data = await response.json()
120 if "categories" in data:
121 model = data["categories"][-1]["default_model"]
122 else:
123 RuntimeError(f"Response: {data}")
124 cls._default_model = model
125 return model
126
127 @classmethod
128 def create_messages(cls, prompt: str, image_response: ImageResponse = None):
129 if not image_response:
130 content = {"content_type": "text", "parts": [prompt]}
131 else:
132 content = {
133 "content_type": "multimodal_text",
134 "parts": [{
135 "asset_pointer": f"file-service://{image_response.get('file_id')}",
136 "height": image_response.get("height"),
137 "size_bytes": image_response.get("file_size"),
138 "width": image_response.get("width"),
139 }, prompt]
140 }
141 messages = [{
142 "id": str(uuid.uuid4()),
143 "author": {"role": "user"},
144 "content": content,
145 }]
146 if image_response:
147 messages[0]["metadata"] = {
148 "attachments": [{
149 "height": image_response.get("height"),
150 "id": image_response.get("file_id"),
151 "mimeType": image_response.get("mime_type"),
152 "name": image_response.get("file_name"),
153 "size": image_response.get("file_size"),
154 "width": image_response.get("width"),
155 }]
156 }
157 return messages
158
159 @classmethod
160 async def get_image_response(cls, session: StreamSession, headers: dict, line: dict):
161 if "parts" in line["message"]["content"]:
162 part = line["message"]["content"]["parts"][0]
163 if "asset_pointer" in part and part["metadata"]:
164 file_id = part["asset_pointer"].split("file-service://", 1)[1]
165 prompt = part["metadata"]["dalle"]["prompt"]
166 async with session.get(
167 f"{cls.url}/backend-api/files/{file_id}/download",
168 headers=headers
169 ) as response:
170 response.raise_for_status()
171 download_url = (await response.json())["download_url"]
172 return ImageResponse(download_url, prompt)
66 173
67 174 @classmethod
68 175 async def create_async_generator(
@@ -78,13 +185,12 @@ class OpenaiChat(AsyncGeneratorProvider):
78 185 action: str = "next",
79 186 conversation_id: str = None,
80 187 parent_id: str = None,
188 image: ImageType = None,
81 189 response_fields: bool = False,
82 190 **kwargs
83 191 ) -> AsyncResult:
84 if not model:
85 model = "gpt-3.5"
86 elif model not in models:
87 raise ValueError(f"Model are not supported: {model}")
192 if model in models:
193 model = models[model]
88 194 if not parent_id:
89 195 parent_id = str(uuid.uuid4())
90 196 if not cookies:
@@ -98,115 +204,131 @@ class OpenaiChat(AsyncGeneratorProvider):
98 204 login_url = os.environ.get("G4F_LOGIN_URL")
99 205 if login_url:
100 206 yield f"Please login: [ChatGPT]({login_url})\n\n"
101 cls._cookies["access_token"] = access_token = await cls.browse_access_token(proxy)
207 access_token, cookies = cls.browse_access_token(proxy)
208 cls._cookies = cookies
102 209 headers = {
103 "Accept": "text/event-stream",
104 210 "Authorization": f"Bearer {access_token}",
105 211 }
106 212 async with StreamSession(
107 213 proxies={"https": proxy},
108 214 impersonate="chrome110",
109 headers=headers,
110 215 timeout=timeout,
111 216 cookies=dict([(name, value) for name, value in cookies.items() if name == "_puid"])
112 217 ) as session:
218 if not model:
219 model = await cls.get_default_model(session, headers)
220 try:
221 image_response = None
222 if image:
223 image_response = await cls.upload_image(session, headers, image)
224 yield image_response
225 except Exception as e:
226 yield e
113 227 end_turn = EndTurn()
114 228 while not end_turn.is_end:
115 229 data = {
116 230 "action": action,
117 "arkose_token": await get_arkose_token(proxy, timeout),
231 "arkose_token": await cls.get_arkose_token(session),
118 232 "conversation_id": conversation_id,
119 233 "parent_message_id": parent_id,
120 "model": models[model],
234 "model": model,
121 235 "history_and_training_disabled": history_disabled and not auto_continue,
122 236 }
123 237 if action != "continue":
124 238 prompt = format_prompt(messages) if not conversation_id else messages[-1]["content"]
125 data["messages"] = [{
126 "id": str(uuid.uuid4()),
127 "author": {"role": "user"},
128 "content": {"content_type": "text", "parts": [prompt]},
129 }]
130 async with session.post(f"{cls.url}/backend-api/conversation", json=data) as response:
239 data["messages"] = cls.create_messages(prompt, image_response)
240 async with session.post(
241 f"{cls.url}/backend-api/conversation",
242 json=data,
243 headers={"Accept": "text/event-stream", **headers}
244 ) as response:
131 245 try:
132 246 response.raise_for_status()
133 247 except:
134 raise RuntimeError(f"Error {response.status_code}: {await response.text()}")
135 last_message = 0
136 async for line in response.iter_lines():
137 if not line.startswith(b"data: "):
138 continue
139 line = line[6:]
140 if line == b"[DONE]":
141 break
142 try:
143 line = json.loads(line)
144 except:
145 continue
146 if "message" not in line:
147 continue
148 if "error" in line and line["error"]:
149 raise RuntimeError(line["error"])
150 if "message_type" not in line["message"]["metadata"]:
151 continue
152 if line["message"]["author"]["role"] != "assistant":
153 continue
154 if line["message"]["metadata"]["message_type"] in ("next", "continue", "variant"):
155 conversation_id = line["conversation_id"]
156 parent_id = line["message"]["id"]
157 if response_fields:
158 response_fields = False
159 yield ResponseFields(conversation_id, parent_id, end_turn)
160 new_message = line["message"]["content"]["parts"][0]
161 yield new_message[last_message:]
162 last_message = len(new_message)
163 if "finish_details" in line["message"]["metadata"]:
164 if line["message"]["metadata"]["finish_details"]["type"] == "stop":
165 end_turn.end()
248 raise RuntimeError(f"Response {response.status_code}: {await response.text()}")
249 try:
250 last_message: int = 0
251 async for line in response.iter_lines():
252 if not line.startswith(b"data: "):
253 continue
254 elif line.startswith(b"data: [DONE]"):
255 break
256 try:
257 line = json.loads(line[6:])
258 except:
259 continue
260 if "message" not in line:
261 continue
262 if "error" in line and line["error"]:
263 raise RuntimeError(line["error"])
264 if "message_type" not in line["message"]["metadata"]:
265 continue
266 try:
267 image_response = await cls.get_image_response(session, headers, line)
268 if image_response:
269 yield image_response
270 except Exception as e:
271 yield e
272 if line["message"]["author"]["role"] != "assistant":
273 continue
274 if line["message"]["metadata"]["message_type"] in ("next", "continue", "variant"):
275 conversation_id = line["conversation_id"]
276 parent_id = line["message"]["id"]
277 if response_fields:
278 response_fields = False
279 yield ResponseFields(conversation_id, parent_id, end_turn)
280 if "parts" in line["message"]["content"]:
281 new_message = line["message"]["content"]["parts"][0]
282 if len(new_message) > last_message:
283 yield new_message[last_message:]
284 last_message = len(new_message)
285 if "finish_details" in line["message"]["metadata"]:
286 if line["message"]["metadata"]["finish_details"]["type"] == "stop":
287 end_turn.end()
288 break
289 except Exception as e:
290 yield e
166 291 if not auto_continue:
167 292 break
168 293 action = "continue"
169 294 await asyncio.sleep(5)
295 if history_disabled:
296 async with session.patch(
297 f"{cls.url}/backend-api/conversation/{conversation_id}",
298 json={"is_visible": False},
299 headers=headers
300 ) as response:
301 response.raise_for_status()
170 302
171 303 @classmethod
172 async def browse_access_token(cls, proxy: str = None) -> str:
173 def browse() -> str:
174 driver = get_browser(proxy=proxy)
175 try:
176 driver.get(f"{cls.url}/")
177 WebDriverWait(driver, 1200).until(
178 EC.presence_of_element_located((By.ID, "prompt-textarea"))
179 )
180 javascript = """
304 def browse_access_token(cls, proxy: str = None) -> tuple[str, dict]:
305 driver = get_browser(proxy=proxy)
306 try:
307 driver.get(f"{cls.url}/")
308 WebDriverWait(driver, 1200).until(
309 EC.presence_of_element_located((By.ID, "prompt-textarea"))
310 )
311 javascript = """
181 312 access_token = (await (await fetch('/api/auth/session')).json())['accessToken'];
182 313 expires = new Date(); expires.setTime(expires.getTime() + 60 * 60 * 24 * 7); // One week
183 314 document.cookie = 'access_token=' + access_token + ';expires=' + expires.toUTCString() + ';path=/';
184 315 return access_token;
185 316 """
186 return driver.execute_script(javascript)
187 finally:
188 driver.quit()
189 loop = get_event_loop()
190 return await loop.run_in_executor(
191 None,
192 browse
193 )
194
195 async def get_arkose_token(proxy: str = None, timeout: int = None) -> str:
196 config = {
197 "pkey": "3D86FBBA-9D22-402A-B512-3420086BA6CC",
198 "surl": "https://tcr9i.chat.openai.com",
199 "headers": {
200 "User-Agent": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36'
201 },
202 "site": "https://chat.openai.com",
203 }
204 args_for_request = get_values_for_request(config)
205 async with StreamSession(
206 proxies={"https": proxy},
207 impersonate="chrome107",
208 timeout=timeout
209 ) as session:
317 return driver.execute_script(javascript), get_driver_cookies(driver)
318 finally:
319 driver.quit()
320
321 @classmethod
322 async def get_arkose_token(cls, session: StreamSession) -> str:
323 config = {
324 "pkey": "3D86FBBA-9D22-402A-B512-3420086BA6CC",
325 "surl": "https://tcr9i.chat.openai.com",
326 "headers": {
327 "User-Agent": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36'
328 },
329 "site": cls.url,
330 }
331 args_for_request = get_values_for_request(config)
210 332 async with session.post(**args_for_request) as response:
211 333 response.raise_for_status()
212 334 decoded_json = await response.json()
@@ -236,23 +358,47 @@ class Response():
236 358 def __init__(
237 359 self,
238 360 generator: AsyncResult,
239 fields: ResponseFields,
240 361 action: str,
241 362 messages: Messages,
242 363 options: dict
243 364 ):
244 self.aiter, self.copy = tee(generator)
245 self.fields = fields
246 self.action = action
365 self._generator = generator
366 self.action: str = action
367 self.is_end: bool = False
368 self._message = None
247 369 self._messages = messages
248 370 self._options = options
371 self._fields = None
372
373 async def generator(self):
374 if self._generator:
375 self._generator = None
376 chunks = []
377 async for chunk in self._generator:
378 if isinstance(chunk, ResponseFields):
379 self._fields = chunk
380 else:
381 yield chunk
382 chunks.append(str(chunk))
383 self._message = "".join(chunks)
384 if not self._fields:
385 raise RuntimeError("Missing response fields")
386 self.is_end = self._fields._end_turn.is_end
249 387
250 388 def __aiter__(self):
251 return self.aiter
389 return self.generator()
252 390
253 391 @async_cached_property
254 392 async def message(self) -> str:
255 return "".join([chunk async for chunk in self.copy])
393 [_ async for _ in self.generator()]
394 return self._message
395
396 async def get_fields(self):
397 [_ async for _ in self.generator()]
398 return {
399 "conversation_id": self._fields.conversation_id,
400 "parent_id": self._fields.message_id,
401 }
256 402
257 403 async def next(self, prompt: str, **kwargs) -> Response:
258 404 return await OpenaiChat.create(
@@ -260,20 +406,19 @@ class Response():
260 406 prompt=prompt,
261 407 messages=await self.messages,
262 408 action="next",
263 conversation_id=self.fields.conversation_id,
264 parent_id=self.fields.message_id,
409 **await self.get_fields(),
265 410 **kwargs
266 411 )
267 412
268 413 async def do_continue(self, **kwargs) -> Response:
269 if self.end_turn:
414 fields = await self.get_fields()
415 if self.is_end:
270 416 raise RuntimeError("Can't continue message. Message already finished.")
271 417 return await OpenaiChat.create(
272 418 **self._options,
273 419 messages=await self.messages,
274 420 action="continue",
275 conversation_id=self.fields.conversation_id,
276 parent_id=self.fields.message_id,
421 **fields,
277 422 **kwargs
278 423 )
279 424
@@ -284,8 +429,7 @@ class Response():
284 429 **self._options,
285 430 messages=self._messages,
286 431 action="variant",
287 conversation_id=self.fields.conversation_id,
288 parent_id=self.fields.message_id,
432 **await self.get_fields(),
289 433 **kwargs
290 434 )
291 435
@@ -295,8 +439,4 @@ class Response():
295 439 messages.append({
296 440 "role": "assistant", "content": await self.message
297 441 })
298 return messages
299
300 @property
301 def end_turn(self):
302 return self.fields._end_turn.is_end
442 return messages
Modified g4f/__init__.py +1 -1
@@ -17,7 +17,7 @@ def get_model_and_provider(model : Union[Model, str],
17 17 ignore_stream: bool = False) -> tuple[str, ProviderType]:
18 18 if debug.version_check:
19 19 debug.version_check = False
20 version.utils.check_pypi_version()
20 version.utils.check_version()
21 21
22 22 if isinstance(provider, str):
23 23 if provider in ProviderUtils.convert:
Modified g4f/base_provider.py +1 -1
@@ -2,7 +2,7 @@ from abc import ABC, abstractmethod
2 2 from .typing import Messages, CreateResult, Union
3 3
4 4 class BaseProvider(ABC):
5 url: str
5 url: str = None
6 6 working: bool = False
7 7 needs_auth: bool = False
8 8 supports_stream: bool = False
Modified g4f/gui/client/css/style.css +26 -8
@@ -217,7 +217,6 @@ body {
217 217 }
218 218
219 219 .message {
220
221 220 width: 100%;
222 221 overflow-wrap: break-word;
223 222 display: flex;
@@ -302,10 +301,14 @@ body {
302 301 line-height: 1.3;
303 302 color: var(--colour-3);
304 303 }
305 .message .content pre {
304 .message .content pre{
306 305 white-space: pre-wrap;
307 306 }
308 307
308 .message .content img{
309 max-width: 400px;
310 }
311
309 312 .message .user i {
310 313 position: absolute;
311 314 bottom: -6px;
@@ -401,13 +404,28 @@ body {
401 404 display: none;
402 405 }
403 406
404 input[type="checkbox"] {
407 #image {
408 display: none;
409 }
410
411 label[for="image"]:has(> input:valid){
412 color: var(--accent);
413 }
414
415 label[for="image"] {
416 cursor: pointer;
417 position: absolute;
418 top: 10px;
419 left: 10px;
420 }
421
422 .buttons input[type="checkbox"] {
405 423 height: 0;
406 424 width: 0;
407 425 display: none;
408 426 }
409 427
410 label {
428 .buttons label {
411 429 cursor: pointer;
412 430 text-indent: -9999px;
413 431 width: 50px;
@@ -424,7 +442,7 @@ label {
424 442 transition: 0.33s;
425 443 }
426 444
427 label:after {
445 .buttons label:after {
428 446 content: "";
429 447 position: absolute;
430 448 top: 50%;
@@ -437,11 +455,11 @@ label:after {
437 455 transition: 0.33s;
438 456 }
439 457
440 input:checked+label {
441 background: var(--blur-border);
458 .buttons input:checked+label {
459 background: var(--accent);
442 460 }
443 461
444 input:checked+label:after {
462 .buttons input:checked+label:after {
445 463 left: calc(100% - 5px - 20px);
446 464 }
447 465
Modified g4f/gui/client/html/index.html +18 -75
@@ -36,7 +36,8 @@
36 36
37 37 #message-input {
38 38 margin-right: 30px;
39 height: 80px;
39 height: 82px;
40 margin-left: 20px;
40 41 }
41 42
42 43 #message-input::-webkit-scrollbar {
@@ -113,6 +114,10 @@
113 114 <div class="box input-box">
114 115 <textarea id="message-input" placeholder="Ask a question" cols="30" rows="10"
115 116 style="white-space: pre-wrap;resize: none;"></textarea>
117 <label for="image" title="Works only with Bing and OpenaiChat">
118 <input type="file" id="image" name="image" accept="image/png, image/gif, image/jpeg" required/>
119 <i class="fa-regular fa-image"></i>
120 </label>
116 121 <div id="send-button">
117 122 <i class="fa-solid fa-paper-plane-top"></i>
118 123 </div>
@@ -120,52 +125,7 @@
120 125 </div>
121 126 <div class="buttons">
122 127 <div class="field">
123 <input type="checkbox" id="switch" />
124 <label for="switch"></label>
125 <span class="about">Web Access</span>
126 </div>
127 <div class="field">
128 <input type="checkbox" id="patch" />
129 <label for="patch" title="Works only with Bing and some other providers"></label>
130 <span class="about">Image Generator</span>
131 </div>
132 <div class="field">
133 <select name="model" id="model">
134 <option value="gpt-3.5-turbo" selected="">gpt-3.5-turbo</option>
135 <option value="gpt-3.5-turbo-0613">gpt-3.5-turbo-0613</option>
136 <option value="gpt-3.5-turbo-16k">gpt-3.5-turbo-16k</option>
137 <option value="gpt-3.5-turbo-16k-0613">gpt-3.5-turbo-16k-0613</option>
138 <option value="gpt-4">gpt-4</option>
139 <option value="gpt-4-0613">gpt-4-0613</option>
140 <option value="gpt-4-32k">gpt-4-32k</option>
141 <option value="gpt-4-32k-0613">gpt-4-32k-0613</option>
142 <option value="palm2">palm2</option>
143 <option value="palm">palm</option>
144 <option value="google">google</option>
145 <option value="google-bard">google-bard</option>
146 <option value="google-palm">google-palm</option>
147 <option value="bard">bard</option>
148 <option value="llama2-7b">llama2-7b</option>
149 <option value="llama2-13b">llama2-13b</option>
150 <option value="llama2-70b">llama2-70b</option>
151 <option value="command-nightly">command-nightly</option>
152 <option value="gpt-neox-20b">gpt-neox-20b</option>
153 <option value="santacoder">santacoder</option>
154 <option value="bloom">bloom</option>
155 <option value="flan-t5-xxl">flan-t5-xxl</option>
156 <option value="code-davinci-002">code-davinci-002</option>
157 <option value="text-ada-001">text-ada-001</option>
158 <option value="text-babbage-001">text-babbage-001</option>
159 <option value="text-curie-001">text-curie-001</option>
160 <option value="text-davinci-002">text-davinci-002</option>
161 <option value="text-davinci-003">text-davinci-003</option>
162 <option value="llama70b-v2-chat">llama70b-v2-chat</option>
163 <option value="llama13b-v2-chat">llama13b-v2-chat</option>
164 <option value="llama7b-v2-chat">llama7b-v2-chat</option>
165 <option value="oasst-sft-1-pythia-12b">oasst-sft-1-pythia-12b</option>
166 <option value="oasst-sft-4-pythia-12b-epoch-3.5">oasst-sft-4-pythia-12b-epoch-3.5</option>
167 <option value="command-light-nightly">command-light-nightly</option>
168 </select>
128 <select name="model" id="model"></select>
169 129 </div>
170 130 <div class="field">
171 131 <select name="jailbreak" id="jailbreak" style="display: none;">
@@ -178,36 +138,19 @@
178 138 <option value="gpt-evil-1.0">evil 1.0</option>
179 139 </select>
180 140 <div class="field">
181 <select name="provider" id="provider">
182 <option value="g4f.Provider.Auto" selected>Set Provider</option>
183 <option value="g4f.Provider.AItianhuSpace">AItianhuSpace</option>
184 <option value="g4f.Provider.ChatgptLogin">ChatgptLogin</option>
185 <option value="g4f.Provider.ChatgptDemo">ChatgptDemo</option>
186 <option value="g4f.Provider.ChatgptDuo">ChatgptDuo</option>
187 <option value="g4f.Provider.Vitalentum">Vitalentum</option>
188 <option value="g4f.Provider.ChatgptAi">ChatgptAi</option>
189 <option value="g4f.Provider.AItianhu">AItianhu</option>
190 <option value="g4f.Provider.ChatBase">ChatBase</option>
191 <option value="g4f.Provider.Liaobots">Liaobots</option>
192 <option value="g4f.Provider.Yqcloud">Yqcloud</option>
193 <option value="g4f.Provider.Myshell">Myshell</option>
194 <option value="g4f.Provider.FreeGpt">FreeGpt</option>
195 <option value="g4f.Provider.Vercel">Vercel</option>
196 <option value="g4f.Provider.Aichat">Aichat</option>
197 <option value="g4f.Provider.GPTalk">GPTalk</option>
198 <option value="g4f.Provider.GptGod">GptGod</option>
199 <option value="g4f.Provider.AiAsk">AiAsk</option>
200 <option value="g4f.Provider.GptGo">GptGo</option>
201 <option value="g4f.Provider.Ylokh">Ylokh</option>
202 <option value="g4f.Provider.Bard">Bard</option>
203 <option value="g4f.Provider.Aibn">Aibn</option>
204 <option value="g4f.Provider.Bing">Bing</option>
205 <option value="g4f.Provider.You">You</option>
206 <option value="g4f.Provider.Llama2">Llama2</option>
207 <option value="g4f.Provider.Aivvm">Aivvm</option>
208 </select>
141 <select name="provider" id="provider"></select>
209 142 </div>
210 143 </div>
144 <div class="field">
145 <input type="checkbox" id="switch" />
146 <label for="switch"></label>
147 <span class="about">Web Access</span>
148 </div>
149 <div class="field">
150 <input type="checkbox" id="patch" />
151 <label for="patch" title="Works only with Bing and some other providers"></label>
152 <span class="about">Image Generator</span>
153 </div>
211 154 </div>
212 155 </div>
213 156 </div>
Modified g4f/gui/client/js/chat.v1.js +51 -45
Modified g4f/gui/server/backend.py +42 -16
Added g4f/image.py +116 -0
Modified g4f/requests.py +17 -14
Modified g4f/typing.py +3 -1
Modified g4f/version.py +14 -12
Modified requirements.txt +0 -4
Modified setup.py +0 -4