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

XFEstudio/gpt4free

Add many parameters to API endpoints Support conversational HuggingFace providers Fix streaming in PollinationsAI provider

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

代码差异

7 个文件 +205 -152
Modified g4f/Provider/ARTA.py +3 -3
@@ -131,7 +131,7 @@ class ARTA(AsyncGeneratorProvider, ProviderModelMixin):
131 131 proxy: str = None,
132 132 prompt: str = None,
133 133 negative_prompt: str = "blurry, deformed hands, ugly",
134 images_num: int = 1,
134 n: int = 1,
135 135 guidance_scale: int = 7,
136 136 num_inference_steps: int = 30,
137 137 aspect_ratio: str = "1:1",
@@ -149,7 +149,7 @@ class ARTA(AsyncGeneratorProvider, ProviderModelMixin):
149 149 "prompt": prompt,
150 150 "negative_prompt": negative_prompt,
151 151 "style": model,
152 "images_num": str(images_num),
152 "images_num": str(n),
153 153 "cfg_scale": str(guidance_scale),
154 154 "steps": str(num_inference_steps),
155 155 "aspect_ratio": aspect_ratio,
@@ -181,7 +181,7 @@ class ARTA(AsyncGeneratorProvider, ProviderModelMixin):
181 181 return
182 182 elif status in ("IN_QUEUE", "IN_PROGRESS"):
183 183 yield Reasoning(status=("Waiting" if status == "IN_QUEUE" else "Generating") + "." * counter)
184 await asyncio.sleep(5) # Poll every 5 seconds
184 await asyncio.sleep(2) # Poll every 5 seconds
185 185 counter += 1
186 186 if counter > 3:
187 187 counter = 0
Modified g4f/Provider/PollinationsAI.py +10 -13
@@ -49,7 +49,7 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
49 49 # Models configuration
50 50 default_model = "openai"
51 51 default_image_model = "flux"
52 default_vision_model = "gpt-4o"
52 default_vision_model = default_model
53 53 text_models = [default_model]
54 54 image_models = [default_image_model]
55 55 extra_image_models = ["flux-pro", "flux-dev", "flux-schnell", "midjourney", "dall-e-3"]
@@ -141,6 +141,8 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
141 141 messages: Messages,
142 142 stream: bool = False,
143 143 proxy: str = None,
144 cache: bool = False,
145 # Image generation parameters
144 146 prompt: str = None,
145 147 width: int = 1024,
146 148 height: int = 1024,
@@ -149,19 +151,18 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
149 151 private: bool = False,
150 152 enhance: bool = False,
151 153 safe: bool = False,
154 # Text generation parameters
152 155 images: ImagesType = None,
153 156 temperature: float = None,
154 157 presence_penalty: float = None,
155 158 top_p: float = 1,
156 159 frequency_penalty: float = None,
157 160 response_format: Optional[dict] = None,
158 cache: bool = False,
159 extra_parameters: list[str] = ["tools", "parallel_tool_calls", "tool_choice", "reasoning_effort", "logit_bias"],
161 extra_parameters: list[str] = ["tools", "parallel_tool_calls", "tool_choice", "reasoning_effort", "logit_bias", "voice"],
160 162 **kwargs
161 163 ) -> AsyncResult:
164 # Load model list
162 165 cls.get_models()
163 if images is not None and not model:
164 model = cls.default_vision_model
165 166 try:
166 167 model = cls.get_model(model)
167 168 except ModelNotFoundError:
@@ -231,7 +232,7 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
231 232 }
232 233 query = "&".join(f"{k}={quote_plus(v)}" for k, v in params.items() if v is not None)
233 234 url = f"{cls.image_api_endpoint}prompt/{quote_plus(prompt)}?{query}"
234 yield ImagePreview(url, prompt)
235 #yield ImagePreview(url, prompt)
235 236
236 237 async with ClientSession(headers=DEFAULT_HEADERS, connector=get_connector(proxy=proxy)) as session:
237 238 async with session.get(url, allow_redirects=True) as response:
@@ -276,7 +277,7 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
276 277 messages[-1] = last_message
277 278
278 279 async with ClientSession(headers=DEFAULT_HEADERS, connector=get_connector(proxy=proxy)) as session:
279 if model in cls.audio_models or stream:
280 if model in cls.audio_models:
280 281 #data["voice"] = random.choice(cls.audio_models[model])
281 282 url = cls.text_api_endpoint
282 283 stream = False
@@ -328,12 +329,8 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
328 329 if "tool_calls" in message:
329 330 yield ToolCalls(message["tool_calls"])
330 331
331 if content is not None:
332 if "</think>" in content and "<think>" not in content:
333 yield "<think>"
334
335 if content:
336 yield content.replace("\\(", "(").replace("\\)", ")")
332 if content:
333 yield content
337 334
338 335 if "usage" in result:
339 336 yield Usage(**result["usage"])
Modified g4f/Provider/hf/HuggingFaceAPI.py +42 -21
@@ -1,13 +1,15 @@
1 1 from __future__ import annotations
2 2
3 import requests
4
3 5 from ...providers.types import Messages
4 6 from ...typing import ImagesType
5 7 from ...requests import StreamSession, raise_for_status
6 8 from ...errors import ModelNotSupportedError
7 9 from ...providers.helper import get_last_user_message
10 from ...providers.response import ProviderInfo
8 11 from ..template.OpenaiTemplate import OpenaiTemplate
9 from .models import model_aliases, vision_models, default_vision_model, llama_models
10 from .HuggingChat import HuggingChat
12 from .models import model_aliases, vision_models, default_vision_model, llama_models, text_models
11 13 from ... import debug
12 14
13 15 class HuggingFaceAPI(OpenaiTemplate):
@@ -22,32 +24,47 @@ class HuggingFaceAPI(OpenaiTemplate):
22 24 default_vision_model = default_vision_model
23 25 vision_models = vision_models
24 26 model_aliases = model_aliases
27 fallback_models = text_models + vision_models
28
29 provider_mapping: dict[str, dict] = {}
25 30
26 pipeline_tags: dict[str, str] = {}
31 @classmethod
32 def get_model(cls, model: str, **kwargs) -> str:
33 try:
34 return super().get_model(model, **kwargs)
35 except ModelNotSupportedError:
36 return model
27 37
28 38 @classmethod
29 def get_models(cls, **kwargs):
39 def get_models(cls, **kwargs) -> list[str]:
30 40 if not cls.models:
31 HuggingChat.get_models()
32 cls.models = HuggingChat.text_models.copy()
33 for model in cls.vision_models:
34 if model not in cls.models:
35 cls.models.append(model)
41 url = "https://huggingface.co/api/models?inference=warm&&expand[]=inferenceProviderMapping"
42 response = requests.get(url)
43 if response.ok:
44 cls.models = [
45 model["id"]
46 for model in response.json()
47 if [
48 provider
49 for provider in model.get("inferenceProviderMapping")
50 if provider.get("task") == "conversational"]]
51 else:
52 cls.models = cls.fallback_models
36 53 return cls.models
37 54
38 55 @classmethod
39 async def get_pipline_tag(cls, model: str, api_key: str = None):
40 if model in cls.pipeline_tags:
41 return cls.pipeline_tags[model]
56 async def get_mapping(cls, model: str, api_key: str = None):
57 if model in cls.provider_mapping:
58 return cls.provider_mapping[model]
42 59 async with StreamSession(
43 60 timeout=30,
44 61 headers=cls.get_headers(False, api_key),
45 62 ) as session:
46 async with session.get(f"https://huggingface.co/api/models/{model}") as response:
63 async with session.get(f"https://huggingface.co/api/models/{model}?expand[]=inferenceProviderMapping") as response:
47 64 await raise_for_status(response)
48 65 model_data = await response.json()
49 cls.pipeline_tags[model] = model_data.get("pipeline_tag")
50 return cls.pipeline_tags[model]
66 cls.provider_mapping[model] = model_data.get("inferenceProviderMapping")
67 return cls.provider_mapping[model]
51 68
52 69 @classmethod
53 70 async def create_async_generator(
@@ -65,12 +82,16 @@ class HuggingFaceAPI(OpenaiTemplate):
65 82 model = llama_models["text"] if images is None else llama_models["vision"]
66 83 if model in cls.model_aliases:
67 84 model = cls.model_aliases[model]
68 api_base = f"https://api-inference.huggingface.co/models/{model}/v1"
69 pipeline_tag = await cls.get_pipline_tag(model, api_key)
70 if pipeline_tag not in ("text-generation", "image-text-to-text"):
71 raise ModelNotSupportedError(f"Model is not supported: {model} in: {cls.__name__} pipeline_tag: {pipeline_tag}")
72 elif images and pipeline_tag != "image-text-to-text":
73 raise ModelNotSupportedError(f"Model does not support images: {model} in: {cls.__name__} pipeline_tag: {pipeline_tag}")
85 provider_mapping = await cls.get_mapping(model, api_key)
86 for provider_key in provider_mapping:
87 api_path = provider_key if provider_key == "novita" else f"{provider_key}/v1"
88 api_base = f"https://router.huggingface.co/{api_path}"
89 task = provider_mapping[provider_key]["task"]
90 if task != "conversational":
91 raise ModelNotSupportedError(f"Model is not supported: {model} in: {cls.__name__} task: {task}")
92 model = provider_mapping[provider_key]["providerId"]
93 yield ProviderInfo(**{**cls.get_dict(), "label": f"HuggingFace ({provider_key})"})
94 break
74 95 start = calculate_lenght(messages)
75 96 if start > max_inputs_lenght:
76 97 if len(messages) > 6:
Modified g4f/__init__.py +2 -2
@@ -50,7 +50,7 @@ class ChatCompletion:
50 50
51 51 result = provider.get_create_function()(model, messages, stream=stream, **kwargs)
52 52
53 return result if stream else concat_chunks(result)
53 return result if stream or ignore_stream else concat_chunks(result)
54 54
55 55 @staticmethod
56 56 def create_async(model : Union[Model, str],
@@ -74,7 +74,7 @@ class ChatCompletion:
74 74
75 75 result = provider.get_async_create_function()(model, messages, stream=stream, **kwargs)
76 76
77 if not stream:
77 if not stream and not ignore_stream:
78 78 if hasattr(result, "__aiter__"):
79 79 result = async_concat_chunks(result)
80 80
Modified g4f/api/stubs.py +11 -1
@@ -18,6 +18,9 @@ class ChatCompletionsConfig(BaseModel):
18 18 image_name: Optional[str] = None
19 19 images: Optional[list[tuple[str, str]]] = None
20 20 temperature: Optional[float] = None
21 presence_penalty: Optional[float] = None
22 frequency_penalty: Optional[float] = None
23 top_p: Optional[float] = None
21 24 max_tokens: Optional[int] = None
22 25 stop: Union[list[str], str, None] = None
23 26 api_key: Optional[str] = None
@@ -27,7 +30,6 @@ class ChatCompletionsConfig(BaseModel):
27 30 conversation_id: Optional[str] = None
28 31 conversation: Optional[dict] = None
29 32 history_disabled: Optional[bool] = None
30 auto_continue: Optional[bool] = None
31 33 timeout: Optional[int] = None
32 34 tool_calls: list = Field(default=[], examples=[[
33 35 {
@@ -48,6 +50,14 @@ class ImageGenerationConfig(BaseModel):
48 50 response_format: Optional[str] = None
49 51 api_key: Optional[str] = None
50 52 proxy: Optional[str] = None
53 width: Optional[int] = None
54 height: Optional[int] = None
55 num_inference_steps: Optional[int] = None
56 seed: Optional[int] = None
57 guidance_scale: Optional[int] = None
58 aspect_ratio: Optional[str] = None
59 n: Optional[int] = None
60 negative_prompt: Optional[str] = None
51 61
52 62 class ProviderResponseModel(BaseModel):
53 63 id: str
Modified g4f/gui/client/static/js/chat.v1.js +134 -109
@@ -40,12 +40,12 @@ let parameters_storage = {};
40 40 let finish_storage = {};
41 41 let usage_storage = {};
42 42 let reasoning_storage = {};
43 let generate_storage = {};
44 43 let title_ids_storage = {};
45 44 let image_storage = {};
46 45 let is_demo = false;
47 46 let wakeLock = null;
48 47 let countTokensEnabled = true;
48 let reloadConversation = true;
49 49
50 50 messageInput.addEventListener("blur", () => {
51 51 document.documentElement.scrollTop = 0;
@@ -203,10 +203,12 @@ const highlight = (container) => {
203 203
204 204 const get_message_el = (el) => {
205 205 let message_el = el;
206 while(!("index" in message_el.dataset) && message_el.parentElement) {
206 while(!(message_el.classList.contains('message')) && message_el.parentElement) {
207 207 message_el = message_el.parentElement;
208 208 }
209 return message_el;
209 if (message_el.classList.contains('message')) {
210 return message_el;
211 }
210 212 }
211 213
212 214 function register_message_images() {
@@ -220,7 +222,7 @@ function register_message_images() {
220 222 el.onerror = () => {
221 223 let indexCommand;
222 224 if ((indexCommand = el.src.indexOf("/generate/")) >= 0) {
223 generate_storage[window.conversation_id] = true;
225 reloadConversation = false;
224 226 indexCommand = indexCommand + "/generate/".length + 1;
225 227 let newPath = el.src.substring(indexCommand)
226 228 let filename = newPath.replace(/(?:\?.+?|$)/, "");
@@ -282,147 +284,170 @@ const register_message_buttons = async () => {
282 284 });
283 285 });
284 286
285 message_box.querySelectorAll(".message .fa-xmark").forEach(async (el) => el.addEventListener("click", async () => {
287 message_box.querySelectorAll(".message .fa-xmark").forEach(async (el) => {
286 288 if (el.dataset.click) {
287 289 return
288 290 }
289 291 el.dataset.click = true;
290 const message_el = get_message_el(el);
291 await remove_message(window.conversation_id, message_el.dataset.index);
292 message_el.remove();
293 await safe_load_conversation(window.conversation_id, false);
294 }));
292 el.addEventListener("click", async () => {
293 const message_el = get_message_el(el);
294 if (message_el) {
295 if ("index" in message_el.dataset) {
296 await remove_message(window.conversation_id, message_el.dataset.index);
297 }
298 message_el.remove();
299 }
300 reloadConversation = true;
301 await safe_load_conversation(window.conversation_id, false);
302 });
303 });
295 304
296 message_box.querySelectorAll(".message .fa-clipboard").forEach(async (el) => el.addEventListener("click", async () => {
305 message_box.querySelectorAll(".message .fa-clipboard").forEach(async (el) => {
297 306 if (el.dataset.click) {
298 307 return
299 308 }
300 309 el.dataset.click = true;
301 let message_el = get_message_el(el);
302 let response = await fetch(message_el.dataset.object_url);
303 let copyText = await response.text();
304 try {
305 if (!navigator.clipboard) {
306 throw new Error("navigator.clipboard: Clipboard API unavailable.");
310 el.addEventListener("click", async () => {
311 let message_el = get_message_el(el);
312 let response = await fetch(message_el.dataset.object_url);
313 let copyText = await response.text();
314 try {
315 if (!navigator.clipboard) {
316 throw new Error("navigator.clipboard: Clipboard API unavailable.");
317 }
318 await navigator.clipboard.writeText(copyText);
319 } catch (e) {
320 console.error(e);
321 console.error("Clipboard API writeText() failed! Fallback to document.exec(\"copy\")...");
322 fallback_clipboard(copyText);
307 323 }
308 await navigator.clipboard.writeText(copyText);
309 } catch (e) {
310 console.error(e);
311 console.error("Clipboard API writeText() failed! Fallback to document.exec(\"copy\")...");
312 fallback_clipboard(copyText);
313 }
314 el.classList.add("clicked");
315 setTimeout(() => el.classList.remove("clicked"), 1000);
316 }))
324 el.classList.add("clicked");
325 setTimeout(() => el.classList.remove("clicked"), 1000);
326 });
327 })
317 328
318 message_box.querySelectorAll(".message .fa-file-export").forEach(async (el) => el.addEventListener("click", async () => {
329 message_box.querySelectorAll(".message .fa-file-export").forEach(async (el) => {
319 330 if (el.dataset.click) {
320 331 return
321 332 }
322 333 el.dataset.click = true;
323 const elem = window.document.createElement('a');
324 let filename = `chat ${new Date().toLocaleString()}.txt`.replaceAll(":", "-");
325 const conversation = await get_conversation(window.conversation_id);
326 let buffer = "";
327 conversation.items.forEach(message => {
328 if (message.reasoning) {
329 buffer += render_reasoning_text(message.reasoning);
330 }
331 buffer += `${message.role == 'user' ? 'User' : 'Assistant'}: ${message.content.trim()}\n\n`;
334 el.addEventListener("click", async () => {
335 const elem = window.document.createElement('a');
336 let filename = `chat ${new Date().toLocaleString()}.txt`.replaceAll(":", "-");
337 const conversation = await get_conversation(window.conversation_id);
338 let buffer = "";
339 conversation.items.forEach(message => {
340 if (message.reasoning) {
341 buffer += render_reasoning_text(message.reasoning);
342 }
343 buffer += `${message.role == 'user' ? 'User' : 'Assistant'}: ${message.content.trim()}\n\n`;
344 });
345 var download = document.getElementById("download");
346 download.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(buffer.trim()));
347 download.setAttribute("download", filename);
348 download.click();
349 el.classList.add("clicked");
350 setTimeout(() => el.classList.remove("clicked"), 1000);
332 351 });
333 var download = document.getElementById("download");
334 download.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(buffer.trim()));
335 download.setAttribute("download", filename);
336 download.click();
337 el.classList.add("clicked");
338 setTimeout(() => el.classList.remove("clicked"), 1000);
339 }))
340
341 message_box.querySelectorAll(".message .fa-volume-high").forEach(async (el) => el.addEventListener("click", async () => {
352 })
353
354 message_box.querySelectorAll(".message .fa-volume-high").forEach(async (el) => {
342 355 if (el.dataset.click) {
343 356 return
344 357 }
345 358 el.dataset.click = true;
346 const message_el = get_message_el(el);
347 let audio;
348 if (message_el.dataset.synthesize_url) {
349 el.classList.add("active");
350 setTimeout(()=>el.classList.remove("active"), 2000);
351 const media_player = document.querySelector(".media_player");
352 if (!media_player.classList.contains("show")) {
353 media_player.classList.add("show");
354 audio = new Audio(message_el.dataset.synthesize_url);
355 audio.controls = true;
356 media_player.appendChild(audio);
357 } else {
358 audio = media_player.querySelector("audio");
359 audio.src = message_el.dataset.synthesize_url;
359 el.addEventListener("click", async () => {
360 const message_el = get_message_el(el);
361 let audio;
362 if (message_el.dataset.synthesize_url) {
363 el.classList.add("active");
364 setTimeout(()=>el.classList.remove("active"), 2000);
365 const media_player = document.querySelector(".media_player");
366 if (!media_player.classList.contains("show")) {
367 media_player.classList.add("show");
368 audio = new Audio(message_el.dataset.synthesize_url);
369 audio.controls = true;
370 media_player.appendChild(audio);
371 } else {
372 audio = media_player.querySelector("audio");
373 audio.src = message_el.dataset.synthesize_url;
374 }
375 audio.play();
376 return;
360 377 }
361 audio.play();
362 return;
363 }
364 }));
378 });
379 });
365 380
366 message_box.querySelectorAll(".message .regenerate_button").forEach(async (el) => el.addEventListener("click", async () => {
381 message_box.querySelectorAll(".message .regenerate_button").forEach(async (el) => {
367 382 if (el.dataset.click) {
368 383 return
369 384 }
370 385 el.dataset.click = true;
371 const message_el = get_message_el(el);
372 el.classList.add("clicked");
373 setTimeout(() => el.classList.remove("clicked"), 1000);
374 await ask_gpt(get_message_id(), message_el.dataset.index);
375 }));
386 el.addEventListener("click", async () => {
387 const message_el = get_message_el(el);
388 el.classList.add("clicked");
389 setTimeout(() => el.classList.remove("clicked"), 1000);
390 await ask_gpt(get_message_id(), message_el.dataset.index);
391 });
392 });
376 393
377 message_box.querySelectorAll(".message .continue_button").forEach(async (el) => el.addEventListener("click", async () => {
394 message_box.querySelectorAll(".message .continue_button").forEach(async (el) => {
378 395 if (el.dataset.click) {
379 396 return
380 397 }
381 398 el.dataset.click = true;
382 if (!el.disabled) {
383 el.disabled = true;
384 const message_el = get_message_el(el);
385 el.classList.add("clicked");
386 setTimeout(() => {el.classList.remove("clicked"); el.disabled = false}, 1000);
387 await ask_gpt(get_message_id(), message_el.dataset.index, false, null, null, "continue");
388 }}
389 ));
399 el.addEventListener("click", async () => {
400 if (!el.disabled) {
401 el.disabled = true;
402 const message_el = get_message_el(el);
403 el.classList.add("clicked");
404 setTimeout(() => {el.classList.remove("clicked"); el.disabled = false}, 1000);
405 await ask_gpt(get_message_id(), message_el.dataset.index, false, null, null, "continue");
406 }
407 });
408 });
390 409
391 message_box.querySelectorAll(".message .fa-whatsapp").forEach(async (el) => el.addEventListener("click", async () => {
410 message_box.querySelectorAll(".message .fa-whatsapp").forEach(async (el) => {
392 411 if (el.dataset.click) {
393 412 return
394 413 }
395 414 el.dataset.click = true;
396 const text = get_message_el(el).innerText;
397 window.open(`https://wa.me/?text=${encodeURIComponent(text)}`, '_blank');
398 }));
415 el.addEventListener("click", async () => {
416 const text = get_message_el(el).innerText;
417 window.open(`https://wa.me/?text=${encodeURIComponent(text)}`, '_blank');
418 });
419 });
399 420
400 message_box.querySelectorAll(".message .fa-print").forEach(async (el) => el.addEventListener("click", async () => {
421 message_box.querySelectorAll(".message .fa-print").forEach(async (el) => {
401 422 if (el.dataset.click) {
402 423 return
403 424 }
404 425 el.dataset.click = true;
405 const message_el = get_message_el(el);
406 el.classList.add("clicked");
407 message_box.scrollTop = 0;
408 message_el.classList.add("print");
409 setTimeout(() => {
410 el.classList.remove("clicked");
411 message_el.classList.remove("print");
412 }, 1000);
413 window.print()
414 }));
426 el.addEventListener("click", async () => {
427 const message_el = get_message_el(el);
428 el.classList.add("clicked");
429 message_box.scrollTop = 0;
430 message_el.classList.add("print");
431 setTimeout(() => {
432 el.classList.remove("clicked");
433 message_el.classList.remove("print");
434 }, 1000);
435 window.print()
436 });
437 });
415 438
416 message_box.querySelectorAll(".message .reasoning_title").forEach(async (el) => el.addEventListener("click", async () => {
439 message_box.querySelectorAll(".message .reasoning_title").forEach(async (el) => {
417 440 if (el.dataset.click) {
418 441 return
419 442 }
420 443 el.dataset.click = true;
421 let text_el = el.parentElement.querySelector(".reasoning_text");
422 if (text_el) {
423 text_el.classList[text_el.classList.contains("hidden") ? "remove" : "add"]("hidden");
424 }
425 }));
444 el.addEventListener("click", async () => {
445 let text_el = el.parentElement.querySelector(".reasoning_text");
446 if (text_el) {
447 text_el.classList.toogle("hidden");
448 }
449 });
450 });
426 451 }
427 452
428 453 const delete_conversations = async () => {
@@ -842,7 +867,7 @@ async function add_message_chunk(message, message_id, provider, scroll, finish_m
842 867 audio.controls = true;
843 868 content_map.inner.appendChild(audio);
844 869 audio.play();
845 generate_storage[window.conversation_id] = true;
870 reloadConversation = false;
846 871 } else if (message.type == "content") {
847 872 message_storage[message_id] += message.content;
848 873 update_message(content_map, message_id, null, scroll);
@@ -866,10 +891,11 @@ async function add_message_chunk(message, message_id, provider, scroll, finish_m
866 891 } else if (message.type == "reasoning") {
867 892 if (!reasoning_storage[message_id]) {
868 893 reasoning_storage[message_id] = message;
869 reasoning_storage[message_id].text = message.token || "";
894 reasoning_storage[message_id].text = message_storage[message_id];
895 message_storage[message_id] = "";
870 896 } else if (message.status) {
871 897 reasoning_storage[message_id].status = message.status;
872 } else if (message.token) {
898 } if (message.token) {
873 899 reasoning_storage[message_id].text += message.token;
874 900 }
875 901 update_message(content_map, message_id, render_reasoning(reasoning_storage[message_id]), scroll);
@@ -1039,7 +1065,7 @@ const ask_gpt = async (message_id, message_index = -1, regenerate = false, provi
1039 1065 delete controller_storage[message_id];
1040 1066 }
1041 1067 // Reload conversation if no error
1042 if (!error_storage[message_id] && !generate_storage[window.conversation_id]) {
1068 if (!error_storage[message_id] && reloadConversation) {
1043 1069 await safe_load_conversation(window.conversation_id, scroll);
1044 1070 }
1045 1071 let cursorDiv = message_el.querySelector(".cursor");
@@ -1077,18 +1103,17 @@ const ask_gpt = async (message_id, message_index = -1, regenerate = false, provi
1077 1103 }
1078 1104 }
1079 1105 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) => {
1106 let extra_parameters = [];
1107 for (el of document.getElementById(`${provider}-form`)?.querySelectorAll(".saved input, .saved textarea") || []) {
1082 1108 let value = el.type == "checkbox" ? el.checked : el.value;
1083 extra_parameters[el.name] = value;
1084 1109 if (el.type == "textarea") {
1085 1110 try {
1086 extra_parameters[el.name] = await JSON.parse(value);
1111 value = await JSON.parse(value);
1087 1112 } catch (e) {
1088 1113 }
1089 1114 }
1090 });
1091 console.log(extra_parameters);
1115 extra_parameters[el.name] = value;
1116 };
1092 1117 await api("conversation", {
1093 1118 id: message_id,
1094 1119 conversation_id: window.conversation_id,
Modified g4f/tools/run_tools.py +3 -3
@@ -5,7 +5,7 @@ import json
5 5 import asyncio
6 6 import time
7 7 from pathlib import Path
8 from typing import Optional, Callable, AsyncIterator, Dict, Any, Tuple, List, Union
8 from typing import Optional, Callable, AsyncIterator, Iterator, Dict, Any, Tuple, List, Union
9 9
10 10 from ..typing import Messages
11 11 from ..providers.helper import filter_none
@@ -154,7 +154,7 @@ class ThinkingProcessor:
154 154 results = []
155 155
156 156 # Handle non-thinking chunk
157 if not start_time and "<think>" not in chunk:
157 if not start_time and "<think>" not in chunk and "</think>" not in chunk:
158 158 return 0, [chunk]
159 159
160 160 # Handle thinking start
@@ -255,7 +255,7 @@ def iter_run_tools(
255 255 provider: Optional[str] = None,
256 256 tool_calls: Optional[List[dict]] = None,
257 257 **kwargs
258 ) -> AsyncIterator:
258 ) -> Iterator:
259 259 """Run tools synchronously and yield results"""
260 260 # Process web search
261 261 web_search = kwargs.get('web_search')