XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet
公开
关注 0 Fork 0 Star 1
返回提交历史

XFEstudio/gpt4free

Add headers / styling from in UI @kqlio67

7a75782a
hlohaus <983577+hlohaus@users.noreply.github.com>
提交于

代码差异

6 个文件 +201 -83
Modified g4f/Provider/PollinationsAI.py +14 -15
@@ -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)
Modified g4f/gui/client/index.html +29 -8
@@ -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>
Modified g4f/gui/client/static/css/style.css +121 -25
@@ -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 }
Modified g4f/gui/client/static/js/chat.v1.js +23 -24
@@ -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 });
Modified g4f/gui/server/backend_api.py +9 -8
@@ -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:
Modified g4f/providers/base_provider.py +5 -3
@@ -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)