返回提交历史
Modified
g4f/Provider/Blackbox.py
+10
-3
Modified
g4f/Provider/OIVSCode.py
+6
-92
Modified
g4f/Provider/needs_auth/Custom.py
+1
-0
Modified
g4f/Provider/needs_auth/OpenaiTemplate.py
+35
-19
Modified
g4f/gui/client/demo.html
+1
-0
Modified
g4f/gui/client/index.html
+5
-3
Modified
g4f/gui/client/static/js/chat.v1.js
+50
-20
Modified
g4f/gui/server/backend_api.py
+12
-1
Modified
g4f/providers/response.py
+1
-1
XFEstudio/gpt4free
Check request limit in demo only in API Stop recognition in UI on enter request Fix Ratelimt for Ping in GUI Use OpenaiTemplate for OIVSCode Support Reasoning in Blackbox Add error reporting in UI Support Custom Provider in Demo
c18f1024
代码差异
9 个文件
+121
-139
@@ -14,7 +14,7 @@ from .base_provider import AsyncGeneratorProvider, ProviderModelMixin
14
14
from ..image import ImageResponse, to_data_uri
15
15
from ..cookies import get_cookies_dir
16
16
from .helper import format_prompt
17
from ..providers.response import FinishReason, JsonConversation
17
from ..providers.response import FinishReason, JsonConversation, Reasoning
18
18
19
19
class Conversation(JsonConversation):
20
20
validated_value: str = None
@@ -310,7 +310,14 @@ class Blackbox(AsyncGeneratorProvider, ProviderModelMixin):
310
310
prompt = messages[-1]["content"]
311
311
yield ImageResponse(images=[image_url], alt=prompt)
312
312
else:
313
if "Generated by BLACKBOX.AI" in text_to_yield:
313
if "<think>" in text_to_yield and "</think>" in chunk_text :
314
chunk_text = text_to_yield.split('<think>', 1)
315
yield chunk_text[0]
316
chunk_text = text_to_yield.split('</think>', 1)
317
yield Reasoning(chunk_text[0])
318
yield chunk_text[1]
319
full_response = text_to_yield
320
elif "Generated by BLACKBOX.AI" in text_to_yield:
314
321
conversation.validated_value = await cls.fetch_validated(force_refresh=True)
315
322
if conversation.validated_value:
316
323
data["validated"] = conversation.validated_value
@@ -337,7 +344,7 @@ class Blackbox(AsyncGeneratorProvider, ProviderModelMixin):
337
344
reason = "length"
338
345
else:
339
346
reason = "stop"
340
347
341
348
if return_conversation:
342
349
conversation.message_history.append({"role": "assistant", "content": full_response})
343
350
yield conversation
@@ -1,101 +1,15 @@
1
1
from __future__ import annotations
2
2
3
import json
4
from aiohttp import ClientSession
3
from .needs_auth.OpenaiTemplate import OpenaiTemplate
5
4
6
from ..image import to_data_uri
7
from ..typing import AsyncResult, Messages, ImagesType
8
from ..requests.raise_for_status import raise_for_status
9
from .base_provider import AsyncGeneratorProvider, ProviderModelMixin
10
from .helper import format_prompt
11
from ..providers.response import FinishReason
12
13
14
class OIVSCode(AsyncGeneratorProvider, ProviderModelMixin):
5
class OIVSCode(OpenaiTemplate):
15
6
label = "OI VSCode Server"
16
7
url = "https://oi-vscode-server.onrender.com"
17
api_endpoint = "https://oi-vscode-server.onrender.com/v1/chat/completions"
18
8
api_base = "https://oi-vscode-server.onrender.com/v1"
19
9
working = True
20
supports_stream = True
21
supports_system_message = True
22
supports_message_history = True
10
needs_auth = False
23
11
24
12
default_model = "gpt-4o-mini-2024-07-18"
25
13
default_vision_model = default_model
26
vision_models = [default_model, "gpt-4o-mini"]
27
models = vision_models
28
29
model_aliases = {"gpt-4o-mini": "gpt-4o-mini-2024-07-18"}
30
31
@classmethod
32
async def create_async_generator(
33
cls,
34
model: str,
35
messages: Messages,
36
stream: bool = False,
37
images: ImagesType = None,
38
proxy: str = None,
39
**kwargs
40
) -> AsyncResult:
41
headers = {
42
"accept": "*/*",
43
"accept-language": "en-US,en;q=0.9",
44
"content-type": "application/json",
45
"user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
46
}
47
48
async with ClientSession(headers=headers) as session:
49
50
if images is not None:
51
messages[-1]['content'] = [
52
{
53
"type": "text",
54
"text": messages[-1]['content']
55
},
56
*[
57
{
58
"type": "image_url",
59
"image_url": {
60
"url": to_data_uri(image)
61
}
62
}
63
for image, _ in images
64
]
65
]
66
67
data = {
68
"model": model,
69
"stream": stream,
70
"messages": messages
71
}
72
73
async with session.post(cls.api_endpoint, json=data, proxy=proxy) as response:
74
await raise_for_status(response)
75
76
full_response = ""
77
78
if stream:
79
async for line in response.content:
80
if line:
81
line = line.decode()
82
if line.startswith("data: "):
83
if line.strip() == "data: [DONE]":
84
break
85
try:
86
data = json.loads(line[6:])
87
if content := data["choices"][0]["delta"].get("content"):
88
yield content
89
full_response += content
90
except:
91
continue
92
93
reason = "length" if len(full_response) > 0 else "stop"
94
yield FinishReason(reason)
95
else:
96
response_data = await response.json()
97
full_response = response_data["choices"][0]["message"]["content"]
98
yield full_response
99
100
reason = "length" if len(full_response) > 0 else "stop"
101
yield FinishReason(reason)
14
vision_models = [default_model, "gpt-4o-mini"]
15
model_aliases = {"gpt-4o-mini": "gpt-4o-mini-2024-07-18"}
@@ -5,5 +5,6 @@ from .OpenaiTemplate import OpenaiTemplate
5
5
class Custom(OpenaiTemplate):
6
6
label = "Custom Provider"
7
7
working = True
8
needs_auth = False
8
9
api_base = "http://localhost:8080/v1"
9
10
sort_models = False
@@ -8,7 +8,7 @@ from ..helper import filter_none
8
8
from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin, RaiseErrorMixin
9
9
from ...typing import Union, Optional, AsyncResult, Messages, ImagesType
10
10
from ...requests import StreamSession, raise_for_status
11
from ...providers.response import FinishReason, ToolCalls, Usage, Reasoning
11
from ...providers.response import FinishReason, ToolCalls, Usage, Reasoning, ImageResponse
12
12
from ...errors import MissingAuthError, ResponseError
13
13
from ...image import to_data_uri
14
14
from ... import debug
@@ -59,6 +59,7 @@ class OpenaiTemplate(AsyncGeneratorProvider, ProviderModelMixin, RaiseErrorMixin
59
59
top_p: float = None,
60
60
stop: Union[str, list[str]] = None,
61
61
stream: bool = False,
62
prompt: str = None,
62
63
headers: dict = None,
63
64
impersonate: str = None,
64
65
tools: Optional[list] = None,
@@ -67,32 +68,47 @@ class OpenaiTemplate(AsyncGeneratorProvider, ProviderModelMixin, RaiseErrorMixin
67
68
) -> AsyncResult:
68
69
if cls.needs_auth and api_key is None:
69
70
raise MissingAuthError('Add a "api_key"')
70
if api_base is None:
71
api_base = cls.api_base
72
if images is not None and messages:
73
if not model and hasattr(cls, "default_vision_model"):
74
model = cls.default_vision_model
75
last_message = messages[-1].copy()
76
last_message["content"] = [
77
*[{
78
"type": "image_url",
79
"image_url": {"url": to_data_uri(image)}
80
} for image, _ in images],
81
{
82
"type": "text",
83
"text": messages[-1]["content"]
84
}
85
]
86
messages[-1] = last_message
87
71
async with StreamSession(
88
72
proxy=proxy,
89
73
headers=cls.get_headers(stream, api_key, headers),
90
74
timeout=timeout,
91
75
impersonate=impersonate,
92
76
) as session:
77
model = cls.get_model(model, api_key=api_key, api_base=api_base)
78
if api_base is None:
79
api_base = cls.api_base
80
81
# Proxy for image generation feature
82
if model in cls.image_models:
83
data = {
84
"prompt": messages[-1]["content"] if prompt is None else prompt,
85
"model": model,
86
}
87
async with session.post(f"{api_base.rstrip('/')}/images/generations", json=data) as response:
88
data = await response.json()
89
cls.raise_error(data)
90
await raise_for_status(response)
91
yield ImageResponse([image["url"] for image in data["data"]], prompt)
92
return
93
94
if images is not None and messages:
95
if not model and hasattr(cls, "default_vision_model"):
96
model = cls.default_vision_model
97
last_message = messages[-1].copy()
98
last_message["content"] = [
99
*[{
100
"type": "image_url",
101
"image_url": {"url": to_data_uri(image)}
102
} for image, _ in images],
103
{
104
"type": "text",
105
"text": messages[-1]["content"]
106
}
107
]
108
messages[-1] = last_message
93
109
data = filter_none(
94
110
messages=messages,
95
model=cls.get_model(model, api_key=api_key, api_base=api_base),
111
model=model,
96
112
temperature=temperature,
97
113
max_tokens=max_tokens,
98
114
top_p=top_p,
@@ -216,6 +216,7 @@
216
216
localStorage.setItem("HuggingFace-api_key", accessToken);
217
217
localStorage.setItem("HuggingFace-user", JSON.stringify(user));
218
218
localStorage.setItem("user", user.name);
219
localStorage.setItem("report_error", "true")
219
220
location.href = "/chat/";
220
221
}
221
222
input.addEventListener("input", () => check_access_token());
@@ -151,6 +151,11 @@
151
151
<input type="checkbox" id="track_usage"/>
152
152
<label for="track_usage" class="toogle" title=""></label>
153
153
</div>
154
<div class="field">
155
<span class="label">Report errors</span>
156
<input type="checkbox" id="report_error"/>
157
<label for="report_error" class="toogle" title=""></label>
158
</div>
154
159
<div class="field box">
155
160
<label for="systemPrompt" class="label" title="">System prompt</label>
156
161
<textarea id="systemPrompt" placeholder="You are a helpful assistant."></textarea>
@@ -162,9 +167,6 @@
162
167
<div class="field box">
163
168
<label for="recognition-language" class="label" title="">Speech recognition language</label>
164
169
<input type="text" id="recognition-language" value="" placeholder="navigator.language"/>
165
<script>
166
document.getElementById('recognition-language').placeholder = navigator.language;
167
</script>
168
170
</div>
169
171
<div class="field mem0 hidden">
170
172
<span class="label">Enable Memory with Mem0</span>
@@ -732,6 +732,7 @@ async function add_message_chunk(message, message_id, provider, scroll, finish_m
732
732
let p = document.createElement("p");
733
733
p.innerText = message.error;
734
734
log_storage.appendChild(p);
735
await api("log", {...message, provider: provider_storage[message_id]});
735
736
} else if (message.type == "preview") {
736
737
if (content_map.inner.clientHeight > 200)
737
738
content_map.inner.style.height = content_map.inner.clientHeight + "px";
@@ -794,6 +795,9 @@ const ask_gpt = async (message_id, message_index = -1, regenerate = false, provi
794
795
provider = providerSelect.options[providerSelect.selectedIndex].value;
795
796
}
796
797
let conversation = await get_conversation(window.conversation_id);
798
if (!conversation) {
799
return;
800
}
797
801
messages = prepare_messages(conversation.items, message_index, action=="continue");
798
802
message_storage[message_id] = "";
799
803
stop_generating.classList.remove("stop_generating-hidden");
@@ -1869,12 +1873,15 @@ async function on_api() {
1869
1873
if (prompt_lock) return;
1870
1874
prompt_lock = true;
1871
1875
setTimeout(()=>prompt_lock=false, 3000);
1876
stop_recognition();
1872
1877
await handle_ask();
1873
1878
});
1874
1879
sendButton.querySelector(".fa-square-plus").addEventListener(`click`, async () => {
1880
stop_recognition();
1875
1881
await handle_ask(false);
1876
1882
});
1877
1883
messageInput.focus();
1884
1878
1885
let provider_options = [];
1879
1886
models = await api("models");
1880
1887
models.forEach((model) => {
@@ -1891,7 +1898,8 @@ async function on_api() {
1891
1898
location.href = "/";
1892
1899
return;
1893
1900
}
1894
providerSelect.innerHTML = '<option value="" selected>Demo Mode</option>'
1901
providerSelect.innerHTML = '<option value="">Demo Mode</option><option value="Custom">Custom Provider</option>';
1902
providerSelect.selectedIndex = 0;
1895
1903
document.getElementById("pin").disabled = true;
1896
1904
document.getElementById("refine")?.parentElement.classList.add("hidden")
1897
1905
const track_usage = document.getElementById("track_usage");
@@ -2020,6 +2028,7 @@ async function on_api() {
2020
2028
2021
2029
const method = switchInput.checked ? "add" : "remove";
2022
2030
searchButton.classList[method]("active");
2031
document.getElementById('recognition-language').placeholder = get_navigator_language();
2023
2032
}
2024
2033
2025
2034
async function load_version() {
@@ -2288,6 +2297,12 @@ async function api(ressource, args=null, files=null, message_id=null, scroll=tru
2288
2297
return;
2289
2298
}
2290
2299
} else if (args) {
2300
if (ressource == "log") {
2301
if (appStorage.getItem("report_error") != "true") {
2302
return;
2303
}
2304
url = `https://roxky-g4f-demo.hf.space${url}"`;
2305
}
2291
2306
headers['content-type'] = 'application/json';
2292
2307
response = await fetch(url, {
2293
2308
method: 'POST',
@@ -2456,25 +2471,33 @@ function import_memory() {
2456
2471
}
2457
2472
}
2458
2473
conversations.sort((a, b) => (a.updated||0)-(b.updated||0));
2459
conversations.forEach(async (conversation, i)=>{
2460
setTimeout(async ()=>{
2461
let body = JSON.stringify(conversation);
2462
response = await fetch(`/backend-api/v2/memory/${user_id}`, {
2463
method: 'POST',
2464
body: body,
2465
headers: {
2466
"content-type": "application/json",
2467
"x_api_key": appStorage.getItem("mem0-api_key")
2468
}
2469
});
2470
const result = await response.json();
2471
count += result.count;
2472
inputCount.innerText = `${count} Messages were imported`;
2473
}, (i+1)*1000);
2474
});
2474
async function add_conversation_to_memory(i) {
2475
if (i > conversations.length - 1) {
2476
return;
2477
}
2478
let body = JSON.stringify(conversations[i]);
2479
response = await fetch(`/backend-api/v2/memory/${user_id}`, {
2480
method: 'POST',
2481
body: body,
2482
headers: {
2483
"content-type": "application/json",
2484
"x_api_key": appStorage.getItem("mem0-api_key")
2485
}
2486
});
2487
const result = await response.json();
2488
count += result.count;
2489
inputCount.innerText = `${count} Messages were imported`;
2490
add_conversation_to_memory(i + 1);
2491
}
2492
add_conversation_to_memory(0)
2493
}
2494
2495
function get_navigator_language() {
2496
return navigator.languages.filter((v)=>v.includes("-"))[0] || navigator.language;
2475
2497
}
2476
2498
2477
2499
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
2500
let stop_recognition = ()=>{};
2478
2501
if (SpeechRecognition) {
2479
2502
const mircoIcon = microLabel.querySelector("i");
2480
2503
mircoIcon.classList.add("fa-microphone");
@@ -2524,15 +2547,22 @@ if (SpeechRecognition) {
2524
2547
}
2525
2548
};
2526
2549
2527
microLabel.addEventListener("click", (e) => {
2550
stop_recognition = ()=>{
2528
2551
if (microLabel.classList.contains("recognition")) {
2529
2552
microLabel.classList.remove("recognition");
2530
2553
recognition.stop();
2531
2554
messageInput.value = `${startValue ? startValue + "\n" : ""}${buffer}`;
2532
} else {
2555
count_input();
2556
return true;
2557
}
2558
return false;
2559
}
2560
2561
microLabel.addEventListener("click", (e) => {
2562
if (!stop_recognition()) {
2533
2563
microLabel.classList.add("recognition");
2534
2564
const lang = document.getElementById("recognition-language")?.value;
2535
recognition.lang = lang || navigator.language;
2565
recognition.lang = lang || get_navigator_language();
2536
2566
recognition.start();
2537
2567
}
2538
2568
});
@@ -68,6 +68,7 @@ class Backend_Api(Api):
68
68
app=app,
69
69
default_limits=["200 per day", "50 per hour"],
70
70
storage_uri="memory://",
71
auto_check=False
71
72
)
72
73
73
74
if has_flask_limiter and app.demo:
@@ -133,7 +134,7 @@ class Backend_Api(Api):
133
134
else:
134
135
json_data = request.json
135
136
136
if app.demo:
137
if app.demo and json_data.get("provider") != "Custom":
137
138
model = json_data.get("model")
138
139
if model != "default" and model in models.demo_models:
139
140
json_data["provider"] = random.choice(models.demo_models[model][1])
@@ -156,6 +157,7 @@ class Backend_Api(Api):
156
157
@app.route('/backend-api/v2/conversation', methods=['POST'])
157
158
@limiter.limit("4 per minute") # 1 request in 15 seconds
158
159
def _handle_conversation():
160
limiter.check()
159
161
return handle_conversation()
160
162
else:
161
163
@app.route('/backend-api/v2/conversation', methods=['POST'])
@@ -171,6 +173,15 @@ class Backend_Api(Api):
171
173
f.write(f"{json.dumps(request.json)}\n")
172
174
return {}
173
175
176
@app.route('/backend-api/v2/log', methods=['POST'])
177
def add_log():
178
cache_dir = Path(get_cookies_dir()) / ".logging"
179
cache_file = cache_dir / f"{datetime.date.today()}.jsonl"
180
cache_dir.mkdir(parents=True, exist_ok=True)
181
with cache_file.open("a" if cache_file.exists() else "w") as f:
182
f.write(f"{json.dumps(request.json)}\n")
183
return {}
184
174
185
@app.route('/backend-api/v2/memory/<user_id>', methods=['POST'])
175
186
def add_memory(user_id: str):
176
187
api_key = request.headers.get("x_api_key")
@@ -126,7 +126,7 @@ class Reasoning(ResponseType):
126
126
self.status = status
127
127
128
128
def __str__(self) -> str:
129
return "" if self.token is None else self.token
129
return f"{self.status}\n" if self.token is None else self.token
130
130
131
131
class Sources(ResponseType):
132
132
def __init__(self, sources: list[dict[str, str]]) -> None: