返回提交历史
Modified
g4f/Provider/CablyAI.py
+4
-4
Modified
g4f/Provider/PollinationsAI.py
+14
-10
Added
g4f/Provider/PollinationsImage.py
+58
-0
Modified
g4f/Provider/__init__.py
+1
-0
Modified
g4f/Provider/hf/HuggingFaceAPI.py
+3
-1
Modified
g4f/gui/client/static/js/chat.v1.js
+4
-4
Modified
g4f/image/copy_images.py
+10
-1
Modified
g4f/tools/run_tools.py
+1
-1
XFEstudio/gpt4free
Fix api_key in HuggingFace provider Split PollinationsAI provider in two provider Update model list in CablyAI Return backup url, if copy images failed Update url for logging in UI
1d00be5b
代码差异
8 个文件
+95
-21
@@ -5,14 +5,14 @@ from .template import OpenaiTemplate
5
5
6
6
class CablyAI(OpenaiTemplate):
7
7
url = "https://cablyai.com"
8
login_url = None
8
login_url = url
9
9
needs_auth = False
10
10
api_base = "https://cablyai.com/v1"
11
11
working = True
12
12
13
default_model = "Cably-80B"
14
models = [default_model]
15
model_aliases = {"cably-80b": default_model}
13
default_model = "o3-mini-low"
14
fallback_models = [default_model, "Cably-80B"]
15
model_aliases = {"cably-80b": "Cably-80B"}
16
16
17
17
@classmethod
18
18
def create_async_generator(
@@ -11,6 +11,7 @@ from .helper import filter_none, format_image_prompt
11
11
from .base_provider import AsyncGeneratorProvider, ProviderModelMixin
12
12
from ..typing import AsyncResult, Messages, ImagesType
13
13
from ..image import to_data_uri
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
17
from ..providers.response import ImageResponse, ImagePreview, FinishReason, Usage, Reasoning
@@ -64,20 +65,20 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
64
65
"deepseek-r1": "deepseek-reasoner",
65
66
66
67
### Image Models ###
67
"sdxl-turbo": "turbo",
68
"sdxl-turbo": "turbo",
69
"flux-schnell": "flux",
68
70
}
69
71
text_models = []
70
72
71
73
@classmethod
72
74
def get_models(cls, **kwargs):
73
if not cls.image_models:
75
if not cls.text_models:
74
76
url = "https://image.pollinations.ai/models"
75
77
response = requests.get(url)
76
78
raise_for_status(response)
77
cls.image_models = response.json()
78
cls.image_models = list(dict.fromkeys([*cls.image_models, *cls.extra_image_models]))
79
new_image_models = response.json()
80
cls.extra_image_models = list(dict.fromkeys([*cls.image_models, *cls.extra_image_models, *new_image_models]))
79
81
80
if not cls.text_models:
81
82
url = "https://text.pollinations.ai/models"
82
83
response = requests.get(url)
83
84
raise_for_status(response)
@@ -87,8 +88,7 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
87
88
if model not in cls.extra_text_models
88
89
]
89
90
cls.text_models = list(dict.fromkeys(combined_text))
90
91
return list(dict.fromkeys([*cls.text_models, *cls.image_models]))
91
return cls.text_models
92
92
93
93
@classmethod
94
94
async def create_async_generator(
@@ -115,11 +115,15 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
115
115
) -> AsyncResult:
116
116
if images is not None and not model:
117
117
model = cls.default_vision_model
118
model = cls.get_model(model)
118
try:
119
model = cls.get_model(model)
120
except ModelNotFoundError:
121
if model not in cls.extra_image_models:
122
raise
119
123
if not cache and seed is None:
120
seed = random.randint(0, 100000)
124
seed = random.randint(0, 10000)
121
125
122
if model in cls.image_models:
126
if model in cls.image_models and model not in cls.extra_image_models:
123
127
async for chunk in cls._generate_image(
124
128
model=model,
125
129
prompt=format_image_prompt(messages, prompt),
@@ -0,0 +1,58 @@
1
from __future__ import annotations
2
3
from typing import Optional
4
5
from .helper import format_image_prompt
6
from ..typing import AsyncResult, Messages
7
from .PollinationsAI import PollinationsAI
8
9
class PollinationsImage(PollinationsAI):
10
# From: https://pollinations.ai/static/js/components/FeedImage/ImageEditor.js
11
image_models = [
12
"flux",
13
"flux-pro",
14
"flux-realism",
15
"flux-anime",
16
"flux-3d",
17
"flux-cablyai",
18
"turbo",
19
]
20
default_model = "flux"
21
default_vision_model = None
22
default_image_model = default_model
23
24
@classmethod
25
def get_models(cls, **kwargs):
26
if not cls.models:
27
cls.models = list(dict.fromkeys([*cls.image_models, *cls.extra_image_models, *PollinationsAI.extra_image_models]))
28
return cls.models
29
30
@classmethod
31
async def create_async_generator(
32
cls,
33
model: str,
34
messages: Messages,
35
proxy: str = None,
36
prompt: str = None,
37
width: int = 1024,
38
height: int = 1024,
39
seed: Optional[int] = None,
40
nologo: bool = True,
41
private: bool = False,
42
enhance: bool = False,
43
safe: bool = False,
44
**kwargs
45
) -> AsyncResult:
46
async for chunk in cls._generate_image(
47
model=model,
48
prompt=format_image_prompt(messages, prompt),
49
proxy=proxy,
50
width=width,
51
height=height,
52
seed=seed,
53
nologo=nologo,
54
private=private,
55
enhance=enhance,
56
safe=safe
57
):
58
yield chunk
@@ -39,6 +39,7 @@ from .PerplexityLabs import PerplexityLabs
39
39
from .Pi import Pi
40
40
from .Pizzagpt import Pizzagpt
41
41
from .PollinationsAI import PollinationsAI
42
from .PollinationsImage import PollinationsImage
42
43
from .Prodia import Prodia
43
44
from .TeachAnything import TeachAnything
44
45
from .You import You
@@ -38,6 +38,7 @@ class HuggingFaceAPI(OpenaiTemplate):
38
38
model: str,
39
39
messages: Messages,
40
40
api_base: str = None,
41
api_key: str = None,
41
42
max_tokens: int = 2048,
42
43
max_inputs_lenght: int = 10000,
43
44
images: ImagesType = None,
@@ -50,6 +51,7 @@ class HuggingFaceAPI(OpenaiTemplate):
50
51
api_base = f"https://api-inference.huggingface.co/models/{model_name}/v1"
51
52
async with StreamSession(
52
53
timeout=30,
54
headers=cls.get_headers(False, api_key),
53
55
) as session:
54
56
async with session.get(f"https://huggingface.co/api/models/{model}") as response:
55
57
if response.status == 404:
@@ -71,7 +73,7 @@ class HuggingFaceAPI(OpenaiTemplate):
71
73
if len(messages) > 1 and calculate_lenght(messages) > max_inputs_lenght:
72
74
messages = [messages[-1]]
73
75
debug.log(f"Messages trimmed from: {start} to: {calculate_lenght(messages)}")
74
async for chunk in super().create_async_generator(model, messages, api_base=api_base, max_tokens=max_tokens, images=images, **kwargs):
76
async for chunk in super().create_async_generator(model, messages, api_base=api_base, api_key=api_key, max_tokens=max_tokens, images=images, **kwargs):
75
77
yield chunk
76
78
77
79
def calculate_lenght(messages: Messages) -> int:
@@ -1959,7 +1959,7 @@ async function on_api() {
1959
1959
messageInput.addEventListener("keydown", async (evt) => {
1960
1960
if (prompt_lock) return;
1961
1961
// If not mobile and not shift enter
1962
let do_enter = messageInput.value.endsWith("\n\n");
1962
let do_enter = messageInput.value.endsWith("\n\n\n\n");
1963
1963
if (do_enter || !window.matchMedia("(pointer:coarse)").matches && evt.keyCode === 13 && !evt.shiftKey) {
1964
1964
evt.preventDefault();
1965
1965
console.log("pressed enter");
@@ -2447,11 +2447,11 @@ async function api(ressource, args=null, files=null, message_id=null, scroll=tru
2447
2447
return;
2448
2448
}
2449
2449
} else if (args) {
2450
if (ressource == "log") {
2451
if (!document.getElementById("report_error").checked) {
2450
if (ressource in ("log", "usage")) {
2451
if (ressource == "log" && !document.getElementById("report_error").checked) {
2452
2452
return;
2453
2453
}
2454
url = `https://roxky-g4f-demo.hf.space${url}`;
2454
url = `https://roxky-g4f-backup.hf.space${url}`;
2455
2455
}
2456
2456
headers['content-type'] = 'application/json';
2457
2457
response = await fetch(url, {
@@ -28,6 +28,15 @@ def get_image_extension(image: str) -> str:
28
28
def ensure_images_dir():
29
29
os.makedirs(images_dir, exist_ok=True)
30
30
31
def get_source_url(image: str, default: str = None) -> str:
32
source_url = image.split("url=", 1)
33
if len(source_url) > 1:
34
source_url = source_url[1]
35
source_url = source_url.replace("%2F", "/").replace("%3A", ":").replace("%3F", "?").replace("%3D", "=")
36
if source_url.startswith("https://"):
37
return source_url
38
return default
39
31
40
async def copy_images(
32
41
images: list[str],
33
42
cookies: Optional[Cookies] = None,
@@ -68,7 +77,7 @@ async def copy_images(
68
77
f.write(chunk)
69
78
except ClientError as e:
70
79
debug.log(f"copy_images failed: {e.__class__.__name__}: {e}")
71
return image
80
return get_source_url(image, image)
72
81
if "." not in target:
73
82
with open(target, "rb") as f:
74
83
extension = is_accepted_format(f.read(12)).split("/")[-1]
@@ -154,7 +154,7 @@ def iter_run_tools(
154
154
if not isinstance(chunk, str):
155
155
yield chunk
156
156
continue
157
if "<think>" in chunk:
157
if "<think>" in chunk and not "`<think>`" in chunk:
158
158
if chunk != "<think>":
159
159
chunk = chunk.split("<think>", 1)
160
160
if len(chunk) > 0 and chunk[0]: