返回提交历史
Modified
g4f/Provider/Bing.py
+1
-1
Added
g4f/Provider/DeepInfraImage.py
+74
-0
Modified
g4f/Provider/You.py
+7
-4
Modified
g4f/Provider/__init__.py
+1
-0
Added
g4f/Provider/needs_auth/OpenRouter.py
+31
-0
Modified
g4f/Provider/needs_auth/Openai.py
+3
-10
Modified
g4f/Provider/needs_auth/OpenaiChat.py
+1
-1
Modified
g4f/Provider/needs_auth/__init__.py
+2
-1
Modified
g4f/api/__init__.py
+1
-1
Modified
g4f/gui/client/index.html
+8
-0
Modified
g4f/gui/client/static/css/style.css
+7
-3
Modified
g4f/gui/client/static/js/chat.v1.js
+18
-14
Modified
g4f/gui/server/api.py
+2
-2
Modified
g4f/providers/helper.py
+8
-1
XFEstudio/gpt4free
Add OpenRouter and DeepInfraImage Provider (#1814)
00951eb7
代码差异
14 个文件
+164
-38
@@ -47,7 +47,7 @@ class Bing(AsyncGeneratorProvider, ProviderModelMixin):
47
47
proxy: str = None,
48
48
timeout: int = 900,
49
49
api_key: str = None,
50
cookies: Cookies = None,
50
cookies: Cookies = {},
51
51
connector: BaseConnector = None,
52
52
tone: str = None,
53
53
image: ImageType = None,
@@ -0,0 +1,74 @@
1
from __future__ import annotations
2
3
import requests
4
5
from .base_provider import AsyncGeneratorProvider, ProviderModelMixin
6
from ..typing import AsyncResult, Messages
7
from ..requests import StreamSession, raise_for_status
8
from ..image import ImageResponse
9
10
class DeepInfraImage(AsyncGeneratorProvider, ProviderModelMixin):
11
url = "https://deepinfra.com"
12
working = True
13
default_model = 'stability-ai/sdxl'
14
15
@classmethod
16
def get_models(cls):
17
if not cls.models:
18
url = 'https://api.deepinfra.com/models/featured'
19
models = requests.get(url).json()
20
cls.models = [model['model_name'] for model in models if model["reported_type"] == "text-to-image"]
21
return cls.models
22
23
@classmethod
24
async def create_async_generator(
25
cls,
26
model: str,
27
messages: Messages,
28
**kwargs
29
) -> AsyncResult:
30
yield await cls.create_async(messages[-1]["content"], model, **kwargs)
31
32
@classmethod
33
async def create_async(
34
cls,
35
prompt: str,
36
model: str,
37
api_key: str = None,
38
api_base: str = "https://api.deepinfra.com/v1/inference",
39
proxy: str = None,
40
timeout: int = 180,
41
extra_data: dict = {},
42
**kwargs
43
) -> ImageResponse:
44
headers = {
45
'Accept-Encoding': 'gzip, deflate, br',
46
'Accept-Language': 'en-US',
47
'Connection': 'keep-alive',
48
'Origin': 'https://deepinfra.com',
49
'Referer': 'https://deepinfra.com/',
50
'Sec-Fetch-Dest': 'empty',
51
'Sec-Fetch-Mode': 'cors',
52
'Sec-Fetch-Site': 'same-site',
53
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36',
54
'X-Deepinfra-Source': 'web-embed',
55
'sec-ch-ua': '"Google Chrome";v="119", "Chromium";v="119", "Not?A_Brand";v="24"',
56
'sec-ch-ua-mobile': '?0',
57
'sec-ch-ua-platform': '"macOS"',
58
}
59
if api_key is not None:
60
headers["Authorization"] = f"Bearer {api_key}"
61
async with StreamSession(
62
proxies={"all": proxy},
63
headers=headers,
64
timeout=timeout
65
) as session:
66
model = cls.get_model(model)
67
data = {"prompt": prompt, **extra_data}
68
data = {"input": data} if model == cls.default_model else data
69
async with session.post(f"{api_base.rstrip('/')}/{model}", json=data) as response:
70
await raise_for_status(response)
71
data = await response.json()
72
images = data["output"] if "output" in data else data["images"]
73
images = images[0] if len(images) == 1 else images
74
return ImageResponse(images, prompt)
@@ -8,8 +8,9 @@ import uuid
8
8
from ..typing import AsyncResult, Messages, ImageType, Cookies
9
9
from .base_provider import AsyncGeneratorProvider, ProviderModelMixin
10
10
from .helper import format_prompt
11
from ..image import to_bytes, ImageResponse
11
from ..image import ImageResponse, to_bytes, is_accepted_format
12
12
from ..requests import StreamSession, FormData, raise_for_status
13
from ..errors import MissingRequirementsError
13
14
14
15
from .you.har_file import get_dfp_telemetry_id
15
16
@@ -46,6 +47,7 @@ class You(AsyncGeneratorProvider, ProviderModelMixin):
46
47
image: ImageType = None,
47
48
image_name: str = None,
48
49
proxy: str = None,
50
timeout: int = 240,
49
51
chat_mode: str = "default",
50
52
**kwargs,
51
53
) -> AsyncResult:
@@ -55,12 +57,14 @@ class You(AsyncGeneratorProvider, ProviderModelMixin):
55
57
...
56
58
elif model.startswith("dall-e"):
57
59
chat_mode = "create"
60
messages = [messages[-1]]
58
61
else:
59
62
chat_mode = "custom"
60
63
model = cls.get_model(model)
61
64
async with StreamSession(
62
65
proxies={"all": proxy},
63
impersonate="chrome"
66
impersonate="chrome",
67
timeout=(30, timeout)
64
68
) as session:
65
69
cookies = await cls.get_cookies(session) if chat_mode != "default" else None
66
70
upload = json.dumps([await cls.upload_file(session, cookies, to_bytes(image), image_name)]) if image else ""
@@ -73,7 +77,6 @@ class You(AsyncGeneratorProvider, ProviderModelMixin):
73
77
"q": format_prompt(messages),
74
78
"domain": "youchat",
75
79
"selectedChatMode": chat_mode,
76
#"chat": json.dumps(chat),
77
80
}
78
81
params = {
79
82
"userFiles": upload,
@@ -113,7 +116,7 @@ class You(AsyncGeneratorProvider, ProviderModelMixin):
113
116
await raise_for_status(response)
114
117
upload_nonce = await response.text()
115
118
data = FormData()
116
data.add_field('file', file, filename=filename)
119
data.add_field('file', file, content_type=is_accepted_format(file), filename=filename)
117
120
async with client.post(
118
121
f"{cls.url}/api/upload",
119
122
data=data,
@@ -21,6 +21,7 @@ from .ChatgptFree import ChatgptFree
21
21
from .ChatgptNext import ChatgptNext
22
22
from .ChatgptX import ChatgptX
23
23
from .DeepInfra import DeepInfra
24
from .DeepInfraImage import DeepInfraImage
24
25
from .DuckDuckGo import DuckDuckGo
25
26
from .FlowGpt import FlowGpt
26
27
from .FreeChatgpt import FreeChatgpt
@@ -0,0 +1,31 @@
1
from __future__ import annotations
2
3
import requests
4
5
from .Openai import Openai
6
from ...typing import AsyncResult, Messages
7
8
class OpenRouter(Openai):
9
url = "https://openrouter.ai"
10
working = True
11
default_model = "openrouter/auto"
12
13
@classmethod
14
def get_models(cls):
15
if not cls.models:
16
url = 'https://openrouter.ai/api/v1/models'
17
models = requests.get(url).json()["data"]
18
cls.models = [model['id'] for model in models]
19
return cls.models
20
21
@classmethod
22
def create_async_generator(
23
cls,
24
model: str,
25
messages: Messages,
26
api_base: str = "https://openrouter.ai/api/v1",
27
**kwargs
28
) -> AsyncResult:
29
return super().create_async_generator(
30
model, messages, api_base=api_base, **kwargs
31
)
@@ -2,10 +2,10 @@ from __future__ import annotations
2
2
3
3
import json
4
4
5
from ..helper import filter_none
5
6
from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin, FinishReason
6
7
from ...typing import Union, Optional, AsyncResult, Messages
7
from ...requests.raise_for_status import raise_for_status
8
from ...requests import StreamSession
8
from ...requests import StreamSession, raise_for_status
9
9
from ...errors import MissingAuthError, ResponseError
10
10
11
11
class Openai(AsyncGeneratorProvider, ProviderModelMixin):
@@ -98,11 +98,4 @@ class Openai(AsyncGeneratorProvider, ProviderModelMixin):
98
98
else {}
99
99
),
100
100
**({} if headers is None else headers)
101
}
102
103
def filter_none(**kwargs) -> dict:
104
return {
105
key: value
106
for key, value in kwargs.items()
107
if value is not None
108
}
101
}
@@ -334,7 +334,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
334
334
RuntimeError: If an error occurs during processing.
335
335
"""
336
336
async with StreamSession(
337
proxies={"https": proxy},
337
proxies={"all": proxy},
338
338
impersonate="chrome",
339
339
timeout=timeout
340
340
) as session:
@@ -5,4 +5,5 @@ from .ThebApi import ThebApi
5
5
from .OpenaiChat import OpenaiChat
6
6
from .Poe import Poe
7
7
from .Openai import Openai
8
from .Groq import Groq
8
from .Groq import Groq
9
from .OpenRouter import OpenRouter
@@ -76,7 +76,7 @@ class Api:
76
76
@self.app.get("/v1/models")
77
77
async def models():
78
78
model_list = dict(
79
(model, g4f.ModelUtils.convert[model])
79
(model, g4f.models.ModelUtils.convert[model])
80
80
for model in g4f.Model.__all__()
81
81
)
82
82
model_list = [{
@@ -132,10 +132,18 @@
132
132
<label for="GeminiPro-api_key" class="label" title="">GeminiPro: api_key</label>
133
133
<textarea id="GeminiPro-api_key" name="GeminiPro[api_key]" placeholder="..."></textarea>
134
134
</div>
135
<div class="field box">
136
<label for="OpenRouter-api_key" class="label" title="">OpenRouter: api_key</label>
137
<textarea id="OpenRouter-api_key" name="OpenRouter[api_key]" placeholder="..."></textarea>
138
</div>
135
139
<div class="field box">
136
140
<label for="HuggingFace-api_key" class="label" title="">HuggingFace: api_key</label>
137
141
<textarea id="HuggingFace-api_key" name="HuggingFace[api_key]" placeholder="..."></textarea>
138
142
</div>
143
<div class="field box">
144
<label for="DeepInfra-api_key" class="label" title="">DeepInfra: api_key</label>
145
<textarea id="DeepInfra-api_key" name="DeepInfra[api_key]" placeholder="..."></textarea>
146
</div>
139
147
</div>
140
148
<div class="bottom_buttons">
141
149
<button onclick="delete_conversations()">
@@ -109,7 +109,7 @@ body {
109
109
}
110
110
111
111
.conversations {
112
max-width: 280px;
112
max-width: 300px;
113
113
padding: var(--section-gap);
114
114
overflow: auto;
115
115
flex-shrink: 0;
@@ -207,9 +207,9 @@ body {
207
207
gap: 4px;
208
208
}
209
209
210
.conversations .convo .fa-trash {
210
.conversations .convo .fa-ellipsis-vertical {
211
211
position: absolute;
212
right: 8px;
212
right: 14px;
213
213
}
214
214
215
215
.conversations .convo .choise {
@@ -1075,6 +1075,10 @@ a:-webkit-any-link {
1075
1075
resize: vertical;
1076
1076
}
1077
1077
1078
.settings textarea {
1079
height: 51px;
1080
}
1081
1078
1082
.settings {
1079
1083
width: 100%;
1080
1084
display: flex;
@@ -42,7 +42,7 @@ appStorage = window.localStorage || {
42
42
const markdown = window.markdownit();
43
43
const markdown_render = (content) => {
44
44
return markdown.render(content
45
.replaceAll(/<!-- generated images start -->[\s\S]+<!-- generated images end -->/gm, "")
45
.replaceAll(/<!-- generated images start -->|<!-- generated images end -->/gm, "")
46
46
.replaceAll(/<img data-prompt="[^>]+">/gm, "")
47
47
)
48
48
.replaceAll("<a href=", '<a target="_blank" href=')
@@ -127,9 +127,6 @@ const register_message_buttons = async () => {
127
127
sound.controls = 'controls';
128
128
sound.src = url;
129
129
sound.type = 'audio/wav';
130
if (ended && !stopped) {
131
sound.autoplay = true;
132
}
133
130
sound.onended = function() {
134
131
ended = true;
135
132
};
@@ -140,6 +137,9 @@ const register_message_buttons = async () => {
140
137
container.classList.add("audio");
141
138
container.appendChild(sound);
142
139
content_el.appendChild(container);
140
if (ended && !stopped) {
141
sound.play();
142
}
143
143
}
144
144
if (lines.length < 1 || stopped) {
145
145
el.classList.remove("active");
@@ -608,12 +608,11 @@ async function get_messages(conversation_id) {
608
608
}
609
609
610
610
async function add_conversation(conversation_id, content) {
611
if (content.length > 17) {
612
title = content.substring(0, 17) + '...'
611
if (content.length > 18) {
612
title = content.substring(0, 18) + '...'
613
613
} else {
614
title = content + ' '.repeat(19 - content.length)
614
title = content + ' '.repeat(20 - content.length)
615
615
}
616
617
616
if (appStorage.getItem(`conversation:${conversation_id}`) == null) {
618
617
await save_conversation(conversation_id, {
619
618
id: conversation_id,
@@ -623,7 +622,6 @@ async function add_conversation(conversation_id, content) {
623
622
items: [],
624
623
});
625
624
}
626
627
625
history.pushState({}, null, `/chat/${conversation_id}`);
628
626
}
629
627
@@ -695,27 +693,31 @@ const load_conversations = async () => {
695
693
696
694
await clear_conversations();
697
695
698
for (conversation of conversations) {
696
conversations.sort((a, b) => (b.updated||0)-(a.updated||0));
697
698
let html = "";
699
conversations.forEach((conversation) => {
699
700
let updated = "";
700
701
if (conversation.updated) {
701
702
const date = new Date(conversation.updated);
702
703
updated = date.toLocaleString('en-GB', {dateStyle: 'short', timeStyle: 'short', monthStyle: 'short'});
703
704
updated = updated.replace("/" + date.getFullYear(), "")
704
705
}
705
box_conversations.innerHTML += `
706
html += `
706
707
<div class="convo" id="convo-${conversation.id}">
707
708
<div class="left" onclick="set_conversation('${conversation.id}')">
708
709
<i class="fa-regular fa-comments"></i>
709
710
<span class="convo-title"><span class="datetime">${updated}</span> ${conversation.title}</span>
710
711
</div>
711
<i onclick="show_option('${conversation.id}')" class="fa-regular fa-trash" id="conv-${conversation.id}"></i>
712
<i onclick="show_option('${conversation.id}')" class="fa-solid fa-ellipsis-vertical" id="conv-${conversation.id}"></i>
712
713
<div id="cho-${conversation.id}" class="choise" style="display:none;">
713
<i onclick="delete_conversation('${conversation.id}')" class="fa-regular fa-check"></i>
714
<i onclick="delete_conversation('${conversation.id}')" class="fa-regular fa-trash"></i>
714
715
<i onclick="hide_option('${conversation.id}')" class="fa-regular fa-x"></i>
715
716
</div>
716
717
</div>
717
718
`;
718
}
719
});
720
box_conversations.innerHTML = html;
719
721
};
720
722
721
723
document.getElementById("cancelButton").addEventListener("click", async () => {
@@ -804,6 +806,7 @@ const register_settings_storage = async () => {
804
806
appStorage.setItem(element.id, element.selectedIndex);
805
807
break;
806
808
case "text":
809
case "number":
807
810
appStorage.setItem(element.id, element.value);
808
811
break;
809
812
default:
@@ -828,6 +831,7 @@ const load_settings_storage = async () => {
828
831
element.selectedIndex = parseInt(value);
829
832
break;
830
833
case "text":
834
case "number":
831
835
case "textarea":
832
836
element.value = value;
833
837
break;