返回提交历史
Modified
g4f/Provider/PollinationsAI.py
+21
-4
Modified
g4f/Provider/needs_auth/Gemini.py
+4
-4
Modified
g4f/gui/client/static/js/chat.v1.js
+12
-6
Modified
g4f/gui/server/api.py
+2
-0
Modified
g4f/providers/response.py
+9
-0
Modified
g4f/requests/raise_for_status.py
+1
-1
XFEstudio/gpt4free
Audio model support in PollinationsAI Allow Fullscreen for Youtube in Gemini
14817cdd
代码差异
6 个文件
+49
-15
@@ -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", {})
@@ -250,12 +250,12 @@ class Gemini(AsyncGeneratorProvider, ProviderModelMixin):
250
250
return f"})"
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))
@@ -80,8 +80,8 @@ if (window.markdownit) {
80
80
.replaceAll('<code>', '<code class="language-plaintext">')
81
81
.replaceAll('<i class="', '<i class="')
82
82
.replaceAll('"></i>', '"></i>')
83
.replaceAll('<iframe type="text/html" src="', '<iframe type="text/html" frameborder="0" src="')
84
.replaceAll('"></iframe>', `?enablejsapi=1&origin=${new URL(location.href).origin}` + '"></iframe>')
83
.replaceAll('<iframe type="text/html" src="', '<iframe type="text/html" frameborder="0" allow="fullscreen" src="')
84
.replaceAll('"></iframe>', `?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) {
@@ -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):
@@ -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 ""
@@ -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()