返回提交历史
Modified
g4f/Provider/PollinationsAI.py
+14
-15
Modified
g4f/gui/client/index.html
+29
-8
Modified
g4f/gui/client/static/css/style.css
+121
-25
Modified
g4f/gui/client/static/js/chat.v1.js
+23
-24
Modified
g4f/gui/server/backend_api.py
+9
-8
Modified
g4f/providers/base_provider.py
+5
-3
XFEstudio/gpt4free
Add headers / styling from in UI @kqlio67
7a75782a
代码差异
6 个文件
+201
-83
@@ -12,7 +12,7 @@ from .helper import filter_none, format_image_prompt
12
12
from .base_provider import AsyncGeneratorProvider, ProviderModelMixin
13
13
from ..typing import AsyncResult, Messages, MediaListType
14
14
from ..image import is_data_an_audio
15
from ..errors import ModelNotFoundError
15
from ..errors import ModelNotFoundError, ResponseError
16
16
from ..requests.raise_for_status import raise_for_status
17
17
from ..requests.aiohttp import get_connector
18
18
from ..image.copy_images import save_response_media
@@ -279,7 +279,7 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
279
279
**kwargs
280
280
) -> AsyncResult:
281
281
if not cache and seed is None:
282
seed = random.randint(9999, 99999999)
282
seed = random.randint(0, 2**32)
283
283
json_mode = False
284
284
if response_format and response_format.get("type") == "json_object":
285
285
json_mode = True
@@ -318,31 +318,30 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
318
318
if line[6:].startswith(b"[DONE]"):
319
319
break
320
320
result = json.loads(line[6:])
321
if "usage" in result:
322
yield Usage(**result["usage"])
321
323
choices = result.get("choices", [{}])
322
324
choice = choices.pop() if choices else {}
323
325
content = choice.get("delta", {}).get("content")
324
326
if content:
325
327
yield content
326
if "usage" in result:
327
yield Usage(**result["usage"])
328
328
finish_reason = choice.get("finish_reason")
329
329
if finish_reason:
330
330
yield FinishReason(finish_reason)
331
331
return
332
332
result = await response.json()
333
choice = result["choices"][0]
334
message = choice.get("message", {})
335
content = message.get("content", "")
336
337
if "tool_calls" in message:
338
yield ToolCalls(message["tool_calls"])
339
340
if content:
341
yield content
342
333
if "choices" in result:
334
choice = result["choices"][0]
335
message = choice.get("message", {})
336
content = message.get("content", "")
337
if content:
338
yield content
339
if "tool_calls" in message:
340
yield ToolCalls(message["tool_calls"])
341
else:
342
raise ResponseError(result)
343
343
if "usage" in result:
344
344
yield Usage(**result["usage"])
345
346
345
finish_reason = choice.get("finish_reason")
347
346
if finish_reason:
348
347
yield FinishReason(finish_reason)
@@ -75,6 +75,12 @@
75
75
</script>
76
76
<div class="gradient"></div>
77
77
<div class="sidebar shown">
78
<div class="sidebar-header">
79
<div class="sidebar-logo">G4F Chat</div>
80
<div class="mobile-sidebar-toggle">
81
<i class="fa-solid fa-bars"></i>
82
</div>
83
</div>
78
84
<div class="top">
79
85
<button class="new_convo" onclick="new_conversation()">
80
86
<i class="fa-regular fa-plus"></i>
@@ -104,8 +110,13 @@
104
110
</div>
105
111
<div class="settings hidden">
106
112
<div class="paper">
107
<h3>Settings</h3>
108
<div class="field">
113
<div class="settings-top-bar">
114
<button class="settings-back-button" onclick="open_settings();">
115
<i class="fa-solid fa-arrow-left"></i>
116
</button>
117
<span>Settings</span>
118
</div>
119
<div class="field">
109
120
<span class="label">Enable Dark Mode</span>
110
121
<input type="checkbox" id="darkMode" checked />
111
122
<label for="darkMode" class="toogle" title=""></label>
@@ -221,8 +232,14 @@
221
232
</div>
222
233
</div>
223
234
<div class="chat-container">
224
<div class="chat-header box">
225
G4F Chat
235
<div class="chat-top-panel">
236
<div class="mobile-sidebar-toggle">
237
<i class="fa-solid fa-bars"></i>
238
</div>
239
<div class="convo-title">New Conversation</div>
240
<button class="new_convo_icon" onclick="new_conversation()">
241
<i class="fa-regular fa-plus"></i>
242
</button>
226
243
</div>
227
244
<textarea id="chatPrompt" class="box" placeholder="System prompt"></textarea>
228
245
<button class="slide-header">
@@ -337,9 +354,13 @@
337
354
</div>
338
355
</div>
339
356
</div>
340
<div class="log hidden"></div>
341
<div class="mobile-sidebar">
342
<i class="fa-solid fa-bars"></i>
343
</div>
357
<div class="log hidden">
358
<div class="log-top-bar">
359
<button class="log-back-button" onclick="open_settings();">
360
<i class="fa-solid fa-arrow-left"></i>
361
</button>
362
<span>Logs</span>
363
</div>
364
</div>
344
365
</body>
345
366
</html>
@@ -57,15 +57,14 @@ html,
57
57
body {
58
58
scroll-behavior: smooth;
59
59
overflow: hidden;
60
max-height: 100%;
61
max-width: 100%;
62
60
}
63
61
64
62
body {
65
63
background: var(--background);
66
64
color: var(--colour-3);
67
height: 100vh;
68
65
display: flex;
66
height: 100vh;
67
max-height: 100vh;
69
68
}
70
69
71
70
body:not(.white) a:link,
@@ -936,7 +935,7 @@ input.model:hover
936
935
937
936
.slide-header {
938
937
position: absolute;
939
top: 0;
938
top: 58px;
940
939
z-index: 1;
941
940
padding: 10px;
942
941
border: none;
@@ -1101,26 +1100,35 @@ ul {
1101
1100
visibility: hidden;
1102
1101
}
1103
1102
1104
.mobile-sidebar {
1105
position: fixed;
1106
z-index: 1000;
1107
top: 10px;
1108
right: 10px;
1109
width: 40px;
1110
height: 40px;
1103
.sidebar-header {
1104
padding: 12px 0;
1105
margin-bottom: 12px;
1106
display: flex;
1107
justify-content: space-between;
1108
align-items: center;
1109
padding: 0 0 10px 0;
1110
border-bottom: 1px solid var(--blur-border);
1111
}
1112
1113
.sidebar-logo {
1114
font-size: 16px;
1115
font-weight: 500;
1116
}
1117
1118
.mobile-sidebar-toggle {
1119
display: flex;
1120
justify-content: center;
1121
align-items: center;
1122
width: 36px;
1123
height: 36px;
1111
1124
background-color: var(--blur-bg);
1112
1125
border: 1px solid var(--blur-border);
1113
border-radius: 10px;
1114
backdrop-filter: blur(20px);
1115
-webkit-backdrop-filter: blur(20px);
1126
border-radius: 8px;
1116
1127
cursor: pointer;
1117
justify-content: center;
1118
align-items: center;
1119
1128
transition: all 0.3s ease;
1120
padding: 10px 12px;
1121
1129
}
1122
1130
1123
.mobile-sidebar i {
1131
.mobile-sidebar-toggle i {
1124
1132
transition: 0.33s;
1125
1133
}
1126
1134
@@ -1328,7 +1336,9 @@ form textarea {
1328
1336
1329
1337
.settings, .log, form {
1330
1338
overflow: auto;
1331
width: 100%;
1339
display: flex;
1340
flex-direction: column;
1341
flex: 1;
1332
1342
}
1333
1343
1334
1344
.log {
@@ -1508,13 +1518,59 @@ form textarea {
1508
1518
flex-direction: column;
1509
1519
overflow: hidden;
1510
1520
}
1511
.chat-header {
1512
padding: 10px 28px;
1513
font-weight: 500;
1514
white-space: nowrap;
1515
text-overflow: ellipsis;
1516
overflow: hidden;
1521
.chat-top-panel {
1522
padding: 12px 15px;
1523
display: flex;
1524
justify-content: space-between;
1525
align-items: center;
1526
}
1527
1528
.chat-logo {
1529
font-size: 16px;
1530
}
1531
1532
.chat-top-panel .convo-title {
1533
margin: 0 10px;
1534
font-size: 14px;
1535
text-align: center;
1536
flex: 1;
1537
}
1538
1539
.chat-top-panel .mobile-sidebar-toggle {
1540
display: flex;
1541
justify-content: center;
1542
align-items: center;
1543
width: 36px;
1544
height: 36px;
1545
background-color: var(--blur-bg);
1546
border: 1px solid var(--blur-border);
1547
border-radius: 8px;
1548
cursor: pointer;
1549
}
1550
@media only screen and (min-width: 40em) {
1551
.chat-top-panel .mobile-sidebar-toggle {
1552
display: none;
1553
}
1554
}
1555
.chat-top-panel .new_convo_icon {
1556
display: flex;
1557
justify-content: center;
1558
align-items: center;
1559
width: 36px;
1560
height: 36px;
1561
background-color: var(--blur-bg);
1562
border: 1px solid var(--blur-border);
1563
border-radius: 8px;
1564
cursor: pointer;
1565
color: var(--colour-3);
1566
font-size: 16px;
1567
}
1568
1569
.chat-top-panel .mobile-sidebar-toggle:hover,
1570
.chat-top-panel .new_convo_icon:hover {
1571
background-color: var(--button-hover);
1517
1572
}
1573
1518
1574
@media only screen and (min-width: 40em) {
1519
1575
.sidebar {
1520
1576
width: 300px;
@@ -1564,4 +1620,44 @@ form textarea {
1564
1620
border-style: solid;
1565
1621
box-shadow: none;
1566
1622
background-color: var(--button-hover);
1623
}
1624
1625
.settings-top-bar, .log-top-bar {
1626
display: flex;
1627
align-items: center;
1628
padding: 15px 0;
1629
margin-bottom: 15px;
1630
border-bottom: 1px solid var(--blur-border);
1631
gap: 12px; /* Space between button and title */
1632
}
1633
1634
.settings-back-button, .log-back-button {
1635
display: flex;
1636
align-items: center;
1637
justify-content: center;
1638
background: none;
1639
border: none;
1640
color: var(--colour-3);
1641
font-size: 18px;
1642
cursor: pointer;
1643
width: 40px;
1644
height: 40px;
1645
border-radius: 8px;
1646
}
1647
1648
.settings-back-button:hover, .log-back-button:hover {
1649
background-color: var(--blur-border);
1650
}
1651
1652
.settings-title, .log-title {
1653
font-size: 20px;
1654
font-weight: 500;
1655
color: var(--colour-3);
1656
user-select: none; /* Prevent text selection */
1657
}
1658
1659
.settings-top-bar span, .log-top-bar span {
1660
font-weight: 600;
1661
font-size: 18px;
1662
margin-left: 8px;
1567
1663
}
@@ -5,7 +5,7 @@ const box_conversations = document.querySelector(`.top`);
5
5
const stop_generating = document.querySelector(`.stop_generating`);
6
6
const regenerate_button = document.querySelector(`.regenerate`);
7
7
const sidebar = document.querySelector(".sidebar");
8
const sidebar_button = document.querySelector(".mobile-sidebar");
8
const sidebar_buttons = document.querySelectorAll(".mobile-sidebar-toggle");
9
9
const sendButton = document.getElementById("sendButton");
10
10
const addButton = document.getElementById("addButton");
11
11
const imageInput = document.querySelector(".image-label");
@@ -570,6 +570,7 @@ const handle_ask = async (do_ask_gpt = true, message = null) => {
570
570
}
571
571
} else {
572
572
await safe_load_conversation(window.conversation_id, true);
573
await load_conversations();
573
574
}
574
575
};
575
576
@@ -1360,10 +1361,12 @@ const set_conversation = async (conversation_id) => {
1360
1361
};
1361
1362
1362
1363
const new_conversation = async () => {
1363
history.pushState({}, null, `/chat/`);
1364
if (!/\/chat\/(share|\?|$)/.test(window.location.href)) {
1365
history.pushState({}, null, `/chat/`);
1366
}
1364
1367
window.conversation_id = generateUUID();
1365
1368
document.title = window.title || document.title;
1366
document.querySelector(".chat-header").innerText = "New Conversation - G4F";
1369
document.querySelector(".chat-top-panel .convo-title").innerText = "New Conversation";
1367
1370
1368
1371
await clear_conversation();
1369
1372
if (chatPrompt) {
@@ -1429,16 +1432,16 @@ const load_conversation = async (conversation, scroll=true) => {
1429
1432
let messages = conversation?.items || [];
1430
1433
console.debug("Conversation:", conversation.id)
1431
1434
1432
let title = conversation.new_title || conversation.title;
1433
title = title ? `${title} - G4F` : window.title;
1435
let conversation_title = conversation.new_title || conversation.title;
1436
title = conversation_title ? `${conversation_title} - G4F` : window.title;
1434
1437
if (title) {
1435
1438
document.title = title;
1436
1439
}
1437
const chatHeader = document.querySelector(".chat-header");
1440
const chatHeader = document.querySelector(".chat-top-panel .convo-title");
1438
1441
if (window.share_id && conversation.id == window.start_id) {
1439
chatHeader.innerHTML = '<i class="fa-solid fa-qrcode"></i> ' + escapeHtml(title);
1442
chatHeader.innerHTML = '<i class="fa-solid fa-qrcode"></i> ' + escapeHtml(conversation_title);
1440
1443
} else {
1441
chatHeader.innerText = title;
1444
chatHeader.innerText = conversation_title;
1442
1445
}
1443
1446
1444
1447
if (chatPrompt) {
@@ -1860,7 +1863,7 @@ async function hide_sidebar(remove_shown=false) {
1860
1863
if (remove_shown) {
1861
1864
sidebar.classList.remove("shown");
1862
1865
}
1863
sidebar_button.classList.remove("rotated");
1866
sidebar_buttons.forEach((el)=>el.classList.remove("rotated"))
1864
1867
settings.classList.add("hidden");
1865
1868
chat.classList.remove("hidden");
1866
1869
log_storage.classList.add("hidden");
@@ -1878,18 +1881,16 @@ async function hide_settings() {
1878
1881
1879
1882
window.addEventListener('popstate', hide_sidebar, false);
1880
1883
1881
sidebar_button.addEventListener("click", async () => {
1882
if (sidebar.classList.contains("shown") || sidebar_button.classList.contains("rotated")) {
1883
await hide_sidebar();
1884
sidebar_buttons.forEach((el)=>el.addEventListener("click", async () => {
1885
if (sidebar.classList.contains("shown") || el.classList.contains("rotated")) {
1886
await hide_sidebar(true);
1884
1887
chat.classList.remove("hidden");
1885
sidebar.classList.remove("shown");
1886
sidebar_button.classList.remove("rotated");
1887
1888
} else {
1888
1889
await show_menu();
1889
1890
chat.classList.add("hidden");
1890
1891
}
1891
1892
window.scrollTo(0, 0);
1892
});
1893
}));
1893
1894
1894
1895
function add_url_to_history(url) {
1895
1896
if (!window?.pywebview) {
@@ -1899,7 +1900,7 @@ function add_url_to_history(url) {
1899
1900
1900
1901
async function show_menu() {
1901
1902
sidebar.classList.add("shown");
1902
sidebar_button.classList.add("rotated");
1903
sidebar_buttons.forEach((el)=>el.classList.add("rotated"))
1903
1904
await hide_settings();
1904
1905
add_url_to_history("/chat/menu/");
1905
1906
}
@@ -2137,7 +2138,7 @@ window.addEventListener('load', async function() {
2137
2138
if (!appStorage.getItem(`conversation:${window.conversation_id}`) || conversation.id == window.conversation_id) {
2138
2139
// Copy conversation from share
2139
2140
if (conversation.id != window.conversation_id) {
2140
conversation.id = window.conversation_id;
2141
window.conversation_id = conversation.id;
2141
2142
conversation.updated = Date.now();
2142
2143
window.share_id = null;
2143
2144
}
@@ -2210,7 +2211,8 @@ async function on_load() {
2210
2211
count_input();
2211
2212
if (/\/settings\//.test(window.location.href)) {
2212
2213
open_settings();
2213
} else if (/\/chat\/share/.test(window.location.href)) {
2214
} else if (/\/chat\/(share|\?|$)/.test(window.location.href)) {
2215
chatPrompt.value = document.getElementById("systemPrompt")?.value || "";
2214
2216
chatPrompt.value = document.getElementById("systemPrompt")?.value || "";
2215
2217
let chat_url = new URL(window.location.href)
2216
2218
let chat_params = new URLSearchParams(chat_url.search);
@@ -2218,11 +2220,9 @@ async function on_load() {
2218
2220
userInput.value = chat_params.get("prompt");
2219
2221
userInput.style.height = userInput.scrollHeight + "px";
2220
2222
userInput.focus();
2221
//await handle_ask();
2223
} else {
2224
new_conversation();
2222
2225
}
2223
} else if (/\/chat\/[?$]/.test(window.location.href)) {
2224
chatPrompt.value = document.getElementById("systemPrompt")?.value || "";
2225
say_hello();
2226
2226
} else {
2227
2227
//load_conversation(window.conversation_id);
2228
2228
}
@@ -2475,8 +2475,7 @@ async function on_api() {
2475
2475
});
2476
2476
document.querySelector(".slide-header")?.addEventListener("click", () => {
2477
2477
const checked = slide_systemPrompt_icon.classList.contains("fa-angles-up");
2478
document.querySelector(".chat-header").classList[checked ? "add": "remove"]("hidden");
2479
chatPrompt.classList[checked || hide_systemPrompt.checked ? "add": "remove"]("hidden");
2478
chatPrompt.classList[checked ? "add": "remove"]("hidden");
2480
2479
slide_systemPrompt_icon.classList[checked ? "remove": "add"]("fa-angles-up");
2481
2480
slide_systemPrompt_icon.classList[checked ? "add": "remove"]("fa-angles-down");
2482
2481
});
@@ -18,6 +18,7 @@ from hashlib import sha256
18
18
19
19
from ...client.service import convert_to_provider
20
20
from ...providers.asyncio import to_sync_generator
21
from ...providers.response import FinishReason
21
22
from ...client.helper import filter_markdown
22
23
from ...tools.files import supports_filename, get_streaming, get_bucket_dir, get_buckets
23
24
from ...tools.run_tools import iter_run_tools
@@ -79,16 +80,12 @@ class Backend_Api(Api):
79
80
@app.route('/backend-api/v2/models', methods=['GET'])
80
81
def jsonify_models(**kwargs):
81
82
response = get_demo_models() if app.demo else self.get_models(**kwargs)
82
if isinstance(response, list):
83
return jsonify(response)
84
return response
83
return jsonify(response)
85
84
86
85
@app.route('/backend-api/v2/models/<provider>', methods=['GET'])
87
86
def jsonify_provider_models(**kwargs):
88
87
response = self.get_provider_models(**kwargs)
89
if isinstance(response, list):
90
return jsonify(response)
91
return response
88
return jsonify(response)
92
89
93
90
@app.route('/backend-api/v2/providers', methods=['GET'])
94
91
def jsonify_providers(**kwargs):
@@ -259,9 +256,11 @@ class Backend_Api(Api):
259
256
else:
260
257
response = iter_run_tools(ChatCompletion.create, **parameters)
261
258
cache_dir.mkdir(parents=True, exist_ok=True)
259
copy_response = [chunk for chunk in response]
262
260
with cache_file.open("w") as f:
263
for chunk in response:
261
for chunk in copy_response:
264
262
f.write(str(chunk))
263
response = copy_response
265
264
else:
266
265
response = iter_run_tools(ChatCompletion.create, **parameters)
267
266
@@ -269,7 +268,9 @@ class Backend_Api(Api):
269
268
return Response(filter_markdown("".join([str(chunk) for chunk in response]), do_filter_markdown), mimetype='text/plain')
270
269
def cast_str():
271
270
for chunk in response:
272
if not isinstance(chunk, Exception):
271
if isinstance(chunk, FinishReason):
272
yield f"[{chunk.reason}]" if chunk.reason != "stop" else ""
273
elif not isinstance(chunk, Exception):
273
274
yield str(chunk)
274
275
return Response(cast_str(), mimetype='text/plain')
275
276
except Exception as e:
@@ -424,7 +424,10 @@ class AsyncAuthedProvider(AsyncGeneratorProvider, AuthFileMixin):
424
424
def write_cache_file(cls, cache_file: Path, auth_result: AuthResult = None):
425
425
if auth_result is not None:
426
426
cache_file.parent.mkdir(parents=True, exist_ok=True)
427
cache_file.write_text(json.dumps(auth_result.get_dict()))
427
try:
428
cache_file.write_text(json.dumps(auth_result.get_dict()))
429
except TypeError:
430
raise RuntimeError(f"Failed to save: {auth_result.get_dict()}")
428
431
elif cache_file.exists():
429
432
cache_file.unlink()
430
433
@@ -489,5 +492,4 @@ class AsyncAuthedProvider(AsyncGeneratorProvider, AuthFileMixin):
489
492
cache_file = None
490
493
yield chunk
491
494
finally:
492
if cache_file is not None:
493
cls.write_cache_file(cache_file, auth_result)
495
cls.write_cache_file(cache_file, auth_result)