返回提交历史
Modified
g4f/api/__init__.py
+33
-0
Modified
g4f/gui/client/index.html
+13
-6
Modified
g4f/gui/client/static/css/style.css
+9
-5
Modified
g4f/gui/client/static/js/chat.v1.js
+41
-20
Modified
g4f/gui/server/api.py
+1
-1
Modified
g4f/gui/server/backend_api.py
+14
-8
Modified
g4f/gui/server/website.py
+6
-3
Modified
g4f/requests/__init__.py
+1
-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
代码差异
8 个文件
+118
-44
@@ -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},
@@ -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">
@@ -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;
@@ -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
@@ -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()
@@ -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': {
@@ -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()))
@@ -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