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

XFEstudio/gpt4free

Add aiohttp_socks to requirements Fix preview for uploaded and generated images in gui Improve typing, readme

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

代码差异

14 个文件 +148 -80
Modified README.md +52 -3
@@ -99,8 +99,29 @@ or set the api base in your client to: [http://localhost:1337/v1](http://localho
99 99
100 100 ##### Install using pypi:
101 101
102 Install all supported tools / all used packages:
102 103 ```
103 pip install -U "g4f[all]"
104 pip install -U g4f[all]
105 ```
106 Install packages for uploading / generating images:
107 ```
108 pip install -U g4f[image]
109 ```
110 Install the packages required for providers with webdriver:
111 ```
112 pip install -U g4f[webdriver]
113 ```
114 Install the packages required for the OpenaiChat provider:
115 ```
116 pip install -U g4f[openai]
117 ```
118 Install the packages required for the interference api:
119 ```
120 pip install -U g4f[api]
121 ```
122 Install the packages required for the web gui:
123 ```
124 pip install -U g4f[gui]
104 125 ```
105 126
106 127 ##### or:
@@ -202,8 +223,9 @@ docker-compose down
202 223
203 224 ### The Web UI
204 225
205 To use it in the web interface, type the following codes in the command line.
206 ```python3
226 To start the web interface, type the following codes in the command line.
227
228 ```python
207 229 from g4f.gui import run_gui
208 230 run_gui()
209 231 ```
@@ -283,6 +305,33 @@ for message in response:
283 305 print(message)
284 306 ```
285 307
308 ##### Cookies / Access Token
309
310 For generating images with Bing and for the OpenAi Chat you need cookies or a token from your browser session. From Bing you need the "_U" cookie and from OpenAI you need the "access_token". You can pass the cookies / the access token in the create function or you use the `set_cookies` setter:
311
312 ```python
313 from g4f import set_cookies
314
315 set_cookies(".bing", {
316 "_U": "cookie value"
317 })
318 set_cookies("chat.openai.com", {
319 "access_token": "token value"
320 })
321
322 from g4f.gui import run_gui
323 run_gui()
324 ```
325
326 Alternatively, g4f reads the cookies with “browser_cookie3” from your browser
327 or it starts a browser instance with selenium "webdriver" for logging in.
328 If you use the pip package, you have to install “browser_cookie3” or "webdriver" by yourself.
329
330 ```bash
331 pip install browser_cookie3
332 pip install g4f[webdriver]
333 ```
334
286 335 ##### Using Browser
287 336
288 337 Some providers using a browser to bypass the bot protection. They using the selenium webdriver to control the browser. The browser settings and the login data are saved in a custom directory. If the headless mode is enabled, the browser windows are loaded invisibly. For performance reasons, it is recommended to reuse the browser instances and close them yourself at the end:
Modified docker/Dockerfile +1 -2
@@ -86,6 +86,5 @@ RUN pip install --upgrade pip && pip install -r requirements.txt
86 86 # Copy the entire package into the container.
87 87 ADD --chown=$G4F_USER:$G4F_USER g4f $G4F_DIR/g4f
88 88
89
90 89 # Expose ports
91 EXPOSE 8080 1337
90 EXPOSE 8080 1337
Modified g4f/Provider/Bing.py +1 -3
@@ -288,8 +288,6 @@ async def stream_generate(
288 288 ) as session:
289 289 conversation = await create_conversation(session)
290 290 image_request = await upload_image(session, image, tone) if image else None
291 if image_request:
292 yield image_request
293 291
294 292 try:
295 293 async with session.ws_connect(
@@ -327,7 +325,7 @@ async def stream_generate(
327 325 elif message.get('contentType') == "IMAGE":
328 326 prompt = message.get('text')
329 327 try:
330 image_response = ImageResponse(await create_images(session, prompt), prompt)
328 image_response = ImageResponse(await create_images(session, prompt), prompt, {"preview": "{image}?w=200&h=200"})
331 329 except:
332 330 response_txt += f"\nhttps://www.bing.com/images/create?q={parse.quote(prompt)}"
333 331 final = True
Modified g4f/Provider/bing/create_images.py +4 -5
@@ -187,11 +187,11 @@ def get_cookies_from_browser(proxy: str = None) -> dict[str, str]:
187 187
188 188 class CreateImagesBing:
189 189 """A class for creating images using Bing."""
190
190
191 191 def __init__(self, cookies: dict[str, str] = {}, proxy: str = None) -> None:
192 192 self.cookies = cookies
193 193 self.proxy = proxy
194
194
195 195 def create_completion(self, prompt: str) -> Generator[ImageResponse, None, None]:
196 196 """
197 197 Generator for creating imagecompletion based on a prompt.
@@ -229,9 +229,7 @@ class CreateImagesBing:
229 229 proxy = os.environ.get("G4F_PROXY")
230 230 async with create_session(cookies, proxy) as session:
231 231 images = await create_images(session, prompt, self.proxy)
232 return ImageResponse(images, prompt)
233
234 service = CreateImagesBing()
232 return ImageResponse(images, prompt, {"preview": "{image}?w=200&h=200"})
235 233
236 234 def patch_provider(provider: ProviderType) -> CreateImagesProvider:
237 235 """
@@ -243,6 +241,7 @@ def patch_provider(provider: ProviderType) -> CreateImagesProvider:
243 241 Returns:
244 242 CreateImagesProvider: The patched provider with image creation capabilities.
245 243 """
244 service = CreateImagesBing()
246 245 return CreateImagesProvider(
247 246 provider,
248 247 service.create_completion,
Modified g4f/Provider/bing/upload_image.py +1 -1
@@ -149,4 +149,4 @@ def parse_image_response(response: dict) -> ImageRequest:
149 149 if IMAGE_CONFIG["enableFaceBlurDebug"] else
150 150 f"https://www.bing.com/images/blob?bcid={result['bcid']}"
151 151 )
152 return ImageRequest(result["imageUrl"], "", result)
152 return ImageRequest(result)
Modified g4f/Provider/needs_auth/OpenaiChat.py +15 -16
@@ -150,8 +150,8 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
150 150 headers=headers
151 151 ) as response:
152 152 response.raise_for_status()
153 download_url = (await response.json())["download_url"]
154 return ImageRequest(download_url, image_data["file_name"], image_data)
153 image_data["download_url"] = (await response.json())["download_url"]
154 return ImageRequest(image_data)
155 155
156 156 @classmethod
157 157 async def get_default_model(cls, session: StreamSession, headers: dict):
@@ -175,7 +175,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
175 175 return cls.default_model
176 176
177 177 @classmethod
178 def create_messages(cls, prompt: str, image_response: ImageRequest = None):
178 def create_messages(cls, prompt: str, image_request: ImageRequest = None):
179 179 """
180 180 Create a list of messages for the user input
181 181
@@ -187,7 +187,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
187 187 A list of messages with the user input and the image, if any
188 188 """
189 189 # Check if there is an image response
190 if not image_response:
190 if not image_request:
191 191 # Create a content object with the text type and the prompt
192 192 content = {"content_type": "text", "parts": [prompt]}
193 193 else:
@@ -195,10 +195,10 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
195 195 content = {
196 196 "content_type": "multimodal_text",
197 197 "parts": [{
198 "asset_pointer": f"file-service://{image_response.get('file_id')}",
199 "height": image_response.get("height"),
200 "size_bytes": image_response.get("file_size"),
201 "width": image_response.get("width"),
198 "asset_pointer": f"file-service://{image_request.get('file_id')}",
199 "height": image_request.get("height"),
200 "size_bytes": image_request.get("file_size"),
201 "width": image_request.get("width"),
202 202 }, prompt]
203 203 }
204 204 # Create a message object with the user role and the content
@@ -208,16 +208,16 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
208 208 "content": content,
209 209 }]
210 210 # Check if there is an image response
211 if image_response:
211 if image_request:
212 212 # Add the metadata object with the attachments
213 213 messages[0]["metadata"] = {
214 214 "attachments": [{
215 "height": image_response.get("height"),
216 "id": image_response.get("file_id"),
217 "mimeType": image_response.get("mime_type"),
218 "name": image_response.get("file_name"),
219 "size": image_response.get("file_size"),
220 "width": image_response.get("width"),
215 "height": image_request.get("height"),
216 "id": image_request.get("file_id"),
217 "mimeType": image_request.get("mime_type"),
218 "name": image_request.get("file_name"),
219 "size": image_request.get("file_size"),
220 "width": image_request.get("width"),
221 221 }]
222 222 }
223 223 return messages
@@ -352,7 +352,6 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
352 352 image_response = None
353 353 if image:
354 354 image_response = await cls.upload_image(session, headers, image)
355 yield image_response
356 355 except Exception as e:
357 356 yield e
358 357 end_turn = EndTurn()
Added g4f/defaults.py +13 -0
@@ -0,0 +1,13 @@
1 DEFAULT_HEADERS = {
2 'Accept': '*/*',
3 'Accept-Encoding': 'gzip, deflate, br',
4 'Accept-Language': 'en-US',
5 'Connection': 'keep-alive',
6 'Sec-Ch-Ua': '"Not A(Brand";v="99", "Google Chrome";v="121", "Chromium";v="121"',
7 'Sec-Ch-Ua-Mobile': '?0',
8 'Sec-Ch-Ua-Platform': '"Windows"',
9 'Sec-Fetch-Dest': 'empty',
10 'Sec-Fetch-Mode': 'cors',
11 'Sec-Fetch-Site': 'same-site',
12 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36'
13 }
Modified g4f/gui/client/js/chat.v1.js +16 -0
@@ -59,6 +59,10 @@ const handle_ask = async () => {
59 59 </div>
60 60 <div class="content" id="user_${token}">
61 61 ${markdown_render(message)}
62 ${imageInput.dataset.src
63 ? '<img src="' + imageInput.dataset.src + '" alt="Image upload">'
64 : ''
65 }
62 66 </div>
63 67 </div>
64 68 `;
@@ -666,6 +670,18 @@ observer.observe(message_input, { attributes: true });
666 670 })()
667 671 imageInput.addEventListener('click', async (event) => {
668 672 imageInput.value = '';
673 delete imageInput.dataset.src;
674 });
675 imageInput.addEventListener('change', async (event) => {
676 if (imageInput.files.length) {
677 const reader = new FileReader();
678 reader.addEventListener('load', (event) => {
679 imageInput.dataset.src = event.target.result;
680 });
681 reader.readAsDataURL(imageInput.files[0]);
682 } else {
683 delete imageInput.dataset.src;
684 }
669 685 });
670 686 fileInput.addEventListener('click', async (event) => {
671 687 fileInput.value = '';
Modified g4f/image.py +24 -15
@@ -3,14 +3,13 @@ from __future__ import annotations
3 3 import re
4 4 from io import BytesIO
5 5 import base64
6 from .typing import ImageType, Union
6 from .typing import ImageType, Union, Image
7 7
8 8 try:
9 from PIL.Image import open as open_image, new as new_image, Image
9 from PIL.Image import open as open_image, new as new_image
10 10 from PIL.Image import FLIP_LEFT_RIGHT, ROTATE_180, ROTATE_270, ROTATE_90
11 11 has_requirements = True
12 12 except ImportError:
13 Image = type
14 13 has_requirements = False
15 14
16 15 from .errors import MissingRequirementsError
@@ -29,6 +28,9 @@ def to_image(image: ImageType, is_svg: bool = False) -> Image:
29 28 """
30 29 if not has_requirements:
31 30 raise MissingRequirementsError('Install "pillow" package for images')
31 if isinstance(image, str):
32 is_data_uri_an_image(image)
33 image = extract_data_uri(image)
32 34 if is_svg:
33 35 try:
34 36 import cairosvg
@@ -39,9 +41,6 @@ def to_image(image: ImageType, is_svg: bool = False) -> Image:
39 41 buffer = BytesIO()
40 42 cairosvg.svg2png(image, write_to=buffer)
41 43 return open_image(buffer)
42 if isinstance(image, str):
43 is_data_uri_an_image(image)
44 image = extract_data_uri(image)
45 44 if isinstance(image, bytes):
46 45 is_accepted_format(image)
47 46 return open_image(BytesIO(image))
@@ -79,9 +78,9 @@ def is_data_uri_an_image(data_uri: str) -> bool:
79 78 if not re.match(r'data:image/(\w+);base64,', data_uri):
80 79 raise ValueError("Invalid data URI image.")
81 80 # Extract the image format from the data URI
82 image_format = re.match(r'data:image/(\w+);base64,', data_uri).group(1)
81 image_format = re.match(r'data:image/(\w+);base64,', data_uri).group(1).lower()
83 82 # Check if the image format is one of the allowed formats (jpg, jpeg, png, gif)
84 if image_format.lower() not in ALLOWED_EXTENSIONS:
83 if image_format not in ALLOWED_EXTENSIONS and image_format != "svg+xml":
85 84 raise ValueError("Invalid image format (from mime file type).")
86 85
87 86 def is_accepted_format(binary_data: bytes) -> bool:
@@ -187,7 +186,7 @@ def to_base64_jpg(image: Image, compression_rate: float) -> str:
187 186 image.save(output_buffer, format="JPEG", quality=int(compression_rate * 100))
188 187 return base64.b64encode(output_buffer.getvalue()).decode()
189 188
190 def format_images_markdown(images, alt: str, preview: str="{image}?w=200&h=200") -> str:
189 def format_images_markdown(images, alt: str, preview: str = None) -> str:
191 190 """
192 191 Formats the given images as a markdown string.
193 192
@@ -200,9 +199,12 @@ def format_images_markdown(images, alt: str, preview: str="{image}?w=200&h=200")
200 199 str: The formatted markdown string.
201 200 """
202 201 if isinstance(images, str):
203 images = f"[![{alt}]({preview.replace('{image}', images)})]({images})"
202 images = f"[![{alt}]({preview.replace('{image}', images) if preview else images})]({images})"
204 203 else:
205 images = [f"[![#{idx+1} {alt}]({preview.replace('{image}', image)})]({image})" for idx, image in enumerate(images)]
204 images = [
205 f"[![#{idx+1} {alt}]({preview.replace('{image}', image) if preview else image})]({image})"
206 for idx, image in enumerate(images)
207 ]
206 208 images = "\n".join(images)
207 209 start_flag = "<!-- generated images start -->\n"
208 210 end_flag = "<!-- generated images end -->\n"
@@ -223,7 +225,7 @@ def to_bytes(image: Image) -> bytes:
223 225 image.seek(0)
224 226 return bytes_io.getvalue()
225 227
226 class ImageResponse():
228 class ImageResponse:
227 229 def __init__(
228 230 self,
229 231 images: Union[str, list],
@@ -235,10 +237,17 @@ class ImageResponse():
235 237 self.options = options
236 238
237 239 def __str__(self) -> str:
238 return format_images_markdown(self.images, self.alt)
240 return format_images_markdown(self.images, self.alt, self.get("preview"))
239 241
240 242 def get(self, key: str):
241 243 return self.options.get(key)
242 244
243 class ImageRequest(ImageResponse):
244 pass
245 class ImageRequest:
246 def __init__(
247 self,
248 options: dict = {}
249 ):
250 self.options = options
251
252 def get(self, key: str):
253 return self.options.get(key)
Modified g4f/requests.py +8 -16
@@ -7,13 +7,13 @@ try:
7 7 from .requests_curl_cffi import StreamResponse, StreamSession
8 8 has_curl_cffi = True
9 9 except ImportError:
10 Session = type
10 from typing import Type as Session
11 11 from .requests_aiohttp import StreamResponse, StreamSession
12 12 has_curl_cffi = False
13 13
14 14 from .webdriver import WebDriver, WebDriverSession, bypass_cloudflare, get_driver_cookies
15 15 from .errors import MissingRequirementsError
16
16 from .defaults import DEFAULT_HEADERS
17 17
18 18 def get_args_from_browser(url: str, webdriver: WebDriver = None, proxy: str = None, timeout: int = 120) -> dict:
19 19 """
@@ -36,22 +36,14 @@ def get_args_from_browser(url: str, webdriver: WebDriver = None, proxy: str = No
36 36 return {
37 37 'cookies': cookies,
38 38 'headers': {
39 'accept': '*/*',
40 "accept-language": "en-US",
41 "accept-encoding": "gzip, deflate, br",
42 'authority': parse.netloc,
43 'origin': f'{parse.scheme}://{parse.netloc}',
44 'referer': url,
45 "sec-ch-ua": "\"Google Chrome\";v=\"121\", \"Not;A=Brand\";v=\"8\", \"Chromium\";v=\"121\"",
46 "sec-ch-ua-mobile": "?0",
47 "sec-ch-ua-platform": "Windows",
48 'sec-fetch-dest': 'empty',
49 'sec-fetch-mode': 'cors',
50 'sec-fetch-site': 'same-origin',
51 'user-agent': user_agent,
39 **DEFAULT_HEADERS,
40 'Authority': parse.netloc,
41 'Origin': f'{parse.scheme}://{parse.netloc}',
42 'Referer': url,
43 'User-Agent': user_agent,
52 44 },
53 45 }
54
46
55 47 def get_session_from_browser(url: str, webdriver: WebDriver = None, proxy: str = None, timeout: int = 120) -> Session:
56 48 if not has_curl_cffi:
57 49 raise MissingRequirementsError('Install "curl_cffi" package')
Modified g4f/requests_aiohttp.py +2 -11
@@ -4,6 +4,7 @@ from aiohttp import ClientSession, ClientResponse, ClientTimeout
4 4 from typing import AsyncGenerator, Any
5 5
6 6 from .Provider.helper import get_connector
7 from .defaults import DEFAULT_HEADERS
7 8
8 9 class StreamResponse(ClientResponse):
9 10 async def iter_lines(self) -> AsyncGenerator[bytes, None]:
@@ -17,17 +18,7 @@ class StreamSession(ClientSession):
17 18 def __init__(self, headers: dict = {}, timeout: int = None, proxies: dict = {}, impersonate = None, **kwargs):
18 19 if impersonate:
19 20 headers = {
20 'Accept-Encoding': 'gzip, deflate, br',
21 'Accept-Language': 'en-US',
22 'Connection': 'keep-alive',
23 'Sec-Fetch-Dest': 'empty',
24 'Sec-Fetch-Mode': 'cors',
25 'Sec-Fetch-Site': 'same-site',
26 "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',
27 'Accept': '*/*',
28 'sec-ch-ua': '"Google Chrome";v="107", "Chromium";v="107", "Not?A_Brand";v="24"',
29 'sec-ch-ua-mobile': '?0',
30 'sec-ch-ua-platform': '"Windows"',
21 **DEFAULT_HEADERS,
31 22 **headers
32 23 }
33 24 super().__init__(
Modified g4f/typing.py +5 -2
@@ -1,9 +1,10 @@
1 1 import sys
2 2 from typing import Any, AsyncGenerator, Generator, NewType, Tuple, Union, List, Dict, Type, IO, Optional
3
3 4 try:
4 5 from PIL.Image import Image
5 6 except ImportError:
6 Image = type
7 from typing import Type as Image
7 8
8 9 if sys.version_info >= (3, 8):
9 10 from typing import TypedDict
@@ -14,7 +15,7 @@ SHA256 = NewType('sha_256_hash', str)
14 15 CreateResult = Generator[str, None, None]
15 16 AsyncResult = AsyncGenerator[str, None]
16 17 Messages = List[Dict[str, str]]
17 Cookies = List[Dict[str, str]]
18 Cookies = Dict[str, str]
18 19 ImageType = Union[str, bytes, IO, Image, None]
19 20
20 21 __all__ = [
@@ -33,5 +34,7 @@ __all__ = [
33 34 'CreateResult',
34 35 'AsyncResult',
35 36 'Messages',
37 'Cookies',
38 'Image',
36 39 'ImageType'
37 40 ]
Modified g4f/webdriver.py +4 -5
Modified requirements.txt +2 -1