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

XFEstudio/gpt4free

feat: Refactor PollinationsAI and ARTA provider structure

- Updated `PollinationsAI.py` to strip trailing periods and newlines from the prompt before encoding. - Modified the encoding of the prompt to remove trailing percent signs after URL encoding. - Simplified the audio response handling in `PollinationsAI.py` by removing unnecessary checks and yielding chunks directly. - Renamed `ARTA.py` to `deprecated/ARTA.py` and updated import paths accordingly in `__init__.py`. - Changed the `working` status of the `ARTA` class to `False` to indicate it is deprecated. - Enhanced the `Video` class in `Video.py` to include aspect ratio handling and improved URL response caching. - Updated the `RequestConfig` class to use a dictionary for storing URLs associated with prompts. - Removed references to the `ARTA` provider in various files, including `models.py` and `any_provider.py`. - Adjusted the `best_provider` assignments in `models.py` to exclude `ARTA` and include `HuggingFaceMedia` where applicable. - Updated the response handling in `Video.py` to yield cached responses when available.

d824d77d
hlohaus <983577+hlohaus@users.noreply.github.com>
提交于

代码差异

11 个文件 +106 -87
Modified g4f/Provider/PollinationsAI.py +3 -11
@@ -392,10 +392,10 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
392 392 **params
393 393 }, "1:1" if aspect_ratio is None else aspect_ratio)
394 394 query = "&".join(f"{k}={quote_plus(str(v))}" for k, v in params.items() if v is not None)
395 encoded_prompt = prompt
395 encoded_prompt = prompt.strip(". \n")
396 396 if model == "gptimage" and aspect_ratio is not None:
397 397 encoded_prompt = f"{encoded_prompt} aspect-ratio: {aspect_ratio}"
398 encoded_prompt = quote_plus(encoded_prompt)[:4096-len(cls.image_api_endpoint)-len(query)-8]
398 encoded_prompt = quote_plus(encoded_prompt)[:4096-len(cls.image_api_endpoint)-len(query)-8].rstrip("%")
399 399 url = f"{cls.image_api_endpoint}prompt/{encoded_prompt}?{query}"
400 400 def get_url_with_seed(i: int, seed: Optional[int] = None):
401 401 if model == "gptimage":
@@ -583,15 +583,7 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
583 583 audio = message.get("audio", {})
584 584 if "data" in audio:
585 585 async for chunk in save_response_media(audio["data"], prompt, [model, extra_body.get("audio", {}).get("voice")]):
586 if isinstance(chunk, AudioResponse) and not download_media and voice and len(messages) == 1:
587 prompt = messages[0].get("content")
588 if isinstance(prompt, str):
589 url = f"https://text.pollinations.ai/{quote(prompt)}?model={quote(model)}&voice={quote(voice)}&seed={quote(str(seed))}"
590 yield AudioResponse(url)
591 else:
592 yield chunk
593 else:
594 yield chunk
586 yield chunk
595 587 if "transcript" in audio:
596 588 yield "\n\n"
597 589 yield audio["transcript"]
Modified g4f/Provider/__init__.py +1 -1
@@ -32,7 +32,7 @@ try:
32 32 except ImportError as e:
33 33 debug.error("Audio providers not loaded:", e)
34 34
35 from .ARTA import ARTA
35 from .deprecated.ARTA import ARTA
36 36 from .Blackbox import Blackbox
37 37 from .Chatai import Chatai
38 38 from .Cloudflare import Cloudflare
Renamed g4f/Provider/deprecated/ARTA.py +8 -8
@@ -8,13 +8,13 @@ from pathlib import Path
8 8 from aiohttp import ClientSession, ClientResponse
9 9 import asyncio
10 10
11 from ..typing import AsyncResult, Messages
12 from ..providers.response import ImageResponse, Reasoning
13 from ..errors import ResponseError, ModelNotFoundError
14 from ..cookies import get_cookies_dir
15 from .base_provider import AsyncGeneratorProvider, ProviderModelMixin
16 from .helper import format_media_prompt
17 from .. import debug
11 from ...typing import AsyncResult, Messages
12 from ...providers.response import ImageResponse, Reasoning
13 from ...errors import ResponseError, ModelNotFoundError
14 from ...cookies import get_cookies_dir
15 from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin
16 from ..helper import format_media_prompt
17 from ... import debug
18 18
19 19 class ARTA(AsyncGeneratorProvider, ProviderModelMixin):
20 20 url = "https://ai-arta.com"
@@ -23,7 +23,7 @@ class ARTA(AsyncGeneratorProvider, ProviderModelMixin):
23 23 image_generation_url = "https://img-gen-prod.ai-arta.com/api/v1/text2image"
24 24 status_check_url = "https://img-gen-prod.ai-arta.com/api/v1/text2image/{record_id}/status"
25 25
26 working = True
26 working = False # Take down request
27 27
28 28 default_model = "flux"
29 29 default_image_model = default_model
Modified g4f/Provider/needs_auth/Video.py +63 -29
@@ -10,22 +10,33 @@ from aiohttp import ClientSession
10 10
11 11 try:
12 12 import nodriver
13 from nodriver.core.connection import ProtocolException
13 14 except:
14 15 pass
15 16
16 17 from ...typing import Messages, AsyncResult
17 from ...providers.response import VideoResponse, Reasoning, ContinueResponse
18 from ...providers.response import VideoResponse, Reasoning, ContinueResponse, ProviderInfo
18 19 from ...requests import get_nodriver
19 20 from ...errors import MissingRequirementsError
20 from ..base_provider import AsyncGeneratorProvider
21 from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin
21 22 from ..helper import format_media_prompt
22 23 from ... import debug
23 24
24 25 class RequestConfig:
25 urls: list[str] = []
26 urls: dict[str, list[str]] = {}
26 27 headers: dict = {}
27 28
28 class Video(AsyncGeneratorProvider):
29 @classmethod
30 def get_response(cls, prompt: str) -> VideoResponse | None:
31 if prompt in cls.urls and cls.urls[prompt]:
32 cls.urls[prompt] = list(set(cls.urls[prompt]))
33 debug.log(f"Video URL: {len(cls.urls[prompt])}")
34 return VideoResponse(cls.urls[prompt], prompt, {
35 "headers": {"authorization": cls.headers.get("authorization")} if cls.headers.get("authorization") else {},
36 "preview": [url.replace("md.mp4", "thumb.webp") for url in cls.urls[prompt]]
37 })
38
39 class Video(AsyncGeneratorProvider, ProviderModelMixin):
29 40 urls = [
30 41 "https://sora.chatgpt.com/explore",
31 42 #"https://aistudio.google.com/generate-video"
@@ -35,6 +46,10 @@ class Video(AsyncGeneratorProvider):
35 46 search_url = f"{pub_url}/search/video+"
36 47 drive_url = "https://www.googleapis.com/drive/v3/"
37 48
49 active_by_default = True
50 default_model = "sora"
51 video_models = [default_model]
52
38 53 needs_auth = True
39 54 working = True
40 55
@@ -48,26 +63,33 @@ class Video(AsyncGeneratorProvider):
48 63 messages: Messages,
49 64 proxy: str = None,
50 65 prompt: str = None,
66 aspect_ratio: str = None,
51 67 **kwargs
52 68 ) -> AsyncResult:
69 yield ProviderInfo(**cls.get_dict(), model="sora")
53 70 started = time.time()
54 71 prompt = format_media_prompt(messages, prompt)
55 72 if not prompt:
56 73 raise ValueError("Prompt cannot be empty.")
57 74 async with ClientSession() as session:
58 75 yield Reasoning(label="Lookup")
59 has_video = False
76 found_urls = []
60 77 for skip in range(0, 9):
61 78 async with session.get(cls.search_url + quote_plus(prompt) + f"?skip={skip}", timeout=ClientTimeout(total=10)) as response:
62 79 if response.ok:
63 80 yield Reasoning(label=f"Found {skip+1}", status="")
64 yield VideoResponse(str(response.url), prompt)
65 has_video = True
81 found_urls.append(str(response.url))
66 82 else:
67 83 break
68 if has_video:
84 if found_urls:
69 85 yield Reasoning(label=f"Finished", status="")
86 yield VideoResponse(found_urls, prompt)
70 87 return
88 response = RequestConfig.get_response(prompt)
89 if response:
90 yield Reasoning(label="Found cached Video", status="")
91 yield response
92 return
71 93 try:
72 94 yield Reasoning(label="Open browser")
73 95 browser, stop_browser = await get_nodriver(proxy=proxy, user_data_dir="gemini")
@@ -87,17 +109,14 @@ class Video(AsyncGeneratorProvider):
87 109 yield VideoResponse(str(response.url), prompt)
88 110 return
89 111 raise MissingRequirementsError("Video provider requires a browser to be installed.")
90 RequestConfig.urls = []
91 112 try:
92 113 cls.page = await browser.get(random.choice(cls.urls))
93 114 except Exception as e:
94 115 debug.error(f"Error opening page:", e)
95 if RequestConfig.urls:
96 RequestConfig.urls = list(set(RequestConfig.urls))
97 debug.log(f"Video URL: {len(RequestConfig.urls)}")
98 yield VideoResponse(RequestConfig.urls, prompt, {
99 "headers": {"authorization": RequestConfig.headers.get("authorization")} if RequestConfig.headers.get("authorization") else {}
100 })
116 response = RequestConfig.get_response(prompt)
117 if response:
118 yield Reasoning(label="Found", status="")
119 yield response
101 120 return
102 121 try:
103 122 page = cls.page
@@ -117,6 +136,21 @@ class Video(AsyncGeneratorProvider):
117 136 debug.error("No 'Video' button found.")
118 137 except Exception as e:
119 138 debug.error(f"Error clicking button:", e)
139 try:
140 if aspect_ratio:
141 button = await page.find("2:3")
142 if button:
143 await button.click()
144 else:
145 debug.error("No '2:3' button found.")
146 button = await page.find(aspect_ratio)
147 if button:
148 await button.click()
149 yield Reasoning(label=f"Clicked '{aspect_ratio}' button")
150 else:
151 debug.error(f"No '{aspect_ratio}' button found.")
152 except Exception as e:
153 debug.error(f"Error clicking button:", e)
120 154 debug.log(f"Using prompt: {prompt}")
121 155 textarea = await page.select("textarea", 180)
122 156 await textarea.send_keys(prompt)
@@ -148,35 +182,35 @@ class Video(AsyncGeneratorProvider):
148 182 await button.click()
149 183 yield Reasoning(label=f"Clicked 'Queued' button")
150 184 break
151 except Exception as e:
152 debug.error(f"Error clicking 'Queued' button:", e)
153 yield Reasoning(label=f"Waiting for Video URL...")
185 except ProtocolException as e:
186 pass
187 if prompt not in RequestConfig.urls:
188 RequestConfig.urls[prompt] = []
154 189 def on_request(event: nodriver.cdp.network.RequestWillBeSent, page=None):
155 if "mp4" in event.request.url:
190 if ".mp4" in event.request.url:
156 191 RequestConfig.headers = {}
157 192 for key, value in event.request.headers.items():
158 193 RequestConfig.headers[key.lower()] = value
159 RequestConfig.urls.append(event.request.url)
194 RequestConfig.urls[prompt].append(event.request.url)
160 195 elif event.request.url.startswith(cls.drive_url):
161 196 RequestConfig.headers = {}
162 197 for key, value in event.request.headers.items():
163 198 RequestConfig.headers[key.lower()] = value
164 RequestConfig.urls.append(event.request.url)
199 RequestConfig.urls[prompt].append(event.request.url)
165 200 await page.send(nodriver.cdp.network.enable())
166 201 page.add_handler(nodriver.cdp.network.RequestWillBeSent, on_request)
167 202 for idx in range(600):
203 yield Reasoning(label=f"Waiting for Video... {idx+1}/600")
168 204 if time.time() - started > 30:
169 205 yield ContinueResponse("Timeout waiting for Video URL")
170 206 await asyncio.sleep(1)
171 if RequestConfig.urls:
207 if RequestConfig.urls[prompt]:
172 208 await asyncio.sleep(2)
173 RequestConfig.urls = list(set(RequestConfig.urls))
174 debug.log(f"Video URL: {len(RequestConfig.urls)}")
175 yield VideoResponse(RequestConfig.urls, prompt, {
176 "headers": {"authorization": RequestConfig.headers.get("authorization")} if RequestConfig.headers.get("authorization") else {}
177 })
178 yield Reasoning(label=f"Finished", status="")
179 break
209 response = RequestConfig.get_response(prompt)
210 if response:
211 yield Reasoning(label="Finished", status="")
212 yield response
213 return
180 214 if idx == 599:
181 215 raise RuntimeError("Failed to get Video URL")
182 216 finally:
Modified g4f/Provider/needs_auth/hf/models.py +4 -1
@@ -34,12 +34,15 @@ model_aliases = {
34 34 "flux": "black-forest-labs/FLUX.1-dev",
35 35 "flux-dev": "black-forest-labs/FLUX.1-dev",
36 36 "flux-schnell": "black-forest-labs/FLUX.1-schnell",
37 "stable-diffusion-3.5-large": "stabilityai/stable-diffusion-3.5-large",
38 "sdxl-1.0": "stabilityai/stable-diffusion-xl-base-1.0",
39 "sdxl-turbo": "stabilityai/sdxl-turbo",
40 "sd-3.5-large": "stabilityai/stable-diffusion-3.5-large",
37 41 ### Used in other providers ###
38 42 "qwen-2-vl-7b": "Qwen/Qwen2-VL-7B-Instruct",
39 43 "gemma-2-27b": "google/gemma-2-27b-it",
40 44 "qwen-2-72b": "Qwen/Qwen2-72B-Instruct",
41 45 "qvq-72b": "Qwen/QVQ-72B-Preview",
42 "stable-diffusion-3.5-large": "stabilityai/stable-diffusion-3.5-large",
43 46 }
44 47 extra_models = [
45 48 "meta-llama/Llama-3.2-11B-Vision-Instruct",
Modified g4f/api/__init__.py +5 -0
@@ -82,6 +82,7 @@ from g4f import debug
82 82 logger = logging.getLogger(__name__)
83 83
84 84 DEFAULT_PORT = 1337
85 DEFAULT_TIMEOUT = 600
85 86
86 87 @asynccontextmanager
87 88 async def lifespan(app: FastAPI):
@@ -141,6 +142,7 @@ def create_app_with_demo_and_debug():
141 142 g4f.debug.logging = True
142 143 AppConfig.gui = True
143 144 AppConfig.demo = True
145 AppConfig.timeout = 60
144 146 return create_app()
145 147
146 148 class ErrorResponse(Response):
@@ -169,6 +171,7 @@ class AppConfig:
169 171 proxy: str = None
170 172 gui: bool = False
171 173 demo: bool = False
174 timeout: int = DEFAULT_TIMEOUT
172 175
173 176 @classmethod
174 177 def set_config(cls, **data):
@@ -347,6 +350,8 @@ class Api:
347 350 config.provider = AppConfig.provider if provider is None else provider
348 351 if config.conversation_id is None:
349 352 config.conversation_id = conversation_id
353 if config.timeout is None:
354 config.timeout = AppConfig.timeout
350 355 if credentials is not None and credentials.credentials != "secret":
351 356 config.api_key = credentials.credentials
352 357
Modified g4f/gui/server/backend_api.py +4 -2
@@ -386,12 +386,14 @@ class Backend_Api(Api):
386 386 process_image(image, save=os.path.join(thumbnail_dir, filename))
387 387 except Exception as e:
388 388 logger.exception(e)
389 elif is_supported:
389 elif is_supported and not result:
390 390 newfile = os.path.join(bucket_dir, filename)
391 391 filenames.append(filename)
392 392 else:
393 393 os.remove(copyfile)
394 raise ValueError(f"Unsupported file type: {filename}")
394 if not result:
395 raise ValueError(f"Unsupported file type: {filename}")
396 continue
395 397 try:
396 398 os.rename(copyfile, newfile)
397 399 except OSError:
Modified g4f/models.py +7 -20
@@ -6,7 +6,6 @@ from typing import Dict, List, Optional
6 6 from .Provider import IterListProvider, ProviderType
7 7 from .Provider import (
8 8 ### No Auth Required ###
9 ARTA,
10 9 Blackbox,
11 10 Chatai,
12 11 Cloudflare,
@@ -41,6 +40,7 @@ from .Provider import (
41 40 HailuoAI,
42 41 HuggingChat,
43 42 HuggingFace,
43 HuggingFaceMedia,
44 44 HuggingFaceAPI,
45 45 MetaAI,
46 46 MicrosoftDesigner,
@@ -287,7 +287,7 @@ dall_e_3 = ImageModel(
287 287 gpt_image = ImageModel(
288 288 name = 'gpt-image',
289 289 base_provider = 'OpenAI',
290 best_provider = IterListProvider([PollinationsImage, ARTA])
290 best_provider = IterListProvider([PollinationsImage])
291 291 )
292 292
293 293 ### Meta ###
@@ -880,48 +880,35 @@ evil = Model(
880 880 best_provider = PollinationsAI
881 881 )
882 882
883 ### Stability AI ###
884 sdxl_1_0 = ImageModel(
885 name = 'sdxl-1.0',
886 base_provider = 'Stability AI',
887 best_provider = ARTA
888 )
889
890 sdxl_l = ImageModel(
891 name = 'sdxl-l',
892 base_provider = 'Stability AI',
893 best_provider = ARTA
894 )
895
896 883 sdxl_turbo = ImageModel(
897 884 name = 'sdxl-turbo',
898 885 base_provider = 'Stability AI',
899 best_provider = IterListProvider([PollinationsImage, ImageLabs])
886 best_provider = IterListProvider([HuggingFaceMedia, PollinationsImage, ImageLabs])
900 887 )
901 888
902 889 sd_3_5_large = ImageModel(
903 890 name = 'sd-3.5-large',
904 891 base_provider = 'Stability AI',
905 best_provider = HuggingSpace
892 best_provider = IterListProvider([HuggingFaceMedia, HuggingSpace])
906 893 )
907 894
908 895 ### Black Forest Labs ###
909 896 flux = ImageModel(
910 897 name = 'flux',
911 898 base_provider = 'Black Forest Labs',
912 best_provider = IterListProvider([PollinationsImage, Websim, Together, HuggingSpace, ARTA])
899 best_provider = IterListProvider([HuggingFaceMedia, PollinationsImage, Websim, Together, HuggingSpace])
913 900 )
914 901
915 902 flux_pro = ImageModel(
916 903 name = 'flux-pro',
917 904 base_provider = 'Black Forest Labs',
918 best_provider = IterListProvider([PollinationsImage, Together, ARTA])
905 best_provider = IterListProvider([PollinationsImage, Together])
919 906 )
920 907
921 908 flux_dev = ImageModel(
922 909 name = 'flux-dev',
923 910 base_provider = 'Black Forest Labs',
924 best_provider = IterListProvider([PollinationsImage, HuggingSpace, Together, ARTA, HuggingChat, HuggingFace])
911 best_provider = IterListProvider([PollinationsImage, HuggingSpace, Together, HuggingChat, HuggingFace])
925 912 )
926 913
927 914 flux_schnell = ImageModel(
Modified g4f/providers/any_provider.py +6 -8
@@ -11,7 +11,7 @@ from ..Provider.hf_space import HuggingSpace
11 11 from ..Provider import __map__
12 12 from ..Provider import Cloudflare, Gemini, Grok, DeepSeekAPI, PerplexityLabs, LambdaChat, PollinationsAI, PuterJS
13 13 from ..Provider import Microsoft_Phi_4_Multimodal, DeepInfraChat, Blackbox, OIVSCodeSer2, OIVSCodeSer0501, TeachAnything
14 from ..Provider import Together, WeWordle, Yqcloud, Chatai, Free2GPT, ARTA, ImageLabs, LegacyLMArena, LMArenaBeta
14 from ..Provider import Together, WeWordle, Yqcloud, Chatai, Free2GPT, ImageLabs, LegacyLMArena, LMArenaBeta
15 15 from ..Provider import EdgeTTS, gTTS, MarkItDown, OpenAIFM, Video
16 16 from ..Provider import HarProvider, HuggingFace, HuggingFaceMedia
17 17 from .base_provider import AsyncGeneratorProvider, ProviderModelMixin
@@ -21,13 +21,13 @@ from .. import debug
21 21
22 22 PROVIERS_LIST_1 = [
23 23 OpenaiChat, PollinationsAI, Cloudflare, PerplexityLabs, Gemini, Grok, DeepSeekAPI, Blackbox, OpenAIFM,
24 OIVSCodeSer2, OIVSCodeSer0501, TeachAnything, Together, WeWordle, Yqcloud, Chatai, Free2GPT, ARTA, ImageLabs,
24 OIVSCodeSer2, OIVSCodeSer0501, TeachAnything, Together, WeWordle, Yqcloud, Chatai, Free2GPT, ImageLabs,
25 25 HarProvider, LegacyLMArena, LMArenaBeta, LambdaChat, CopilotAccount, DeepInfraChat,
26 26 HuggingSpace, HuggingFace, HuggingFaceMedia, Together
27 27 ]
28 28
29 29 PROVIERS_LIST_2 = [
30 OpenaiChat, CopilotAccount, PollinationsAI, PerplexityLabs, Gemini, Grok, ARTA
30 OpenaiChat, CopilotAccount, PollinationsAI, PerplexityLabs, Gemini, Grok
31 31 ]
32 32
33 33 PROVIERS_LIST_3 = [
@@ -48,7 +48,6 @@ LABELS = {
48 48 "phi": "Microsoft: Phi / WizardLM",
49 49 "mistral": "Mistral",
50 50 "PollinationsAI": "Pollinations AI",
51 "ARTA": "ARTA",
52 51 "voices": "Voices",
53 52 "perplexity": "Perplexity Labs",
54 53 "openrouter": "OpenRouter",
@@ -81,7 +80,7 @@ class AnyProvider(AsyncGeneratorProvider, ProviderModelMixin):
81 80 added = False
82 81 # Check for models with prefix
83 82 start = model.split(":")[0]
84 if start in ("PollinationsAI", "ARTA", "openrouter"):
83 if start in ("PollinationsAI", "openrouter"):
85 84 submodel = model.split(":", maxsplit=1)[1]
86 85 if submodel in OpenAIFM.voices or submodel in PollinationsAI.audio_models[PollinationsAI.default_audio_model]:
87 86 groups["voices"].append(submodel)
@@ -184,13 +183,12 @@ class AnyProvider(AsyncGeneratorProvider, ProviderModelMixin):
184 183 try:
185 184 if provider == CopilotAccount:
186 185 all_models.extend(list(provider.model_aliases.keys()))
187 elif provider in [PollinationsAI, ARTA]:
186 elif provider == PollinationsAI:
188 187 all_models.extend([f"{provider.__name__}:{model}" for model in provider.get_models() if model not in all_models])
189 188 cls.audio_models.update({f"{provider.__name__}:{model}": [] for model in provider.get_models() if model in provider.audio_models})
190 189 cls.image_models.extend([f"{provider.__name__}:{model}" for model in provider.get_models() if model in provider.image_models])
191 190 cls.vision_models.extend([f"{provider.__name__}:{model}" for model in provider.get_models() if model in provider.vision_models])
192 if provider == PollinationsAI:
193 all_models.extend(list(provider.model_aliases.keys()))
191 all_models.extend(list(provider.model_aliases.keys()))
194 192 else:
195 193 all_models.extend(provider.get_models())
196 194 except Exception as e:
Modified g4f/providers/base_provider.py +2 -4
@@ -22,8 +22,6 @@ from .helper import concat_chunks
22 22 from ..cookies import get_cookies_dir
23 23 from ..errors import ModelNotFoundError, ResponseError, MissingAuthError, NoValidHarFileError, PaymentRequiredError
24 24
25 DEFAULT_TIMEOUT = 600
26
27 25 SAFE_PARAMETERS = [
28 26 "model", "messages", "stream", "timeout",
29 27 "proxy", "media", "response_format",
@@ -97,7 +95,7 @@ class AbstractProvider(BaseProvider):
97 95 model: str,
98 96 messages: Messages,
99 97 *,
100 timeout: int = DEFAULT_TIMEOUT,
98 timeout: int = None,
101 99 loop: AbstractEventLoop = None,
102 100 executor: ThreadPoolExecutor = None,
103 101 **kwargs
@@ -295,7 +293,7 @@ class AsyncGeneratorProvider(AbstractProvider):
295 293 model: str,
296 294 messages: Messages,
297 295 stream: bool = True,
298 timeout: int = DEFAULT_TIMEOUT,
296 timeout: int = None,
299 297 **kwargs
300 298 ) -> CreateResult:
301 299 """
Modified g4f/tools/files.py +3 -3
@@ -199,7 +199,7 @@ def stream_read_files(bucket_dir: Path, filenames: list[str], delete_files: bool
199 199 else:
200 200 os.unlink(filepath)
201 201 continue
202 yield f"```{filename}\n"
202 yield f"<!-- File: {filename} -->\n"
203 203 if has_pypdf2 and filename.endswith(".pdf"):
204 204 try:
205 205 reader = PyPDF2.PdfReader(file_path)
@@ -237,8 +237,8 @@ def stream_read_files(bucket_dir: Path, filenames: list[str], delete_files: bool
237 237 elif has_beautifulsoup4 and filename.endswith(".html"):
238 238 yield from scrape_text(file_path.read_text(errors="ignore"))
239 239 elif extension in PLAIN_FILE_EXTENSIONS:
240 yield file_path.read_text(errors="ignore")
241 yield f"\n```\n\n"
240 yield file_path.read_text(errors="ignore").strip()
241 yield f"\n<-- End -->\n\n"
242 242
243 243 def cache_stream(stream: Iterator[str], bucket_dir: Path) -> Iterator[str]:
244 244 cache_file = bucket_dir / PLAIN_CACHE