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

XFEstudio/gpt4free

Fix upload files in demo mode Updates for memory with mem0 Fix asyncio import in nodriver function Add provider specific api endpoints Support for open settings in UI at /chat/settings

19bf5b7e
hlohaus <983577+hlohaus@users.noreply.github.com>
提交于

代码差异

8 个文件 +118 -44
Modified g4f/api/__init__.py +33 -0
@@ -245,6 +245,25 @@ class Api:
245 245 ]
246 246 }
247 247
248 @self.app.get("/{provider}/models", responses={
249 HTTP_200_OK: {"model": List[ModelResponseModel]},
250 })
251 async def models(provider: str):
252 if provider not in ProviderUtils.convert:
253 return ErrorResponse.from_message("The provider does not exist.", 404)
254 provider: ProviderType = ProviderUtils.convert[provider]
255 return {
256 "object": "list",
257 "data": [{
258 "id": model,
259 "object": "model",
260 "created": 0,
261 "owned_by": getattr(provider, "label", provider.__name__),
262 "image": model in getattr(provider, "image_models", []),
263 "image": model in getattr(provider, "vision_models", []),
264 } for model in provider.get_models() if hasattr(provider, "get_models")]
265 }
266
248 267 @self.app.get("/v1/models/{model_name}", responses={
249 268 HTTP_200_OK: {"model": ModelResponseModel},
250 269 HTTP_404_NOT_FOUND: {"model": ErrorResponseModel},
@@ -352,6 +371,20 @@ class Api:
352 371 logger.exception(e)
353 372 return ErrorResponse.from_exception(e, config, HTTP_500_INTERNAL_SERVER_ERROR)
354 373
374 @self.app.post("/{provider}/chat/completions", responses={
375 HTTP_200_OK: {"model": ChatCompletion},
376 HTTP_401_UNAUTHORIZED: {"model": ErrorResponseModel},
377 HTTP_404_NOT_FOUND: {"model": ErrorResponseModel},
378 HTTP_422_UNPROCESSABLE_ENTITY: {"model": ErrorResponseModel},
379 HTTP_500_INTERNAL_SERVER_ERROR: {"model": ErrorResponseModel},
380 })
381 async def provider_chat_completions(
382 provider: str,
383 config: ChatCompletionsConfig,
384 credentials: Annotated[HTTPAuthorizationCredentials, Depends(Api.security)] = None,
385 ):
386 return await chat_completions(config, credentials, provider)
387
355 388 responses = {
356 389 HTTP_200_OK: {"model": ImagesResponse},
357 390 HTTP_401_UNAUTHORIZED: {"model": ErrorResponseModel},
Modified g4f/gui/client/index.html +13 -6
@@ -162,6 +162,19 @@
162 162 document.getElementById('recognition-language').placeholder = navigator.language;
163 163 </script>
164 164 </div>
165 <div class="field mem0 hidden">
166 <span class="label">Enable Memory with Mem0</span>
167 <input type="checkbox" id="mem0"/>
168 <label for="mem0" class="toogle" title=""></label>
169 <button onclick="import_memory()">
170 <i class="fa-solid fa-arrow-up-from-bracket"></i>
171 <span>Import Messages to Mem0</span>
172 </button>
173 </div>
174 <div class="field box hidden">
175 <label for="mem0-api_key" class="label" title="">Mem0 API:</label>
176 <input type="text" id="mem0-api_key" name="mem0[api_key]" placeholder="api_key"/>
177 </div>
165 178 <div class="field box">
166 179 <label for="Custom-api_base" class="label" title="">Custom Provider (Base Url):</label>
167 180 <input type="text" id="Custom-api_base" name="Custom[api_base]" placeholder="http://localhost:8080/v1"/>
@@ -189,12 +202,6 @@
189 202 <a href="" onclick="return false;">Show log</a>
190 203 </button>
191 204 </div>
192 <div class="bottom_buttons memory hidden">
193 <button onclick="import_memory()">
194 <i class="fa-solid fa-arrow-up-from-bracket"></i>
195 <a href="" onclick="return false;">Import Messages to Memory</a>
196 </button>
197 </div>
198 205 </div>
199 206 <div class="provider_forms hidden">
200 207 <div class="bottom_buttons">
Modified g4f/gui/client/static/css/style.css +9 -5
@@ -221,12 +221,12 @@ body:not(.white) a:visited{
221 221 background-color: var(--blur-bg);
222 222 }
223 223
224 .conversations i, .bottom_buttons i {
224 .conversations i, .bottom_buttons i, .mem0 button i {
225 225 color: var(--conversations);
226 226 cursor: pointer;
227 227 }
228 228
229 .bottom_buttons i {
229 .bottom_buttons i, .mem0 button i {
230 230 width: 14px;
231 231 }
232 232
@@ -998,7 +998,7 @@ select:hover,
998 998 margin: 4px 0;
999 999 }
1000 1000
1001 .bottom_buttons button {
1001 .bottom_buttons button, .mem0 button {
1002 1002 padding: 8px 12px;
1003 1003 display: flex;
1004 1004 gap: 18px;
@@ -1011,10 +1011,15 @@ select:hover,
1011 1011 width: 100%;
1012 1012 }
1013 1013
1014 .mem0 button {
1015 width: auto;
1016 }
1017
1014 1018 .bottom_buttons button a,
1015 1019 .bottom_buttons button span,
1016 1020 .bottom_buttons .info a,
1017 .bottom_buttons .info i {
1021 .bottom_buttons .info i,
1022 .mem0 button span {
1018 1023 color: var(--colour-3);
1019 1024 font-weight: 500;
1020 1025 }
@@ -1129,7 +1134,6 @@ ul {
1129 1134 padding-left: 10px;
1130 1135 padding-top: 10px;
1131 1136 }
1132
1133 1137 @media screen and (max-width: 990px) {
1134 1138 .conversations {
1135 1139 display: none;
Modified g4f/gui/client/static/js/chat.v1.js +41 -20
@@ -1533,7 +1533,7 @@ async function hide_sidebar() {
1533 1533 chat.classList.remove("hidden");
1534 1534 log_storage.classList.add("hidden");
1535 1535 await hide_settings();
1536 if (window.location.pathname == "/menu/" || window.location.pathname == "/settings/") {
1536 if (window.location.pathname.endsWith("/menu/") || window.location.pathname.endsWith("/settings/")) {
1537 1537 history.back();
1538 1538 }
1539 1539 }
@@ -1550,10 +1550,7 @@ sidebar_button.addEventListener("click", async () => {
1550 1550 if (sidebar.classList.contains("shown")) {
1551 1551 await hide_sidebar();
1552 1552 } else {
1553 sidebar.classList.add("shown");
1554 sidebar_button.classList.add("rotated");
1555 await hide_settings();
1556 add_url_to_history("/menu/");
1553 await show_menu();
1557 1554 }
1558 1555 window.scrollTo(0, 0);
1559 1556 });
@@ -1564,12 +1561,19 @@ function add_url_to_history(url) {
1564 1561 }
1565 1562 }
1566 1563
1564 async function show_menu() {
1565 sidebar.classList.add("shown");
1566 sidebar_button.classList.add("rotated");
1567 await hide_settings();
1568 add_url_to_history("/chat/menu/");
1569 }
1570
1567 1571 function open_settings() {
1568 1572 if (settings.classList.contains("hidden")) {
1569 1573 chat.classList.add("hidden");
1570 1574 sidebar.classList.remove("shown");
1571 1575 settings.classList.remove("hidden");
1572 add_url_to_history("/settings/");
1576 add_url_to_history("/chat/settings/");
1573 1577 } else {
1574 1578 settings.classList.add("hidden");
1575 1579 chat.classList.remove("hidden");
@@ -1782,7 +1786,9 @@ window.addEventListener('pywebviewready', async function() {
1782 1786
1783 1787 async function on_load() {
1784 1788 count_input();
1785 if (/\/chat\/[^?]+/.test(window.location.href)) {
1789 if (/\/settings\//.test(window.location.href)) {
1790 open_settings();
1791 } else if (/\/chat\/[^?]+/.test(window.location.href)) {
1786 1792 load_conversation(window.conversation_id);
1787 1793 } else {
1788 1794 chatPrompt.value = document.getElementById("systemPrompt")?.value || "";
@@ -1878,7 +1884,7 @@ async function on_api() {
1878 1884 }
1879 1885 providerSelect.innerHTML = '<option value="" selected>Demo Mode</option>'
1880 1886 document.getElementById("pin").disabled = true;
1881 document.getElementById("refine")?.parentElement.remove();
1887 document.getElementById("refine")?.parentElement.classList.add("hidden")
1882 1888 const track_usage = document.getElementById("track_usage");
1883 1889 track_usage.checked = true;
1884 1890 track_usage.disabled = true;
@@ -2099,7 +2105,7 @@ async function upload_files(fileInput) {
2099 2105 body: formData
2100 2106 });
2101 2107
2102 let do_refine = document.getElementById("refine").checked;
2108 let do_refine = document.getElementById("refine")?.checked;
2103 2109 function connectToSSE(url) {
2104 2110 const eventSource = new EventSource(url);
2105 2111 eventSource.onmessage = (event) => {
@@ -2417,7 +2423,18 @@ function save_storage() {
2417 2423 }
2418 2424
2419 2425 function import_memory() {
2426 if (!appStorage.getItem("mem0-api_key")) {
2427 return;
2428 }
2420 2429 hide_sidebar();
2430
2431 let count = 0;
2432 let user_id = appStorage.getItem("user") || appStorage.getItem("mem0-user_id");
2433 if (!user_id) {
2434 user_id = uuid();
2435 appStorage.setItem("mem0-user_id", user_id);
2436 }
2437 inputCount.innerText = `Start importing to Mem0...`;
2421 2438 let conversations = [];
2422 2439 for (let i = 0; i < appStorage.length; i++) {
2423 2440 if (appStorage.key(i).startsWith("conversation:")) {
@@ -2426,17 +2443,21 @@ function import_memory() {
2426 2443 }
2427 2444 }
2428 2445 conversations.sort((a, b) => (a.updated||0)-(b.updated||0));
2429 let count = 0;
2430 conversations.forEach(async (conversation)=>{
2431 let body = JSON.stringify(conversation);
2432 response = await fetch("/backend-api/v2/memory", {
2433 method: 'POST',
2434 body: body,
2435 headers: {"content-type": "application/json"}
2436 });
2437 const result = await response.json();
2438 count += result.count;
2439 inputCount.innerText = `${count} Messages are imported`;
2446 conversations.forEach(async (conversation, i)=>{
2447 setTimeout(async ()=>{
2448 let body = JSON.stringify(conversation);
2449 response = await fetch(`/backend-api/v2/memory/${user_id}`, {
2450 method: 'POST',
2451 body: body,
2452 headers: {
2453 "content-type": "application/json",
2454 "x_api_key": appStorage.getItem("mem0-api_key")
2455 }
2456 });
2457 const result = await response.json();
2458 count += result.count;
2459 inputCount.innerText = `${count} Messages were imported`;
2460 }, (i+1)*1000);
2440 2461 });
2441 2462 }
2442 2463
Modified g4f/gui/server/api.py +1 -1
@@ -42,7 +42,7 @@ class Api:
42 42 if provider in ProviderUtils.convert:
43 43 provider = ProviderUtils.convert[provider]
44 44 if issubclass(provider, ProviderModelMixin):
45 if api_key is not None and "api_key" in signature(provider.get_models).parameters:
45 if "api_key" in signature(provider.get_models).parameters:
46 46 models = provider.get_models(api_key=api_key, api_base=api_base)
47 47 else:
48 48 models = provider.get_models()
Modified g4f/gui/server/backend_api.py +14 -8
@@ -171,16 +171,16 @@ class Backend_Api(Api):
171 171 f.write(f"{json.dumps(request.json)}\n")
172 172 return {}
173 173
174 @app.route('/backend-api/v2/memory', methods=['POST'])
175 def add_memory():
174 @app.route('/backend-api/v2/memory/<user_id>', methods=['POST'])
175 def add_memory(user_id: str):
176 176 api_key = request.headers.get("x_api_key")
177 177 json_data = request.json
178 178 from mem0 import MemoryClient
179 179 client = MemoryClient(api_key=api_key)
180 180 client.add(
181 181 [{"role": item["role"], "content": item["content"]} for item in json_data.get("items")],
182 user_id="user",
183 metadata={"conversation_id": json_data.get("id"), "title": json_data.get("title")}
182 user_id=user_id,
183 metadata={"conversation_id": json_data.get("id")}
184 184 )
185 185 return {"count": len(json_data.get("items"))}
186 186
@@ -189,13 +189,19 @@ class Backend_Api(Api):
189 189 api_key = request.headers.get("x_api_key")
190 190 from mem0 import MemoryClient
191 191 client = MemoryClient(api_key=api_key)
192 if request.args.search:
192 if request.args.get("search"):
193 193 return client.search(
194 request.args.search,
194 request.args.get("search"),
195 195 user_id=user_id,
196 metadata=json.loads(request.args.metadata) if request.args.metadata else None
196 filters=json.loads(request.args.get("filters", "null")),
197 metadata=json.loads(request.args.get("metadata", "null"))
197 198 )
198 return {}
199 return client.get_all(
200 user_id=user_id,
201 page=request.args.get("page", 1),
202 page_size=request.args.get("page_size", 100),
203 filters=json.loads(request.args.get("filters", "null")),
204 )
199 205
200 206 self.routes = {
201 207 '/backend-api/v2/version': {
Modified g4f/gui/server/website.py +6 -3
@@ -16,12 +16,12 @@ class Website:
16 16 'function': self._chat,
17 17 'methods': ['GET', 'POST']
18 18 },
19 '/menu/': {
19 '/chat/menu/': {
20 20 'function': redirect_home,
21 21 'methods': ['GET', 'POST']
22 22 },
23 '/settings/': {
24 'function': redirect_home,
23 '/chat/settings/': {
24 'function': self._settings,
25 25 'methods': ['GET', 'POST']
26 26 },
27 27 '/images/': {
@@ -36,4 +36,7 @@ class Website:
36 36 return render_template('index.html', chat_id=conversation_id)
37 37
38 38 def _index(self):
39 return render_template('index.html', chat_id=str(uuid.uuid4()))
40
41 def _settings(self):
39 42 return render_template('index.html', chat_id=str(uuid.uuid4()))
Modified g4f/requests/__init__.py +1 -1
@@ -7,6 +7,7 @@ from urllib.parse import urlparse
7 7 from typing import Iterator
8 8 from http.cookies import Morsel
9 9 from pathlib import Path
10 import asyncio
10 11 try:
11 12 from curl_cffi.requests import Session, Response
12 13 from .curl_cffi import StreamResponse, StreamSession, FormData
@@ -17,7 +18,6 @@ except ImportError:
17 18 has_curl_cffi = False
18 19 try:
19 20 import webview
20 import asyncio
21 21 has_webview = True
22 22 except ImportError:
23 23 has_webview = False