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

XFEstudio/gpt4free

Audio model support in PollinationsAI Allow Fullscreen for Youtube in Gemini

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

代码差异

6 个文件 +49 -15
Modified g4f/Provider/PollinationsAI.py +21 -4
@@ -13,7 +13,7 @@ from ..image import to_data_uri
13 13 from ..errors import ModelNotFoundError
14 14 from ..requests.raise_for_status import raise_for_status
15 15 from ..requests.aiohttp import get_connector
16 from ..providers.response import ImageResponse, ImagePreview, FinishReason, Usage
16 from ..providers.response import ImageResponse, ImagePreview, FinishReason, Usage, Audio
17 17 from .. import debug
18 18
19 19 DEFAULT_HEADERS = {
@@ -32,7 +32,8 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
32 32 supports_message_history = True
33 33
34 34 # API endpoints
35 text_api_endpoint = "https://text.pollinations.ai/openai"
35 text_api_endpoint = "https://text.pollinations.ai"
36 openai_endpoint = "https://text.pollinations.ai/openai"
36 37 image_api_endpoint = "https://image.pollinations.ai/"
37 38
38 39 # Models configuration
@@ -44,6 +45,7 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
44 45 extra_image_models = ["flux-pro", "flux-dev", "flux-schnell", "midjourney", "dall-e-3"]
45 46 vision_models = [default_vision_model, "gpt-4o-mini", "o1-mini"]
46 47 extra_text_models = ["claude", "claude-email", "deepseek-reasoner", "deepseek-r1"] + vision_models
48 audio_models = {}
47 49 _models_loaded = False
48 50 model_aliases = {
49 51 ### Text Models ###
@@ -90,10 +92,17 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
90 92 # Update of text models
91 93 text_response = requests.get("https://text.pollinations.ai/models")
92 94 text_response.raise_for_status()
95 models = text_response.json()
93 96 original_text_models = [
94 97 model.get("name")
95 for model in text_response.json()
98 for model in models
99 if model.get("type") == "chat"
96 100 ]
101 cls.audio_models = {
102 model.get("name"): model.get("voices")
103 for model in models
104 if model.get("audio")
105 }
97 106
98 107 # Combining text models
99 108 combined_text = (
@@ -266,8 +275,16 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
266 275 })
267 276 if "gemini" in model:
268 277 data.pop("seed")
269 async with session.post(cls.text_api_endpoint, json=data) as response:
278 if model in cls.audio_models:
279 data["voice"] = random.choice(cls.audio_models[model])
280 url = f"{cls.text_api_endpoint}"
281 else:
282 url = cls.openai_endpoint
283 async with session.post(url, json=data) as response:
270 284 await raise_for_status(response)
285 if response.headers["content-type"] == "audio/mpeg":
286 yield Audio(await response.read())
287 return
271 288 result = await response.json()
272 289 choice = result["choices"][0]
273 290 message = choice.get("message", {})
Modified g4f/Provider/needs_auth/Gemini.py +4 -4
@@ -250,12 +250,12 @@ class Gemini(AsyncGeneratorProvider, ProviderModelMixin):
250 250 return f"![](https:{match.group(0)})"
251 251 reasoning = re.sub(r"//yt3.(?:ggpht.com|googleusercontent.com/ytc)/[\w=-]+", replace_image, reasoning)
252 252 reasoning = re.sub(r"\nyoutube\n", "\n\n\n", reasoning)
253 reasoning = re.sub(r"\nyoutube_tool\n", "\n\n", reasoning)
253 254 reasoning = re.sub(r"\nYouTube\n", "\nYouTube ", reasoning)
254 reasoning = reasoning.replace('https://www.gstatic.com/images/branding/productlogos/youtube/v9/192px.svg', '<i class="fa-brands fa-youtube"></i>')
255 reasoning = reasoning.replace('\nhttps://www.gstatic.com/images/branding/productlogos/youtube/v9/192px.svg', '<i class="fa-brands fa-youtube"></i>')
255 256 content = response_part[4][0][1][0]
256 257 if reasoning:
257 yield Reasoning(status="🤔")
258 yield Reasoning(reasoning)
258 yield Reasoning(reasoning, status="🤔")
259 259 except (ValueError, KeyError, TypeError, IndexError) as e:
260 260 debug.error(f"{cls.__name__} {type(e).__name__}: {e}")
261 261 continue
@@ -283,7 +283,7 @@ class Gemini(AsyncGeneratorProvider, ProviderModelMixin):
283 283 except (TypeError, IndexError, KeyError):
284 284 pass
285 285 youtube_ids = []
286 pattern = re.compile(r"http://www.youtube.com/watch\?v=(\w+)")
286 pattern = re.compile(r"http://www.youtube.com/watch\?v=([\w-]+)")
287 287 for match in pattern.finditer(content):
288 288 if match.group(1) not in youtube_ids:
289 289 youtube_ids.append(match.group(1))
Modified g4f/gui/client/static/js/chat.v1.js +12 -6
@@ -80,8 +80,8 @@ if (window.markdownit) {
80 80 .replaceAll('<code>', '<code class="language-plaintext">')
81 81 .replaceAll('&lt;i class=&quot;', '<i class="')
82 82 .replaceAll('&quot;&gt;&lt;/i&gt;', '"></i>')
83 .replaceAll('&lt;iframe type=&quot;text/html&quot; src=&quot;', '<iframe type="text/html" frameborder="0" src="')
84 .replaceAll('&quot;&gt;&lt;/iframe&gt;', `?enablejsapi=1&origin=${new URL(location.href).origin}` + '"></iframe>')
83 .replaceAll('&lt;iframe type=&quot;text/html&quot; src=&quot;', '<iframe type="text/html" frameborder="0" allow="fullscreen" src="')
84 .replaceAll('&quot;&gt;&lt;/iframe&gt;', `?enablejsapi=1&origin=${new URL(location.href).origin}"></iframe>`)
85 85 }
86 86 }
87 87
@@ -305,17 +305,17 @@ const register_message_buttons = async () => {
305 305
306 306 message_box.querySelectorAll(".message .fa-file-export").forEach(async (el) => el.addEventListener("click", async () => {
307 307 const elem = window.document.createElement('a');
308 let filename = `chat ${new Date().toLocaleString()}.md`.replaceAll(":", "-");
308 let filename = `chat ${new Date().toLocaleString()}.txt`.replaceAll(":", "-");
309 309 const conversation = await get_conversation(window.conversation_id);
310 310 let buffer = "";
311 311 conversation.items.forEach(message => {
312 312 if (message.reasoning) {
313 313 buffer += render_reasoning_text(message.reasoning);
314 314 }
315 buffer += `${message.role == 'user' ? 'User' : 'Assistant'}: ${message.content.trim()}\n\n\n`;
315 buffer += `${message.role == 'user' ? 'User' : 'Assistant'}: ${message.content.trim()}\n\n`;
316 316 });
317 317 var download = document.getElementById("download");
318 download.setAttribute("href", "data:text/markdown;charset=utf-8," + encodeURIComponent(buffer.trim()));
318 download.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(buffer.trim()));
319 319 download.setAttribute("download", filename);
320 320 download.click();
321 321 el.classList.add("clicked");
@@ -796,6 +796,12 @@ async function add_message_chunk(message, message_id, provider, scroll, finish_m
796 796 content_map.inner.innerHTML = markdown_render(message.preview);
797 797 await register_message_images();
798 798 }
799 } else if (message.type == "audio") {
800 audio = new Audio(message.audio);
801 audio.controls = true;
802 content_map.inner.appendChild(audio);
803 audio.play();
804 generate_storage[window.conversation_id] = true;
799 805 } else if (message.type == "content") {
800 806 message_storage[message_id] += message.content;
801 807 update_message(content_map, message_id, null, scroll);
@@ -819,7 +825,7 @@ async function add_message_chunk(message, message_id, provider, scroll, finish_m
819 825 } else if (message.type == "reasoning") {
820 826 if (!reasoning_storage[message_id]) {
821 827 reasoning_storage[message_id] = message;
822 reasoning_storage[message_id].text = "";
828 reasoning_storage[message_id].text = message.token || "";
823 829 } else if (message.status) {
824 830 reasoning_storage[message_id].status = message.status;
825 831 } else if (message.token) {
Modified g4f/gui/server/api.py +2 -0
@@ -216,6 +216,8 @@ class Api:
216 216 yield self._format_json("usage", chunk.get_dict())
217 217 elif isinstance(chunk, Reasoning):
218 218 yield self._format_json("reasoning", **chunk.get_dict())
219 elif isinstance(chunk, Audio):
220 yield self._format_json("audio", chunk.to_string())
219 221 elif isinstance(chunk, DebugResponse):
220 222 yield self._format_json("log", chunk.log)
221 223 elif isinstance(chunk, RawResponse):
Modified g4f/providers/response.py +9 -0
@@ -1,6 +1,7 @@
1 1 from __future__ import annotations
2 2
3 3 import re
4 import base64
4 5 from typing import Union
5 6 from abc import abstractmethod
6 7 from urllib.parse import quote_plus, unquote_plus
@@ -175,6 +176,14 @@ class YouTube(ResponseType):
175 176 for id in self.ids
176 177 ]))
177 178
179 class Audio(HiddenResponse):
180 def __init__(self, data: bytes) -> None:
181 self.data = data
182
183 def to_string(self) -> str:
184 data_base64 = base64.b64encode(self.data).decode()
185 return f"data:audio/mpeg;base64,{data_base64}"
186
178 187 class BaseConversation(ResponseType):
179 188 def __str__(self) -> str:
180 189 return ""
Modified g4f/requests/raise_for_status.py +1 -1
@@ -24,7 +24,7 @@ async def raise_for_status_async(response: Union[StreamResponse, ClientResponse]
24 24 if response.ok:
25 25 return
26 26 if message is None:
27 # content_type = response.headers.get("content-type", "")
27 content_type = response.headers.get("content-type", "")
28 28 # if content_type.startswith("application/json"):
29 29 # try:
30 30 # data = await response.json()