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

XFEstudio/gpt4free

Add ARTA image provider Add ToolSupport in PollinationsAI provider Add default value for model in chat completions Add Streaming Support for PollinationsAI provider

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

代码差异

14 个文件 +353 -103
Modified docs/pydantic_ai.md +7 -4
@@ -109,15 +109,18 @@ This example shows how to initialize an agent with a specific model (`gpt-4o`) a
109 109 from pydantic import BaseModel
110 110 from pydantic_ai import Agent
111 111 from pydantic_ai.models import ModelSettings
112 from g4f.integration.pydantic_ai import patch_infer_model
112 from g4f.integration.pydantic_ai import AIModel
113 from g4f.Provider import PollinationsAI
113 114
114 patch_infer_model("your_api_key")
115 115
116 116 class MyModel(BaseModel):
117 117 city: str
118 118 country: str
119 119
120 agent = Agent('g4f:Groq:llama3-70b-8192', result_type=MyModel, model_settings=ModelSettings(temperature=0))
120 nt = Agent(AIModel(
121 "gpt-4o", # Specify the provider and model
122 PollinationsAI # Use a supported provider to handle tool-based response formatting
123 ), result_type=MyModel, model_settings=ModelSettings(temperature=0))
121 124
122 125 if __name__ == '__main__':
123 126 result = agent.run_sync('The windy city in the US of A.')
@@ -152,7 +155,7 @@ class MyModel(BaseModel):
152 155
153 156 # Create the agent for a model with tool support (using one tool)
154 157 agent = Agent(AIModel(
155 "PollinationsAI:openai", # Specify the provider and model
158 "OpenaiChat:gpt-4o", # Specify the provider and model
156 159 ToolSupportProvider # Use ToolSupportProvider to handle tool-based response formatting
157 160 ), result_type=MyModel, model_settings=ModelSettings(temperature=0))
158 161
Added g4f/Provider/ARTA.py +189 -0
@@ -0,0 +1,189 @@
1 from __future__ import annotations
2
3 import os
4 import time
5 import json
6 from pathlib import Path
7 from aiohttp import ClientSession
8 import asyncio
9
10 from ..typing import AsyncResult, Messages
11 from ..providers.response import ImageResponse, Reasoning
12 from ..errors import ResponseError
13 from ..cookies import get_cookies_dir
14 from .base_provider import AsyncGeneratorProvider, ProviderModelMixin
15 from .helper import format_image_prompt
16
17 class ARTA(AsyncGeneratorProvider, ProviderModelMixin):
18 url = "https://img-gen-prod.ai-arta.com"
19 auth_url = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/signupNewUser?key=AIzaSyB3-71wG0fIt0shj0ee4fvx1shcjJHGrrQ"
20 token_refresh_url = "https://securetoken.googleapis.com/v1/token?key=AIzaSyB3-71wG0fIt0shj0ee4fvx1shcjJHGrrQ"
21 image_generation_url = "https://img-gen-prod.ai-arta.com/api/v1/text2image"
22 status_check_url = "https://img-gen-prod.ai-arta.com/api/v1/text2image/{record_id}/status"
23
24 working = True
25
26 default_model = "Flux"
27 default_image_model = default_model
28 model_aliases = {
29 "flux": "Flux",
30 "medieval": "Medieval",
31 "vincent_van_gogh": "Vincent Van Gogh",
32 "f_dev": "F Dev",
33 "low_poly": "Low Poly",
34 "dreamshaper_xl": "Dreamshaper-xl",
35 "anima_pencil_xl": "Anima-pencil-xl",
36 "biomech": "Biomech",
37 "trash_polka": "Trash Polka",
38 "no_style": "No Style",
39 "cheyenne_xl": "Cheyenne-xl",
40 "chicano": "Chicano",
41 "embroidery_tattoo": "Embroidery tattoo",
42 "red_and_black": "Red and Black",
43 "fantasy_art": "Fantasy Art",
44 "watercolor": "Watercolor",
45 "dotwork": "Dotwork",
46 "old_school_colored": "Old school colored",
47 "realistic_tattoo": "Realistic tattoo",
48 "japanese_2": "Japanese_2",
49 "realistic_stock_xl": "Realistic-stock-xl",
50 "f_pro": "F Pro",
51 "revanimated": "RevAnimated",
52 "katayama_mix_xl": "Katayama-mix-xl",
53 "sdxl_l": "SDXL L",
54 "cor_epica_xl": "Cor-epica-xl",
55 "anime_tattoo": "Anime tattoo",
56 "new_school": "New School",
57 "death_metal": "Death metal",
58 "old_school": "Old School",
59 "juggernaut_xl": "Juggernaut-xl",
60 "photographic": "Photographic",
61 "sdxl_1_0": "SDXL 1.0",
62 "graffiti": "Graffiti",
63 "mini_tattoo": "Mini tattoo",
64 "surrealism": "Surrealism",
65 "neo_traditional": "Neo-traditional",
66 "on_limbs_black": "On limbs black",
67 "yamers_realistic_xl": "Yamers-realistic-xl",
68 "pony_xl": "Pony-xl",
69 "playground_xl": "Playground-xl",
70 "anything_xl": "Anything-xl",
71 "flame_design": "Flame design",
72 "kawaii": "Kawaii",
73 "cinematic_art": "Cinematic Art",
74 "professional": "Professional",
75 "flux_black_ink": "Flux Black Ink"
76 }
77 image_models = [*model_aliases.keys()]
78 models = image_models
79
80 @classmethod
81 def get_auth_file(cls):
82 path = Path(get_cookies_dir())
83 path.mkdir(exist_ok=True)
84 filename = f"auth_{cls.__name__}.json"
85 return path / filename
86
87 @classmethod
88 async def create_token(cls, path: Path, proxy: str | None = None):
89 async with ClientSession() as session:
90 # Step 1: Generate Authentication Token
91 auth_payload = {"clientType": "CLIENT_TYPE_ANDROID"}
92 async with session.post(cls.auth_url, json=auth_payload, proxy=proxy) as auth_response:
93 auth_data = await auth_response.json()
94 auth_token = auth_data.get("idToken")
95 #refresh_token = auth_data.get("refreshToken")
96 if not auth_token:
97 raise ResponseError("Failed to obtain authentication token.")
98 json.dump(auth_data, path.open("w"))
99 return auth_data
100
101 @classmethod
102 async def refresh_token(cls, refresh_token: str, proxy: str = None) -> tuple[str, str]:
103 async with ClientSession() as session:
104 payload = {
105 "grant_type": "refresh_token",
106 "refresh_token": refresh_token,
107 }
108 async with session.post(cls.token_refresh_url, data=payload, proxy=proxy) as response:
109 response_data = await response.json()
110 return response_data.get("id_token"), response_data.get("refresh_token")
111
112 @classmethod
113 async def read_and_refresh_token(cls, proxy: str | None = None) -> str:
114 path = cls.get_auth_file()
115 if path.is_file():
116 auth_data = json.load(path.open("rb"))
117 diff = time.time() - os.path.getmtime(path)
118 expiresIn = int(auth_data.get("expiresIn"))
119 if diff < expiresIn:
120 if diff > expiresIn / 2:
121 auth_data["idToken"], auth_data["refreshToken"] = await cls.refresh_token(auth_data.get("refreshToken"), proxy)
122 json.dump(auth_data, path.open("w"))
123 return auth_data
124 return await cls.create_token(path, proxy)
125
126 @classmethod
127 async def create_async_generator(
128 cls,
129 model: str,
130 messages: Messages,
131 proxy: str = None,
132 prompt: str = None,
133 negative_prompt: str = "blurry, deformed hands, ugly",
134 images_num: int = 1,
135 guidance_scale: int = 7,
136 num_inference_steps: int = 30,
137 aspect_ratio: str = "1:1",
138 **kwargs
139 ) -> AsyncResult:
140 model = cls.get_model(model)
141 prompt = format_image_prompt(messages, prompt)
142
143 # Step 1: Get Authentication Token
144 auth_data = await cls.read_and_refresh_token(proxy)
145
146 async with ClientSession() as session:
147 # Step 2: Generate Images
148 image_payload = {
149 "prompt": prompt,
150 "negative_prompt": negative_prompt,
151 "style": model,
152 "images_num": str(images_num),
153 "cfg_scale": str(guidance_scale),
154 "steps": str(num_inference_steps),
155 "aspect_ratio": aspect_ratio,
156 }
157
158 headers = {
159 "Authorization": auth_data.get("idToken"),
160 }
161
162 async with session.post(cls.image_generation_url, data=image_payload, headers=headers, proxy=proxy) as image_response:
163 image_data = await image_response.json()
164 record_id = image_data.get("record_id")
165
166 if not record_id:
167 raise ResponseError(f"Failed to initiate image generation: {image_data}")
168
169 # Step 3: Check Generation Status
170 status_url = cls.status_check_url.format(record_id=record_id)
171 counter = 0
172 while True:
173 async with session.get(status_url, headers=headers, proxy=proxy) as status_response:
174 status_data = await status_response.json()
175 status = status_data.get("status")
176
177 if status == "DONE":
178 image_urls = [image["url"] for image in status_data.get("response", [])]
179 yield Reasoning(status="Finished")
180 yield ImageResponse(images=image_urls, alt=prompt)
181 return
182 elif status in ("IN_QUEUE", "IN_PROGRESS"):
183 yield Reasoning(status=("Waiting" if status == "IN_QUEUE" else "Generating") + "." * counter)
184 await asyncio.sleep(5) # Poll every 5 seconds
185 counter += 1
186 if counter > 3:
187 counter = 0
188 else:
189 raise ResponseError(f"Image generation failed with status: {status}")
Modified g4f/Provider/PollinationsAI.py +46 -16
@@ -1,5 +1,6 @@
1 1 from __future__ import annotations
2 2
3 import json
3 4 import random
4 5 import requests
5 6 from urllib.parse import quote_plus
@@ -13,7 +14,7 @@ from ..image import to_data_uri
13 14 from ..errors import ModelNotFoundError
14 15 from ..requests.raise_for_status import raise_for_status
15 16 from ..requests.aiohttp import get_connector
16 from ..providers.response import ImageResponse, ImagePreview, FinishReason, Usage, Audio
17 from ..providers.response import ImageResponse, ImagePreview, FinishReason, Usage, Audio, ToolCalls
17 18 from .. import debug
18 19
19 20 DEFAULT_HEADERS = {
@@ -52,7 +53,7 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
52 53 text_models = [default_model]
53 54 image_models = [default_image_model]
54 55 extra_image_models = ["flux-pro", "flux-dev", "flux-schnell", "midjourney", "dall-e-3"]
55 vision_models = [default_vision_model, "gpt-4o-mini", "o1-mini"]
56 vision_models = [default_vision_model, "gpt-4o-mini", "o1-mini", "openai", "openai-large"]
56 57 extra_text_models = vision_models
57 58 _models_loaded = False
58 59 model_aliases = {
@@ -138,6 +139,7 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
138 139 cls,
139 140 model: str,
140 141 messages: Messages,
142 stream: bool = False,
141 143 proxy: str = None,
142 144 prompt: str = None,
143 145 width: int = 1024,
@@ -154,6 +156,7 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
154 156 frequency_penalty: float = None,
155 157 response_format: Optional[dict] = None,
156 158 cache: bool = False,
159 extra_parameters: list[str] = ["tools", "parallel_tool_calls", "tool_choice", "reasoning_effort", "logit_bias"],
157 160 **kwargs
158 161 ) -> AsyncResult:
159 162 cls.get_models()
@@ -193,6 +196,9 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
193 196 response_format=response_format,
194 197 seed=seed,
195 198 cache=cache,
199 stream=stream,
200 extra_parameters=extra_parameters,
201 **kwargs
196 202 ):
197 203 yield result
198 204
@@ -246,7 +252,10 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
246 252 frequency_penalty: float,
247 253 response_format: Optional[dict],
248 254 seed: Optional[int],
249 cache: bool
255 cache: bool,
256 stream: bool,
257 extra_parameters: list[str],
258 **kwargs
250 259 ) -> AsyncResult:
251 260 if not cache and seed is None:
252 261 seed = random.randint(9999, 99999999)
@@ -267,6 +276,13 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
267 276 messages[-1] = last_message
268 277
269 278 async with ClientSession(headers=DEFAULT_HEADERS, connector=get_connector(proxy=proxy)) as session:
279 if model in cls.audio_models or stream:
280 #data["voice"] = random.choice(cls.audio_models[model])
281 url = cls.text_api_endpoint
282 stream = False
283 else:
284 url = cls.openai_endpoint
285 extra_parameters = {param: kwargs[param] for param in extra_parameters if param in kwargs}
270 286 data = filter_none(**{
271 287 "messages": messages,
272 288 "model": model,
@@ -275,17 +291,11 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
275 291 "top_p": top_p,
276 292 "frequency_penalty": frequency_penalty,
277 293 "jsonMode": json_mode,
278 "stream": False,
294 "stream": stream,
279 295 "seed": seed,
280 "cache": cache
296 "cache": cache,
297 **extra_parameters
281 298 })
282 if "gemini" in model:
283 data.pop("seed")
284 if model in cls.audio_models:
285 #data["voice"] = random.choice(cls.audio_models[model])
286 url = cls.text_api_endpoint
287 else:
288 url = cls.openai_endpoint
289 299 async with session.post(url, json=data) as response:
290 300 await raise_for_status(response)
291 301 if response.headers["content-type"] == "audio/mpeg":
@@ -294,16 +304,36 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
294 304 elif response.headers["content-type"].startswith("text/plain"):
295 305 yield await response.text()
296 306 return
307 elif response.headers["content-type"].startswith("text/event-stream"):
308 async for line in response.content:
309 if line.startswith(b"data: "):
310 if line[6:].startswith(b"[DONE]"):
311 break
312 result = json.loads(line[6:])
313 choice = result.get("choices", [{}])[0]
314 content = choice.get("delta", {}).get("content")
315 if content:
316 yield content
317 if "usage" in result:
318 yield Usage(**result["usage"])
319 finish_reason = choice.get("finish_reason")
320 if finish_reason:
321 yield FinishReason(finish_reason)
322 return
297 323 result = await response.json()
298 324 choice = result["choices"][0]
299 325 message = choice.get("message", {})
300 326 content = message.get("content", "")
301 327
302 if "</think>" in content and "<think>" not in content:
303 yield "<think>"
328 if "tool_calls" in message:
329 yield ToolCalls(message["tool_calls"])
330
331 if content is not None:
332 if "</think>" in content and "<think>" not in content:
333 yield "<think>"
304 334
305 if content:
306 yield content.replace("\\(", "(").replace("\\)", ")")
335 if content:
336 yield content.replace("\\(", "(").replace("\\)", ")")
307 337
308 338 if "usage" in result:
309 339 yield Usage(**result["usage"])
Modified g4f/Provider/__init__.py +1 -0
@@ -15,6 +15,7 @@ from .mini_max import HailuoAI, MiniMax
15 15 from .template import OpenaiTemplate, BackendApi
16 16
17 17 from .AllenAI import AllenAI
18 from .ARTA import ARTA
18 19 from .Blackbox import Blackbox
19 20 from .ChatGLM import ChatGLM
20 21 from .ChatGpt import ChatGpt
Modified g4f/Provider/needs_auth/OpenaiChat.py +3 -2
@@ -623,8 +623,9 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
623 623 page.add_handler(nodriver.cdp.network.RequestWillBeSent, on_request)
624 624 page = await browser.get(cls.url)
625 625 user_agent = await page.evaluate("window.navigator.userAgent")
626 await page.select("textarea.text-token-text-primary", 240)
627 await page.evaluate("document.querySelector('textarea.text-token-text-primary').value = 'Hello'")
626 await page.select("#prompt-textarea", 240)
627 await page.evaluate("document.getElementById('prompt-textarea').innerText = 'Hello'")
628 await page.select("[data-testid=\"send-button\"]", 30)
628 629 await page.evaluate("document.querySelector('[data-testid=\"send-button\"]').click()")
629 630 while True:
630 631 body = await page.evaluate("JSON.stringify(window.__remixContext)")
Modified g4f/client/__init__.py +4 -4
@@ -276,7 +276,7 @@ class Completions:
276 276 def create(
277 277 self,
278 278 messages: Messages,
279 model: str,
279 model: str = "",
280 280 provider: Optional[ProviderType] = None,
281 281 stream: Optional[bool] = False,
282 282 proxy: Optional[str] = None,
@@ -330,7 +330,7 @@ class Completions:
330 330 def stream(
331 331 self,
332 332 messages: Messages,
333 model: str,
333 model: str = "",
334 334 **kwargs
335 335 ) -> IterResponse:
336 336 return self.create(messages, model, stream=True, **kwargs)
@@ -564,7 +564,7 @@ class AsyncCompletions:
564 564 def create(
565 565 self,
566 566 messages: Messages,
567 model: str,
567 model: str = "",
568 568 provider: Optional[ProviderType] = None,
569 569 stream: Optional[bool] = False,
570 570 proxy: Optional[str] = None,
@@ -619,7 +619,7 @@ class AsyncCompletions:
619 619 def stream(
620 620 self,
621 621 messages: Messages,
622 model: str,
622 model: str = "",
623 623 **kwargs
624 624 ) -> AsyncIterator[ChatCompletionChunk]:
625 625 return self.create(messages, model, stream=True, **kwargs)
Modified g4f/gui/client/static/css/style.css +3 -21
@@ -112,7 +112,7 @@ body:not(.white) a:visited{
112 112
113 113 .new_version {
114 114 position: absolute;
115 right: 0;
115 left: 0;
116 116 top: 0;
117 117 padding: 10px;
118 118 font-weight: 500;
@@ -143,6 +143,7 @@ body:not(.white) a:visited{
143 143
144 144 .conversation {
145 145 width: 100%;
146 height: 100%;
146 147 display: flex;
147 148 flex-direction: column;
148 149 gap: 5px;
@@ -238,8 +239,7 @@ body:not(.white) a:visited{
238 239
239 240 #close_provider_forms {
240 241 max-width: 210px;
241 margin-left: auto;
242 margin-right: 8px;
242 margin-left: 12px;
243 243 margin-top: 12px;
244 244 }
245 245
@@ -1584,19 +1584,6 @@ form .field.saved .fa-xmark {
1584 1584 }
1585 1585 }
1586 1586
1587
1588 /* Basic adaptation */
1589 .row {
1590 flex-wrap: wrap;
1591 gap: 10px;
1592 }
1593
1594 .conversations, .settings, .conversation {
1595 flex: 1 1 300px;
1596 min-width: 0;
1597 height: 100%;
1598 }
1599
1600 1587 /* Media queries for mobile devices */
1601 1588 @media (max-width: 768px) {
1602 1589 .row {
@@ -1608,11 +1595,6 @@ form .field.saved .fa-xmark {
1608 1595 max-width: 100%;
1609 1596 margin: 0;
1610 1597 }
1611
1612 .conversation {
1613 order: -1;
1614 min-height: 80vh;
1615 }
1616 1598 }
1617 1599
1618 1600 @media (max-width: 480px) {
Modified g4f/gui/client/static/js/chat.v1.js +65 -10
@@ -259,6 +259,10 @@ function register_message_images() {
259 259
260 260 const register_message_buttons = async () => {
261 261 message_box.querySelectorAll(".message .content .provider").forEach(async (el) => {
262 if (el.dataset.click) {
263 return
264 }
265 el.dataset.click = true;
262 266 const provider_forms = document.querySelector(".provider_forms");
263 267 const provider_form = provider_forms.querySelector(`#${el.dataset.provider}-form`);
264 268 const provider_link = el.querySelector("a");
@@ -279,6 +283,10 @@ const register_message_buttons = async () => {
279 283 });
280 284
281 285 message_box.querySelectorAll(".message .fa-xmark").forEach(async (el) => el.addEventListener("click", async () => {
286 if (el.dataset.click) {
287 return
288 }
289 el.dataset.click = true;
282 290 const message_el = get_message_el(el);
283 291 await remove_message(window.conversation_id, message_el.dataset.index);
284 292 message_el.remove();
@@ -286,6 +294,10 @@ const register_message_buttons = async () => {
286 294 }));
287 295
288 296 message_box.querySelectorAll(".message .fa-clipboard").forEach(async (el) => el.addEventListener("click", async () => {
297 if (el.dataset.click) {
298 return
299 }
300 el.dataset.click = true;
289 301 let message_el = get_message_el(el);
290 302 let response = await fetch(message_el.dataset.object_url);
291 303 let copyText = await response.text();
@@ -304,6 +316,10 @@ const register_message_buttons = async () => {
304 316 }))
305 317
306 318 message_box.querySelectorAll(".message .fa-file-export").forEach(async (el) => el.addEventListener("click", async () => {
319 if (el.dataset.click) {
320 return
321 }
322 el.dataset.click = true;
307 323 const elem = window.document.createElement('a');
308 324 let filename = `chat ${new Date().toLocaleString()}.txt`.replaceAll(":", "-");
309 325 const conversation = await get_conversation(window.conversation_id);
@@ -323,6 +339,10 @@ const register_message_buttons = async () => {
323 339 }))
324 340
325 341 message_box.querySelectorAll(".message .fa-volume-high").forEach(async (el) => el.addEventListener("click", async () => {
342 if (el.dataset.click) {
343 return
344 }
345 el.dataset.click = true;
326 346 const message_el = get_message_el(el);
327 347 let audio;
328 348 if (message_el.dataset.synthesize_url) {
@@ -344,6 +364,10 @@ const register_message_buttons = async () => {
344 364 }));
345 365
346 366 message_box.querySelectorAll(".message .regenerate_button").forEach(async (el) => el.addEventListener("click", async () => {
367 if (el.dataset.click) {
368 return
369 }
370 el.dataset.click = true;
347 371 const message_el = get_message_el(el);
348 372 el.classList.add("clicked");
349 373 setTimeout(() => el.classList.remove("clicked"), 1000);
@@ -351,6 +375,10 @@ const register_message_buttons = async () => {
351 375 }));
352 376
353 377 message_box.querySelectorAll(".message .continue_button").forEach(async (el) => el.addEventListener("click", async () => {
378 if (el.dataset.click) {
379 return
380 }
381 el.dataset.click = true;
354 382 if (!el.disabled) {
355 383 el.disabled = true;
356 384 const message_el = get_message_el(el);
@@ -361,11 +389,19 @@ const register_message_buttons = async () => {
361 389 ));
362 390
363 391 message_box.querySelectorAll(".message .fa-whatsapp").forEach(async (el) => el.addEventListener("click", async () => {
392 if (el.dataset.click) {
393 return
394 }
395 el.dataset.click = true;
364 396 const text = get_message_el(el).innerText;
365 397 window.open(`https://wa.me/?text=${encodeURIComponent(text)}`, '_blank');
366 398 }));
367 399
368 400 message_box.querySelectorAll(".message .fa-print").forEach(async (el) => el.addEventListener("click", async () => {
401 if (el.dataset.click) {
402 return
403 }
404 el.dataset.click = true;
369 405 const message_el = get_message_el(el);
370 406 el.classList.add("clicked");
371 407 message_box.scrollTop = 0;
@@ -378,6 +414,10 @@ const register_message_buttons = async () => {
378 414 }));
379 415
380 416 message_box.querySelectorAll(".message .reasoning_title").forEach(async (el) => el.addEventListener("click", async () => {
417 if (el.dataset.click) {
418 return
419 }
420 el.dataset.click = true;
381 421 let text_el = el.parentElement.querySelector(".reasoning_text");
382 422 if (text_el) {
383 423 text_el.classList[text_el.classList.contains("hidden") ? "remove" : "add"]("hidden");
@@ -569,9 +609,9 @@ const prepare_messages = (messages, message_index = -1, do_continue = false, do_
569 609 }
570 610
571 611 // Remove history, only add new user messages
572 let filtered_messages = [];
573 612 // The message_index is null on count total tokens
574 if (document.getElementById('history')?.checked && do_filter && message_index != null) {
613 if (!do_continue && document.getElementById('history')?.checked && do_filter && message_index != null) {
614 let filtered_messages = [];
575 615 while (last_message = messages.pop()) {
576 616 if (last_message["role"] == "user") {
577 617 filtered_messages.push(last_message);
@@ -630,9 +670,9 @@ async function load_provider_parameters(provider) {
630 670 form_el.id = form_id;
631 671 form_el.classList.add("hidden");
632 672 appStorage.setItem(form_el.id, JSON.stringify(parameters_storage[provider]));
633 let old_form = message_box.querySelector(`#${provider}-form`);
673 let old_form = document.getElementById(form_id);
634 674 if (old_form) {
635 provider_forms.removeChild(old_form);
675 old_form.remove();
636 676 }
637 677 Object.entries(parameters_storage[provider]).forEach(([key, value]) => {
638 678 let el_id = `${provider}-${key}`;
@@ -649,7 +689,7 @@ async function load_provider_parameters(provider) {
649 689 saved_value = value;
650 690 }
651 691 field_el.innerHTML = `<span class="label">${key}:</span>
652 <input type="checkbox" id="${el_id}" name="${provider}[${key}]">
692 <input type="checkbox" id="${el_id}" name="${key}">
653 693 <label for="${el_id}" class="toogle" title=""></label>
654 694 <i class="fa-solid fa-xmark"></i>`;
655 695 form_el.appendChild(field_el);
@@ -679,15 +719,15 @@ async function load_provider_parameters(provider) {
679 719 placeholder = value == null ? "null" : value;
680 720 }
681 721 field_el.innerHTML = `<label for="${el_id}" title="">${key}:</label>`;
682 if (Number.isInteger(value) && value != 1) {
683 max = value >= 4096 ? 8192 : 4096;
684 field_el.innerHTML += `<input type="range" id="${el_id}" name="${provider}[${key}]" value="${escapeHtml(value)}" class="slider" min="0" max="${max}" step="1"/><output>${escapeHtml(value)}</output>`;
722 if (Number.isInteger(value)) {
723 max = value == 42 || value >= 4096 ? 8192 : value >= 100 ? 4096 : value == 1 ? 10 : 100;
724 field_el.innerHTML += `<input type="range" id="${el_id}" name="${key}" value="${escapeHtml(value)}" class="slider" min="0" max="${max}" step="1"/><output>${escapeHtml(value)}</output>`;
685 725 field_el.innerHTML += `<i class="fa-solid fa-xmark"></i>`;
686 726 } else if (typeof value == "number") {
687 field_el.innerHTML += `<input type="range" id="${el_id}" name="${provider}[${key}]" value="${escapeHtml(value)}" class="slider" min="0" max="2" step="0.1"/><output>${escapeHtml(value)}</output>`;
727 field_el.innerHTML += `<input type="range" id="${el_id}" name="${key}" value="${escapeHtml(value)}" class="slider" min="0" max="2" step="0.1"/><output>${escapeHtml(value)}</output>`;
688 728 field_el.innerHTML += `<i class="fa-solid fa-xmark"></i>`;
689 729 } else {
690 field_el.innerHTML += `<textarea id="${el_id}" name="${provider}[${key}]"></textarea>`;
730 field_el.innerHTML += `<textarea id="${el_id}" name="${key}"></textarea>`;
691 731 field_el.innerHTML += `<i class="fa-solid fa-xmark"></i>`;
692 732 input_el = field_el.querySelector("textarea");
693 733 if (value != null) {
@@ -723,6 +763,7 @@ async function load_provider_parameters(provider) {
723 763 input_el = field_el.querySelector("input");
724 764 input_el.dataset.value = value;
725 765 input_el.value = saved_value;
766 input_el.nextElementSibling.value = input_el.value;
726 767 input_el.oninput = () => {
727 768 input_el.nextElementSibling.value = input_el.value;
728 769 field_el.classList.add("saved");
@@ -1008,6 +1049,7 @@ const ask_gpt = async (message_id, message_index = -1, regenerate = false, provi
1008 1049 }
1009 1050 await safe_remove_cancel_button();
1010 1051 await register_message_images();
1052 await register_message_buttons();
1011 1053 await load_conversations();
1012 1054 regenerate_button.classList.remove("regenerate-hidden");
1013 1055 }
@@ -1035,6 +1077,18 @@ const ask_gpt = async (message_id, message_index = -1, regenerate = false, provi
1035 1077 }
1036 1078 }
1037 1079 const ignored = Array.from(settings.querySelectorAll("input.provider:not(:checked)")).map((el)=>el.value);
1080 let extra_parameters = {};
1081 document.getElementById(`${provider}-form`)?.querySelectorAll(".saved input, .saved textarea").forEach(async (el) => {
1082 let value = el.type == "checkbox" ? el.checked : el.value;
1083 extra_parameters[el.name] = value;
1084 if (el.type == "textarea") {
1085 try {
1086 extra_parameters[el.name] = await JSON.parse(value);
1087 } catch (e) {
1088 }
1089 }
1090 });
1091 console.log(extra_parameters);
1038 1092 await api("conversation", {
1039 1093 id: message_id,
1040 1094 conversation_id: window.conversation_id,
@@ -1048,6 +1102,7 @@ const ask_gpt = async (message_id, message_index = -1, regenerate = false, provi
1048 1102 api_key: api_key,
1049 1103 api_base: api_base,
1050 1104 ignored: ignored,
1105 ...extra_parameters
1051 1106 }, Object.values(image_storage), message_id, scroll, finish_message);
1052 1107 } catch (e) {
1053 1108 console.error(e);
Modified g4f/gui/server/api.py +3 -17
@@ -89,25 +89,17 @@ class Api:
89 89 ensure_images_dir()
90 90 return send_from_directory(os.path.abspath(images_dir), name)
91 91
92 def _prepare_conversation_kwargs(self, json_data: dict, kwargs: dict):
92 def _prepare_conversation_kwargs(self, json_data: dict):
93 kwargs = {**json_data}
93 94 model = json_data.get('model')
94 95 provider = json_data.get('provider')
95 96 messages = json_data.get('messages')
96 api_key = json_data.get("api_key")
97 if api_key:
98 kwargs["api_key"] = api_key
99 api_base = json_data.get("api_base")
100 if api_base:
101 kwargs["api_base"] = api_base
102 97 kwargs["tool_calls"] = [{
103 98 "function": {
104 99 "name": "bucket_tool"
105 100 },
106 101 "type": "function"
107 102 }]
108 web_search = json_data.get('web_search')
109 if web_search:
110 kwargs["web_search"] = web_search
111 103 action = json_data.get('action')
112 104 if action == "continue":
113 105 kwargs["tool_calls"].append({
@@ -117,19 +109,13 @@ class Api:
117 109 "type": "function"
118 110 })
119 111 conversation = json_data.get("conversation")
120 if conversation is not None:
112 if isinstance(conversation, dict):
121 113 kwargs["conversation"] = JsonConversation(**conversation)
122 114 else:
123 115 conversation_id = json_data.get("conversation_id")
124 116 if conversation_id and provider:
125 117 if provider in conversations and conversation_id in conversations[provider]:
126 118 kwargs["conversation"] = conversations[provider][conversation_id]
127
128 if json_data.get("ignored"):
129 kwargs["ignored"] = json_data["ignored"]
130 if json_data.get("action"):
131 kwargs["action"] = json_data["action"]
132
133 119 return {
134 120 "model": model,
135 121 "provider": provider,
Modified g4f/gui/server/backend_api.py +6 -9
@@ -106,17 +106,16 @@ class Backend_Api(Api):
106 106 Returns:
107 107 Response: A Flask response object for streaming.
108 108 """
109 kwargs = {}
109 if "json" in request.form:
110 json_data = json.loads(request.form['json'])
111 else:
112 json_data = request.json
110 113 if "files" in request.files:
111 114 images = []
112 115 for file in request.files.getlist('files'):
113 116 if file.filename != '' and is_allowed_extension(file.filename):
114 117 images.append((to_image(file.stream, file.filename.endswith('.svg')), file.filename))
115 kwargs['images'] = images
116 if "json" in request.form:
117 json_data = json.loads(request.form['json'])
118 else:
119 json_data = request.json
118 json_data['images'] = images
120 119
121 120 if app.demo and not json_data.get("provider"):
122 121 model = json_data.get("model")
@@ -126,9 +125,7 @@ class Backend_Api(Api):
126 125 if not model or model == "default":
127 126 json_data["model"] = models.demo_models["default"][0].name
128 127 json_data["provider"] = random.choice(models.demo_models["default"][1])
129 if "images" in json_data:
130 kwargs["images"] = json_data["images"]
131 kwargs = self._prepare_conversation_kwargs(json_data, kwargs)
128 kwargs = self._prepare_conversation_kwargs(json_data)
132 129 return self.app.response_class(
133 130 self._create_response_stream(
134 131 kwargs,
Modified g4f/gui/server/js_api.py +3 -3
@@ -21,12 +21,12 @@ from .api import Api
21 21
22 22 class JsApi(Api):
23 23
24 def get_conversation(self, options: dict, message_id: str = None, scroll: bool = None, **kwargs) -> Iterator:
24 def get_conversation(self, options: dict, message_id: str = None, scroll: bool = None) -> Iterator:
25 25 window = webview.windows[0]
26 26 if hasattr(self, "image") and self.image is not None:
27 kwargs["image"] = open(self.image, "rb")
27 options["image"] = open(self.image, "rb")
28 28 for message in self._create_response_stream(
29 self._prepare_conversation_kwargs(options, kwargs),
29 self._prepare_conversation_kwargs(options),
30 30 options.get("conversation_id"),
31 31 options.get('provider')
32 32 ):
Modified g4f/providers/base_provider.py +1 -1
@@ -34,7 +34,7 @@ SAFE_PARAMETERS = [
34 34 "api_key", "api_base", "seed", "width", "height",
35 35 "proof_token", "max_retries", "web_search",
36 36 "guidance_scale", "num_inference_steps", "randomize_seed",
37 "safe", "enhance", "private",
37 "safe", "enhance", "private", "aspect_ratio", "images_num",
38 38 ]
39 39
40 40 BASIC_PARAMETERS = {
Modified g4f/providers/tool_support.py +12 -5
Modified g4f/tools/run_tools.py +10 -11