返回提交历史
Modified
g4f/api/__init__.py
+4
-2
Modified
g4f/gui/client/index.html
+1
-1
Modified
g4f/gui/client/static/css/style.css
+16
-8
Modified
g4f/gui/client/static/js/chat.v1.js
+34
-14
XFEstudio/gpt4free
Fix styling in GUI, improve mobile support
63b7e96c
代码差异
4 个文件
+55
-25
@@ -9,6 +9,7 @@ import shutil
9
9
from email.utils import formatdate
10
10
import os.path
11
11
import hashlib
12
import asyncio
12
13
from fastapi import FastAPI, Response, Request, UploadFile, Depends
13
14
from fastapi.middleware.wsgi import WSGIMiddleware
14
15
from fastapi.responses import StreamingResponse, RedirectResponse, HTMLResponse, JSONResponse
@@ -540,8 +541,9 @@ class Api:
540
541
content_type = getattr(provider_handler, "synthesize_content_type", "application/octet-stream")
541
542
return StreamingResponse(response_data, media_type=content_type)
542
543
543
@self.app.get("/json/{filename}")
544
@self.app.post("/json/{filename}")
544
545
async def get_json(filename, request: Request):
546
await asyncio.sleep(30)
545
547
return ""
546
548
547
549
@self.app.get("/images/{filename}", responses={
@@ -580,7 +582,7 @@ class Api:
580
582
if len(source_url) > 1:
581
583
source_url = source_url[1]
582
584
source_url = source_url.replace("%2F", "/").replace("%3A", ":").replace("%3F", "?")
583
if source_url.startswith("http"):
585
if source_url.startswith("https://"):
584
586
await copy_images(
585
587
[source_url],
586
588
target=target)
@@ -248,7 +248,7 @@
248
248
</div>
249
249
</div>
250
250
<div class="user-input">
251
<div class="box input-box">
251
<div class="box input-area">
252
252
<textarea id="message-input" placeholder="Ask a question" cols="30" rows="10"
253
253
style="white-space: pre-wrap;resize: none;"></textarea>
254
254
<label class="file-label image-label" for="image" title="">
@@ -971,10 +971,10 @@ select:hover,
971
971
}
972
972
}
973
973
974
.input-box {
974
.input-area {
975
975
display: flex;
976
976
align-items: center;
977
cursor: pointer;
977
padding: 10px;
978
978
}
979
979
980
980
.info {
@@ -1150,6 +1150,7 @@ ul {
1150
1150
align-items: flex-start;
1151
1151
flex-wrap: wrap;
1152
1152
gap: 8px;
1153
padding-left: 4px;
1153
1154
}
1154
1155
1155
1156
.mobile-sidebar {
@@ -1317,19 +1318,20 @@ ul {
1317
1318
border: 1px dashed #e4d4ffa6;
1318
1319
border-radius: 4px;
1319
1320
cursor: pointer;
1320
padding-left: 4px;
1321
padding-right: 5px;
1322
padding-top: 2px;
1323
padding-bottom: 2px;
1324
1321
position: absolute;
1325
1322
bottom: 8px;
1326
right: 8px;
1323
right: 4px;
1324
padding: 4px;
1327
1325
}
1328
1326
1329
1327
#send-button:hover {
1330
1328
border: 1px solid #e4d4ffc9;
1331
1329
}
1332
1330
1331
#send-button i {
1332
padding: 2px;
1333
}
1334
1333
1335
form textarea {
1334
1336
height: 20px;
1335
1337
min-height: 20px;
@@ -1434,7 +1436,13 @@ form .field.saved .fa-xmark {
1434
1436
1435
1437
#message-input {
1436
1438
height: 90px;
1437
margin-left: 20px;
1439
flex: 1;
1440
padding: 10px;
1441
padding-left: 20px;
1442
border-radius: 20px;
1443
font-size: 14px;
1444
margin-right: 10px;
1445
outline: none;
1438
1446
max-height: 200px;
1439
1447
}
1440
1448
@@ -39,7 +39,7 @@ let reasoning_storage = {}
39
39
let is_demo = false;
40
40
41
41
messageInput.addEventListener("blur", () => {
42
window.scrollTo(0, 0);
42
document.documentElement.scrollTop = 0;
43
43
});
44
44
45
45
messageInput.addEventListener("focus", () => {
@@ -244,16 +244,23 @@ const register_message_buttons = async () => {
244
244
if (!("click" in el.dataset)) {
245
245
el.dataset.click = "true";
246
246
el.addEventListener("click", async () => {
247
let message_el = get_message_el(el);
248
247
const elem = window.document.createElement('a');
249
248
let filename = `chat ${new Date().toLocaleString()}.md`.replaceAll(":", "-");
250
elem.href = message_el.dataset.object_url;
249
const conversation = await get_conversation(window.conversation_id);
250
let buffer = "";
251
conversation.items.forEach(message => {
252
buffer += `${message.role == 'user' ? 'User' : 'Assistant'}: ${message.content.trim()}\n\n\n`;
253
});
254
const file = new File([buffer.trim()], 'message.md', {type: 'text/plain'});
255
const objectUrl = URL.createObjectURL(file);
256
elem.href = objectUrl;
251
257
elem.download = filename;
252
258
document.body.appendChild(elem);
253
259
elem.click();
254
260
document.body.removeChild(elem);
255
261
el.classList.add("clicked");
256
262
setTimeout(() => el.classList.remove("clicked"), 1000);
263
URL.revokeObjectURL(objectUrl);
257
264
})
258
265
}
259
266
});
@@ -1771,7 +1778,11 @@ function update_message(content_map, message_id, content = null, scroll = true)
1771
1778
let countFocus = messageInput;
1772
1779
const count_input = async () => {
1773
1780
if (countFocus.value) {
1774
inputCount.innerText = count_words_and_tokens(countFocus.value, get_selected_model()?.value);
1781
if (window.matchMedia("(pointer:coarse)")) {
1782
inputCount.innerText = `(${count_tokens(get_selected_model()?.value, countFocus.value)} tokens)`;
1783
} else {
1784
inputCount.innerText = count_words_and_tokens(countFocus.value, get_selected_model()?.value);
1785
}
1775
1786
} else {
1776
1787
inputCount.innerText = "";
1777
1788
}
@@ -1815,11 +1826,7 @@ async function on_load() {
1815
1826
let chat_url = new URL(window.location.href)
1816
1827
let chat_params = new URLSearchParams(chat_url.search);
1817
1828
if (chat_params.get("prompt")) {
1818
messageInput.value = chat_params.title
1819
+ chat_params.title ? "\n\n\n" : ""
1820
+ chat_params.prompt
1821
+ chat_params.prompt && chat_params.url ? "\n\n\n" : ""
1822
+ chat_params.url;
1829
messageInput.value = `${chat_params.title}\n${chat_params.prompt}\n${chat_params.url}`.trim();
1823
1830
messageInput.style.height = messageInput.scrollHeight + "px";
1824
1831
messageInput.focus();
1825
1832
//await handle_ask();
@@ -1870,7 +1877,8 @@ async function on_api() {
1870
1877
messageInput.addEventListener("keydown", async (evt) => {
1871
1878
if (prompt_lock) return;
1872
1879
// If not mobile and not shift enter
1873
if (!window.matchMedia("(pointer:coarse)").matches && evt.keyCode === 13 && !evt.shiftKey) {
1880
let do_enter = messageInput.value.endsWith("\n\n");
1881
if (do_enter || !window.matchMedia("(pointer:coarse)").matches && evt.keyCode === 13 && !evt.shiftKey) {
1874
1882
evt.preventDefault();
1875
1883
console.log("pressed enter");
1876
1884
prompt_lock = true;
@@ -1892,7 +1900,9 @@ async function on_api() {
1892
1900
stop_recognition();
1893
1901
await handle_ask(false);
1894
1902
});
1895
messageInput.focus();
1903
messageInput.addEventListener(`click`, async () => {
1904
stop_recognition();
1905
});
1896
1906
1897
1907
let provider_options = [];
1898
1908
models = await api("models");
@@ -2394,14 +2404,18 @@ async function load_provider_models(provider=null) {
2394
2404
if (models && models.length > 0) {
2395
2405
modelSelect.classList.add("hidden");
2396
2406
modelProvider.classList.remove("hidden");
2397
models.forEach((model) => {
2407
let defaultIndex = 0;
2408
models.forEach((model, i) => {
2398
2409
let option = document.createElement('option');
2399
2410
option.value = model.model;
2400
2411
option.dataset.label = model.model;
2401
2412
option.text = `${model.model}${model.image ? " (Image Generation)" : ""}${model.vision ? " (Image Upload)" : ""}`;
2402
option.selected = model.default;
2403
2413
modelProvider.appendChild(option);
2414
if (model.default) {
2415
defaultIndex = i;
2416
}
2404
2417
});
2418
modelProvider.selectedIndex = defaultIndex;
2405
2419
let value = appStorage.getItem(modelProvider.name);
2406
2420
if (value) {
2407
2421
modelProvider.value = value;
@@ -2411,7 +2425,12 @@ async function load_provider_models(provider=null) {
2411
2425
modelSelect.classList.remove("hidden");
2412
2426
}
2413
2427
};
2414
providerSelect.addEventListener("change", () => load_provider_models());
2428
providerSelect.addEventListener("change", () => {
2429
load_provider_models()
2430
messageInput.focus();
2431
});
2432
modelSelect.addEventListener("change", () => messageInput.focus());
2433
modelProvider.addEventListener("change", () => messageInput.focus());
2415
2434
2416
2435
document.getElementById("pin").addEventListener("click", async () => {
2417
2436
const pin_container = document.getElementById("pin_container");
@@ -2446,6 +2465,7 @@ switchInput.addEventListener("change", () => {
2446
2465
});
2447
2466
searchButton.addEventListener("click", async () => {
2448
2467
switchInput.click();
2468
messageInput.focus();
2449
2469
});
2450
2470
2451
2471
function save_storage() {