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
This commit is contained in:
hlohaus
2025-01-26 21:56:31 +01:00
parent 595dafd71a
commit 19bf5b7ef6
8 changed files with 118 additions and 44 deletions
+33
View File
@@ -245,6 +245,25 @@ class Api:
]
}
@self.app.get("/{provider}/models", responses={
HTTP_200_OK: {"model": List[ModelResponseModel]},
})
async def models(provider: str):
if provider not in ProviderUtils.convert:
return ErrorResponse.from_message("The provider does not exist.", 404)
provider: ProviderType = ProviderUtils.convert[provider]
return {
"object": "list",
"data": [{
"id": model,
"object": "model",
"created": 0,
"owned_by": getattr(provider, "label", provider.__name__),
"image": model in getattr(provider, "image_models", []),
"image": model in getattr(provider, "vision_models", []),
} for model in provider.get_models() if hasattr(provider, "get_models")]
}
@self.app.get("/v1/models/{model_name}", responses={
HTTP_200_OK: {"model": ModelResponseModel},
HTTP_404_NOT_FOUND: {"model": ErrorResponseModel},
@@ -352,6 +371,20 @@ class Api:
logger.exception(e)
return ErrorResponse.from_exception(e, config, HTTP_500_INTERNAL_SERVER_ERROR)
@self.app.post("/{provider}/chat/completions", responses={
HTTP_200_OK: {"model": ChatCompletion},
HTTP_401_UNAUTHORIZED: {"model": ErrorResponseModel},
HTTP_404_NOT_FOUND: {"model": ErrorResponseModel},
HTTP_422_UNPROCESSABLE_ENTITY: {"model": ErrorResponseModel},
HTTP_500_INTERNAL_SERVER_ERROR: {"model": ErrorResponseModel},
})
async def provider_chat_completions(
provider: str,
config: ChatCompletionsConfig,
credentials: Annotated[HTTPAuthorizationCredentials, Depends(Api.security)] = None,
):
return await chat_completions(config, credentials, provider)
responses = {
HTTP_200_OK: {"model": ImagesResponse},
HTTP_401_UNAUTHORIZED: {"model": ErrorResponseModel},
+13 -6
View File
@@ -162,6 +162,19 @@
document.getElementById('recognition-language').placeholder = navigator.language;
</script>
</div>
<div class="field mem0 hidden">
<span class="label">Enable Memory with Mem0</span>
<input type="checkbox" id="mem0"/>
<label for="mem0" class="toogle" title=""></label>
<button onclick="import_memory()">
<i class="fa-solid fa-arrow-up-from-bracket"></i>
<span>Import Messages to Mem0</span>
</button>
</div>
<div class="field box hidden">
<label for="mem0-api_key" class="label" title="">Mem0 API:</label>
<input type="text" id="mem0-api_key" name="mem0[api_key]" placeholder="api_key"/>
</div>
<div class="field box">
<label for="Custom-api_base" class="label" title="">Custom Provider (Base Url):</label>
<input type="text" id="Custom-api_base" name="Custom[api_base]" placeholder="http://localhost:8080/v1"/>
@@ -189,12 +202,6 @@
<a href="" onclick="return false;">Show log</a>
</button>
</div>
<div class="bottom_buttons memory hidden">
<button onclick="import_memory()">
<i class="fa-solid fa-arrow-up-from-bracket"></i>
<a href="" onclick="return false;">Import Messages to Memory</a>
</button>
</div>
</div>
<div class="provider_forms hidden">
<div class="bottom_buttons">
+9 -5
View File
@@ -221,12 +221,12 @@ body:not(.white) a:visited{
background-color: var(--blur-bg);
}
.conversations i, .bottom_buttons i {
.conversations i, .bottom_buttons i, .mem0 button i {
color: var(--conversations);
cursor: pointer;
}
.bottom_buttons i {
.bottom_buttons i, .mem0 button i {
width: 14px;
}
@@ -998,7 +998,7 @@ select:hover,
margin: 4px 0;
}
.bottom_buttons button {
.bottom_buttons button, .mem0 button {
padding: 8px 12px;
display: flex;
gap: 18px;
@@ -1011,10 +1011,15 @@ select:hover,
width: 100%;
}
.mem0 button {
width: auto;
}
.bottom_buttons button a,
.bottom_buttons button span,
.bottom_buttons .info a,
.bottom_buttons .info i {
.bottom_buttons .info i,
.mem0 button span {
color: var(--colour-3);
font-weight: 500;
}
@@ -1129,7 +1134,6 @@ ul {
padding-left: 10px;
padding-top: 10px;
}
@media screen and (max-width: 990px) {
.conversations {
display: none;
+41 -20
View File
@@ -1533,7 +1533,7 @@ async function hide_sidebar() {
chat.classList.remove("hidden");
log_storage.classList.add("hidden");
await hide_settings();
if (window.location.pathname == "/menu/" || window.location.pathname == "/settings/") {
if (window.location.pathname.endsWith("/menu/") || window.location.pathname.endsWith("/settings/")) {
history.back();
}
}
@@ -1550,10 +1550,7 @@ sidebar_button.addEventListener("click", async () => {
if (sidebar.classList.contains("shown")) {
await hide_sidebar();
} else {
sidebar.classList.add("shown");
sidebar_button.classList.add("rotated");
await hide_settings();
add_url_to_history("/menu/");
await show_menu();
}
window.scrollTo(0, 0);
});
@@ -1564,12 +1561,19 @@ function add_url_to_history(url) {
}
}
async function show_menu() {
sidebar.classList.add("shown");
sidebar_button.classList.add("rotated");
await hide_settings();
add_url_to_history("/chat/menu/");
}
function open_settings() {
if (settings.classList.contains("hidden")) {
chat.classList.add("hidden");
sidebar.classList.remove("shown");
settings.classList.remove("hidden");
add_url_to_history("/settings/");
add_url_to_history("/chat/settings/");
} else {
settings.classList.add("hidden");
chat.classList.remove("hidden");
@@ -1782,7 +1786,9 @@ window.addEventListener('pywebviewready', async function() {
async function on_load() {
count_input();
if (/\/chat\/[^?]+/.test(window.location.href)) {
if (/\/settings\//.test(window.location.href)) {
open_settings();
} else if (/\/chat\/[^?]+/.test(window.location.href)) {
load_conversation(window.conversation_id);
} else {
chatPrompt.value = document.getElementById("systemPrompt")?.value || "";
@@ -1878,7 +1884,7 @@ async function on_api() {
}
providerSelect.innerHTML = '<option value="" selected>Demo Mode</option>'
document.getElementById("pin").disabled = true;
document.getElementById("refine")?.parentElement.remove();
document.getElementById("refine")?.parentElement.classList.add("hidden")
const track_usage = document.getElementById("track_usage");
track_usage.checked = true;
track_usage.disabled = true;
@@ -2099,7 +2105,7 @@ async function upload_files(fileInput) {
body: formData
});
let do_refine = document.getElementById("refine").checked;
let do_refine = document.getElementById("refine")?.checked;
function connectToSSE(url) {
const eventSource = new EventSource(url);
eventSource.onmessage = (event) => {
@@ -2417,7 +2423,18 @@ function save_storage() {
}
function import_memory() {
if (!appStorage.getItem("mem0-api_key")) {
return;
}
hide_sidebar();
let count = 0;
let user_id = appStorage.getItem("user") || appStorage.getItem("mem0-user_id");
if (!user_id) {
user_id = uuid();
appStorage.setItem("mem0-user_id", user_id);
}
inputCount.innerText = `Start importing to Mem0...`;
let conversations = [];
for (let i = 0; i < appStorage.length; i++) {
if (appStorage.key(i).startsWith("conversation:")) {
@@ -2426,17 +2443,21 @@ function import_memory() {
}
}
conversations.sort((a, b) => (a.updated||0)-(b.updated||0));
let count = 0;
conversations.forEach(async (conversation)=>{
let body = JSON.stringify(conversation);
response = await fetch("/backend-api/v2/memory", {
method: 'POST',
body: body,
headers: {"content-type": "application/json"}
});
const result = await response.json();
count += result.count;
inputCount.innerText = `${count} Messages are imported`;
conversations.forEach(async (conversation, i)=>{
setTimeout(async ()=>{
let body = JSON.stringify(conversation);
response = await fetch(`/backend-api/v2/memory/${user_id}`, {
method: 'POST',
body: body,
headers: {
"content-type": "application/json",
"x_api_key": appStorage.getItem("mem0-api_key")
}
});
const result = await response.json();
count += result.count;
inputCount.innerText = `${count} Messages were imported`;
}, (i+1)*1000);
});
}
+1 -1
View File
@@ -42,7 +42,7 @@ class Api:
if provider in ProviderUtils.convert:
provider = ProviderUtils.convert[provider]
if issubclass(provider, ProviderModelMixin):
if api_key is not None and "api_key" in signature(provider.get_models).parameters:
if "api_key" in signature(provider.get_models).parameters:
models = provider.get_models(api_key=api_key, api_base=api_base)
else:
models = provider.get_models()
+14 -8
View File
@@ -171,16 +171,16 @@ class Backend_Api(Api):
f.write(f"{json.dumps(request.json)}\n")
return {}
@app.route('/backend-api/v2/memory', methods=['POST'])
def add_memory():
@app.route('/backend-api/v2/memory/<user_id>', methods=['POST'])
def add_memory(user_id: str):
api_key = request.headers.get("x_api_key")
json_data = request.json
from mem0 import MemoryClient
client = MemoryClient(api_key=api_key)
client.add(
[{"role": item["role"], "content": item["content"]} for item in json_data.get("items")],
user_id="user",
metadata={"conversation_id": json_data.get("id"), "title": json_data.get("title")}
user_id=user_id,
metadata={"conversation_id": json_data.get("id")}
)
return {"count": len(json_data.get("items"))}
@@ -189,13 +189,19 @@ class Backend_Api(Api):
api_key = request.headers.get("x_api_key")
from mem0 import MemoryClient
client = MemoryClient(api_key=api_key)
if request.args.search:
if request.args.get("search"):
return client.search(
request.args.search,
request.args.get("search"),
user_id=user_id,
metadata=json.loads(request.args.metadata) if request.args.metadata else None
filters=json.loads(request.args.get("filters", "null")),
metadata=json.loads(request.args.get("metadata", "null"))
)
return {}
return client.get_all(
user_id=user_id,
page=request.args.get("page", 1),
page_size=request.args.get("page_size", 100),
filters=json.loads(request.args.get("filters", "null")),
)
self.routes = {
'/backend-api/v2/version': {
+6 -3
View File
@@ -16,12 +16,12 @@ class Website:
'function': self._chat,
'methods': ['GET', 'POST']
},
'/menu/': {
'/chat/menu/': {
'function': redirect_home,
'methods': ['GET', 'POST']
},
'/settings/': {
'function': redirect_home,
'/chat/settings/': {
'function': self._settings,
'methods': ['GET', 'POST']
},
'/images/': {
@@ -36,4 +36,7 @@ class Website:
return render_template('index.html', chat_id=conversation_id)
def _index(self):
return render_template('index.html', chat_id=str(uuid.uuid4()))
def _settings(self):
return render_template('index.html', chat_id=str(uuid.uuid4()))
+1 -1
View File
@@ -7,6 +7,7 @@ from urllib.parse import urlparse
from typing import Iterator
from http.cookies import Morsel
from pathlib import Path
import asyncio
try:
from curl_cffi.requests import Session, Response
from .curl_cffi import StreamResponse, StreamSession, FormData
@@ -17,7 +18,6 @@ except ImportError:
has_curl_cffi = False
try:
import webview
import asyncio
has_webview = True
except ImportError:
has_webview = False