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

XFEstudio/gpt4free

Refactor code with AI Add doctypes to many functions Add file upload for text files Add alternative url to FreeChatgpt Add webp to allowed image types

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

代码差异

19 个文件 +1400 -564
Modified g4f/Provider/Bing.py +126 -104
@@ -15,12 +15,18 @@ from .bing.upload_image import upload_image
15 15 from .bing.create_images import create_images
16 16 from .bing.conversation import Conversation, create_conversation, delete_conversation
17 17
18 class Tones():
18 class Tones:
19 """
20 Defines the different tone options for the Bing provider.
21 """
19 22 creative = "Creative"
20 23 balanced = "Balanced"
21 24 precise = "Precise"
22 25
23 26 class Bing(AsyncGeneratorProvider):
27 """
28 Bing provider for generating responses using the Bing API.
29 """
24 30 url = "https://bing.com/chat"
25 31 working = True
26 32 supports_message_history = True
@@ -38,6 +44,19 @@ class Bing(AsyncGeneratorProvider):
38 44 web_search: bool = False,
39 45 **kwargs
40 46 ) -> AsyncResult:
47 """
48 Creates an asynchronous generator for producing responses from Bing.
49
50 :param model: The model to use.
51 :param messages: Messages to process.
52 :param proxy: Proxy to use for requests.
53 :param timeout: Timeout for requests.
54 :param cookies: Cookies for the session.
55 :param tone: The tone of the response.
56 :param image: The image type to be used.
57 :param web_search: Flag to enable or disable web search.
58 :return: An asynchronous result object.
59 """
41 60 if len(messages) < 2:
42 61 prompt = messages[0]["content"]
43 62 context = None
@@ -56,65 +75,48 @@ class Bing(AsyncGeneratorProvider):
56 75
57 76 return stream_generate(prompt, tone, image, context, proxy, cookies, web_search, gpt4_turbo, timeout)
58 77
59 def create_context(messages: Messages):
78 def create_context(messages: Messages) -> str:
79 """
80 Creates a context string from a list of messages.
81
82 :param messages: A list of message dictionaries.
83 :return: A string representing the context created from the messages.
84 """
60 85 return "".join(
61 f"[{message['role']}]" + ("(#message)" if message['role']!="system" else "(#additional_instructions)") + f"\n{message['content']}\n\n"
86 f"[{message['role']}]" + ("(#message)" if message['role'] != "system" else "(#additional_instructions)") + f"\n{message['content']}\n\n"
62 87 for message in messages
63 88 )
64 89
65 90 class Defaults:
91 """
92 Default settings and configurations for the Bing provider.
93 """
66 94 delimiter = "\x1e"
67 95 ip_address = f"13.{random.randint(104, 107)}.{random.randint(0, 255)}.{random.randint(0, 255)}"
68 96
97 # List of allowed message types for Bing responses
69 98 allowedMessageTypes = [
70 "ActionRequest",
71 "Chat",
72 "Context",
73 # "Disengaged", unwanted
74 "Progress",
75 # "AdsQuery", unwanted
76 "SemanticSerp",
77 "GenerateContentQuery",
78 "SearchQuery",
79 # The following message types should not be added so that it does not flood with
80 # useless messages (such as "Analyzing images" or "Searching the web") while it's retrieving the AI response
81 # "InternalSearchQuery",
82 # "InternalSearchResult",
83 "RenderCardRequest",
84 # "RenderContentRequest"
99 "ActionRequest", "Chat", "Context", "Progress", "SemanticSerp",
100 "GenerateContentQuery", "SearchQuery", "RenderCardRequest"
85 101 ]
86 102
87 103 sliceIds = [
88 'abv2',
89 'srdicton',
90 'convcssclick',
91 'stylewv2',
92 'contctxp2tf',
93 '802fluxv1pc_a',
94 '806log2sphs0',
95 '727savemem',
96 '277teditgnds0',
97 '207hlthgrds0',
104 'abv2', 'srdicton', 'convcssclick', 'stylewv2', 'contctxp2tf',
105 '802fluxv1pc_a', '806log2sphs0', '727savemem', '277teditgnds0', '207hlthgrds0'
98 106 ]
99 107
108 # Default location settings
100 109 location = {
101 "locale": "en-US",
102 "market": "en-US",
103 "region": "US",
104 "locationHints": [
105 {
106 "country": "United States",
107 "state": "California",
108 "city": "Los Angeles",
109 "timezoneoffset": 8,
110 "countryConfidence": 8,
111 "Center": {"Latitude": 34.0536909, "Longitude": -118.242766},
112 "RegionType": 2,
113 "SourceType": 1,
114 }
115 ],
110 "locale": "en-US", "market": "en-US", "region": "US",
111 "locationHints": [{
112 "country": "United States", "state": "California", "city": "Los Angeles",
113 "timezoneoffset": 8, "countryConfidence": 8,
114 "Center": {"Latitude": 34.0536909, "Longitude": -118.242766},
115 "RegionType": 2, "SourceType": 1
116 }],
116 117 }
117 118
119 # Default headers for requests
118 120 headers = {
119 121 'accept': '*/*',
120 122 'accept-language': 'en-US,en;q=0.9',
@@ -139,23 +141,13 @@ class Defaults:
139 141 }
140 142
141 143 optionsSets = [
142 'nlu_direct_response_filter',
143 'deepleo',
144 'disable_emoji_spoken_text',
145 'responsible_ai_policy_235',
146 'enablemm',
147 'iyxapbing',
148 'iycapbing',
149 'gencontentv3',
150 'fluxsrtrunc',
151 'fluxtrunc',
152 'fluxv1',
153 'rai278',
154 'replaceurl',
155 'eredirecturl',
156 'nojbfedge'
144 'nlu_direct_response_filter', 'deepleo', 'disable_emoji_spoken_text',
145 'responsible_ai_policy_235', 'enablemm', 'iyxapbing', 'iycapbing',
146 'gencontentv3', 'fluxsrtrunc', 'fluxtrunc', 'fluxv1', 'rai278',
147 'replaceurl', 'eredirecturl', 'nojbfedge'
157 148 ]
158 149
150 # Default cookies
159 151 cookies = {
160 152 'SRCHD' : 'AF=NOFORM',
161 153 'PPLState' : '1',
@@ -166,6 +158,12 @@ class Defaults:
166 158 }
167 159
168 160 def format_message(msg: dict) -> str:
161 """
162 Formats a message dictionary into a JSON string with a delimiter.
163
164 :param msg: The message dictionary to format.
165 :return: A formatted string representation of the message.
166 """
169 167 return json.dumps(msg, ensure_ascii=False) + Defaults.delimiter
170 168
171 169 def create_message(
@@ -177,7 +175,20 @@ def create_message(
177 175 web_search: bool = False,
178 176 gpt4_turbo: bool = False
179 177 ) -> str:
178 """
179 Creates a message for the Bing API with specified parameters.
180
181 :param conversation: The current conversation object.
182 :param prompt: The user's input prompt.
183 :param tone: The desired tone for the response.
184 :param context: Additional context for the prompt.
185 :param image_response: The response if an image is involved.
186 :param web_search: Flag to enable web search.
187 :param gpt4_turbo: Flag to enable GPT-4 Turbo.
188 :return: A formatted string message for the Bing API.
189 """
180 190 options_sets = Defaults.optionsSets
191 # Append tone-specific options
181 192 if tone == Tones.creative:
182 193 options_sets.append("h3imaginative")
183 194 elif tone == Tones.precise:
@@ -186,54 +197,49 @@ def create_message(
186 197 options_sets.append("galileo")
187 198 else:
188 199 options_sets.append("harmonyv3")
189
200
201 # Additional configurations based on parameters
190 202 if not web_search:
191 203 options_sets.append("nosearchall")
192
193 204 if gpt4_turbo:
194 205 options_sets.append("dlgpt4t")
195
206
196 207 request_id = str(uuid.uuid4())
197 208 struct = {
198 'arguments': [
199 {
200 'source': 'cib',
201 'optionsSets': options_sets,
202 'allowedMessageTypes': Defaults.allowedMessageTypes,
203 'sliceIds': Defaults.sliceIds,
204 'traceId': os.urandom(16).hex(),
205 'isStartOfSession': True,
209 'arguments': [{
210 'source': 'cib', 'optionsSets': options_sets,
211 'allowedMessageTypes': Defaults.allowedMessageTypes,
212 'sliceIds': Defaults.sliceIds,
213 'traceId': os.urandom(16).hex(), 'isStartOfSession': True,
214 'requestId': request_id,
215 'message': {
216 **Defaults.location,
217 'author': 'user',
218 'inputMethod': 'Keyboard',
219 'text': prompt,
220 'messageType': 'Chat',
206 221 'requestId': request_id,
207 'message': {**Defaults.location, **{
208 'author': 'user',
209 'inputMethod': 'Keyboard',
210 'text': prompt,
211 'messageType': 'Chat',
212 'requestId': request_id,
213 'messageId': request_id,
214 }},
215 "verbosity": "verbose",
216 "scenario": "SERP",
217 "plugins":[
218 {"id":"c310c353-b9f0-4d76-ab0d-1dd5e979cf68", "category": 1}
219 ] if web_search else [],
220 'tone': tone,
221 'spokenTextMode': 'None',
222 'conversationId': conversation.conversationId,
223 'participant': {
224 'id': conversation.clientId
225 },
226 }
227 ],
222 'messageId': request_id
223 },
224 "verbosity": "verbose",
225 "scenario": "SERP",
226 "plugins": [{"id": "c310c353-b9f0-4d76-ab0d-1dd5e979cf68", "category": 1}] if web_search else [],
227 'tone': tone,
228 'spokenTextMode': 'None',
229 'conversationId': conversation.conversationId,
230 'participant': {'id': conversation.clientId},
231 }],
228 232 'invocationId': '1',
229 233 'target': 'chat',
230 234 'type': 4
231 235 }
232 if image_response.get('imageUrl') and image_response.get('originalImageUrl'):
236
237 if image_response and image_response.get('imageUrl') and image_response.get('originalImageUrl'):
233 238 struct['arguments'][0]['message']['originalImageUrl'] = image_response.get('originalImageUrl')
234 239 struct['arguments'][0]['message']['imageUrl'] = image_response.get('imageUrl')
235 240 struct['arguments'][0]['experienceType'] = None
236 241 struct['arguments'][0]['attachedFileInfo'] = {"fileName": None, "fileType": None}
242
237 243 if context:
238 244 struct['arguments'][0]['previousMessages'] = [{
239 245 "author": "user",
@@ -242,30 +248,46 @@ def create_message(
242 248 "messageType": "Context",
243 249 "messageId": "discover-web--page-ping-mriduna-----"
244 250 }]
251
245 252 return format_message(struct)
246 253
247 254 async def stream_generate(
248 prompt: str,
249 tone: str,
250 image: ImageType = None,
251 context: str = None,
252 proxy: str = None,
253 cookies: dict = None,
254 web_search: bool = False,
255 gpt4_turbo: bool = False,
256 timeout: int = 900
257 ):
255 prompt: str,
256 tone: str,
257 image: ImageType = None,
258 context: str = None,
259 proxy: str = None,
260 cookies: dict = None,
261 web_search: bool = False,
262 gpt4_turbo: bool = False,
263 timeout: int = 900
264 ):
265 """
266 Asynchronously streams generated responses from the Bing API.
267
268 :param prompt: The user's input prompt.
269 :param tone: The desired tone for the response.
270 :param image: The image type involved in the response.
271 :param context: Additional context for the prompt.
272 :param proxy: Proxy settings for the request.
273 :param cookies: Cookies for the session.
274 :param web_search: Flag to enable web search.
275 :param gpt4_turbo: Flag to enable GPT-4 Turbo.
276 :param timeout: Timeout for the request.
277 :return: An asynchronous generator yielding responses.
278 """
258 279 headers = Defaults.headers
259 280 if cookies:
260 281 headers["Cookie"] = "; ".join(f"{k}={v}" for k, v in cookies.items())
282
261 283 async with ClientSession(
262 timeout=ClientTimeout(total=timeout),
263 headers=headers
284 timeout=ClientTimeout(total=timeout), headers=headers
264 285 ) as session:
265 286 conversation = await create_conversation(session, proxy)
266 287 image_response = await upload_image(session, image, tone, proxy) if image else None
267 288 if image_response:
268 289 yield image_response
290
269 291 try:
270 292 async with session.ws_connect(
271 293 'wss://sydney.bing.com/sydney/ChatHub',
@@ -289,7 +311,7 @@ async def stream_generate(
289 311 if obj is None or not obj:
290 312 continue
291 313 response = json.loads(obj)
292 if response.get('type') == 1 and response['arguments'][0].get('messages'):
314 if response and response.get('type') == 1 and response['arguments'][0].get('messages'):
293 315 message = response['arguments'][0]['messages'][0]
294 316 image_response = None
295 317 if (message['contentOrigin'] != 'Apology'):
Modified g4f/Provider/FreeChatgpt.py +10 -5
@@ -1,16 +1,20 @@
1 1 from __future__ import annotations
2 2
3 import json
3 import json, random
4 4 from aiohttp import ClientSession
5 5
6 6 from ..typing import AsyncResult, Messages
7 7 from .base_provider import AsyncGeneratorProvider
8 8
9
10 9 models = {
11 "claude-v2": "claude-2.0",
12 "gemini-pro": "google-gemini-pro"
10 "claude-v2": "claude-2.0",
11 "claude-v2.1":"claude-2.1",
12 "gemini-pro": "google-gemini-pro"
13 13 }
14 urls = [
15 "https://free.chatgpt.org.uk",
16 "https://ai.chatgpt.org.uk"
17 ]
14 18
15 19 class FreeChatgpt(AsyncGeneratorProvider):
16 20 url = "https://free.chatgpt.org.uk"
@@ -31,6 +35,7 @@ class FreeChatgpt(AsyncGeneratorProvider):
31 35 model = models[model]
32 36 elif not model:
33 37 model = "gpt-3.5-turbo"
38 url = random.choice(urls)
34 39 headers = {
35 40 "Accept": "application/json, text/event-stream",
36 41 "Content-Type":"application/json",
@@ -55,7 +60,7 @@ class FreeChatgpt(AsyncGeneratorProvider):
55 60 "top_p":1,
56 61 **kwargs
57 62 }
58 async with session.post(f'{cls.url}/api/openai/v1/chat/completions', json=data, proxy=proxy) as response:
63 async with session.post(f'{url}/api/openai/v1/chat/completions', json=data, proxy=proxy) as response:
59 64 response.raise_for_status()
60 65 started = False
61 66 async for line in response.content:
Modified g4f/Provider/base_provider.py +62 -65
@@ -1,28 +1,29 @@
1 1 from __future__ import annotations
2
3 2 import sys
4 3 import asyncio
5 from asyncio import AbstractEventLoop
4 from asyncio import AbstractEventLoop
6 5 from concurrent.futures import ThreadPoolExecutor
7 from abc import abstractmethod
8 from inspect import signature, Parameter
9 from .helper import get_event_loop, get_cookies, format_prompt
10 from ..typing import CreateResult, AsyncResult, Messages
11 from ..base_provider import BaseProvider
6 from abc import abstractmethod
7 from inspect import signature, Parameter
8 from .helper import get_event_loop, get_cookies, format_prompt
9 from ..typing import CreateResult, AsyncResult, Messages
10 from ..base_provider import BaseProvider
12 11
13 12 if sys.version_info < (3, 10):
14 13 NoneType = type(None)
15 14 else:
16 15 from types import NoneType
17 16
18 # Change event loop policy on windows for curl_cffi
17 # Set Windows event loop policy for better compatibility with asyncio and curl_cffi
19 18 if sys.platform == 'win32':
20 if isinstance(
21 asyncio.get_event_loop_policy(), asyncio.WindowsProactorEventLoopPolicy
22 ):
19 if isinstance(asyncio.get_event_loop_policy(), asyncio.WindowsProactorEventLoopPolicy):
23 20 asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
24 21
25 22 class AbstractProvider(BaseProvider):
23 """
24 Abstract class for providing asynchronous functionality to derived classes.
25 """
26
26 27 @classmethod
27 28 async def create_async(
28 29 cls,
@@ -33,62 +34,50 @@ class AbstractProvider(BaseProvider):
33 34 executor: ThreadPoolExecutor = None,
34 35 **kwargs
35 36 ) -> str:
36 if not loop:
37 loop = get_event_loop()
37 """
38 Asynchronously creates a result based on the given model and messages.
39 """
40 loop = loop or get_event_loop()
38 41
39 42 def create_func() -> str:
40 return "".join(cls.create_completion(
41 model,
42 messages,
43 False,
44 **kwargs
45 ))
43 return "".join(cls.create_completion(model, messages, False, **kwargs))
46 44
47 45 return await asyncio.wait_for(
48 loop.run_in_executor(
49 executor,
50 create_func
51 ),
46 loop.run_in_executor(executor, create_func),
52 47 timeout=kwargs.get("timeout", 0)
53 48 )
54 49
55 50 @classmethod
56 51 @property
57 52 def params(cls) -> str:
58 if issubclass(cls, AsyncGeneratorProvider):
59 sig = signature(cls.create_async_generator)
60 elif issubclass(cls, AsyncProvider):
61 sig = signature(cls.create_async)
62 else:
63 sig = signature(cls.create_completion)
53 """
54 Returns the parameters supported by the provider.
55 """
56 sig = signature(
57 cls.create_async_generator if issubclass(cls, AsyncGeneratorProvider) else
58 cls.create_async if issubclass(cls, AsyncProvider) else
59 cls.create_completion
60 )
64 61
65 62 def get_type_name(annotation: type) -> str:
66 if hasattr(annotation, "__name__"):
67 annotation = annotation.__name__
68 elif isinstance(annotation, NoneType):
69 annotation = "None"
70 return str(annotation)
71
63 return annotation.__name__ if hasattr(annotation, "__name__") else str(annotation)
64
72 65 args = ""
73 66 for name, param in sig.parameters.items():
74 if name in ("self", "kwargs"):
67 if name in ("self", "kwargs") or (name == "stream" and not cls.supports_stream):
75 68 continue
76 if name == "stream" and not cls.supports_stream:
77 continue
78 if args:
79 args += ", "
80 args += "\n " + name
81 if name != "model" and param.annotation is not Parameter.empty:
82 args += f": {get_type_name(param.annotation)}"
83 if param.default == "":
84 args += ' = ""'
85 elif param.default is not Parameter.empty:
86 args += f" = {param.default}"
69 args += f"\n {name}"
70 args += f": {get_type_name(param.annotation)}" if param.annotation is not Parameter.empty else ""
71 args += f' = "{param.default}"' if param.default == "" else f" = {param.default}" if param.default is not Parameter.empty else ""
87 72
88 73 return f"g4f.Provider.{cls.__name__} supports: ({args}\n)"
89 74
90 75
91 76 class AsyncProvider(AbstractProvider):
77 """
78 Provides asynchronous functionality for creating completions.
79 """
80
92 81 @classmethod
93 82 def create_completion(
94 83 cls,
@@ -99,8 +88,10 @@ class AsyncProvider(AbstractProvider):
99 88 loop: AbstractEventLoop = None,
100 89 **kwargs
101 90 ) -> CreateResult:
102 if not loop:
103 loop = get_event_loop()
91 """
92 Creates a completion result synchronously.
93 """
94 loop = loop or get_event_loop()
104 95 coro = cls.create_async(model, messages, **kwargs)
105 96 yield loop.run_until_complete(coro)
106 97
@@ -111,10 +102,16 @@ class AsyncProvider(AbstractProvider):
111 102 messages: Messages,
112 103 **kwargs
113 104 ) -> str:
105 """
106 Abstract method for creating asynchronous results.
107 """
114 108 raise NotImplementedError()
115 109
116 110
117 111 class AsyncGeneratorProvider(AsyncProvider):
112 """
113 Provides asynchronous generator functionality for streaming results.
114 """
118 115 supports_stream = True
119 116
120 117 @classmethod
@@ -127,15 +124,13 @@ class AsyncGeneratorProvider(AsyncProvider):
127 124 loop: AbstractEventLoop = None,
128 125 **kwargs
129 126 ) -> CreateResult:
130 if not loop:
131 loop = get_event_loop()
132 generator = cls.create_async_generator(
133 model,
134 messages,
135 stream=stream,
136 **kwargs
137 )
127 """
128 Creates a streaming completion result synchronously.
129 """
130 loop = loop or get_event_loop()
131 generator = cls.create_async_generator(model, messages, stream=stream, **kwargs)
138 132 gen = generator.__aiter__()
133
139 134 while True:
140 135 try:
141 136 yield loop.run_until_complete(gen.__anext__())
@@ -149,21 +144,23 @@ class AsyncGeneratorProvider(AsyncProvider):
149 144 messages: Messages,
150 145 **kwargs
151 146 ) -> str:
147 """
148 Asynchronously creates a result from a generator.
149 """
152 150 return "".join([
153 chunk async for chunk in cls.create_async_generator(
154 model,
155 messages,
156 stream=False,
157 **kwargs
158 ) if not isinstance(chunk, Exception)
151 chunk async for chunk in cls.create_async_generator(model, messages, stream=False, **kwargs)
152 if not isinstance(chunk, Exception)
159 153 ])
160 154
161 155 @staticmethod
162 156 @abstractmethod
163 def create_async_generator(
157 async def create_async_generator(
164 158 model: str,
165 159 messages: Messages,
166 160 stream: bool = True,
167 161 **kwargs
168 162 ) -> AsyncResult:
169 raise NotImplementedError()
163 """
164 Abstract method for creating an asynchronous generator.
165 """
166 raise NotImplementedError()
Modified g4f/Provider/bing/conversation.py +42 -2
@@ -1,13 +1,33 @@
1 1 from aiohttp import ClientSession
2 2
3
4 class Conversation():
3 class Conversation:
4 """
5 Represents a conversation with specific attributes.
6 """
5 7 def __init__(self, conversationId: str, clientId: str, conversationSignature: str) -> None:
8 """
9 Initialize a new conversation instance.
10
11 Args:
12 conversationId (str): Unique identifier for the conversation.
13 clientId (str): Client identifier.
14 conversationSignature (str): Signature for the conversation.
15 """
6 16 self.conversationId = conversationId
7 17 self.clientId = clientId
8 18 self.conversationSignature = conversationSignature
9 19
10 20 async def create_conversation(session: ClientSession, proxy: str = None) -> Conversation:
21 """
22 Create a new conversation asynchronously.
23
24 Args:
25 session (ClientSession): An instance of aiohttp's ClientSession.
26 proxy (str, optional): Proxy URL. Defaults to None.
27
28 Returns:
29 Conversation: An instance representing the created conversation.
30 """
11 31 url = 'https://www.bing.com/turing/conversation/create?bundleVersion=1.1199.4'
12 32 async with session.get(url, proxy=proxy) as response:
13 33 try:
@@ -24,12 +44,32 @@ async def create_conversation(session: ClientSession, proxy: str = None) -> Conv
24 44 return Conversation(conversationId, clientId, conversationSignature)
25 45
26 46 async def list_conversations(session: ClientSession) -> list:
47 """
48 List all conversations asynchronously.
49
50 Args:
51 session (ClientSession): An instance of aiohttp's ClientSession.
52
53 Returns:
54 list: A list of conversations.
55 """
27 56 url = "https://www.bing.com/turing/conversation/chats"
28 57 async with session.get(url) as response:
29 58 response = await response.json()
30 59 return response["chats"]
31 60
32 61 async def delete_conversation(session: ClientSession, conversation: Conversation, proxy: str = None) -> bool:
62 """
63 Delete a conversation asynchronously.
64
65 Args:
66 session (ClientSession): An instance of aiohttp's ClientSession.
67 conversation (Conversation): The conversation to delete.
68 proxy (str, optional): Proxy URL. Defaults to None.
69
70 Returns:
71 bool: True if deletion was successful, False otherwise.
72 """
33 73 url = "https://sydney.bing.com/sydney/DeleteSingleConversation"
34 74 json = {
35 75 "conversationId": conversation.conversationId,
Modified g4f/Provider/bing/create_images.py +157 -67
@@ -1,9 +1,16 @@
1 """
2 This module provides functionalities for creating and managing images using Bing's service.
3 It includes functions for user login, session creation, image creation, and processing.
4 """
5
1 6 import asyncio
2 import time, json, os
7 import time
8 import json
9 import os
3 10 from aiohttp import ClientSession
4 11 from bs4 import BeautifulSoup
5 12 from urllib.parse import quote
6 from typing import Generator
13 from typing import Generator, List, Dict
7 14
8 15 from ..create_images import CreateImagesProvider
9 16 from ..helper import get_cookies, get_event_loop
@@ -12,23 +19,47 @@ from ...base_provider import ProviderType
12 19 from ...image import format_images_markdown
13 20
14 21 BING_URL = "https://www.bing.com"
22 TIMEOUT_LOGIN = 1200
23 TIMEOUT_IMAGE_CREATION = 300
24 ERRORS = [
25 "this prompt is being reviewed",
26 "this prompt has been blocked",
27 "we're working hard to offer image creator in more languages",
28 "we can't create your images right now"
29 ]
30 BAD_IMAGES = [
31 "https://r.bing.com/rp/in-2zU3AJUdkgFe7ZKv19yPBHVs.png",
32 "https://r.bing.com/rp/TX9QuO3WzcCJz1uaaSwQAz39Kb0.jpg",
33 ]
34
35 def wait_for_login(driver: WebDriver, timeout: int = TIMEOUT_LOGIN) -> None:
36 """
37 Waits for the user to log in within a given timeout period.
15 38
16 def wait_for_login(driver: WebDriver, timeout: int = 1200) -> None:
39 Args:
40 driver (WebDriver): Webdriver for browser automation.
41 timeout (int): Maximum waiting time in seconds.
42
43 Raises:
44 RuntimeError: If the login process exceeds the timeout.
45 """
17 46 driver.get(f"{BING_URL}/")
18 value = driver.get_cookie("_U")
19 if value:
20 return
21 47 start_time = time.time()
22 while True:
48 while not driver.get_cookie("_U"):
23 49 if time.time() - start_time > timeout:
24 50 raise RuntimeError("Timeout error")
25 value = driver.get_cookie("_U")
26 if value:
27 time.sleep(1)
28 return
29 51 time.sleep(0.5)
30 52
31 def create_session(cookies: dict) -> ClientSession:
53 def create_session(cookies: Dict[str, str]) -> ClientSession:
54 """
55 Creates a new client session with specified cookies and headers.
56
57 Args:
58 cookies (Dict[str, str]): Cookies to be used for the session.
59
60 Returns:
61 ClientSession: The created client session.
62 """
32 63 headers = {
33 64 "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
34 65 "accept-encoding": "gzip, deflate, br",
@@ -47,28 +78,32 @@ def create_session(cookies: dict) -> ClientSession:
47 78 "upgrade-insecure-requests": "1",
48 79 }
49 80 if cookies:
50 headers["cookie"] = "; ".join(f"{k}={v}" for k, v in cookies.items())
81 headers["Cookie"] = "; ".join(f"{k}={v}" for k, v in cookies.items())
51 82 return ClientSession(headers=headers)
52 83
53 async def create_images(session: ClientSession, prompt: str, proxy: str = None, timeout: int = 300) -> list:
54 url_encoded_prompt = quote(prompt)
84 async def create_images(session: ClientSession, prompt: str, proxy: str = None, timeout: int = TIMEOUT_IMAGE_CREATION) -> List[str]:
85 """
86 Creates images based on a given prompt using Bing's service.
87
88 Args:
89 session (ClientSession): Active client session.
90 prompt (str): Prompt to generate images.
91 proxy (str, optional): Proxy configuration.
92 timeout (int): Timeout for the request.
93
94 Returns:
95 List[str]: A list of URLs to the created images.
96
97 Raises:
98 RuntimeError: If image creation fails or times out.
99 """
100 url_encoded_prompt = quote(prompt)
55 101 payload = f"q={url_encoded_prompt}&rt=4&FORM=GENCRE"
56 102 url = f"{BING_URL}/images/create?q={url_encoded_prompt}&rt=4&FORM=GENCRE"
57 async with session.post(
58 url,
59 allow_redirects=False,
60 data=payload,
61 timeout=timeout,
62 ) as response:
103 async with session.post(url, allow_redirects=False, data=payload, timeout=timeout) as response:
63 104 response.raise_for_status()
64 errors = [
65 "this prompt is being reviewed",
66 "this prompt has been blocked",
67 "we're working hard to offer image creator in more languages",
68 "we can't create your images right now"
69 ]
70 105 text = (await response.text()).lower()
71 for error in errors:
106 for error in ERRORS:
72 107 if error in text:
73 108 raise RuntimeError(f"Create images failed: {error}")
74 109 if response.status != 302:
@@ -107,54 +142,109 @@ async def create_images(session: ClientSession, prompt: str, proxy: str = None,
107 142 raise RuntimeError(error)
108 143 return read_images(text)
109 144
110 def read_images(text: str) -> list:
111 html_soup = BeautifulSoup(text, "html.parser")
112 tags = html_soup.find_all("img")
113 image_links = [img["src"] for img in tags if "mimg" in img["class"]]
114 images = [link.split("?w=")[0] for link in image_links]
115 bad_images = [
116 "https://r.bing.com/rp/in-2zU3AJUdkgFe7ZKv19yPBHVs.png",
117 "https://r.bing.com/rp/TX9QuO3WzcCJz1uaaSwQAz39Kb0.jpg",
118 ]
119 if any(im in bad_images for im in images):
145 def read_images(html_content: str) -> List[str]:
146 """
147 Extracts image URLs from the HTML content.
148
149 Args:
150 html_content (str): HTML content containing image URLs.
151
152 Returns:
153 List[str]: A list of image URLs.
154 """
155 soup = BeautifulSoup(html_content, "html.parser")
156 tags = soup.find_all("img", class_="mimg")
157 images = [img["src"].split("?w=")[0] for img in tags]
158 if any(im in BAD_IMAGES for im in images):
120 159 raise RuntimeError("Bad images found")
121 160 if not images:
122 161 raise RuntimeError("No images found")
123 162 return images
124 163
125 async def create_images_markdown(cookies: dict, prompt: str, proxy: str = None) -> str:
126 session = create_session(cookies)
127 try:
164 async def create_images_markdown(cookies: Dict[str, str], prompt: str, proxy: str = None) -> str:
165 """
166 Creates markdown formatted string with images based on the prompt.
167
168 Args:
169 cookies (Dict[str, str]): Cookies to be used for the session.
170 prompt (str): Prompt to generate images.
171 proxy (str, optional): Proxy configuration.
172
173 Returns:
174 str: Markdown formatted string with images.
175 """
176 async with create_session(cookies) as session:
128 177 images = await create_images(session, prompt, proxy)
129 178 return format_images_markdown(images, prompt)
130 finally:
131 await session.close()
132 179
133 def get_cookies_from_browser(proxy: str = None) -> dict:
134 driver = get_browser(proxy=proxy)
135 try:
180 def get_cookies_from_browser(proxy: str = None) -> Dict[str, str]:
181 """
182 Retrieves cookies from the browser using webdriver.
183
184 Args:
185 proxy (str, optional): Proxy configuration.
186
187 Returns:
188 Dict[str, str]: Retrieved cookies.
189 """
190 with get_browser(proxy=proxy) as driver:
136 191 wait_for_login(driver)
192 time.sleep(1)
137 193 return get_driver_cookies(driver)
138 finally:
139 driver.quit()
140
141 def create_completion(prompt: str, cookies: dict = None, proxy: str = None) -> Generator:
142 loop = get_event_loop()
143 if not cookies:
144 cookies = get_cookies(".bing.com")
145 if "_U" not in cookies:
146 login_url = os.environ.get("G4F_LOGIN_URL")
147 if login_url:
148 yield f"Please login: [Bing]({login_url})\n\n"
149 cookies = get_cookies_from_browser(proxy)
150 yield loop.run_until_complete(create_images_markdown(cookies, prompt, proxy))
151
152 async def create_async(prompt: str, cookies: dict = None, proxy: str = None) -> str:
153 if not cookies:
154 cookies = get_cookies(".bing.com")
155 if "_U" not in cookies:
156 cookies = get_cookies_from_browser(proxy)
157 return await create_images_markdown(cookies, prompt, proxy)
194
195 class CreateImagesBing:
196 """A class for creating images using Bing."""
197
198 _cookies: Dict[str, str] = {}
199
200 @classmethod
201 def create_completion(cls, prompt: str, cookies: Dict[str, str] = None, proxy: str = None) -> Generator[str]:
202 """
203 Generator for creating imagecompletion based on a prompt.
204
205 Args:
206 prompt (str): Prompt to generate images.
207 cookies (Dict[str, str], optional): Cookies for the session. If None, cookies are retrieved automatically.
208 proxy (str, optional): Proxy configuration.
209
210 Yields:
211 Generator[str, None, None]: The final output as markdown formatted string with images.
212 """
213 loop = get_event_loop()
214 cookies = cookies or cls._cookies or get_cookies(".bing.com")
215 if "_U" not in cookies:
216 login_url = os.environ.get("G4F_LOGIN_URL")
217 if login_url:
218 yield f"Please login: [Bing]({login_url})\n\n"
219 cls._cookies = cookies = get_cookies_from_browser(proxy)
220 yield loop.run_until_complete(create_images_markdown(cookies, prompt, proxy))
221
222 @classmethod
223 async def create_async(cls, prompt: str, cookies: Dict[str, str] = None, proxy: str = None) -> str:
224 """
225 Asynchronously creates a markdown formatted string with images based on the prompt.
226
227 Args:
228 prompt (str): Prompt to generate images.
229 cookies (Dict[str, str], optional): Cookies for the session. If None, cookies are retrieved automatically.
230 proxy (str, optional): Proxy configuration.
231
232 Returns:
233 str: Markdown formatted string with images.
234 """
235 cookies = cookies or cls._cookies or get_cookies(".bing.com")
236 if "_U" not in cookies:
237 cls._cookies = cookies = get_cookies_from_browser(proxy)
238 return await create_images_markdown(cookies, prompt, proxy)
158 239
159 240 def patch_provider(provider: ProviderType) -> CreateImagesProvider:
160 return CreateImagesProvider(provider, create_completion, create_async)
241 """
242 Patches a provider to include image creation capabilities.
243
244 Args:
245 provider (ProviderType): The provider to be patched.
246
247 Returns:
248 CreateImagesProvider: The patched provider with image creation capabilities.
249 """
250 return CreateImagesProvider(provider, CreateImagesBing.create_completion, CreateImagesBing.create_async)
Modified g4f/Provider/bing/upload_image.py +128 -60
@@ -1,64 +1,107 @@
1 from __future__ import annotations
1 """
2 Module to handle image uploading and processing for Bing AI integrations.
3 """
2 4
5 from __future__ import annotations
3 6 import string
4 7 import random
5 8 import json
6 9 import math
7 from ...typing import ImageType
8 10 from aiohttp import ClientSession
11 from PIL import Image
12
13 from ...typing import ImageType, Tuple
9 14 from ...image import to_image, process_image, to_base64, ImageResponse
10 15
11 image_config = {
16 IMAGE_CONFIG = {
12 17 "maxImagePixels": 360000,
13 18 "imageCompressionRate": 0.7,
14 "enableFaceBlurDebug": 0,
19 "enableFaceBlurDebug": False,
15 20 }
16 21
17 22 async def upload_image(
18 session: ClientSession,
19 image: ImageType,
20 tone: str,
23 session: ClientSession,
24 image_data: ImageType,
25 tone: str,
21 26 proxy: str = None
22 27 ) -> ImageResponse:
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 * math.sqrt(max_image_pixels / (width * height)))
28 new_height = int(height * math.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'
28 """
29 Uploads an image to Bing's AI service and returns the image response.
30
31 Args:
32 session (ClientSession): The active session.
33 image_data (bytes): The image data to be uploaded.
34 tone (str): The tone of the conversation.
35 proxy (str, optional): Proxy if any. Defaults to None.
36
37 Raises:
38 RuntimeError: If the image upload fails.
39
40 Returns:
41 ImageResponse: The response from the image upload.
42 """
43 image = to_image(image_data)
44 new_width, new_height = calculate_new_dimensions(image)
45 processed_img = process_image(image, new_width, new_height)
46 img_binary_data = to_base64(processed_img, IMAGE_CONFIG['imageCompressionRate'])
47
48 data, boundary = build_image_upload_payload(img_binary_data, tone)
49 headers = prepare_headers(session, boundary)
50
39 51 async with session.post("https://www.bing.com/images/kblob", data=data, headers=headers, proxy=proxy) as response:
40 52 if response.status != 200:
41 53 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 ImageResponse(result["imageUrl"], "", result)
59
60 def build_image_upload_api_payload(image_bin: str, tone: str):
61 payload = {
54 return parse_image_response(await response.json())
55
56 def calculate_new_dimensions(image: Image.Image) -> Tuple[int, int]:
57 """
58 Calculates the new dimensions for the image based on the maximum allowed pixels.
59
60 Args:
61 image (Image): The PIL Image object.
62
63 Returns:
64 Tuple[int, int]: The new width and height for the image.
65 """
66 width, height = image.size
67 max_image_pixels = IMAGE_CONFIG['maxImagePixels']
68 if max_image_pixels / (width * height) < 1:
69 scale_factor = math.sqrt(max_image_pixels / (width * height))
70 return int(width * scale_factor), int(height * scale_factor)
71 return width, height
72
73 def build_image_upload_payload(image_bin: str, tone: str) -> Tuple[str, str]:
74 """
75 Builds the payload for image uploading.
76
77 Args:
78 image_bin (str): Base64 encoded image binary data.
79 tone (str): The tone of the conversation.
80
81 Returns:
82 Tuple[str, str]: The data and boundary for the payload.
83 """
84 boundary = "----WebKitFormBoundary" + ''.join(random.choices(string.ascii_letters + string.digits, k=16))
85 data = f"--{boundary}\r\n" \
86 f"Content-Disposition: form-data; name=\"knowledgeRequest\"\r\n\r\n" \
87 f"{json.dumps(build_knowledge_request(tone), ensure_ascii=False)}\r\n" \
88 f"--{boundary}\r\n" \
89 f"Content-Disposition: form-data; name=\"imageBase64\"\r\n\r\n" \
90 f"{image_bin}\r\n" \
91 f"--{boundary}--\r\n"
92 return data, boundary
93
94 def build_knowledge_request(tone: str) -> dict:
95 """
96 Builds the knowledge request payload.
97
98 Args:
99 tone (str): The tone of the conversation.
100
101 Returns:
102 dict: The knowledge request payload.
103 """
104 return {
62 105 'invokedSkills': ["ImageById"],
63 106 'subscriptionId': "Bing.Chat.Multimodal",
64 107 'invokedSkillsRequestData': {
@@ -69,21 +112,46 @@ def build_image_upload_api_payload(image_bin: str, tone: str):
69 112 'convotone': tone
70 113 }
71 114 }
72 knowledge_request = {
73 'imageInfo': {},
74 'knowledgeRequest': payload
75 }
76 boundary="----WebKitFormBoundary" + ''.join(random.choices(string.ascii_letters + string.digits, k=16))
77 data = (
78 f'--{boundary}'
79 + '\r\nContent-Disposition: form-data; name="knowledgeRequest"\r\n\r\n'
80 + json.dumps(knowledge_request, ensure_ascii=False)
81 + "\r\n--"
82 + boundary
83 + '\r\nContent-Disposition: form-data; name="imageBase64"\r\n\r\n'
84 + image_bin
85 + "\r\n--"
86 + boundary
87 + "--\r\n"
115
116 def prepare_headers(session: ClientSession, boundary: str) -> dict:
117 """
118 Prepares the headers for the image upload request.
119
120 Args:
121 session (ClientSession): The active session.
122 boundary (str): The boundary string for the multipart/form-data.
123
124 Returns:
125 dict: The headers for the request.
126 """
127 headers = session.headers.copy()
128 headers["Content-Type"] = f'multipart/form-data; boundary={boundary}'
129 headers["Referer"] = 'https://www.bing.com/search?q=Bing+AI&showconv=1&FORM=hpcodx'
130 headers["Origin"] = 'https://www.bing.com'
131 return headers
132
133 def parse_image_response(response: dict) -> ImageResponse:
134 """
135 Parses the response from the image upload.
136
137 Args:
138 response (dict): The response dictionary.
139
140 Raises:
141 RuntimeError: If parsing the image info fails.
142
143 Returns:
144 ImageResponse: The parsed image response.
145 """
146 if not response.get('blobId'):
147 raise RuntimeError("Failed to parse image info.")
148
149 result = {'bcid': response.get('blobId', ""), 'blurredBcid': response.get('processedBlobId', "")}
150 result["imageUrl"] = f"https://www.bing.com/images/blob?bcid={result['blurredBcid'] or result['bcid']}"
151
152 result['originalImageUrl'] = (
153 f"https://www.bing.com/images/blob?bcid={result['blurredBcid']}"
154 if IMAGE_CONFIG["enableFaceBlurDebug"] else
155 f"https://www.bing.com/images/blob?bcid={result['bcid']}"
88 156 )
89 return data, boundary
157 return ImageResponse(result["imageUrl"], "", result)
Modified g4f/Provider/helper.py +83 -60
@@ -1,36 +1,31 @@
1 1 from __future__ import annotations
2 2
3 3 import asyncio
4 import webbrowser
4 import os
5 5 import random
6 import string
7 6 import secrets
8 import os
9 from os import path
7 import string
10 8 from asyncio import AbstractEventLoop, BaseEventLoop
11 9 from platformdirs import user_config_dir
12 10 from browser_cookie3 import (
13 chrome,
14 chromium,
15 opera,
16 opera_gx,
17 brave,
18 edge,
19 vivaldi,
20 firefox,
21 _LinuxPasswordManager
11 chrome, chromium, opera, opera_gx,
12 brave, edge, vivaldi, firefox,
13 _LinuxPasswordManager, BrowserCookieError
22 14 )
23
24 15 from ..typing import Dict, Messages
25 16 from .. import debug
26 17
27 # Local Cookie Storage
18 # Global variable to store cookies
28 19 _cookies: Dict[str, Dict[str, str]] = {}
29 20
30 # If loop closed or not set, create new event loop.
31 # If event loop is already running, handle nested event loops.
32 # If "nest_asyncio" is installed, patch the event loop.
33 21 def get_event_loop() -> AbstractEventLoop:
22 """
23 Get the current asyncio event loop. If the loop is closed or not set, create a new event loop.
24 If a loop is running, handle nested event loops. Patch the loop if 'nest_asyncio' is installed.
25
26 Returns:
27 AbstractEventLoop: The current or new event loop.
28 """
34 29 try:
35 30 loop = asyncio.get_event_loop()
36 31 if isinstance(loop, BaseEventLoop):
@@ -39,61 +34,50 @@ def get_event_loop() -> AbstractEventLoop:
39 34 loop = asyncio.new_event_loop()
40 35 asyncio.set_event_loop(loop)
41 36 try:
42 # Is running event loop
43 37 asyncio.get_running_loop()
44 38 if not hasattr(loop.__class__, "_nest_patched"):
45 39 import nest_asyncio
46 40 nest_asyncio.apply(loop)
47 41 except RuntimeError:
48 # No running event loop
49 42 pass
50 43 except ImportError:
51 44 raise RuntimeError(
52 'Use "create_async" instead of "create" function in a running event loop. Or install the "nest_asyncio" package.'
45 'Use "create_async" instead of "create" function in a running event loop. Or install "nest_asyncio" package.'
53 46 )
54 47 return loop
55 48
56 def init_cookies():
57 urls = [
58 'https://chat-gpt.org',
59 'https://www.aitianhu.com',
60 'https://chatgptfree.ai',
61 'https://gptchatly.com',
62 'https://bard.google.com',
63 'https://huggingface.co/chat',
64 'https://open-assistant.io/chat'
65 ]
66
67 browsers = ['google-chrome', 'chrome', 'firefox', 'safari']
68
69 def open_urls_in_browser(browser):
70 b = webbrowser.get(browser)
71 for url in urls:
72 b.open(url, new=0, autoraise=True)
73
74 for browser in browsers:
75 try:
76 open_urls_in_browser(browser)
77 break
78 except webbrowser.Error:
79 continue
80
81 # Check for broken dbus address in docker image
82 49 if os.environ.get('DBUS_SESSION_BUS_ADDRESS') == "/dev/null":
83 50 _LinuxPasswordManager.get_password = lambda a, b: b"secret"
84
85 # Load cookies for a domain from all supported browsers.
86 # Cache the results in the "_cookies" variable.
87 def get_cookies(domain_name=''):
51
52 def get_cookies(domain_name: str = '') -> Dict[str, str]:
53 """
54 Load cookies for a given domain from all supported browsers and cache the results.
55
56 Args:
57 domain_name (str): The domain for which to load cookies.
58
59 Returns:
60 Dict[str, str]: A dictionary of cookie names and values.
61 """
88 62 if domain_name in _cookies:
89 63 return _cookies[domain_name]
90 def g4f(domain_name):
91 user_data_dir = user_config_dir("g4f")
92 cookie_file = path.join(user_data_dir, "Default", "Cookies")
93 return [] if not path.exists(cookie_file) else chrome(cookie_file, domain_name)
64
65 cookies = _load_cookies_from_browsers(domain_name)
66 _cookies[domain_name] = cookies
67 return cookies
68
69 def _load_cookies_from_browsers(domain_name: str) -> Dict[str, str]:
70 """
71 Helper function to load cookies from various browsers.
72
73 Args:
74 domain_name (str): The domain for which to load cookies.
94 75
76 Returns:
77 Dict[str, str]: A dictionary of cookie names and values.
78 """
95 79 cookies = {}
96 for cookie_fn in [g4f, chrome, chromium, opera, opera_gx, brave, edge, vivaldi, firefox]:
80 for cookie_fn in [_g4f, chrome, chromium, opera, opera_gx, brave, edge, vivaldi, firefox]:
97 81 try:
98 82 cookie_jar = cookie_fn(domain_name=domain_name)
99 83 if len(cookie_jar) and debug.logging:
@@ -101,13 +85,38 @@ def get_cookies(domain_name=''):
101 85 for cookie in cookie_jar:
102 86 if cookie.name not in cookies:
103 87 cookies[cookie.name] = cookie.value
104 except:
88 except BrowserCookieError:
105 89 pass
106 _cookies[domain_name] = cookies
107 return _cookies[domain_name]
90 except Exception as e:
91 if debug.logging:
92 print(f"Error reading cookies from {cookie_fn.__name__} for {domain_name}: {e}")
93 return cookies
94
95 def _g4f(domain_name: str) -> list:
96 """
97 Load cookies from the 'g4f' browser (if exists).
98
99 Args:
100 domain_name (str): The domain for which to load cookies.
108 101
102 Returns:
103 list: List of cookies.
104 """
105 user_data_dir = user_config_dir("g4f")
106 cookie_file = os.path.join(user_data_dir, "Default", "Cookies")
107 return [] if not os.path.exists(cookie_file) else chrome(cookie_file, domain_name)
109 108
110 109 def format_prompt(messages: Messages, add_special_tokens=False) -> str:
110 """
111 Format a series of messages into a single string, optionally adding special tokens.
112
113 Args:
114 messages (Messages): A list of message dictionaries, each containing 'role' and 'content'.
115 add_special_tokens (bool): Whether to add special formatting tokens.
116
117 Returns:
118 str: A formatted string containing all messages.
119 """
111 120 if not add_special_tokens and len(messages) <= 1:
112 121 return messages[0]["content"]
113 122 formatted = "\n".join([
@@ -116,12 +125,26 @@ def format_prompt(messages: Messages, add_special_tokens=False) -> str:
116 125 ])
117 126 return f"{formatted}\nAssistant:"
118 127
119
120 128 def get_random_string(length: int = 10) -> str:
129 """
130 Generate a random string of specified length, containing lowercase letters and digits.
131
132 Args:
133 length (int, optional): Length of the random string to generate. Defaults to 10.
134
135 Returns:
136 str: A random string of the specified length.
137 """
121 138 return ''.join(
122 139 random.choice(string.ascii_lowercase + string.digits)
123 140 for _ in range(length)
124 141 )
125 142
126 143 def get_random_hex() -> str:
144 """
145 Generate a random hexadecimal string of a fixed length.
146
147 Returns:
148 str: A random hexadecimal string of 32 characters (16 bytes).
149 """
127 150 return secrets.token_hex(16).zfill(32)
Modified g4f/Provider/needs_auth/OpenaiChat.py +229 -110
Modified g4f/Provider/retry_provider.py +49 -9
Modified g4f/__init__.py +88 -3
Modified g4f/base_provider.py +71 -10
Modified g4f/gui/client/css/style.css +11 -2
@@ -404,7 +404,7 @@ body {
404 404 display: none;
405 405 }
406 406
407 #image {
407 #image, #file {
408 408 display: none;
409 409 }
410 410
@@ -412,13 +412,22 @@ label[for="image"]:has(> input:valid){
412 412 color: var(--accent);
413 413 }
414 414
415 label[for="image"] {
415 label[for="file"]:has(> input:valid){
416 color: var(--accent);
417 }
418
419 label[for="image"], label[for="file"] {
416 420 cursor: pointer;
417 421 position: absolute;
418 422 top: 10px;
419 423 left: 10px;
420 424 }
421 425
426 label[for="file"] {
427 top: 32px;
428 left: 10px;
429 }
430
422 431 .buttons input[type="checkbox"] {
423 432 height: 0;
424 433 width: 0;
Modified g4f/gui/client/html/index.html +22 -2
Modified g4f/gui/client/js/chat.v1.js +53 -21
Modified g4f/image.py +100 -5
Modified g4f/models.py +15 -0
@@ -31,12 +31,21 @@ from .Provider import (
31 31
32 32 @dataclass(unsafe_hash=True)
33 33 class Model:
34 """
35 Represents a machine learning model configuration.
36
37 Attributes:
38 name (str): Name of the model.
39 base_provider (str): Default provider for the model.
40 best_provider (ProviderType): The preferred provider for the model, typically with retry logic.
41 """
34 42 name: str
35 43 base_provider: str
36 44 best_provider: ProviderType = None
37 45
38 46 @staticmethod
39 47 def __all__() -> list[str]:
48 """Returns a list of all model names."""
40 49 return _all_models
41 50
42 51 default = Model(
@@ -298,6 +307,12 @@ pi = Model(
298 307 )
299 308
300 309 class ModelUtils:
310 """
311 Utility class for mapping string identifiers to Model instances.
312
313 Attributes:
314 convert (dict[str, Model]): Dictionary mapping model string identifiers to Model instances.
315 """
301 316 convert: dict[str, Model] = {
302 317 # gpt-3.5
303 318 'gpt-3.5-turbo' : gpt_35_turbo,
Modified g4f/requests.py +42 -6
Modified g4f/version.py +59 -10
Modified g4f/webdriver.py +53 -23