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

XFEstudio/gpt4free

add upload_file

- add upload_file - add conversation_mode - add temporary

fe24210d
Ammar <ammar.alkotb@gmail.com>
提交于

代码差异

2 个文件 +195 -36
Modified g4f/Provider/needs_auth/OpenaiChat.py +77 -36
@@ -22,7 +22,7 @@ from ...typing import AsyncResult, Messages, Cookies, MediaListType
22 22 from ...requests.raise_for_status import raise_for_status
23 23 from ...requests import StreamSession
24 24 from ...requests import get_nodriver
25 from ...image import ImageRequest, to_image, to_bytes, is_accepted_format
25 from ...image import ImageRequest, to_image, to_bytes, is_accepted_format, detect_file_type
26 26 from ...errors import MissingAuthError, NoValidHarFileError, ModelNotFoundError
27 27 from ...providers.response import JsonConversation, FinishReason, SynthesizeData, AuthResult, ImageResponse, ImagePreview, ResponseType, format_link
28 28 from ...providers.response import TitleGeneration, RequestLogin, Reasoning
@@ -126,54 +126,66 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
126 126 )
127 127
128 128 @classmethod
129 async def upload_images(
129 async def upload_files(
130 130 cls,
131 131 session: StreamSession,
132 132 auth_result: AuthResult,
133 133 media: MediaListType,
134 ) -> ImageRequest:
134 ) -> list[ImageRequest]:
135 135 """
136 136 Upload an image to the service and get the download URL
137 137
138 138 Args:
139 139 session: The StreamSession object to use for requests
140 140 headers: The headers to include in the requests
141 media: The images to upload, either a PIL Image object or a bytes object
141 media: The files to upload, either a PIL Image object or a bytes object
142 142
143 143 Returns:
144 144 An ImageRequest object that contains the download URL, file name, and other data
145 145 """
146 async def upload_image(image, image_name):
147 debug.log(f"Uploading image: {image_name}")
148 # Convert the image to a PIL Image object and get the extension
149 data_bytes = to_bytes(image)
150 image = to_image(data_bytes)
151 extension = image.format.lower()
146 async def upload_file(file, image_name=None):
147 debug.log(f"Uploading file: {image_name}")
148 file_data = {}
149
150 data_bytes = to_bytes(file)
151 extension, mime_type = detect_file_type(data_bytes)
152 if "image" in mime_type:
153 # Convert the image to a PIL Image object
154 file = to_image(data_bytes)
155 use_case = "multimodal"
156 file_data.update({"height": file.height, "width": file.width})
157 else:
158 use_case = "my_files"
159 image_name = (
160 f"file-{len(data_bytes)}{extension}"
161 if image_name is None
162 else image_name
163 )
152 164 data = {
153 "file_name": "" if image_name is None else image_name,
165 "file_name": image_name,
154 166 "file_size": len(data_bytes),
155 "use_case": "multimodal"
167 "use_case": use_case,
156 168 }
157 169 # Post the image data to the service and get the image data
158 170 async with session.post(f"{cls.url}/backend-api/files", json=data, headers=cls._headers) as response:
159 171 cls._update_request_args(auth_result, session)
160 172 await raise_for_status(response, "Create file failed")
161 image_data = {
162 **data,
163 **await response.json(),
164 "mime_type": is_accepted_format(data_bytes),
165 "extension": extension,
166 "height": image.height,
167 "width": image.width
168 }
173 file_data.update(
174 {
175 **data,
176 **await response.json(),
177 "mime_type": mime_type,
178 "extension": extension,
179 }
180 )
169 181 # Put the image bytes to the upload URL and check the status
170 182 await asyncio.sleep(1)
171 183 async with session.put(
172 image_data["upload_url"],
184 file_data["upload_url"],
173 185 data=data_bytes,
174 186 headers={
175 187 **UPLOAD_HEADERS,
176 "Content-Type": image_data["mime_type"],
188 "Content-Type": file_data["mime_type"],
177 189 "x-ms-blob-type": "BlockBlob",
178 190 "x-ms-version": "2020-04-08",
179 191 "Origin": "https://chatgpt.com",
@@ -182,15 +194,22 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
182 194 await raise_for_status(response)
183 195 # Post the file ID to the service and get the download URL
184 196 async with session.post(
185 f"{cls.url}/backend-api/files/{image_data['file_id']}/uploaded",
197 f"{cls.url}/backend-api/files/{file_data['file_id']}/uploaded",
186 198 json={},
187 199 headers=auth_result.headers
188 200 ) as response:
189 201 cls._update_request_args(auth_result, session)
190 202 await raise_for_status(response, "Get download url failed")
191 image_data["download_url"] = (await response.json())["download_url"]
192 return ImageRequest(image_data)
193 return [await upload_image(image, image_name) for image, image_name in media]
203 uploaded_data = await response.json()
204 file_data["download_url"] = uploaded_data["download_url"]
205 return ImageRequest(file_data)
206
207 medias = []
208 for item in media:
209 item = item if isinstance(item, tuple) else (item,)
210 __uploaded_media = await upload_file(*item)
211 medias.append(__uploaded_media)
212 return medias
194 213
195 214 @classmethod
196 215 def create_messages(cls, messages: Messages, image_requests: ImageRequest = None, system_hints: list = None):
@@ -237,18 +256,27 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
237 256 "size_bytes": image_request.get("file_size"),
238 257 "width": image_request.get("width"),
239 258 }
240 for image_request in image_requests],
259 for image_request in image_requests
260 # Add For Images Only
261 if image_request.get("use_case") == "multimodal"
262 ],
241 263 messages[-1]["content"]["parts"][0]]
242 264 }
243 265 # Add the metadata object with the attachments
244 266 messages[-1]["metadata"] = {
245 267 "attachments": [{
246 "height": image_request.get("height"),
247 268 "id": image_request.get("file_id"),
248 269 "mimeType": image_request.get("mime_type"),
249 270 "name": image_request.get("file_name"),
250 271 "size": image_request.get("file_size"),
251 "width": image_request.get("width"),
272 **(
273 {
274 "height": image_request.get("height"),
275 "width": image_request.get("width"),
276 }
277 if image_request.get("use_case") == "multimodal"
278 else {}
279 ),
252 280 }
253 281 for image_request in image_requests]
254 282 }
@@ -308,6 +336,8 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
308 336 return_conversation: bool = True,
309 337 web_search: bool = False,
310 338 prompt: str = None,
339 conversation_mode=None,
340 temporary=False,
311 341 **kwargs
312 342 ) -> AsyncResult:
313 343 """
@@ -353,11 +383,12 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
353 383 async with session.get(cls.url, headers=cls._headers) as response:
354 384 cls._update_request_args(auth_result, session)
355 385 await raise_for_status(response)
356 try:
357 image_requests = await cls.upload_images(session, auth_result, media)
358 except Exception as e:
359 debug.error("OpenaiChat: Upload image failed")
360 debug.error(e)
386
387 # try:
388 image_requests = await cls.upload_files(session, auth_result, media)
389 # except Exception as e:
390 # debug.error("OpenaiChat: Upload image failed")
391 # debug.error(e)
361 392 try:
362 393 model = cls.get_model(model)
363 394 except ModelNotFoundError:
@@ -370,6 +401,10 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
370 401 conversation = Conversation(None, str(uuid.uuid4()), getattr(auth_result, "cookies", {}).get("oai-did"))
371 402 else:
372 403 conversation = copy(conversation)
404
405 if conversation_mode is None:
406 conversation_mode = {"kind": "primary_assistant"}
407
373 408 if getattr(auth_result, "cookies", {}).get("oai-did") != getattr(conversation, "user_id", None):
374 409 conversation = Conversation(None, str(uuid.uuid4()))
375 410 if cls._api_key is None:
@@ -394,6 +429,9 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
394 429 "supports_buffering": True,
395 430 "supported_encodings": ["v1"]
396 431 }
432 if temporary:
433 data["history_and_training_disabled"] = True
434
397 435 async with session.post(
398 436 prepare_url,
399 437 json=data,
@@ -434,11 +472,11 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
434 472 user_agent=user_agent,
435 473 proof_token=proof_token
436 474 )
437 [debug.log(text) for text in (
475 # [debug.log(text) for text in (
438 476 #f"Arkose: {'False' if not need_arkose else auth_result.arkose_token[:12]+'...'}",
439 477 #f"Proofofwork: {'False' if proofofwork is None else proofofwork[:12]+'...'}",
440 478 #f"AccessToken: {'False' if cls._api_key is None else cls._api_key[:12]+'...'}",
441 )]
479 # )]
442 480 data = {
443 481 "action": "next",
444 482 "parent_message_id": conversation.message_id,
@@ -453,6 +491,9 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
453 491 "client_contextual_info":{"is_dark_mode":False,"time_since_loaded":random.randint(20, 500),"page_height":578,"page_width":1850,"pixel_ratio":1,"screen_height":1080,"screen_width":1920},
454 492 "paragen_cot_summary_display_override":"allow"
455 493 }
494 if temporary:
495 data["history_and_training_disabled"] = True
496
456 497 if conversation.conversation_id is not None:
457 498 data["conversation_id"] = conversation.conversation_id
458 499 debug.log(f"OpenaiChat: Use conversation: {conversation.conversation_id}")
Modified g4f/image/__init__.py +118 -0
@@ -191,6 +191,124 @@ def is_accepted_format(binary_data: bytes) -> str:
191 191 else:
192 192 raise ValueError("Invalid image format (from magic code).")
193 193
194
195
196 def detect_file_type(binary_data: bytes) -> tuple[str, str] | None:
197 """
198 Detect file type from magic number / header signature.
199
200 Args:
201 binary_data (bytes): File binary data
202
203 Returns:
204 tuple: (extension, MIME type)
205
206 Raises:
207 ValueError: If file type is unknown
208 """
209
210 # ---- Images ----
211 if binary_data.startswith(b"\xff\xd8\xff"):
212 return ".jpg", "image/jpeg"
213 elif binary_data.startswith(b"\x89PNG\r\n\x1a\n"):
214 return ".png", "image/png"
215 elif binary_data.startswith((b"GIF87a", b"GIF89a")):
216 return ".gif", "image/gif"
217 elif binary_data.startswith(b"RIFF") and binary_data[8:12] == b"WEBP":
218 return ".webp", "image/webp"
219 elif binary_data.startswith(b"BM"):
220 return ".bmp", "image/bmp"
221 elif binary_data.startswith(b"II*\x00") or binary_data.startswith(b"MM\x00*"):
222 return ".tiff", "image/tiff"
223 elif binary_data.startswith(b"\x00\x00\x01\x00"):
224 return ".ico", "image/x-icon"
225 elif binary_data.startswith(b"\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a"):
226 return ".jp2", "image/jp2"
227 elif len(binary_data) > 12 and binary_data[4:8] == b"ftyp":
228 brand = binary_data[8:12]
229 if brand in [b"heic", b"heix", b"hevc", b"hevx", b"mif1", b"msf1"]:
230 return ".heic", "image/heif"
231 elif brand in [b"avif"]:
232 return ".avif", "image/avif"
233 elif binary_data.lstrip().startswith((b"<?xml", b"<svg")):
234 return ".svg", "image/svg+xml"
235
236 # ---- Documents ----
237 elif binary_data.startswith(b"%PDF"):
238 return ".pdf", "application/pdf"
239 elif binary_data.startswith(b"PK\x03\x04"):
240 return".zip", "application/zip-based",
241 # could be docx/xlsx/pptx/jar/apk/odt
242 elif binary_data.startswith(b"\xd0\xcf\x11\xe0"):
243 return ".doc", "application/vnd.ms-office"
244 elif binary_data.startswith(b"{\\rtf"):
245 return ".rtf", "application/rtf"
246 elif binary_data.startswith(b"7z\xbc\xaf\x27\x1c"):
247 return ".7z", "application/x-7z-compressed"
248 elif binary_data.startswith(b"Rar!\x1a\x07\x00"):
249 return ".rar", "application/vnd.rar"
250 elif binary_data.startswith(b"\x1f\x8b"):
251 return ".gz", "application/gzip"
252 elif binary_data.startswith(b"BZh"):
253 return ".bz2", "application/x-bzip2"
254 elif binary_data.startswith(b"\xfd7zXZ\x00"):
255 return ".xz", "application/x-xz"
256
257 # ---- Executables / Libraries ----
258 elif binary_data.startswith(b"MZ"):
259 return ".exe", "application/x-msdownload"
260 elif binary_data.startswith(b"\x7fELF"):
261 return ".elf", "application/x-elf"
262 elif binary_data.startswith(b"\xca\xfe\xba\xbe") or binary_data.startswith(
263 b"\xca\xfe\xd0\x0d"
264 ):
265 return ".class", "application/java-vm"
266 elif (
267 binary_data.startswith(b"\x50\x4b\x03\x04")
268 and b"META-INF" in binary_data[:200]
269 ):
270 return ".jar", "application/java-archive"
271
272 # ---- Audio ----
273 elif binary_data.startswith(b"ID3") or binary_data[0:2] == b"\xff\xfb":
274 return ".mp3", "audio/mpeg"
275 elif binary_data.startswith(b"OggS"):
276 return ".ogg", "audio/ogg"
277 elif binary_data.startswith(b"fLaC"):
278 return ".flac", "audio/flac"
279 elif binary_data.startswith(b"RIFF") and binary_data[8:12] == b"WAVE":
280 return ".wav", "audio/wav"
281 elif binary_data.startswith(b"MThd"):
282 return ".mid", "audio/midi"
283
284 # ---- Video ----
285 elif binary_data.startswith(b"\x00\x00\x00") and b"ftyp" in binary_data[4:12]:
286 return ".mp4", "video/mp4"
287 elif binary_data.startswith(b"RIFF") and binary_data[8:12] == b"AVI ":
288 return ".avi", "video/x-msvideo"
289 elif binary_data.startswith(b"OggS"):
290 return ".ogv", "video/ogg"
291 elif binary_data.startswith(b"\x1a\x45\xdf\xa3"):
292 return ".mkv", "video/webm"
293 elif binary_data.startswith(b"\x00\x00\x01\xba"):
294 return ".mpg", "video/mpeg"
295
296 # ---- Text / Scripts ----
297 elif binary_data.lstrip().startswith(b"#!"):
298 return ".sh", "text/x-script"
299 elif binary_data.lstrip().startswith((b"{", b"[")):
300 return ".json", "application/json"
301 elif binary_data.lstrip().startswith((b"<", b"<!DOCTYPE")):
302 return ".html", "text/html"
303 elif binary_data.lstrip().startswith(b"<?xml"):
304 return ".xml", "application/xml"
305 elif all(32 <= b <= 127 or b in (9, 10, 13) for b in binary_data[:100]):
306 return ".txt", "text/plain"
307
308 else:
309 raise ValueError("Unknown or unsupported file type")
310
311
194 312 def extract_data_uri(data_uri: str) -> bytes:
195 313 """
196 314 Extracts the binary data from the given data URI.