返回提交历史
Modified
g4f/Provider/Jmuz.py
+3
-0
Modified
g4f/Provider/PollinationsAI.py
+4
-4
Added
g4f/Provider/hf_space/Qwen_QVQ_72B.py
+70
-0
Added
g4f/Provider/hf_space/StableDiffusion35Large.py
+71
-0
Modified
g4f/Provider/hf_space/__init__.py
+9
-3
Modified
g4f/Provider/needs_auth/HuggingFace.py
+4
-3
Modified
g4f/Provider/needs_auth/HuggingFaceAPI.py
+2
-2
Modified
g4f/gui/client/home.html
+5
-1
Modified
g4f/gui/client/index.html
+1
-1
Modified
g4f/gui/client/static/js/chat.v1.js
+10
-9
Modified
g4f/gui/server/backend_api.py
+3
-3
Modified
g4f/providers/asyncio.py
+3
-10
Modified
g4f/providers/base_provider.py
+3
-2
Modified
g4f/tools/web_search.py
+18
-7
XFEstudio/gpt4free
Add login utl to HuggingFace in Web UI Add some HuggingSpace providers Add icon to home.html Remove duplicate lines from web search results Fix object async_generator can't be used in 'await' Fix for continue message
c908dba0
代码差异
14 个文件
+206
-45
@@ -5,6 +5,7 @@ from .needs_auth.OpenaiAPI import OpenaiAPI
5
5
6
6
class Jmuz(OpenaiAPI):
7
7
label = "Jmuz"
8
login_url = None
8
9
api_base = "https://jmuz.me/gpt/api/v2"
9
10
api_key = "prod"
10
11
@@ -33,6 +34,8 @@ class Jmuz(OpenaiAPI):
33
34
model: str,
34
35
messages: Messages,
35
36
stream: bool = False,
37
aoi_key: str = None,
38
api_base: str = None,
36
39
**kwargs
37
40
) -> AsyncResult:
38
41
model = cls.get_model(model)
@@ -170,13 +170,13 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
170
170
params = {k: v for k, v in params.items() if v is not None}
171
171
172
172
async with ClientSession(headers=headers) as session:
173
prompt = quote(messages[-1]["content"] if prompt is None else prompt)
173
prompt = messages[-1]["content"] if prompt is None else prompt
174
174
param_string = "&".join(f"{k}={v}" for k, v in params.items())
175
url = f"{cls.image_api_endpoint}/prompt/{prompt}?{param_string}"
175
url = f"{cls.image_api_endpoint}/prompt/{quote(prompt)}?{param_string}"
176
176
177
177
async with session.head(url, proxy=proxy) as response:
178
178
if response.status == 200:
179
image_response = ImageResponse(images=url, alt=messages[-1]["content"] if prompt is None else prompt)
179
image_response = ImageResponse(images=url, alt=prompt)
180
180
yield image_response
181
181
182
182
@classmethod
@@ -225,4 +225,4 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
225
225
content = json_response['choices'][0]['message']['content']
226
226
yield content
227
227
except json.JSONDecodeError:
228
yield decoded_chunk
228
pass
@@ -0,0 +1,70 @@
1
from __future__ import annotations
2
3
import json
4
from aiohttp import ClientSession, FormData
5
6
from ...typing import AsyncResult, Messages, ImagesType
7
from ...requests import raise_for_status
8
from ...errors import ResponseError
9
from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin
10
from ..helper import format_prompt, get_random_string
11
from ...image import to_bytes, is_accepted_format
12
13
class Qwen_QVQ_72B(AsyncGeneratorProvider, ProviderModelMixin):
14
url = "https://qwen-qvq-72b-preview.hf.space"
15
api_endpoint = "/gradio_api/call/generate"
16
17
working = True
18
19
default_model = "Qwen/QwQ-32B-Preview"
20
models = [default_model]
21
22
@classmethod
23
async def create_async_generator(
24
cls, model: str, messages: Messages,
25
images: ImagesType = None,
26
api_key: str = None,
27
proxy: str = None,
28
**kwargs
29
) -> AsyncResult:
30
headers = {
31
"Accept": "application/json",
32
}
33
if api_key is not None:
34
headers["Authorization"] = f"Bearer {api_key}"
35
async with ClientSession(headers=headers) as session:
36
if images:
37
data = FormData()
38
data_bytes = to_bytes(images[0][0])
39
data.add_field("files", data_bytes, content_type=is_accepted_format(data_bytes), filename=images[0][1])
40
url = f"https://qwen-qvq-72b-preview.hf.space/gradio_api/upload?upload_id={get_random_string()}"
41
async with session.post(url, data=data, proxy=proxy) as response:
42
await raise_for_status(response)
43
image = await response.json()
44
data = {"data": [{"path": image[0]}, format_prompt(messages)]}
45
else:
46
data = {"data": [None, format_prompt(messages)]}
47
async with session.post(f"{cls.url}{cls.api_endpoint}", json=data, proxy=proxy) as response:
48
await raise_for_status(response)
49
event_id = (await response.json()).get("event_id")
50
async with session.get(f"{cls.url}{cls.api_endpoint}/{event_id}") as event_response:
51
await raise_for_status(event_response)
52
event = None
53
text_position = 0
54
async for chunk in event_response.content:
55
if chunk.startswith(b"event: "):
56
event = chunk[7:].decode(errors="replace").strip()
57
if chunk.startswith(b"data: "):
58
if event == "error":
59
raise ResponseError(f"GPU token limit exceeded: {chunk.decode(errors='replace')}")
60
if event in ("complete", "generating"):
61
try:
62
data = json.loads(chunk[6:])
63
except (json.JSONDecodeError, KeyError, TypeError) as e:
64
raise RuntimeError(f"Failed to read response: {chunk.decode(errors='replace')}", e)
65
if event == "generating":
66
if isinstance(data[0], str):
67
yield data[0][text_position:]
68
text_position = len(data[0])
69
else:
70
break
@@ -0,0 +1,71 @@
1
from __future__ import annotations
2
3
import json
4
from aiohttp import ClientSession
5
6
from ...typing import AsyncResult, Messages
7
from ...image import ImageResponse, ImagePreview
8
from ...errors import ResponseError
9
from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin
10
11
class StableDiffusion35Large(AsyncGeneratorProvider, ProviderModelMixin):
12
url = "https://stabilityai-stable-diffusion-3-5-large.hf.space"
13
api_endpoint = "/gradio_api/call/infer"
14
15
working = True
16
17
default_model = 'stable-diffusion-3.5-large'
18
models = [default_model]
19
image_models = [default_model]
20
21
@classmethod
22
async def create_async_generator(
23
cls, model: str, messages: Messages,
24
prompt: str = None,
25
negative_prompt: str = None,
26
api_key: str = None,
27
proxy: str = None,
28
width: int = 1024,
29
height: int = 1024,
30
guidance_scale: float = 4.5,
31
num_inference_steps: int = 50,
32
seed: int = 0,
33
randomize_seed: bool = True,
34
**kwargs
35
) -> AsyncResult:
36
headers = {
37
"Content-Type": "application/json",
38
"Accept": "application/json",
39
}
40
if api_key is not None:
41
headers["Authorization"] = f"Bearer {api_key}"
42
async with ClientSession(headers=headers) as session:
43
prompt = messages[-1]["content"] if prompt is None else prompt
44
data = {
45
"data": [prompt, negative_prompt, seed, randomize_seed, width, height, guidance_scale, num_inference_steps]
46
}
47
async with session.post(f"{cls.url}{cls.api_endpoint}", json=data, proxy=proxy) as response:
48
response.raise_for_status()
49
event_id = (await response.json()).get("event_id")
50
async with session.get(f"{cls.url}{cls.api_endpoint}/{event_id}") as event_response:
51
event_response.raise_for_status()
52
event = None
53
async for chunk in event_response.content:
54
if chunk.startswith(b"event: "):
55
event = chunk[7:].decode(errors="replace").strip()
56
if chunk.startswith(b"data: "):
57
if event == "error":
58
raise ResponseError(f"GPU token limit exceeded: {chunk.decode(errors='replace')}")
59
if event in ("complete", "generating"):
60
try:
61
data = json.loads(chunk[6:])
62
if data is None:
63
continue
64
url = data[0]["url"]
65
except (json.JSONDecodeError, KeyError, TypeError) as e:
66
raise RuntimeError(f"Failed to parse image URL: {chunk.decode(errors='replace')}", e)
67
if event == "generating":
68
yield ImagePreview(url, prompt)
69
else:
70
yield ImageResponse(url, prompt)
71
break
@@ -1,18 +1,22 @@
1
1
from __future__ import annotations
2
2
3
from ...typing import AsyncResult, Messages
3
from ...typing import AsyncResult, Messages, ImagesType
4
4
from ...errors import ResponseError
5
5
from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin
6
6
7
7
from .BlackForestLabsFlux1Dev import BlackForestLabsFlux1Dev
8
8
from .BlackForestLabsFlux1Schnell import BlackForestLabsFlux1Schnell
9
9
from .VoodoohopFlux1Schnell import VoodoohopFlux1Schnell
10
from .StableDiffusion35Large import StableDiffusion35Large
11
from .Qwen_QVQ_72B import Qwen_QVQ_72B
10
12
11
13
class HuggingSpace(AsyncGeneratorProvider, ProviderModelMixin):
12
14
url = "https://huggingface.co/spaces"
15
parent = "HuggingFace"
13
16
working = True
14
17
default_model = BlackForestLabsFlux1Dev.default_model
15
providers = [BlackForestLabsFlux1Dev, BlackForestLabsFlux1Schnell, VoodoohopFlux1Schnell]
18
default_vision_model = Qwen_QVQ_72B.default_model
19
providers = [BlackForestLabsFlux1Dev, BlackForestLabsFlux1Schnell, VoodoohopFlux1Schnell, StableDiffusion35Large, Qwen_QVQ_72B]
16
20
17
21
@classmethod
18
22
def get_parameters(cls, **kwargs) -> dict:
@@ -33,8 +37,10 @@ class HuggingSpace(AsyncGeneratorProvider, ProviderModelMixin):
33
37
34
38
@classmethod
35
39
async def create_async_generator(
36
cls, model: str, messages: Messages, **kwargs
40
cls, model: str, messages: Messages, images: ImagesType = None, **kwargs
37
41
) -> AsyncResult:
42
if not model and images is not None:
43
model = cls.default_vision_model
38
44
is_started = False
39
45
for provider in cls.providers:
40
46
if model in provider.model_aliases:
@@ -17,6 +17,7 @@ from .HuggingChat import HuggingChat
17
17
18
18
class HuggingFace(AsyncGeneratorProvider, ProviderModelMixin):
19
19
url = "https://huggingface.co"
20
login_url = "https://huggingface.co/settings/tokens"
20
21
working = True
21
22
supports_message_history = True
22
23
default_model = HuggingChat.default_model
@@ -149,14 +150,14 @@ class HuggingFace(AsyncGeneratorProvider, ProviderModelMixin):
149
150
def format_prompt_mistral(messages: Messages, do_continue: bool = False) -> str:
150
151
system_messages = [message["content"] for message in messages if message["role"] == "system"]
151
152
question = " ".join([messages[-1]["content"], *system_messages])
152
history = "".join([
153
history = "\n".join([
153
154
f"<s>[INST]{messages[idx-1]['content']} [/INST] {message['content']}</s>"
154
155
for idx, message in enumerate(messages)
155
156
if message["role"] == "assistant"
156
157
])
157
158
if do_continue:
158
159
return history[:-len('</s>')]
159
return f"{history}<s>[INST] {question} [/INST]"
160
return f"{history}\n<s>[INST] {question} [/INST]"
160
161
161
162
def format_prompt_qwen(messages: Messages, do_continue: bool = False) -> str:
162
163
prompt = "".join([
@@ -185,7 +186,7 @@ def format_prompt_custom(messages: Messages, end_token: str = "</s>", do_continu
185
186
def get_inputs(messages: Messages, model_data: dict, model_type: str, do_continue: bool = False) -> str:
186
187
if model_type in ("gpt2", "gpt_neo", "gemma", "gemma2"):
187
188
inputs = format_prompt(messages, do_continue=do_continue)
188
elif model_type in ("mistral"):
189
elif model_type == "mistral" and model_data.get("author") == "mistralai":
189
190
inputs = format_prompt_mistral(messages, do_continue)
190
191
elif "config" in model_data and "tokenizer_config" in model_data["config"] and "eos_token" in model_data["config"]["tokenizer_config"]:
191
192
eos_token = model_data["config"]["tokenizer_config"]["eos_token"]
@@ -5,8 +5,8 @@ from .HuggingChat import HuggingChat
5
5
6
6
class HuggingFaceAPI(OpenaiAPI):
7
7
label = "HuggingFace (Inference API)"
8
url = "https://api-inference.huggingface.co"
9
login_url = "https://huggingface.co/settings/tokens"
8
parent = "HuggingFace"
9
url = "https://api-inference.huggingface.com"
10
10
api_base = "https://api-inference.huggingface.co/v1"
11
11
working = True
12
12
default_model = "meta-llama/Llama-3.2-11B-Vision-Instruct"
@@ -5,6 +5,10 @@
5
5
<meta charset="UTF-8">
6
6
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7
7
<title>G4F GUI</title>
8
<link rel="apple-touch-icon" sizes="180x180" href="/static/img/apple-touch-icon.png">
9
<link rel="icon" type="image/png" sizes="32x32" href="/static/img/favicon-32x32.png">
10
<link rel="icon" type="image/png" sizes="16x16" href="/static/img/favicon-16x16.png">
11
<link rel="manifest" href="/static/img/site.webmanifest">
8
12
<style>
9
13
:root {
10
14
--colour-1: #000000;
@@ -287,7 +291,7 @@
287
291
288
292
(async () => {
289
293
const today = new Date().toJSON().slice(0, 10);
290
const max = 100;
294
const max = 5;
291
295
const cache_id = Math.floor(Math.random() * max);
292
296
let prompt;
293
297
if (cache_id % 2 == 0) {
@@ -152,7 +152,7 @@
152
152
</div>
153
153
<div class="field box hidden">
154
154
<label for="BingCreateImages-api_key" class="label" title="">Microsoft Designer in Bing:</label>
155
<textarea id="BingCreateImages-api_key" name="BingCreateImages[api_key]" placeholder=""_U" cookie"></textarea>
155
<input type="text" id="BingCreateImages-api_key" name="BingCreateImages[api_key]" placeholder=""_U" cookie"/>
156
156
</div>
157
157
</div>
158
158
<div class="bottom_buttons">
@@ -1110,7 +1110,7 @@ const load_conversation = async (conversation_id, scroll=true) => {
1110
1110
if (lastLine.endsWith("[aborted]") || lastLine.endsWith("[error]")) {
1111
1111
reason = "error";
1112
1112
// Has an even number of start or end code tags
1113
} else if (reason = "stop" && buffer.split("```").length - 1 % 2 === 1) {
1113
} else if (reason == "stop" && buffer.split("```").length - 1 % 2 === 1) {
1114
1114
reason = "length";
1115
1115
}
1116
1116
if (reason == "length" || reason == "max_tokens" || reason == "error") {
@@ -1724,19 +1724,19 @@ async function on_api() {
1724
1724
option.dataset.parent = provider.parent;
1725
1725
providerSelect.appendChild(option);
1726
1726
1727
if (provider.login_url) {
1727
if (provider.parent) {
1728
if (!login_urls[provider.parent]) {
1729
login_urls[provider.parent] = [provider.label, provider.login_url, [provider.name]];
1730
} else {
1731
login_urls[provider.parent][2].push(provider.name);
1732
}
1733
} else if (provider.login_url) {
1728
1734
if (!login_urls[provider.name]) {
1729
1735
login_urls[provider.name] = [provider.label, provider.login_url, []];
1730
1736
} else {
1731
1737
login_urls[provider.name][0] = provider.label;
1732
1738
login_urls[provider.name][1] = provider.login_url;
1733
1739
}
1734
} else if (provider.parent) {
1735
if (!login_urls[provider.parent]) {
1736
login_urls[provider.parent] = [provider.label, provider.login_url, [provider.name]];
1737
} else {
1738
login_urls[provider.parent][2].push(provider.name);
1739
}
1740
1740
}
1741
1741
});
1742
1742
for (let [name, [label, login_url, childs]] of Object.entries(login_urls)) {
@@ -1746,9 +1746,10 @@ async function on_api() {
1746
1746
option = document.createElement("div");
1747
1747
option.classList.add("field", "box", "hidden");
1748
1748
childs = childs.map((child)=>`${child}-api_key`).join(" ");
1749
console.log(childs);
1749
1750
option.innerHTML = `
1750
1751
<label for="${name}-api_key" class="label" title="">${label}:</label>
1751
<textarea id="${name}-api_key" name="${name}[api_key]" class="${childs}" placeholder="api_key"></textarea>
1752
<input type="text" id="${name}-api_key" name="${name}[api_key]" class="${childs}" placeholder="api_key"/>
1752
1753
<a href="${login_url}" target="_blank" title="Login to ${label}">Get API key</a>
1753
1754
`;
1754
1755
settings.querySelector(".paper").appendChild(option);
@@ -120,7 +120,7 @@ class Backend_Api(Api):
120
120
tool_calls.append({
121
121
"function": {
122
122
"name": "search_tool",
123
"arguments": {"query": web_search, "instructions": ""} if web_search != "true" else {}
123
"arguments": {"query": web_search, "instructions": "", "max_words": 1000} if web_search != "true" else {}
124
124
},
125
125
"type": "function"
126
126
})
@@ -173,7 +173,7 @@ class Backend_Api(Api):
173
173
@app.route('/backend-api/v2/files/<bucket_id>', methods=['GET', 'DELETE'])
174
174
def manage_files(bucket_id: str):
175
175
bucket_id = secure_filename(bucket_id)
176
bucket_dir = get_bucket_dir(secure_filename(bucket_id))
176
bucket_dir = get_bucket_dir(bucket_id)
177
177
178
178
if not os.path.isdir(bucket_dir):
179
179
return jsonify({"error": {"message": "Bucket directory not found"}}), 404
@@ -231,7 +231,7 @@ class Backend_Api(Api):
231
231
if not file_data:
232
232
return jsonify({"error": {"message": "No file data received"}}), 400
233
233
234
with open(str(file_path), 'wb') as f:
234
with file_path.open('wb') as f:
235
235
f.write(file_data)
236
236
237
237
return jsonify({"message": f"File '{filename}' uploaded successfully to bucket '{bucket_id}'"}), 201
@@ -70,15 +70,8 @@ def to_sync_generator(generator: AsyncIterator, stream: bool = True) -> Iterator
70
70
71
71
# Helper function to convert a synchronous iterator to an async iterator
72
72
async def to_async_iterator(iterator: Iterator) -> AsyncIterator:
73
if isinstance(iterator, str):
74
yield iterator
75
elif hasattr(iterator, "__await__"):
76
yield await iterator
77
elif hasattr(iterator, "__aiter__"):
73
try:
78
74
async for item in iterator:
79
75
yield item
80
elif hasattr(iterator, "__iter__"):
81
for item in iterator:
82
yield item
83
else:
84
yield iterator
76
except TypeError:
77
yield await iterator