返回提交历史
Modified
README.md
+52
-3
Modified
docker/Dockerfile
+1
-2
Modified
g4f/Provider/Bing.py
+1
-3
Modified
g4f/Provider/bing/create_images.py
+4
-5
Modified
g4f/Provider/bing/upload_image.py
+1
-1
Modified
g4f/Provider/needs_auth/OpenaiChat.py
+15
-16
Added
g4f/defaults.py
+13
-0
Modified
g4f/gui/client/js/chat.v1.js
+16
-0
Modified
g4f/image.py
+24
-15
Modified
g4f/requests.py
+8
-16
Modified
g4f/requests_aiohttp.py
+2
-11
Modified
g4f/typing.py
+5
-2
Modified
g4f/webdriver.py
+4
-5
Modified
requirements.txt
+2
-1
XFEstudio/gpt4free
Add aiohttp_socks to requirements Fix preview for uploaded and generated images in gui Improve typing, readme
a28bab93
代码差异
14 个文件
+148
-80
@@ -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:
@@ -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
@@ -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
@@ -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,
@@ -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)
@@ -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()
@@ -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
}
@@ -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 = '';
@@ -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"[})]({images})"
202
images = f"[ if preview else images})]({images})"
204
203
else:
205
images = [f"[})]({image})" for idx, image in enumerate(images)]
204
images = [
205
f"[ 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)
@@ -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')
@@ -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__(
@@ -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
]