From ae97a96379ed85d14e2b70cbd5ede233f1178534 Mon Sep 17 00:00:00 2001 From: Anuraag Jain Date: Sun, 18 Feb 2024 21:29:47 +0200 Subject: [PATCH 01/52] WIP feat: cancel model download --- .../components/chat/Settings/Models.svelte | 44 +++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/src/lib/components/chat/Settings/Models.svelte b/src/lib/components/chat/Settings/Models.svelte index aafa333f..cb557b55 100644 --- a/src/lib/components/chat/Settings/Models.svelte +++ b/src/lib/components/chat/Settings/Models.svelte @@ -52,15 +52,17 @@ // Remove the downloaded model delete modelDownloadStatus[modelName]; - console.log(data); + modelDownloadStatus = {...modelDownloadStatus} + + console.log('Cleaned:',modelDownloadStatus); if (!data.success) { toast.error(data.error); } else { - toast.success(`Model '${modelName}' has been successfully downloaded.`); + toast.success(`Model '${sanitizedModelTag}' has been successfully downloaded.`); const notification = new Notification(WEBUI_NAME, { - body: `Model '${modelName}' has been successfully downloaded.`, + body: `Model '${sanitizedModelTag}' has been successfully downloaded.`, icon: '/favicon.png' }); @@ -266,6 +268,7 @@ downloadProgress = 100; } modelDownloadStatus[opts.modelName] = { + reader, pullProgress: downloadProgress, digest: data.digest }; @@ -286,6 +289,15 @@ opts.callback({ success: true, modelName: opts.modelName }); } }; + const deleteModelPull = async(model: string) => { + const {reader} = modelDownloadStatus[model]; + if(reader){ + await reader.cancel(); + toast.success(`${model} download has been canceled`); + delete modelDownloadStatus[model]; + } + + }
@@ -364,12 +376,36 @@
{model}
-
+
{modelDownloadStatus[model].pullProgress ?? 0}% +
+ + +
+
{modelDownloadStatus[model].digest}
From e6dd0bfbe0a3cd3eff799146332084fc70e00cb5 Mon Sep 17 00:00:00 2001 From: Self Denial Date: Wed, 20 Mar 2024 17:11:36 -0600 Subject: [PATCH 02/52] Migrate to python logging module with env var control. --- backend/apps/audio/main.py | 12 +++-- backend/apps/images/main.py | 9 ++-- backend/apps/litellm/main.py | 9 +++- backend/apps/ollama/main.py | 69 +++++++++++++++------------- backend/apps/openai/main.py | 21 +++++---- backend/apps/rag/main.py | 24 ++++++---- backend/apps/rag/utils.py | 10 ++-- backend/apps/web/internal/db.py | 7 ++- backend/apps/web/models/auths.py | 9 +++- backend/apps/web/models/documents.py | 9 +++- backend/apps/web/models/tags.py | 13 ++++-- backend/apps/web/routers/chats.py | 9 +++- backend/apps/web/routers/users.py | 7 ++- backend/config.py | 38 +++++++++++++-- backend/main.py | 10 +++- 15 files changed, 174 insertions(+), 82 deletions(-) diff --git a/backend/apps/audio/main.py b/backend/apps/audio/main.py index d8cb415f..10cc5671 100644 --- a/backend/apps/audio/main.py +++ b/backend/apps/audio/main.py @@ -1,4 +1,5 @@ import os +import logging from fastapi import ( FastAPI, Request, @@ -21,7 +22,10 @@ from utils.utils import ( ) from utils.misc import calculate_sha256 -from config import CACHE_DIR, UPLOAD_DIR, WHISPER_MODEL, WHISPER_MODEL_DIR +from config import SRC_LOG_LEVELS, CACHE_DIR, UPLOAD_DIR, WHISPER_MODEL, WHISPER_MODEL_DIR + +log = logging.getLogger(__name__) +log.setLevel(SRC_LOG_LEVELS["AUDIO"]) app = FastAPI() app.add_middleware( @@ -38,7 +42,7 @@ def transcribe( file: UploadFile = File(...), user=Depends(get_current_user), ): - print(file.content_type) + log.info(f"file.content_type: {file.content_type}") if file.content_type not in ["audio/mpeg", "audio/wav"]: raise HTTPException( @@ -62,7 +66,7 @@ def transcribe( ) segments, info = model.transcribe(file_path, beam_size=5) - print( + log.info( "Detected language '%s' with probability %f" % (info.language, info.language_probability) ) @@ -72,7 +76,7 @@ def transcribe( return {"text": transcript.strip()} except Exception as e: - print(e) + log.exception(e) raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, diff --git a/backend/apps/images/main.py b/backend/apps/images/main.py index e14b0f6a..7be7c8f5 100644 --- a/backend/apps/images/main.py +++ b/backend/apps/images/main.py @@ -25,9 +25,12 @@ from pathlib import Path import uuid import base64 import json +import logging -from config import CACHE_DIR, AUTOMATIC1111_BASE_URL +from config import SRC_LOG_LEVELS, CACHE_DIR, AUTOMATIC1111_BASE_URL +log = logging.getLogger(__name__) +log.setLevel(SRC_LOG_LEVELS["IMAGES"]) IMAGE_CACHE_DIR = Path(CACHE_DIR).joinpath("./image/generations/") IMAGE_CACHE_DIR.mkdir(parents=True, exist_ok=True) @@ -268,7 +271,7 @@ def save_b64_image(b64_str): return image_id except Exception as e: - print(f"Error saving image: {e}") + log.error(f"Error saving image: {e}") return None @@ -341,7 +344,7 @@ def generate_image( res = r.json() - print(res) + log.debug(f"res: {res}") images = [] diff --git a/backend/apps/litellm/main.py b/backend/apps/litellm/main.py index 21b9e58a..9dc5d6f2 100644 --- a/backend/apps/litellm/main.py +++ b/backend/apps/litellm/main.py @@ -1,10 +1,15 @@ +import logging + from litellm.proxy.proxy_server import ProxyConfig, initialize from litellm.proxy.proxy_server import app from fastapi import FastAPI, Request, Depends, status from fastapi.responses import JSONResponse from utils.utils import get_http_authorization_cred, get_current_user -from config import ENV +from config import SRC_LOG_LEVELS, ENV + +log = logging.getLogger(__name__) +log.setLevel(SRC_LOG_LEVELS["LITELLM"]) proxy_config = ProxyConfig() @@ -33,7 +38,7 @@ async def auth_middleware(request: Request, call_next): if ENV != "dev": try: user = get_current_user(get_http_authorization_cred(auth_header)) - print(user) + log.debug(f"user: {user}") except Exception as e: return JSONResponse(status_code=400, content={"detail": str(e)}) diff --git a/backend/apps/ollama/main.py b/backend/apps/ollama/main.py index 154be97c..777203e5 100644 --- a/backend/apps/ollama/main.py +++ b/backend/apps/ollama/main.py @@ -11,14 +11,17 @@ import json import uuid import aiohttp import asyncio +import logging from apps.web.models.users import Users from constants import ERROR_MESSAGES from utils.utils import decode_token, get_current_user, get_admin_user -from config import OLLAMA_BASE_URLS, MODEL_FILTER_ENABLED, MODEL_FILTER_LIST +from config import SRC_LOG_LEVELS, OLLAMA_BASE_URLS, MODEL_FILTER_ENABLED, MODEL_FILTER_LIST from typing import Optional, List, Union +log = logging.getLogger(__name__) +log.setLevel(SRC_LOG_LEVELS["OLLAMA"]) app = FastAPI() app.add_middleware( @@ -69,7 +72,7 @@ class UrlUpdateForm(BaseModel): async def update_ollama_api_url(form_data: UrlUpdateForm, user=Depends(get_admin_user)): app.state.OLLAMA_BASE_URLS = form_data.urls - print(app.state.OLLAMA_BASE_URLS) + log.info(f"app.state.OLLAMA_BASE_URLS: {app.state.OLLAMA_BASE_URLS}") return {"OLLAMA_BASE_URLS": app.state.OLLAMA_BASE_URLS} @@ -90,7 +93,7 @@ async def fetch_url(url): return await response.json() except Exception as e: # Handle connection error here - print(f"Connection error: {e}") + log.error(f"Connection error: {e}") return None @@ -114,7 +117,7 @@ def merge_models_lists(model_lists): async def get_all_models(): - print("get_all_models") + log.info("get_all_models()") tasks = [fetch_url(f"{url}/api/tags") for url in app.state.OLLAMA_BASE_URLS] responses = await asyncio.gather(*tasks) @@ -155,7 +158,7 @@ async def get_ollama_tags( return r.json() except Exception as e: - print(e) + log.exception(e) error_detail = "Open WebUI: Server Connection Error" if r is not None: try: @@ -201,7 +204,7 @@ async def get_ollama_versions(url_idx: Optional[int] = None): return r.json() except Exception as e: - print(e) + log.exception(e) error_detail = "Open WebUI: Server Connection Error" if r is not None: try: @@ -227,7 +230,7 @@ async def pull_model( form_data: ModelNameForm, url_idx: int = 0, user=Depends(get_admin_user) ): url = app.state.OLLAMA_BASE_URLS[url_idx] - print(url) + log.info(f"url: {url}") r = None @@ -260,7 +263,7 @@ async def pull_model( try: return await run_in_threadpool(get_request) except Exception as e: - print(e) + log.exception(e) error_detail = "Open WebUI: Server Connection Error" if r is not None: try: @@ -299,7 +302,7 @@ async def push_model( ) url = app.state.OLLAMA_BASE_URLS[url_idx] - print(url) + log.debug(f"url: {url}") r = None @@ -331,7 +334,7 @@ async def push_model( try: return await run_in_threadpool(get_request) except Exception as e: - print(e) + log.exception(e) error_detail = "Open WebUI: Server Connection Error" if r is not None: try: @@ -359,9 +362,9 @@ class CreateModelForm(BaseModel): async def create_model( form_data: CreateModelForm, url_idx: int = 0, user=Depends(get_admin_user) ): - print(form_data) + log.debug(f"form_data: {form_data}") url = app.state.OLLAMA_BASE_URLS[url_idx] - print(url) + log.info(f"url: {url}") r = None @@ -383,7 +386,7 @@ async def create_model( r.raise_for_status() - print(r) + log.debug(f"r: {r}") return StreamingResponse( stream_content(), @@ -396,7 +399,7 @@ async def create_model( try: return await run_in_threadpool(get_request) except Exception as e: - print(e) + log.exception(e) error_detail = "Open WebUI: Server Connection Error" if r is not None: try: @@ -434,7 +437,7 @@ async def copy_model( ) url = app.state.OLLAMA_BASE_URLS[url_idx] - print(url) + log.info(f"url: {url}") try: r = requests.request( @@ -444,11 +447,11 @@ async def copy_model( ) r.raise_for_status() - print(r.text) + log.debug(f"r.text: {r.text}") return True except Exception as e: - print(e) + log.exception(e) error_detail = "Open WebUI: Server Connection Error" if r is not None: try: @@ -481,7 +484,7 @@ async def delete_model( ) url = app.state.OLLAMA_BASE_URLS[url_idx] - print(url) + log.info(f"url: {url}") try: r = requests.request( @@ -491,11 +494,11 @@ async def delete_model( ) r.raise_for_status() - print(r.text) + log.debug(f"r.text: {r.text}") return True except Exception as e: - print(e) + log.exception(e) error_detail = "Open WebUI: Server Connection Error" if r is not None: try: @@ -521,7 +524,7 @@ async def show_model_info(form_data: ModelNameForm, user=Depends(get_current_use url_idx = random.choice(app.state.MODELS[form_data.name]["urls"]) url = app.state.OLLAMA_BASE_URLS[url_idx] - print(url) + log.info(f"url: {url}") try: r = requests.request( @@ -533,7 +536,7 @@ async def show_model_info(form_data: ModelNameForm, user=Depends(get_current_use return r.json() except Exception as e: - print(e) + log.exception(e) error_detail = "Open WebUI: Server Connection Error" if r is not None: try: @@ -573,7 +576,7 @@ async def generate_embeddings( ) url = app.state.OLLAMA_BASE_URLS[url_idx] - print(url) + log.info(f"url: {url}") try: r = requests.request( @@ -585,7 +588,7 @@ async def generate_embeddings( return r.json() except Exception as e: - print(e) + log.exception(e) error_detail = "Open WebUI: Server Connection Error" if r is not None: try: @@ -633,7 +636,7 @@ async def generate_completion( ) url = app.state.OLLAMA_BASE_URLS[url_idx] - print(url) + log.info(f"url: {url}") r = None @@ -654,7 +657,7 @@ async def generate_completion( if request_id in REQUEST_POOL: yield chunk else: - print("User: canceled request") + log.warning("User: canceled request") break finally: if hasattr(r, "close"): @@ -731,11 +734,11 @@ async def generate_chat_completion( ) url = app.state.OLLAMA_BASE_URLS[url_idx] - print(url) + log.info(f"url: {url}") r = None - print(form_data.model_dump_json(exclude_none=True).encode()) + log.debug("form_data.model_dump_json(exclude_none=True).encode(): {0} ".format(form_data.model_dump_json(exclude_none=True).encode())) def get_request(): nonlocal form_data @@ -754,7 +757,7 @@ async def generate_chat_completion( if request_id in REQUEST_POOL: yield chunk else: - print("User: canceled request") + log.warning("User: canceled request") break finally: if hasattr(r, "close"): @@ -777,7 +780,7 @@ async def generate_chat_completion( headers=dict(r.headers), ) except Exception as e: - print(e) + log.exception(e) raise e try: @@ -831,7 +834,7 @@ async def generate_openai_chat_completion( ) url = app.state.OLLAMA_BASE_URLS[url_idx] - print(url) + log.info(f"url: {url}") r = None @@ -854,7 +857,7 @@ async def generate_openai_chat_completion( if request_id in REQUEST_POOL: yield chunk else: - print("User: canceled request") + log.warning("User: canceled request") break finally: if hasattr(r, "close"): @@ -947,7 +950,7 @@ async def deprecated_proxy(path: str, request: Request, user=Depends(get_current if request_id in REQUEST_POOL: yield chunk else: - print("User: canceled request") + log.warning("User: canceled request") break finally: if hasattr(r, "close"): diff --git a/backend/apps/openai/main.py b/backend/apps/openai/main.py index 67a99794..4098d73a 100644 --- a/backend/apps/openai/main.py +++ b/backend/apps/openai/main.py @@ -6,6 +6,7 @@ import requests import aiohttp import asyncio import json +import logging from pydantic import BaseModel @@ -19,6 +20,7 @@ from utils.utils import ( get_admin_user, ) from config import ( + SRC_LOG_LEVELS, OPENAI_API_BASE_URLS, OPENAI_API_KEYS, CACHE_DIR, @@ -31,6 +33,9 @@ from typing import List, Optional import hashlib from pathlib import Path +log = logging.getLogger(__name__) +log.setLevel(SRC_LOG_LEVELS["OPENAI"]) + app = FastAPI() app.add_middleware( CORSMiddleware, @@ -134,7 +139,7 @@ async def speech(request: Request, user=Depends(get_verified_user)): return FileResponse(file_path) except Exception as e: - print(e) + log.exception(e) error_detail = "Open WebUI: Server Connection Error" if r is not None: try: @@ -160,7 +165,7 @@ async def fetch_url(url, key): return await response.json() except Exception as e: # Handle connection error here - print(f"Connection error: {e}") + log.error(f"Connection error: {e}") return None @@ -182,7 +187,7 @@ def merge_models_lists(model_lists): async def get_all_models(): - print("get_all_models") + log.info("get_all_models()") if len(app.state.OPENAI_API_KEYS) == 1 and app.state.OPENAI_API_KEYS[0] == "": models = {"data": []} @@ -208,7 +213,7 @@ async def get_all_models(): ) } - print(models) + log.info(f"models: {models}") app.state.MODELS = {model["id"]: model for model in models["data"]} return models @@ -246,7 +251,7 @@ async def get_models(url_idx: Optional[int] = None, user=Depends(get_current_use return response_data except Exception as e: - print(e) + log.exception(e) error_detail = "Open WebUI: Server Connection Error" if r is not None: try: @@ -280,7 +285,7 @@ async def proxy(path: str, request: Request, user=Depends(get_verified_user)): if body.get("model") == "gpt-4-vision-preview": if "max_tokens" not in body: body["max_tokens"] = 4000 - print("Modified body_dict:", body) + log.debug("Modified body_dict:", body) # Fix for ChatGPT calls failing because the num_ctx key is in body if "num_ctx" in body: @@ -292,7 +297,7 @@ async def proxy(path: str, request: Request, user=Depends(get_verified_user)): # Convert the modified body back to JSON body = json.dumps(body) except json.JSONDecodeError as e: - print("Error loading request body into a dictionary:", e) + log.error("Error loading request body into a dictionary:", e) url = app.state.OPENAI_API_BASE_URLS[idx] key = app.state.OPENAI_API_KEYS[idx] @@ -330,7 +335,7 @@ async def proxy(path: str, request: Request, user=Depends(get_verified_user)): response_data = r.json() return response_data except Exception as e: - print(e) + log.exception(e) error_detail = "Open WebUI: Server Connection Error" if r is not None: try: diff --git a/backend/apps/rag/main.py b/backend/apps/rag/main.py index 5fc38b4a..d8145f12 100644 --- a/backend/apps/rag/main.py +++ b/backend/apps/rag/main.py @@ -8,7 +8,7 @@ from fastapi import ( Form, ) from fastapi.middleware.cors import CORSMiddleware -import os, shutil +import os, shutil, logging from pathlib import Path from typing import List @@ -54,6 +54,7 @@ from utils.misc import ( ) from utils.utils import get_current_user, get_admin_user from config import ( + SRC_LOG_LEVELS, UPLOAD_DIR, DOCS_DIR, RAG_EMBEDDING_MODEL, @@ -66,6 +67,9 @@ from config import ( from constants import ERROR_MESSAGES +log = logging.getLogger(__name__) +log.setLevel(SRC_LOG_LEVELS["RAG"]) + # # if RAG_EMBEDDING_MODEL: # sentence_transformer_ef = SentenceTransformer( @@ -124,7 +128,7 @@ def store_data_in_vector_db(data, collection_name, overwrite: bool = False) -> b if overwrite: for collection in CHROMA_CLIENT.list_collections(): if collection_name == collection.name: - print(f"deleting existing collection {collection_name}") + log.info(f"deleting existing collection {collection_name}") CHROMA_CLIENT.delete_collection(name=collection_name) collection = CHROMA_CLIENT.create_collection( @@ -137,7 +141,7 @@ def store_data_in_vector_db(data, collection_name, overwrite: bool = False) -> b ) return True except Exception as e: - print(e) + log.exception(e) if e.__class__.__name__ == "UniqueConstraintError": return True @@ -274,7 +278,7 @@ def query_doc_handler( embedding_function=app.state.sentence_transformer_ef, ) except Exception as e: - print(e) + log.exception(e) raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT(e), @@ -318,7 +322,7 @@ def store_web(form_data: StoreWebForm, user=Depends(get_current_user)): "filename": form_data.url, } except Exception as e: - print(e) + log.exception(e) raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT(e), @@ -416,7 +420,7 @@ def store_doc( ): # "https://www.gutenberg.org/files/1727/1727-h/1727-h.htm" - print(file.content_type) + log.info(f"file.content_type: {file.content_type}") try: filename = file.filename file_path = f"{UPLOAD_DIR}/{filename}" @@ -447,7 +451,7 @@ def store_doc( detail=ERROR_MESSAGES.DEFAULT(), ) except Exception as e: - print(e) + log.exception(e) if "No pandoc was found" in str(e): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, @@ -512,7 +516,7 @@ def scan_docs_dir(user=Depends(get_admin_user)): ) except Exception as e: - print(e) + log.exception(e) return True @@ -533,11 +537,11 @@ def reset(user=Depends(get_admin_user)) -> bool: elif os.path.isdir(file_path): shutil.rmtree(file_path) except Exception as e: - print("Failed to delete %s. Reason: %s" % (file_path, e)) + log.error("Failed to delete %s. Reason: %s" % (file_path, e)) try: CHROMA_CLIENT.reset() except Exception as e: - print(e) + log.exception(e) return True diff --git a/backend/apps/rag/utils.py b/backend/apps/rag/utils.py index a3537d4d..e792970c 100644 --- a/backend/apps/rag/utils.py +++ b/backend/apps/rag/utils.py @@ -1,7 +1,11 @@ import re +import logging from typing import List -from config import CHROMA_CLIENT +from config import SRC_LOG_LEVELS, CHROMA_CLIENT + +log = logging.getLogger(__name__) +log.setLevel(SRC_LOG_LEVELS["RAG"]) def query_doc(collection_name: str, query: str, k: int, embedding_function): @@ -97,7 +101,7 @@ def rag_template(template: str, context: str, query: str): def rag_messages(docs, messages, template, k, embedding_function): - print(docs) + log.debug(f"docs: {docs}") last_user_message_idx = None for i in range(len(messages) - 1, -1, -1): @@ -145,7 +149,7 @@ def rag_messages(docs, messages, template, k, embedding_function): embedding_function=embedding_function, ) except Exception as e: - print(e) + log.exception(e) context = None relevant_contexts.append(context) diff --git a/backend/apps/web/internal/db.py b/backend/apps/web/internal/db.py index d0aa9969..554f8002 100644 --- a/backend/apps/web/internal/db.py +++ b/backend/apps/web/internal/db.py @@ -1,13 +1,16 @@ from peewee import * -from config import DATA_DIR +from config import SRC_LOG_LEVELS, DATA_DIR import os +import logging +log = logging.getLogger(__name__) +log.setLevel(SRC_LOG_LEVELS["DB"]) # Check if the file exists if os.path.exists(f"{DATA_DIR}/ollama.db"): # Rename the file os.rename(f"{DATA_DIR}/ollama.db", f"{DATA_DIR}/webui.db") - print("File renamed successfully.") + log.info("File renamed successfully.") else: pass diff --git a/backend/apps/web/models/auths.py b/backend/apps/web/models/auths.py index 02d2ab86..b26236ef 100644 --- a/backend/apps/web/models/auths.py +++ b/backend/apps/web/models/auths.py @@ -2,6 +2,7 @@ from pydantic import BaseModel from typing import List, Union, Optional import time import uuid +import logging from peewee import * from apps.web.models.users import UserModel, Users @@ -9,6 +10,10 @@ from utils.utils import verify_password from apps.web.internal.db import DB +from config import SRC_LOG_LEVELS +log = logging.getLogger(__name__) +log.setLevel(SRC_LOG_LEVELS["MODELS"]) + #################### # DB MODEL #################### @@ -86,7 +91,7 @@ class AuthsTable: def insert_new_auth( self, email: str, password: str, name: str, role: str = "pending" ) -> Optional[UserModel]: - print("insert_new_auth") + log.info("insert_new_auth") id = str(uuid.uuid4()) @@ -103,7 +108,7 @@ class AuthsTable: return None def authenticate_user(self, email: str, password: str) -> Optional[UserModel]: - print("authenticate_user", email) + log.info(f"authenticate_user: {email}") try: auth = Auth.get(Auth.email == email, Auth.active == True) if auth: diff --git a/backend/apps/web/models/documents.py b/backend/apps/web/models/documents.py index 6a372b2c..f399e7ae 100644 --- a/backend/apps/web/models/documents.py +++ b/backend/apps/web/models/documents.py @@ -3,6 +3,7 @@ from peewee import * from playhouse.shortcuts import model_to_dict from typing import List, Union, Optional import time +import logging from utils.utils import decode_token from utils.misc import get_gravatar_url @@ -11,6 +12,10 @@ from apps.web.internal.db import DB import json +from config import SRC_LOG_LEVELS +log = logging.getLogger(__name__) +log.setLevel(SRC_LOG_LEVELS["MODELS"]) + #################### # Documents DB Schema #################### @@ -118,7 +123,7 @@ class DocumentsTable: doc = Document.get(Document.name == form_data.name) return DocumentModel(**model_to_dict(doc)) except Exception as e: - print(e) + log.exception(e) return None def update_doc_content_by_name( @@ -138,7 +143,7 @@ class DocumentsTable: doc = Document.get(Document.name == name) return DocumentModel(**model_to_dict(doc)) except Exception as e: - print(e) + log.exception(e) return None def delete_doc_by_name(self, name: str) -> bool: diff --git a/backend/apps/web/models/tags.py b/backend/apps/web/models/tags.py index d4264501..476e6693 100644 --- a/backend/apps/web/models/tags.py +++ b/backend/apps/web/models/tags.py @@ -6,9 +6,14 @@ from playhouse.shortcuts import model_to_dict import json import uuid import time +import logging from apps.web.internal.db import DB +from config import SRC_LOG_LEVELS +log = logging.getLogger(__name__) +log.setLevel(SRC_LOG_LEVELS["MODELS"]) + #################### # Tag DB Schema #################### @@ -173,7 +178,7 @@ class TagTable: (ChatIdTag.tag_name == tag_name) & (ChatIdTag.user_id == user_id) ) res = query.execute() # Remove the rows, return number of rows removed. - print(res) + log.debug(f"res: {res}") tag_count = self.count_chat_ids_by_tag_name_and_user_id(tag_name, user_id) if tag_count == 0: @@ -185,7 +190,7 @@ class TagTable: return True except Exception as e: - print("delete_tag", e) + log.error(f"delete_tag: {e}") return False def delete_tag_by_tag_name_and_chat_id_and_user_id( @@ -198,7 +203,7 @@ class TagTable: & (ChatIdTag.user_id == user_id) ) res = query.execute() # Remove the rows, return number of rows removed. - print(res) + log.debug(f"res: {res}") tag_count = self.count_chat_ids_by_tag_name_and_user_id(tag_name, user_id) if tag_count == 0: @@ -210,7 +215,7 @@ class TagTable: return True except Exception as e: - print("delete_tag", e) + log.error(f"delete_tag: {e}") return False def delete_tags_by_chat_id_and_user_id(self, chat_id: str, user_id: str) -> bool: diff --git a/backend/apps/web/routers/chats.py b/backend/apps/web/routers/chats.py index 0c0ac1ce..d018b31b 100644 --- a/backend/apps/web/routers/chats.py +++ b/backend/apps/web/routers/chats.py @@ -5,6 +5,7 @@ from utils.utils import get_current_user, get_admin_user from fastapi import APIRouter from pydantic import BaseModel import json +import logging from apps.web.models.users import Users from apps.web.models.chats import ( @@ -27,6 +28,10 @@ from apps.web.models.tags import ( from constants import ERROR_MESSAGES +from config import SRC_LOG_LEVELS +log = logging.getLogger(__name__) +log.setLevel(SRC_LOG_LEVELS["MODELS"]) + router = APIRouter() ############################ @@ -78,7 +83,7 @@ async def create_new_chat(form_data: ChatForm, user=Depends(get_current_user)): chat = Chats.insert_new_chat(user.id, form_data) return ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)}) except Exception as e: - print(e) + log.exception(e) raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT() ) @@ -95,7 +100,7 @@ async def get_all_tags(user=Depends(get_current_user)): tags = Tags.get_tags_by_user_id(user.id) return tags except Exception as e: - print(e) + log.exception(e) raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT() ) diff --git a/backend/apps/web/routers/users.py b/backend/apps/web/routers/users.py index b8e2732c..67c136da 100644 --- a/backend/apps/web/routers/users.py +++ b/backend/apps/web/routers/users.py @@ -7,6 +7,7 @@ from fastapi import APIRouter from pydantic import BaseModel import time import uuid +import logging from apps.web.models.users import UserModel, UserUpdateForm, UserRoleUpdateForm, Users from apps.web.models.auths import Auths @@ -14,6 +15,10 @@ from apps.web.models.auths import Auths from utils.utils import get_current_user, get_password_hash, get_admin_user from constants import ERROR_MESSAGES +from config import SRC_LOG_LEVELS +log = logging.getLogger(__name__) +log.setLevel(SRC_LOG_LEVELS["MODELS"]) + router = APIRouter() ############################ @@ -83,7 +88,7 @@ async def update_user_by_id( if form_data.password: hashed = get_password_hash(form_data.password) - print(hashed) + log.debug(f"hashed: {hashed}") Auths.update_user_password_by_id(user_id, hashed) Auths.update_email_by_id(user_id, form_data.email.lower()) diff --git a/backend/config.py b/backend/config.py index 099f726c..6d94d223 100644 --- a/backend/config.py +++ b/backend/config.py @@ -1,4 +1,6 @@ import os +import sys +import logging import chromadb from chromadb import Settings from base64 import b64encode @@ -21,7 +23,7 @@ try: load_dotenv(find_dotenv("../.env")) except ImportError: - print("dotenv not installed, skipping...") + log.warning("dotenv not installed, skipping...") WEBUI_NAME = "Open WebUI" shutil.copyfile("../build/favicon.png", "./static/favicon.png") @@ -100,6 +102,34 @@ for version in soup.find_all("h2"): CHANGELOG = changelog_json +#################################### +# LOGGING +#################################### +log_levels = ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"] + +GLOBAL_LOG_LEVEL = os.environ.get("GLOBAL_LOG_LEVEL", "").upper() +if GLOBAL_LOG_LEVEL in log_levels: + logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL, force=True) +else: + GLOBAL_LOG_LEVEL = "INFO" + +log = logging.getLogger(__name__) +log.info(f"GLOBAL_LOG_LEVEL: {GLOBAL_LOG_LEVEL}") + +log_sources = ["AUDIO", "CONFIG", "DB", "IMAGES", "LITELLM", "MAIN", "MODELS", "OLLAMA", "OPENAI", "RAG"] + +SRC_LOG_LEVELS = {} + +for source in log_sources: + log_env_var = source + "_LOG_LEVEL" + SRC_LOG_LEVELS[source] = os.environ.get(log_env_var, "").upper() + if SRC_LOG_LEVELS[source] not in log_levels: + SRC_LOG_LEVELS[source] = GLOBAL_LOG_LEVEL + log.info(f"{log_env_var}: {SRC_LOG_LEVELS[source]}") + +log.setLevel(SRC_LOG_LEVELS["CONFIG"]) + + #################################### # CUSTOM_NAME #################################### @@ -125,7 +155,7 @@ if CUSTOM_NAME: WEBUI_NAME = data["name"] except Exception as e: - print(e) + log.exception(e) pass @@ -194,9 +224,9 @@ def create_config_file(file_path): LITELLM_CONFIG_PATH = f"{DATA_DIR}/litellm/config.yaml" if not os.path.exists(LITELLM_CONFIG_PATH): - print("Config file doesn't exist. Creating...") + log.info("Config file doesn't exist. Creating...") create_config_file(LITELLM_CONFIG_PATH) - print("Config file created successfully.") + log.info("Config file created successfully.") #################################### diff --git a/backend/main.py b/backend/main.py index 25322718..24989c81 100644 --- a/backend/main.py +++ b/backend/main.py @@ -4,6 +4,7 @@ import markdown import time import os import sys +import logging import requests from fastapi import FastAPI, Request, Depends, status @@ -38,9 +39,14 @@ from config import ( FRONTEND_BUILD_DIR, MODEL_FILTER_ENABLED, MODEL_FILTER_LIST, + GLOBAL_LOG_LEVEL, + SRC_LOG_LEVELS, ) from constants import ERROR_MESSAGES +logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL) +log = logging.getLogger(__name__) +log.setLevel(SRC_LOG_LEVELS["MAIN"]) class SPAStaticFiles(StaticFiles): async def get_response(self, path: str, scope): @@ -66,7 +72,7 @@ class RAGMiddleware(BaseHTTPMiddleware): if request.method == "POST" and ( "/api/chat" in request.url.path or "/chat/completions" in request.url.path ): - print(request.url.path) + log.debug(f"request.url.path: {request.url.path}") # Read the original request body body = await request.body() @@ -89,7 +95,7 @@ class RAGMiddleware(BaseHTTPMiddleware): ) del data["docs"] - print(data["messages"]) + log.debug(f"data['messages']: {data['messages']}") modified_body_bytes = json.dumps(data).encode("utf-8") From a1412d0b55d88980cdf93fa19f02ceae13a05d02 Mon Sep 17 00:00:00 2001 From: "tabaco.wang" Date: Thu, 21 Mar 2024 10:31:25 +0800 Subject: [PATCH 03/52] fix: delete duplicate function --- backend/apps/web/models/chats.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/backend/apps/web/models/chats.py b/backend/apps/web/models/chats.py index ca55c71d..c9d13004 100644 --- a/backend/apps/web/models/chats.py +++ b/backend/apps/web/models/chats.py @@ -95,20 +95,6 @@ class ChatTable: except: return None - def update_chat_by_id(self, id: str, chat: dict) -> Optional[ChatModel]: - try: - query = Chat.update( - chat=json.dumps(chat), - title=chat["title"] if "title" in chat else "New Chat", - timestamp=int(time.time()), - ).where(Chat.id == id) - query.execute() - - chat = Chat.get(Chat.id == id) - return ChatModel(**model_to_dict(chat)) - except: - return None - def get_chat_lists_by_user_id( self, user_id: str, skip: int = 0, limit: int = 50 ) -> List[ChatModel]: From f675a18b05735d860bc86b03de3cb243ea1d8b80 Mon Sep 17 00:00:00 2001 From: changchiyou Date: Thu, 21 Mar 2024 19:37:39 +0800 Subject: [PATCH 04/52] enhance: `i18n`'s zh-TW translation for `About.svelte` --- src/lib/i18n/locales/zh-TW/translation.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/i18n/locales/zh-TW/translation.json b/src/lib/i18n/locales/zh-TW/translation.json index 176685d9..cde4804a 100644 --- a/src/lib/i18n/locales/zh-TW/translation.json +++ b/src/lib/i18n/locales/zh-TW/translation.json @@ -2,7 +2,7 @@ "'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' 或 '-1' 表示無期限。", "(Beta)": "(測試版)", "(e.g. `sh webui.sh --api`)": "(例如 `sh webui.sh --api`)", - "(latest)": "", + "(latest)": "(最新版)", "{{modelName}} is thinking...": "{{modelName}} 正在思考...", "{{webUIName}} Backend Required": "需要 {{webUIName}} 後台", "a user": "", From 8935dca2792f2a1a54eee443cd80be6a47a9ebc4 Mon Sep 17 00:00:00 2001 From: Plamen Vatev Date: Thu, 21 Mar 2024 17:14:59 +0200 Subject: [PATCH 05/52] added Bulgarian Translation --- src/lib/i18n/locales/bg-BG/translation.json | 363 ++++++++++++++++++++ 1 file changed, 363 insertions(+) create mode 100644 src/lib/i18n/locales/bg-BG/translation.json diff --git a/src/lib/i18n/locales/bg-BG/translation.json b/src/lib/i18n/locales/bg-BG/translation.json new file mode 100644 index 00000000..7a747cca --- /dev/null +++ b/src/lib/i18n/locales/bg-BG/translation.json @@ -0,0 +1,363 @@ +{ + "'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.", + "(Beta)": "(Бета)", + "(e.g. `sh webui.sh --api`)": "(например `sh webui.sh --api`)", + "(latest)": "(последна)", + "{{modelName}} is thinking...": "{{modelName}} мисли ...", + "{{webUIName}} Backend Required": "{{webUIName}} Изисква се Бекенд", + "a user": "", + "About": "Относно", + "Account": "Акаунт", + "Action": "Действие", + "Add a model": "Добавяне на модел", + "Add a model tag name": "Добавяне на име на таг за модел", + "Add a short description about what this modelfile does": "Добавяне на кратко описание за това какво прави този модфайл", + "Add a short title for this prompt": "Добавяне на кратко заглавие за този промпт", + "Add a tag": "Добавяне на таг", + "Add Docs": "Добавяне на Документи", + "Add Files": "Добавяне на Файлове", + "Add message": "Добавяне на съобщение", + "add tags": "добавяне на тагове", + "Adjusting these settings will apply changes universally to all users.": "При промяна на тези настройки промените се прилагат за всички потребители.", + "admin": "админ", + "Admin Panel": "Панел на Администратор", + "Admin Settings": "Настройки на Администратор", + "Advanced Parameters": "Разширени Параметри", + "all": "всички", + "All Users": "Всички Потребители", + "Allow": "Позволи", + "Allow Chat Deletion": "Позволи Изтриване на Чат", + "alphanumeric characters and hyphens": "алфанумерични знаци и тире", + "Already have an account?": "Вече имате акаунт? ", + "an assistant": "", + "and": "и", + "API Base URL": "API Базов URL", + "API Key": "API Ключ", + "API RPM": "API RPM", + "are allowed - Activate this command by typing": "са разрешени - Активирайте тази команда чрез въвеждане", + "Are you sure?": "", + "Audio": "Аудио", + "Auto-playback response": "Аувтоматично възпроизвеждане на Отговора", + "Auto-send input after 3 sec.": "Аувтоматично изпращане на входа след 3 сек.", + "AUTOMATIC1111 Base URL": "AUTOMATIC1111 Базов URL", + "AUTOMATIC1111 Base URL is required.": "", + "available!": "наличен!", + "Back": "Назад", + "Builder Mode": "Режим на Създаване", + "Cancel": "Отказ", + "Categories": "Категории", + "Change Password": "Промяна на Парола", + "Chat": "Чат", + "Chat History": "Чат История", + "Chat History is off for this browser.": "Чат История е изключен за този браузър.", + "Chats": "Чатове", + "Check Again": "Проверете Още Веднъж", + "Check for updates": "Проверка за актуализации", + "Checking for updates...": "Проверка за актуализации...", + "Choose a model before saving...": "Изберете модел преди запазване...", + "Chunk Overlap": "Chunk Overlap", + "Chunk Params": "Chunk Params", + "Chunk Size": "Chunk Size", + "Click here for help.": "Натиснете тук за помощ.", + "Click here to check other modelfiles.": "Натиснете тук за проверка на други моделфайлове.", + "Click here to select": "", + "Click here to select documents.": "", + "click here.": "натиснете тук.", + "Click on the user role button to change a user's role.": "Натиснете върху бутона за промяна на ролята на потребителя.", + "Close": "Затвори", + "Collection": "Колекция", + "Command": "Команда", + "Confirm Password": "Потвърди Парола", + "Connections": "Връзки", + "Content": "Съдържание", + "Context Length": "Дължина на Контекста", + "Conversation Mode": "Режим на Чат", + "Copy last code block": "Копиране на последен код блок", + "Copy last response": "Копиране на последен отговор", + "Copying to clipboard was successful!": "Копирането в клипборда беше успешно!", + "Create a concise, 3-5 word phrase as a header for the following query, strictly adhering to the 3-5 word limit and avoiding the use of the word 'title':": "Create a concise, 3-5 word phrase as a header for the following query, strictly adhering to the 3-5 word limit and avoiding the use of the word 'title':", + "Create a modelfile": "Създаване на модфайл", + "Create Account": "Създаване на Акаунт", + "Created at": "Създадено на", + "Created by": "Създадено от", + "Current Model": "Текущ модел", + "Current Password": "Текуща Парола", + "Custom": "Персонализиран", + "Customize Ollama models for a specific purpose": "Персонализиране на Ollama моделите за конкретна цел", + "Dark": "Тъмен", + "Database": "База данни", + "DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm", + "Default": "По подразбиране", + "Default (Automatic1111)": "", + "Default (Web API)": "По подразбиране (Web API)", + "Default model updated": "Моделът по подразбиране е обновен", + "Default Prompt Suggestions": "Промпт Предложения по подразбиране", + "Default User Role": "Роля на потребителя по подразбиране", + "delete": "изтриване", + "Delete a model": "Изтриване на модел", + "Delete chat": "Изтриване на чат", + "Delete Chats": "Изтриване на Чатове", + "Deleted {{deleteModelTag}}": "Изтрито {{deleteModelTag}}", + "Deleted {tagName}": "Изтрито {tagName}", + "Description": "Описание", + "Desktop Notifications": "Десктоп Известия", + "Disabled": "Деактивиран", + "Discover a modelfile": "Откриване на модфайл", + "Discover a prompt": "Откриване на промпт", + "Discover, download, and explore custom prompts": "Откриване, сваляне и преглед на персонализирани промптове", + "Discover, download, and explore model presets": "Откриване, сваляне и преглед на пресетове на модели", + "Display the username instead of You in the Chat": "Показване на потребителското име вместо Вие в чата", + "Document": "Документ", + "Document Settings": "Документ Настройки", + "Documents": "Документи", + "does not make any external connections, and your data stays securely on your locally hosted server.": "няма външни връзки, и вашите данни остават сигурни на локално назначен сървър.", + "Don't Allow": "Не Позволявай", + "Don't have an account?": "Нямате акаунт?", + "Download as a File": "Сваляне като Файл", + "Download Database": "Сваляне на база данни", + "Drop any files here to add to the conversation": "Пускане на файлове тук, за да ги добавите в чата", + "e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "напр. '30с','10м'. Валидни единици са 'с', 'м', 'ч'.", + "Edit Doc": "Редактиране на документ", + "Edit User": "Редактиране на потребител", + "Email": "Имейл", + "Enable Chat History": "Вклюване на Чат История", + "Enable New Sign Ups": "Вклюване на Нови Потребители", + "Enabled": "Включено", + "Enter {{role}} message here": "", + "Enter API Key": "", + "Enter Chunk Overlap": "", + "Enter Chunk Size": "", + "Enter Image Size (e.g. 512x512)": "", + "Enter LiteLLM API Base URL (litellm_params.api_base)": "", + "Enter LiteLLM API Key (litellm_params.api_key)": "", + "Enter LiteLLM API RPM (litellm_params.rpm)": "", + "Enter LiteLLM Model (litellm_params.model)": "", + "Enter Max Tokens (litellm_params.max_tokens)": "", + "Enter model tag (e.g. {{modelTag}})": "", + "Enter Number of Steps (e.g. 50)": "", + "Enter stop sequence": "Въведете стоп последователност", + "Enter Top K": "", + "Enter URL (e.g. http://127.0.0.1:7860/)": "", + "Enter Your Email": "Въведете имейл", + "Enter Your Full Name": "Въведете вашето пълно име", + "Enter Your Password": "Въведете вашата парола", + "Experimental": "Експериментално", + "Export All Chats (All Users)": "Експортване на всички чатове (За всички потребители)", + "Export Chats": "Експортване на чатове", + "Export Documents Mapping": "Експортване на документен мапинг", + "Export Modelfiles": "Експортване на модфайлове", + "Export Prompts": "Експортване на промптове", + "Failed to read clipboard contents": "Грешка при четене на съдържанието от клипборда", + "File Mode": "Файл Мод", + "File not found.": "Файл не е намерен.", + "Focus chat input": "Фокусиране на чат вход", + "Format your variables using square brackets like this:": "Format your variables using square brackets like this:", + "From (Base Model)": "От (Базов модел)", + "Full Screen Mode": "На Цял екран", + "General": "Основни", + "General Settings": "Основни Настройки", + "Hello, {{name}}": "Здравей, {{name}}", + "Hide": "Скрий", + "Hide Additional Params": "Скрий допълнителни параметри", + "How can I help you today?": "Как мога да ви помогна днес?", + "Image Generation (Experimental)": "Генерация на изображения (Експериментално)", + "Image Generation Engine": "", + "Image Settings": "Настройки на изображения", + "Images": "Изображения", + "Import Chats": "Импортване на чатове", + "Import Documents Mapping": "Импортване на документен мапинг", + "Import Modelfiles": "Импортване на модфайлове", + "Import Prompts": "Импортване на промптове", + "Include `--api` flag when running stable-diffusion-webui": "Include `--api` flag when running stable-diffusion-webui", + "Interface": "Интерфейс", + "join our Discord for help.": "свържете се с нашия Discord за помощ.", + "JSON": "JSON", + "JWT Expiration": "JWT Expiration", + "JWT Token": "JWT Token", + "Keep Alive": "Keep Alive", + "Keyboard shortcuts": "Клавиши за бърз достъп", + "Language": "Език", + "Light": "Светъл", + "Listening...": "Слушам...", + "LLMs can make mistakes. Verify important information.": "LLMs могат да правят грешки. Проверете важните данни.", + "Made by OpenWebUI Community": "Направено от OpenWebUI общността", + "Make sure to enclose them with": "Уверете се, че са заключени с", + "Manage LiteLLM Models": "Управление на LiteLLM Моделите", + "Manage Models": "Управление на Моделите", + "Manage Ollama Models": "Управление на Ollama Моделите", + "Max Tokens": "Max Tokens", + "Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Максимум 3 модели могат да бъдат сваляни едновременно. Моля, опитайте отново по-късно.", + "Mirostat": "Mirostat", + "Mirostat Eta": "Mirostat Eta", + "Mirostat Tau": "Mirostat Tau", + "MMMM DD, YYYY": "MMMM DD, YYYY", + "Model '{{modelName}}' has been successfully downloaded.": "Моделът '{{modelName}}' беше успешно свален.", + "Model '{{modelTag}}' is already in queue for downloading.": "Моделът '{{modelTag}}' е вече в очакване за сваляне.", + "Model {{modelId}} not found": "Моделът {{modelId}} не е намерен", + "Model {{modelName}} already exists.": "Моделът {{modelName}} вече съществува.", + "Model Name": "Име на модел", + "Model not selected": "Не е избран модел", + "Model Tag Name": "Име на таг на модел", + "Model Whitelisting": "Модел Whitelisting", + "Model(s) Whitelisted": "Модели Whitelisted", + "Modelfile": "Модфайл", + "Modelfile Advanced Settings": "Разширени настройки на модфайл", + "Modelfile Content": "Съдържание на модфайл", + "Modelfiles": "Модфайлове", + "Models": "Модели", + "My Documents": "Мои документи", + "My Modelfiles": "Мои модфайлове", + "My Prompts": "Мои промптове", + "Name": "Име", + "Name Tag": "Име Таг", + "Name your modelfile": "Име на модфайла", + "New Chat": "Нов чат", + "New Password": "Нова парола", + "Not sure what to add?": "Не сте сигурни, какво да добавите?", + "Not sure what to write? Switch to": "Не сте сигурни, какво да напишете? Превключете към", + "Off": "Изкл.", + "Okay, Let's Go!": "ОК, Нека започваме!", + "Ollama Base URL": "", + "Ollama Version": "Ollama Версия", + "On": "Вкл.", + "Only": "Само", + "Only alphanumeric characters and hyphens are allowed in the command string.": "Only alphanumeric characters and hyphens are allowed in the command string.", + "Oops! Hold tight! Your files are still in the processing oven. We're cooking them up to perfection. Please be patient and we'll let you know once they're ready.": "Oops! Hold tight! Your files are still in the processing oven. We're cooking them up to perfection. Please be patient and we'll let you know once they're ready.", + "Oops! Looks like the URL is invalid. Please double-check and try again.": "Oops! Looks like the URL is invalid. Please double-check and try again.", + "Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.", + "Open": "Отвори", + "Open AI": "Open AI", + "Open AI (Dall-E)": "", + "Open new chat": "Отвори нов чат", + "OpenAI API": "OpenAI API", + "OpenAI API Key": "", + "OpenAI API Key is required.": "", + "or": "или", + "Parameters": "Параметри", + "Password": "Парола", + "PDF Extract Images (OCR)": "PDF Extract Images (OCR)", + "pending": "в очакване", + "Permission denied when accessing microphone: {{error}}": "Permission denied when accessing microphone: {{error}}", + "Playground": "Плейграунд", + "Profile": "Профил", + "Prompt Content": "Съдържание на промпта", + "Prompt suggestions": "Промпт предложения", + "Prompts": "Промптове", + "Pull a model from Ollama.com": "Издърпайте модел от Ollama.com", + "Pull Progress": "Прогрес на издърпването", + "Query Params": "Query Параметри", + "RAG Template": "RAG Шаблон", + "Raw Format": "Raw Формат", + "Record voice": "Записване на глас", + "Redirecting you to OpenWebUI Community": "Пренасочване към OpenWebUI общността", + "Release Notes": "Бележки по изданието", + "Repeat Last N": "Repeat Last N", + "Repeat Penalty": "Repeat Penalty", + "Request Mode": "Request Mode", + "Reset Vector Storage": "Ресет Vector Storage", + "Response AutoCopy to Clipboard": "Аувтоматично копиране на отговор в клипборда", + "Role": "Роля", + "Rosé Pine": "Rosé Pine", + "Rosé Pine Dawn": "Rosé Pine Dawn", + "Save": "Запис", + "Save & Create": "Запис & Създаване", + "Save & Submit": "Запис & Изпращане", + "Save & Update": "Запис & Актуализиране", + "Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through", + "Scan": "Сканиране", + "Scan complete!": "Сканиране завършено!", + "Scan for documents from {{path}}": "Сканиране за документи в {{path}}", + "Search": "Търси", + "Search Documents": "Търси Документи", + "Search Prompts": "Търси Промптове", + "See readme.md for instructions": "Виж readme.md за инструкции", + "See what's new": "Виж какво е новото", + "Seed": "Seed", + "Select a mode": "", + "Select a model": "Изберете модел", + "Select an Ollama instance": "", + "Send a Message": "Изпращане на Съобщение", + "Send message": "Изпращане на съобщение", + "Server connection verified": "Server connection verified", + "Set as default": "Задай като по подразбиране", + "Set Default Model": "Задай Модел По Подразбиране", + "Set Image Size": "Задай Размер на Изображението", + "Set Steps": "Задай Стъпки", + "Set Title Auto-Generation Model": "Set Title Auto-Generation Model", + "Set Voice": "Задай Глас", + "Settings": "Настройки", + "Settings saved successfully!": "Настройките са запазени успешно!", + "Share to OpenWebUI Community": "Споделите с OpenWebUI Общността", + "short-summary": "short-summary", + "Show": "Покажи", + "Show Additional Params": "Покажи допълнителни параметри", + "Show shortcuts": "Покажи", + "sidebar": "sidebar", + "Sign in": "Вписване", + "Sign Out": "Изход", + "Sign up": "Регистрация", + "Speech recognition error: {{error}}": "Speech recognition error: {{error}}", + "Speech-to-Text Engine": "Speech-to-Text Engine", + "SpeechRecognition API is not supported in this browser.": "SpeechRecognition API is not supported in this browser.", + "Stop Sequence": "Stop Sequence", + "STT Settings": "STT Настройки", + "Submit": "Изпращане", + "Success": "Успех", + "Successfully updated.": "Успешно обновено.", + "Sync All": "Синхронизиране на всички", + "System": "Система", + "System Prompt": "Системен Промпт", + "Tags": "Тагове", + "Temperature": "Температура", + "Template": "Шаблон", + "Text Completion": "Text Completion", + "Text-to-Speech Engine": "Text-to-Speech Engine", + "Tfs Z": "Tfs Z", + "Theme": "Тема", + "This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "This ensures that your valuable conversations are securely saved to your backend database. Thank you!", + "This setting does not sync across browsers or devices.": "This setting does not sync across browsers or devices.", + "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.", + "Title": "Заглавие", + "Title Auto-Generation": "Автоматично Генериране на Заглавие", + "Title Generation Prompt": "Промпт за Генериране на Заглавие", + "to": "до", + "To access the available model names for downloading,": "To access the available model names for downloading,", + "To access the GGUF models available for downloading,": "To access the GGUF models available for downloading,", + "to chat input.": "to chat input.", + "Toggle settings": "Toggle settings", + "Toggle sidebar": "Toggle sidebar", + "Top K": "Top K", + "Top P": "Top P", + "Trouble accessing Ollama?": "Проблеми с достъпът до Ollama?", + "TTS Settings": "TTS Настройки", + "Type Hugging Face Resolve (Download) URL": "", + "Uh-oh! There was an issue connecting to {{provider}}.": "О, Не! Има проблем в свързването с {{provider}}.", + "Unknown File Type '{{file_type}}', but accepting and treating as plain text": "Непознат файлов тип '{{file_type}}', но се приема и обработва като текст", + "Update password": "Обновяване на парола", + "Upload a GGUF model": "Качване на GGUF модел", + "Upload files": "Качване на файлове", + "Upload Progress": "Прогрес на качването", + "URL Mode": "URL Mode", + "Use '#' in the prompt input to load and select your documents.": "Използвайте '#' във промпта за да заредите и изберете вашите документи.", + "Use Gravatar": "", + "user": "потребител", + "User Permissions": "Права на потребителя", + "Users": "Потребители", + "Utilize": "Използване", + "Valid time units:": "Валидни единици за време:", + "variable": "променлива", + "variable to have them replaced with clipboard content.": "променливи да се заменят съдържанието от клипборд.", + "Version": "Версия", + "Web": "Уеб", + "WebUI Add-ons": "WebUI Добавки", + "WebUI Settings": "WebUI Настройки", + "WebUI will make requests to": "WebUI ще направи заявки към", + "What’s New in": "Какво е новото в", + "When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Когато историята е изключена, нови чатове в този браузър ще не се показват в историята на никои от вашия профил.", + "Whisper (Local)": "Whisper (Локален)", + "Write a prompt suggestion (e.g. Who are you?)": "Напиши предложение за промпт (напр. Кой сте вие?)", + "Write a summary in 50 words that summarizes [topic or keyword].": "Напиши описание в 50 знака, което описва [тема или ключова дума].", + "You": "Вие", + "You're a helpful assistant.": "Вие сте полезен ассистент.", + "You're now logged in.": "Сега, вие влязохте в системата." +} \ No newline at end of file From 5f26b2a9e308e7a26056297a5f7a0cdedbb362a0 Mon Sep 17 00:00:00 2001 From: Plamen Vatev Date: Thu, 21 Mar 2024 17:43:31 +0200 Subject: [PATCH 06/52] added bg-BG (Bulgarian) --- src/lib/i18n/locales/languages.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/i18n/locales/languages.json b/src/lib/i18n/locales/languages.json index 7e30ed92..48f230b8 100644 --- a/src/lib/i18n/locales/languages.json +++ b/src/lib/i18n/locales/languages.json @@ -1,4 +1,8 @@ [ + { + "code": "bg-BG", + "title": "Bulgarian (BG)" + }, { "code": "en-US", "title": "English (US)" @@ -47,4 +51,4 @@ "code": "zh-TW", "title": "Chinese (Traditional)" } -] \ No newline at end of file +] From f3c113cb04d55725ba332043787e5cee97e32b06 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Thu, 21 Mar 2024 14:29:05 -0700 Subject: [PATCH 07/52] fix: languages order --- src/lib/i18n/locales/languages.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib/i18n/locales/languages.json b/src/lib/i18n/locales/languages.json index 48f230b8..a677dbaa 100644 --- a/src/lib/i18n/locales/languages.json +++ b/src/lib/i18n/locales/languages.json @@ -1,12 +1,12 @@ [ - { - "code": "bg-BG", - "title": "Bulgarian (BG)" - }, { "code": "en-US", "title": "English (US)" }, + { + "code": "bg-BG", + "title": "Bulgarian (BG)" + }, { "code": "ca-ES", "title": "Catalan" @@ -51,4 +51,4 @@ "code": "zh-TW", "title": "Chinese (Traditional)" } -] +] \ No newline at end of file From 47a04a7e3cc4a5f1dfc37d975ba8b68f6bbe1348 Mon Sep 17 00:00:00 2001 From: Que Nguyen Date: Fri, 22 Mar 2024 10:42:38 +0700 Subject: [PATCH 08/52] Fixed Vietnamese translation for improved language quality --- package-lock.json | 4 +- src/lib/i18n/locales/vi-VN/translation.json | 180 ++++++++++---------- 2 files changed, 92 insertions(+), 92 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7fa490eb..fd0a8405 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "open-webui", - "version": "0.1.112", + "version": "0.1.113", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "open-webui", - "version": "0.1.112", + "version": "0.1.113", "dependencies": { "@sveltejs/adapter-node": "^1.3.1", "async": "^3.2.5", diff --git a/src/lib/i18n/locales/vi-VN/translation.json b/src/lib/i18n/locales/vi-VN/translation.json index 79f22cee..2985cbba 100644 --- a/src/lib/i18n/locales/vi-VN/translation.json +++ b/src/lib/i18n/locales/vi-VN/translation.json @@ -5,10 +5,10 @@ "(latest)": "(mới nhất)", "{{modelName}} is thinking...": "{{modelName}} đang suy nghĩ...", "{{webUIName}} Backend Required": "{{webUIName}} Yêu cầu Backend", - "a user": "một người dùng", + "a user": "người sử dụng", "About": "Giới thiệu", "Account": "Tài khoản", - "Action": "Hành động", + "Action": "Tác vụ", "Add a model": "Thêm mô hình", "Add a model tag name": "Thêm tên thẻ mô hình (tag)", "Add a short description about what this modelfile does": "Thêm mô tả ngắn về việc tệp mô tả mô hình (modelfile) này làm gì", @@ -18,29 +18,29 @@ "Add Files": "Thêm tệp", "Add message": "Thêm tin nhắn", "add tags": "thêm thẻ", - "Adjusting these settings will apply changes universally to all users.": "Điều chỉnh các cài đặt này sẽ áp dụng thay đổi toàn cầu cho tất cả người dùng.", + "Adjusting these settings will apply changes universally to all users.": "Các thay đổi cài đặt này sẽ áp dụng cho tất cả người sử dụng.", "admin": "quản trị viên", "Admin Panel": "Trang Quản trị", "Admin Settings": "Cài đặt hệ thống", "Advanced Parameters": "Các tham số Nâng cao", "all": "tất cả", - "All Users": "Tất cả Người dùng", + "All Users": "Danh sách người sử dụng", "Allow": "Cho phép", - "Allow Chat Deletion": "Cho phép Xóa cuộc trò chuyện", + "Allow Chat Deletion": "Cho phép Xóa nội dung chat", "alphanumeric characters and hyphens": "ký tự số và gạch nối", - "Already have an account?": "Đã có tài khoản?", - "an assistant": "một trợ lý", + "Already have an account?": "Bạn đã có tài khoản?", + "an assistant": "trợ lý", "and": "và", - "API Base URL": "URL Cơ bản API", - "API Key": "Khóa API", - "API RPM": "RPM API", + "API Base URL": "Đường dẫn tới API (API Base URL)", + "API Key": "API Key", + "API RPM": "API RPM", "are allowed - Activate this command by typing": "được phép - Kích hoạt lệnh này bằng cách gõ", "Are you sure?": "Bạn có chắc chắn không?", "Audio": "Âm thanh", - "Auto-playback response": "Tự động phát lại phản hồi", + "Auto-playback response": "Tự động phát lại phản hồi (Auto-playback)", "Auto-send input after 3 sec.": "Tự động gửi đầu vào sau 3 giây.", - "AUTOMATIC1111 Base URL": "URL Cơ bản AUTOMATIC1111", - "AUTOMATIC1111 Base URL is required.": "Yêu cầu URL Cơ bản AUTOMATIC1111.", + "AUTOMATIC1111 Base URL": "Đường dẫn kết nối tới AUTOMATIC1111 (Base URL)", + "AUTOMATIC1111 Base URL is required.": "Base URL của AUTOMATIC1111 là bắt buộc.", "available!": "có sẵn!", "Back": "Quay lại", "Builder Mode": "Chế độ Builder", @@ -48,30 +48,30 @@ "Categories": "Danh mục", "Change Password": "Đổi Mật khẩu", "Chat": "Trò chuyện", - "Chat History": "Lịch sử Trò chuyện", - "Chat History is off for this browser.": "Lịch sử Trò chuyện đã tắt cho trình duyệt này.", - "Chats": "Cuộc trò chuyện", + "Chat History": "Lịch sử chat", + "Chat History is off for this browser.": "Lịch sử chat đã tắt cho trình duyệt này.", + "Chats": "Chat", "Check Again": "Kiểm tra Lại", "Check for updates": "Kiểm tra cập nhật", "Checking for updates...": "Đang kiểm tra cập nhật...", - "Choose a model before saving...": "Chọn một mô hình trước khi lưu...", - "Chunk Overlap": "Chunk chồng lấn (overlap)", - "Chunk Params": "Cài đặt tham số cho Chunk", - "Chunk Size": "Kích thước Chunk", + "Choose a model before saving...": "Chọn mô hình trước khi lưu...", + "Chunk Overlap": "Kích thước chồng lấn (overlap)", + "Chunk Params": "Cài đặt số lượng ký tự cho khối ký tự (chunk)", + "Chunk Size": "Kích thức khối (size)", "Click here for help.": "Bấm vào đây để được trợ giúp.", "Click here to check other modelfiles.": "Bấm vào đây để kiểm tra các tệp mô tả mô hình (modelfiles) khác.", "Click here to select": "Bấm vào đây để chọn", "Click here to select documents.": "Bấm vào đây để chọn tài liệu.", "click here.": "bấm vào đây.", - "Click on the user role button to change a user's role.": "Bấm vào nút vai trò người dùng để thay đổi vai trò của người dùng.", + "Click on the user role button to change a user's role.": "Bấm vào nút trong cột VAI TRÒ để thay đổi quyền của người sử dụng.", "Close": "Đóng", "Collection": "Bộ sưu tập", "Command": "Lệnh", "Confirm Password": "Xác nhận Mật khẩu", "Connections": "Kết nối", "Content": "Nội dung", - "Context Length": "Độ dài Ngữ cảnh", - "Conversation Mode": "Chế độ Hội thoại", + "Context Length": "Độ dài ngữ cảnh (Context Length)", + "Conversation Mode": "Chế độ hội thoại", "Copy last code block": "Sao chép khối mã cuối cùng", "Copy last response": "Sao chép phản hồi cuối cùng", "Copying to clipboard was successful!": "Sao chép vào clipboard thành công!", @@ -91,39 +91,39 @@ "Default (Automatic1111)": "Mặc định (Automatic1111)", "Default (Web API)": "Mặc định (Web API)", "Default model updated": "Mô hình mặc định đã được cập nhật", - "Default Prompt Suggestions": "Đề nghị Nhắc mặc định", - "Default User Role": "Vai trò Người dùng Mặc định", + "Default Prompt Suggestions": "Đề xuất prompt mặc định", + "Default User Role": "Vai trò mặc định", "delete": "xóa", "Delete a model": "Xóa mô hình", - "Delete chat": "Xóa cuộc trò chuyện", - "Delete Chats": "Xóa Cuộc trò chuyện", + "Delete chat": "Xóa nội dung chat", + "Delete Chats": "Xóa nội dung chat", "Deleted {{deleteModelTag}}": "Đã xóa {{deleteModelTag}}", "Deleted {tagName}": "Đã xóa {tagName}", "Description": "Mô tả", - "Desktop Notifications": "Thông báo Máy tính", + "Desktop Notifications": "Thông báo trên máy tính (Notification)", "Disabled": "Đã vô hiệu hóa", - "Discover a modelfile": "Khám phá một tệp mô hình", - "Discover a prompt": "Khám phá một prompt", - "Discover, download, and explore custom prompts": "Khám phá, tải xuống và khám phá các prompt tùy chỉnh", - "Discover, download, and explore model presets": "Khám phá, tải xuống và khám phá các thiết lập mô hình sẵn", - "Display the username instead of You in the Chat": "Hiển thị tên người dùng thay vì 'Bạn' trong Cuộc trò chuyện", + "Discover a modelfile": "Khám phá thêm các mô hình mới", + "Discover a prompt": "Khám phá thêm prompt mới", + "Discover, download, and explore custom prompts": "Tìm kiếm, tải về và khám phá thêm các prompt tùy chỉnh", + "Discover, download, and explore model presets": "Tìm kiếm, tải về và khám phá thêm các thiết lập mô hình sẵn", + "Display the username instead of You in the Chat": "Hiển thị tên người sử dụng thay vì 'Bạn' trong nội dung chat", "Document": "Tài liệu", - "Document Settings": "Cài đặt Tài liệu", + "Document Settings": "Cấu hình kho tài liệu", "Documents": "Tài liệu", "does not make any external connections, and your data stays securely on your locally hosted server.": "không thực hiện bất kỳ kết nối ngoài nào, và dữ liệu của bạn vẫn được lưu trữ an toàn trên máy chủ lưu trữ cục bộ của bạn.", "Don't Allow": "Không Cho phép", "Don't have an account?": "Không có tài khoản?", "Download as a File": "Tải xuống dưới dạng tệp", "Download Database": "Tải xuống Cơ sở dữ liệu", - "Drop any files here to add to the conversation": "Thả bất kỳ tệp nào ở đây để thêm vào cuộc trò chuyện", + "Drop any files here to add to the conversation": "Thả bất kỳ tệp nào ở đây để thêm vào nội dung chat", "e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "vd: '30s','10m'. Đơn vị thời gian hợp lệ là 's', 'm', 'h'.", - "Edit Doc": "Sửa Tài liệu", - "Edit User": "Sửa Người dùng", + "Edit Doc": "Thay đổi tài liệu", + "Edit User": "Thay đổi thông tin người sử dụng", "Email": "Email", - "Enable Chat History": "Bật Lịch sử Trò chuyện", - "Enable New Sign Ups": "Cho phép Đăng ký Mới", + "Enable Chat History": "Bật Lịch sử chat", + "Enable New Sign Ups": "Cho phép đăng ký mới", "Enabled": "Đã bật", - "Enter {{role}} message here": "Nhập tin nhắn {{role}} ở đây", + "Enter {{role}} message here": "Nhập yêu cầu của {{role}} ở đây", "Enter API Key": "Nhập Khóa API", "Enter Chunk Overlap": "Nhập Chunk chồng lấn (overlap)", "Enter Chunk Size": "Nhập Kích thước Chunk", @@ -134,40 +134,40 @@ "Enter LiteLLM Model (litellm_params.model)": "Nhập Mô hình LiteLLM (litellm_params.model)", "Enter Max Tokens (litellm_params.max_tokens)": "Nhập Số Token Tối đa (litellm_params.max_tokens)", "Enter model tag (e.g. {{modelTag}})": "Nhập thẻ mô hình (vd: {{modelTag}})", - "Enter Number of Steps (e.g. 50)": "Nhập Số Bước (vd: 50)", - "Enter stop sequence": "Nhập trình tự dừng", + "Enter Number of Steps (e.g. 50)": "Nhập số Steps (vd: 50)", + "Enter stop sequence": "Nhập stop sequence", "Enter Top K": "Nhập Top K", "Enter URL (e.g. http://127.0.0.1:7860/)": "Nhập URL (vd: http://127.0.0.1:7860/)", "Enter Your Email": "Nhập Email của bạn", "Enter Your Full Name": "Nhập Họ và Tên của bạn", "Enter Your Password": "Nhập Mật khẩu của bạn", - "Experimental": "Thực nghiệm", - "Export All Chats (All Users)": "Xuất Tất cả Cuộc trò chuyện (Tất cả Người dùng)", - "Export Chats": "Xuất Cuộc trò chuyện", - "Export Documents Mapping": "Xuất Ánh xạ Tài liệu", - "Export Modelfiles": "Xuất Tệp Mô hình", - "Export Prompts": "Xuất prompts", + "Experimental": "Thử nghiệm", + "Export All Chats (All Users)": "Tải về tất cả nội dung chat (tất cả mọi người)", + "Export Chats": "Tải nội dung chat về máy", + "Export Documents Mapping": "Tải cấu trúc tài liệu về máy", + "Export Modelfiles": "Tải tệp mô tả về máy", + "Export Prompts": "Tải các prompt về máy", "Failed to read clipboard contents": "Không thể đọc nội dung clipboard", "File Mode": "Chế độ Tệp văn bản", "File not found.": "Không tìm thấy tệp.", - "Focus chat input": "Tập trung vào đầu vào cuộc trò chuyện", + "Focus chat input": "Tập trung vào nội dung chat", "Format your variables using square brackets like this:": "Định dạng các biến của bạn bằng cách sử dụng dấu ngoặc vuông như thế này:", - "From (Base Model)": "Từ (Mô hình Cơ sở)", + "From (Base Model)": "Từ (Base Model)", "Full Screen Mode": "Chế độ Toàn màn hình", - "General": "Tổng quát", - "General Settings": "Cài đặt tổng quát", + "General": "Cài đặt chung", + "General Settings": "Cấu hình chung", "Hello, {{name}}": "Xin chào, {{name}}", "Hide": "Ẩn", "Hide Additional Params": "Ẩn Các tham số bổ sung", "How can I help you today?": "Tôi có thể giúp gì cho bạn hôm nay?", - "Image Generation (Experimental)": "tạo ảnh (Thực nghiệm)", + "Image Generation (Experimental)": "Tạo ảnh (thử nghiệm)", "Image Generation Engine": "Công cụ tạo ảnh", "Image Settings": "Cài đặt ảnh", "Images": "Hình ảnh", - "Import Chats": "Nạp cuộc trò chuyện (chats)", - "Import Documents Mapping": "Nạp sơ đồ hóa Tài liệu (document mapping)", - "Import Modelfiles": "Nạp tệp mô tả mô hình (modelefiles)", - "Import Prompts": "Nạp Prompts", + "Import Chats": "Nạp lại nội dung chat", + "Import Documents Mapping": "Nạp cấu trúc tài liệu", + "Import Modelfiles": "Nạp tệp mô tả", + "Import Prompts": "Nạp các prompt lên hệ thống", "Include `--api` flag when running stable-diffusion-webui": "Bao gồm flag `--api` khi chạy stable-diffusion-webui", "Interface": "Giao diện", "join our Discord for help.": "tham gia Discord của chúng tôi để được trợ giúp.", @@ -185,7 +185,7 @@ "Manage LiteLLM Models": "Quản lý mô hình với LiteLLM", "Manage Models": "Quản lý mô hình", "Manage Ollama Models": "Quản lý mô hình với Ollama", - "Max Tokens": "Số Token Tối đa", + "Max Tokens": "Max Tokens", "Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Tối đa 3 mô hình có thể được tải xuống cùng lúc. Vui lòng thử lại sau.", "Mirostat": "Mirostat", "Mirostat Eta": "Mirostat Eta", @@ -211,46 +211,46 @@ "Name": "Tên", "Name Tag": "Tên Thẻ", "Name your modelfile": "Đặt tên cho tệp mô hình của bạn", - "New Chat": "Cuộc trò chuyện Mới", - "New Password": "Mật khẩu Mới", + "New Chat": "Tạo cuộc trò chuyện mới", + "New Password": "Mật khẩu mới", "Not sure what to add?": "Không chắc phải thêm gì?", "Not sure what to write? Switch to": "Không chắc phải viết gì? Chuyển sang", "Off": "Tắt", "Okay, Let's Go!": "Được rồi, Bắt đầu thôi!", - "Ollama Base URL": "URL Cơ bản Ollama", + "Ollama Base URL": "Đường dẫn tới API của Ollama (Ollama Base URL)", "Ollama Version": "Phiên bản Ollama", "On": "Bật", - "Only": "Chỉ", + "Only": "Only", "Only alphanumeric characters and hyphens are allowed in the command string.": "Chỉ ký tự số và gạch nối được phép trong chuỗi lệnh.", - "Oops! Hold tight! Your files are still in the processing oven. We're cooking them up to perfection. Please be patient and we'll let you know once they're ready.": "Rất tiếc! Hãy giữ nguyên! Các tệp của bạn vẫn đang trong được phân tích và xử lý. Chúng tôi đang cố gắng hoàn thành chúng. Vui lòng kiên nhẫn và chúng tôi sẽ cho bạn biết khi chúng sẵn sàng.", + "Oops! Hold tight! Your files are still in the processing oven. We're cooking them up to perfection. Please be patient and we'll let you know once they're ready.": "Vui lòng kiên nhẫn chờ đợi! Các tệp của bạn vẫn đang trong được phân tích và xử lý. Chúng tôi đang cố gắng hoàn thành chúng. Vui lòng kiên nhẫn và chúng tôi sẽ cho bạn biết khi chúng sẵn sàng.", "Oops! Looks like the URL is invalid. Please double-check and try again.": "Rất tiếc! URL dường như không hợp lệ. Vui lòng kiểm tra lại và thử lại.", "Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Rất tiếc! Bạn đang sử dụng một phương thức không được hỗ trợ (chỉ dành cho frontend). Vui lòng cung cấp phương thức cho WebUI từ phía backend.", "Open": "Mở", "Open AI": "Open AI", "Open AI (Dall-E)": "Open AI (Dall-E)", - "Open new chat": "Mở cuộc trò chuyện mới", + "Open new chat": "Mở nội dung chat mới", "OpenAI API": "API OpenAI", "OpenAI API Key": "Khóa API OpenAI", - "OpenAI API Key is required.": "Yêu cầu Khóa API OpenAI.", + "OpenAI API Key is required.": "Bắt buộc nhập API OpenAI Key.", "or": "hoặc", "Parameters": "Tham số", "Password": "Mật khẩu", "PDF Extract Images (OCR)": "Trích xuất ảnh từ PDF (OCR)", - "pending": "đang chờ", + "pending": "đang chờ phê duyệt", "Permission denied when accessing microphone: {{error}}": "Quyền truy cập micrô bị từ chối: {{error}}", "Playground": "Thử nghiệm (Playground)", "Profile": "Hồ sơ", "Prompt Content": "Nội dung prompt", - "Prompt suggestions": "Đề nghị prompt", - "Prompts": "prompt", - "Pull a model from Ollama.com": "Tải một mô hình từ Ollama.com", + "Prompt suggestions": "Gợi ý prompt", + "Prompts": "Prompt", + "Pull a model from Ollama.com": "Tải mô hình từ Ollama.com", "Pull Progress": "Tiến trình Tải xuống", "Query Params": "Tham số Truy vấn", "RAG Template": "Mẫu prompt cho RAG", "Raw Format": "Raw Format", "Record voice": "Ghi âm", "Redirecting you to OpenWebUI Community": "Đang chuyển hướng bạn đến Cộng đồng OpenWebUI", - "Release Notes": "Ghi chú Phát hành", + "Release Notes": "Mô tả những cập nhật mới", "Repeat Last N": "Repeat Last N", "Repeat Penalty": "Repeat Penalty", "Request Mode": "Request Mode", @@ -263,18 +263,18 @@ "Save & Create": "Lưu & Tạo", "Save & Submit": "Lưu & Gửi", "Save & Update": "Lưu & Cập nhật", - "Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Không còn hỗ trợ lưu trữ nhật ký trò chuyện trực tiếp vào bộ nhớ trình duyệt của bạn. Vui lòng dành thời gian để tải xuống và xóa nhật ký trò chuyện của bạn bằng cách nhấp vào nút bên dưới. Đừng lo lắng, bạn có thể dễ dàng nhập lại nhật ký trò chuyện của mình vào backend thông qua", - "Scan": "Quét", + "Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Không còn hỗ trợ lưu trữ lịch sử chat trực tiếp vào bộ nhớ trình duyệt của bạn. Vui lòng dành thời gian để tải xuống và xóa lịch sử chat của bạn bằng cách nhấp vào nút bên dưới. Đừng lo lắng, bạn có thể dễ dàng nhập lại lịch sử chat của mình vào backend thông qua", + "Scan": "Quét tài liệu", "Scan complete!": "Quét hoàn tất!", - "Scan for documents from {{path}}": "Quét tài liệu từ {{path}}", + "Scan for documents from {{path}}": "Quét tài liệu từ đường dẫn: {{path}}", "Search": "Tìm kiếm", - "Search Documents": "Tìm Tài liệu", + "Search Documents": "Tìm tài liệu", "Search Prompts": "Tìm prompt", "See readme.md for instructions": "Xem readme.md để biết hướng dẫn", - "See what's new": "Xem những gì mới", - "Seed": "Hạt giống", + "See what's new": "Xem những cập nhật mới", + "Seed": "Seed", "Select a mode": "Chọn một chế độ", - "Select a model": "Chọn một mô hình", + "Select a model": "Chọn mô hình", "Select an Ollama instance": "Chọn một thực thể Ollama", "Send a Message": "Gửi yêu cầu", "Send message": "Gửi yêu cầu", @@ -283,7 +283,7 @@ "Set Default Model": "Đặt Mô hình Mặc định", "Set Image Size": "Đặt Kích thước ảnh", "Set Steps": "Đặt Số Bước", - "Set Title Auto-Generation Model": "Đặt Mô hình Tự động Tạo Tiêu đề", + "Set Title Auto-Generation Model": "Đặt tiêu đề tự động", "Set Voice": "Đặt Giọng nói", "Settings": "Cài đặt", "Settings saved successfully!": "Cài đặt đã được lưu thành công!", @@ -306,20 +306,20 @@ "Successfully updated.": "Đã cập nhật thành công.", "Sync All": "Đồng bộ hóa Tất cả", "System": "Hệ thống", - "System Prompt": "prompt Hệ thống", + "System Prompt": "Prompt Hệ thống (System Prompt)", "Tags": "Thẻ", - "Temperature": "Nhiệt độ", + "Temperature": "Temperature", "Template": "Mẫu", "Text Completion": "Hoàn tất Văn bản", "Text-to-Speech Engine": "Công cụ Chuyển Văn bản thành Giọng nói", "Tfs Z": "Tfs Z", "Theme": "Chủ đề", - "This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Điều này đảm bảo rằng các cuộc trò chuyện có giá trị của bạn được lưu an toàn vào cơ sở dữ liệu backend của bạn. Cảm ơn bạn!", + "This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Điều này đảm bảo rằng các nội dung chat có giá trị của bạn được lưu an toàn vào cơ sở dữ liệu backend của bạn. Cảm ơn bạn!", "This setting does not sync across browsers or devices.": "Cài đặt này không đồng bộ hóa trên các trình duyệt hoặc thiết bị.", "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Mẹo: Cập nhật nhiều khe biến liên tiếp bằng cách nhấn phím tab trong đầu vào trò chuyện sau mỗi việc thay thế.", "Title": "Tiêu đề", "Title Auto-Generation": "Tự động Tạo Tiêu đề", - "Title Generation Prompt": "prompt Tạo Tiêu đề", + "Title Generation Prompt": "Prompt tạo tiêu đề", "to": "đến", "To access the available model names for downloading,": "Để truy cập các tên mô hình có sẵn để tải xuống,", "To access the GGUF models available for downloading,": "Để truy cập các mô hình GGUF có sẵn để tải xuống,", @@ -334,15 +334,15 @@ "Uh-oh! There was an issue connecting to {{provider}}.": "Ồ! Đã xảy ra sự cố khi kết nối với {{provider}}.", "Unknown File Type '{{file_type}}', but accepting and treating as plain text": "Loại Tệp Không xác định '{{file_type}}', nhưng đang chấp nhận và xử lý như văn bản thô", "Update password": "Cập nhật mật khẩu", - "Upload a GGUF model": "Tải lên một mô hình GGUF", - "Upload files": "Tải lên tệp", - "Upload Progress": "Tiến trình Tải lên", + "Upload a GGUF model": "Tải lên mô hình GGUF", + "Upload files": "Tải tệp lên hệ thống", + "Upload Progress": "Tiến trình tải tệp lên hệ thống", "URL Mode": "Chế độ URL", - "Use '#' in the prompt input to load and select your documents.": "Sử dụng '#' trong đầu vào prompt để tải và chọn tài liệu của bạn.", + "Use '#' in the prompt input to load and select your documents.": "Sử dụng '#' trong đầu vào của prompt để tải về và lựa chọn tài liệu của bạn cần truy vấn.", "Use Gravatar": "Sử dụng Gravatar", - "user": "người dùng", - "User Permissions": "Quyền của Người dùng", - "Users": "Người dùng", + "user": "Người sử dụng", + "User Permissions": "Phân quyền sử dụng", + "Users": "người sử dụng", "Utilize": "Sử dụng", "Valid time units:": "Đơn vị thời gian hợp lệ:", "variable": "biến", @@ -353,7 +353,7 @@ "WebUI Settings": "Cài đặt WebUI", "WebUI will make requests to": "WebUI sẽ thực hiện các yêu cầu đến", "What's New in": "Có gì mới trong", - "When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Khi chế độ lịch sử trò chuyện đã tắt, các cuộc trò chuyện mới trên trình duyệt này sẽ không xuất hiện trên bất kỳ thiết bị nào của bạn.", + "When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Khi chế độ lịch sử chat đã tắt, các nội dung chat mới trên trình duyệt này sẽ không xuất hiện trên bất kỳ thiết bị nào của bạn.", "Whisper (Local)": "Whisper (Local)", "Write a prompt suggestion (e.g. Who are you?)": "Hãy viết một prompt (vd: Bạn là ai?)", "Write a summary in 50 words that summarizes [topic or keyword].": "Viết một tóm tắt trong vòng 50 từ cho [chủ đề hoặc từ khóa].", From af4caec4f559790dc689c3844c2893dbbef6f02f Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Thu, 21 Mar 2024 23:45:00 -0700 Subject: [PATCH 09/52] refac: ollama gguf upload --- backend/apps/ollama/main.py | 186 +++++++++++++++++- backend/apps/web/routers/utils.py | 149 -------------- src/lib/apis/ollama/index.ts | 65 ++++++ .../components/chat/Settings/Models.svelte | 44 ++--- 4 files changed, 268 insertions(+), 176 deletions(-) diff --git a/backend/apps/ollama/main.py b/backend/apps/ollama/main.py index 6f56f3cf..8bb01ed4 100644 --- a/backend/apps/ollama/main.py +++ b/backend/apps/ollama/main.py @@ -1,23 +1,39 @@ -from fastapi import FastAPI, Request, Response, HTTPException, Depends, status +from fastapi import ( + FastAPI, + Request, + Response, + HTTPException, + Depends, + status, + UploadFile, + File, + BackgroundTasks, +) from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import StreamingResponse from fastapi.concurrency import run_in_threadpool from pydantic import BaseModel, ConfigDict +import os import random import requests import json import uuid import aiohttp import asyncio +import aiofiles +from urllib.parse import urlparse +from typing import Optional, List, Union + from apps.web.models.users import Users from constants import ERROR_MESSAGES from utils.utils import decode_token, get_current_user, get_admin_user -from config import OLLAMA_BASE_URLS, MODEL_FILTER_ENABLED, MODEL_FILTER_LIST +from utils.misc import calculate_sha256 -from typing import Optional, List, Union + +from config import OLLAMA_BASE_URLS, MODEL_FILTER_ENABLED, MODEL_FILTER_LIST, UPLOAD_DIR app = FastAPI() @@ -897,6 +913,170 @@ async def generate_openai_chat_completion( ) +class UrlForm(BaseModel): + url: str + + +class UploadBlobForm(BaseModel): + filename: str + + +def parse_huggingface_url(hf_url): + try: + # Parse the URL + parsed_url = urlparse(hf_url) + + # Get the path and split it into components + path_components = parsed_url.path.split("/") + + # Extract the desired output + user_repo = "/".join(path_components[1:3]) + model_file = path_components[-1] + + return model_file + except ValueError: + return None + + +async def download_file_stream( + ollama_url, file_url, file_path, file_name, chunk_size=1024 * 1024 +): + done = False + + if os.path.exists(file_path): + current_size = os.path.getsize(file_path) + else: + current_size = 0 + + headers = {"Range": f"bytes={current_size}-"} if current_size > 0 else {} + + timeout = aiohttp.ClientTimeout(total=600) # Set the timeout + + async with aiohttp.ClientSession(timeout=timeout) as session: + async with session.get(file_url, headers=headers) as response: + total_size = int(response.headers.get("content-length", 0)) + current_size + + with open(file_path, "ab+") as file: + async for data in response.content.iter_chunked(chunk_size): + current_size += len(data) + file.write(data) + + done = current_size == total_size + progress = round((current_size / total_size) * 100, 2) + yield f'data: {{"progress": {progress}, "completed": {current_size}, "total": {total_size}}}\n\n' + + if done: + file.seek(0) + hashed = calculate_sha256(file) + file.seek(0) + + url = f"{ollama_url}/api/blobs/sha256:{hashed}" + response = requests.post(url, data=file) + + if response.ok: + res = { + "done": done, + "blob": f"sha256:{hashed}", + "name": file_name, + } + os.remove(file_path) + + yield f"data: {json.dumps(res)}\n\n" + else: + raise "Ollama: Could not create blob, Please try again." + + +# url = "https://huggingface.co/TheBloke/stablelm-zephyr-3b-GGUF/resolve/main/stablelm-zephyr-3b.Q2_K.gguf" +@app.post("/models/download") +@app.post("/models/download/{url_idx}") +async def download_model( + form_data: UrlForm, + url_idx: Optional[int] = None, +): + + if url_idx == None: + url_idx = 0 + url = app.state.OLLAMA_BASE_URLS[url_idx] + + file_name = parse_huggingface_url(form_data.url) + + if file_name: + file_path = f"{UPLOAD_DIR}/{file_name}" + + return StreamingResponse( + download_file_stream(url, form_data.url, file_path, file_name) + ) + else: + return None + + +@app.post("/models/upload") +@app.post("/models/upload/{url_idx}") +def upload_model(file: UploadFile = File(...), url_idx: Optional[int] = None): + if url_idx == None: + url_idx = 0 + ollama_url = app.state.OLLAMA_BASE_URLS[url_idx] + + file_path = f"{UPLOAD_DIR}/{file.filename}" + + # Save file in chunks + with open(file_path, "wb+") as f: + for chunk in file.file: + f.write(chunk) + + def file_process_stream(): + nonlocal ollama_url + total_size = os.path.getsize(file_path) + chunk_size = 1024 * 1024 + try: + with open(file_path, "rb") as f: + total = 0 + done = False + + while not done: + chunk = f.read(chunk_size) + if not chunk: + done = True + continue + + total += len(chunk) + progress = round((total / total_size) * 100, 2) + + res = { + "progress": progress, + "total": total_size, + "completed": total, + } + yield f"data: {json.dumps(res)}\n\n" + + if done: + f.seek(0) + hashed = calculate_sha256(f) + f.seek(0) + + url = f"{ollama_url}/blobs/sha256:{hashed}" + response = requests.post(url, data=f) + + if response.ok: + res = { + "done": done, + "blob": f"sha256:{hashed}", + "name": file.filename, + } + os.remove(file_path) + yield f"data: {json.dumps(res)}\n\n" + else: + raise Exception( + "Ollama: Could not create blob, Please try again." + ) + + except Exception as e: + res = {"error": str(e)} + yield f"data: {json.dumps(res)}\n\n" + + return StreamingResponse(file_process_stream(), media_type="text/event-stream") + + @app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE"]) async def deprecated_proxy(path: str, request: Request, user=Depends(get_current_user)): url = app.state.OLLAMA_BASE_URLS[0] diff --git a/backend/apps/web/routers/utils.py b/backend/apps/web/routers/utils.py index 0d34b040..4b5ac8cf 100644 --- a/backend/apps/web/routers/utils.py +++ b/backend/apps/web/routers/utils.py @@ -21,155 +21,6 @@ from constants import ERROR_MESSAGES router = APIRouter() -class UploadBlobForm(BaseModel): - filename: str - - -from urllib.parse import urlparse - - -def parse_huggingface_url(hf_url): - try: - # Parse the URL - parsed_url = urlparse(hf_url) - - # Get the path and split it into components - path_components = parsed_url.path.split("/") - - # Extract the desired output - user_repo = "/".join(path_components[1:3]) - model_file = path_components[-1] - - return model_file - except ValueError: - return None - - -async def download_file_stream(url, file_path, file_name, chunk_size=1024 * 1024): - done = False - - if os.path.exists(file_path): - current_size = os.path.getsize(file_path) - else: - current_size = 0 - - headers = {"Range": f"bytes={current_size}-"} if current_size > 0 else {} - - timeout = aiohttp.ClientTimeout(total=600) # Set the timeout - - async with aiohttp.ClientSession(timeout=timeout) as session: - async with session.get(url, headers=headers) as response: - total_size = int(response.headers.get("content-length", 0)) + current_size - - with open(file_path, "ab+") as file: - async for data in response.content.iter_chunked(chunk_size): - current_size += len(data) - file.write(data) - - done = current_size == total_size - progress = round((current_size / total_size) * 100, 2) - yield f'data: {{"progress": {progress}, "completed": {current_size}, "total": {total_size}}}\n\n' - - if done: - file.seek(0) - hashed = calculate_sha256(file) - file.seek(0) - - url = f"{OLLAMA_BASE_URLS[0]}/api/blobs/sha256:{hashed}" - response = requests.post(url, data=file) - - if response.ok: - res = { - "done": done, - "blob": f"sha256:{hashed}", - "name": file_name, - } - os.remove(file_path) - - yield f"data: {json.dumps(res)}\n\n" - else: - raise "Ollama: Could not create blob, Please try again." - - -@router.get("/download") -async def download( - url: str, -): - # url = "https://huggingface.co/TheBloke/stablelm-zephyr-3b-GGUF/resolve/main/stablelm-zephyr-3b.Q2_K.gguf" - file_name = parse_huggingface_url(url) - - if file_name: - file_path = f"{UPLOAD_DIR}/{file_name}" - - return StreamingResponse( - download_file_stream(url, file_path, file_name), - media_type="text/event-stream", - ) - else: - return None - - -@router.post("/upload") -def upload(file: UploadFile = File(...)): - file_path = f"{UPLOAD_DIR}/{file.filename}" - - # Save file in chunks - with open(file_path, "wb+") as f: - for chunk in file.file: - f.write(chunk) - - def file_process_stream(): - total_size = os.path.getsize(file_path) - chunk_size = 1024 * 1024 - try: - with open(file_path, "rb") as f: - total = 0 - done = False - - while not done: - chunk = f.read(chunk_size) - if not chunk: - done = True - continue - - total += len(chunk) - progress = round((total / total_size) * 100, 2) - - res = { - "progress": progress, - "total": total_size, - "completed": total, - } - yield f"data: {json.dumps(res)}\n\n" - - if done: - f.seek(0) - hashed = calculate_sha256(f) - f.seek(0) - - url = f"{OLLAMA_BASE_URLS[0]}/blobs/sha256:{hashed}" - response = requests.post(url, data=f) - - if response.ok: - res = { - "done": done, - "blob": f"sha256:{hashed}", - "name": file.filename, - } - os.remove(file_path) - yield f"data: {json.dumps(res)}\n\n" - else: - raise Exception( - "Ollama: Could not create blob, Please try again." - ) - - except Exception as e: - res = {"error": str(e)} - yield f"data: {json.dumps(res)}\n\n" - - return StreamingResponse(file_process_stream(), media_type="text/event-stream") - - @router.get("/gravatar") async def get_gravatar( email: str, diff --git a/src/lib/apis/ollama/index.ts b/src/lib/apis/ollama/index.ts index 2047fede..a461d71b 100644 --- a/src/lib/apis/ollama/index.ts +++ b/src/lib/apis/ollama/index.ts @@ -390,6 +390,71 @@ export const pullModel = async (token: string, tagName: string, urlIdx: string | return res; }; +export const downloadModel = async ( + token: string, + download_url: string, + urlIdx: string | null = null +) => { + let error = null; + + const res = await fetch( + `${OLLAMA_API_BASE_URL}/models/download${urlIdx !== null ? `/${urlIdx}` : ''}`, + { + method: 'POST', + headers: { + Authorization: `Bearer ${token}` + }, + body: JSON.stringify({ + url: download_url + }) + } + ).catch((err) => { + console.log(err); + error = err; + + if ('detail' in err) { + error = err.detail; + } + + return null; + }); + if (error) { + throw error; + } + return res; +}; + +export const uploadModel = async (token: string, file: File, urlIdx: string | null = null) => { + let error = null; + + const formData = new FormData(); + formData.append('file', file); + + const res = await fetch( + `${OLLAMA_API_BASE_URL}/models/upload${urlIdx !== null ? `/${urlIdx}` : ''}`, + { + method: 'POST', + headers: { + Authorization: `Bearer ${token}` + }, + body: formData + } + ).catch((err) => { + console.log(err); + error = err; + + if ('detail' in err) { + error = err.detail; + } + + return null; + }); + if (error) { + throw error; + } + return res; +}; + // export const pullModel = async (token: string, tagName: string) => { // return await fetch(`${OLLAMA_API_BASE_URL}/pull`, { // method: 'POST', diff --git a/src/lib/components/chat/Settings/Models.svelte b/src/lib/components/chat/Settings/Models.svelte index 7b75e372..f74e85ff 100644 --- a/src/lib/components/chat/Settings/Models.svelte +++ b/src/lib/components/chat/Settings/Models.svelte @@ -5,9 +5,11 @@ import { createModel, deleteModel, + downloadModel, getOllamaUrls, getOllamaVersion, - pullModel + pullModel, + uploadModel } from '$lib/apis/ollama'; import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants'; import { WEBUI_NAME, models, user } from '$lib/stores'; @@ -60,7 +62,7 @@ let pullProgress = null; let modelUploadMode = 'file'; - let modelInputFile = ''; + let modelInputFile: File[] | null = null; let modelFileUrl = ''; let modelFileContent = `TEMPLATE """{{ .System }}\nUSER: {{ .Prompt }}\nASSISTANT: """\nPARAMETER num_ctx 4096\nPARAMETER stop ""\nPARAMETER stop "USER:"\nPARAMETER stop "ASSISTANT:"`; let modelFileDigest = ''; @@ -191,30 +193,23 @@ let name = ''; if (modelUploadMode === 'file') { - const file = modelInputFile[0]; - const formData = new FormData(); - formData.append('file', file); + const file = modelInputFile ? modelInputFile[0] : null; - fileResponse = await fetch(`${WEBUI_API_BASE_URL}/utils/upload`, { - method: 'POST', - headers: { - ...($user && { Authorization: `Bearer ${localStorage.token}` }) - }, - body: formData - }).catch((error) => { - console.log(error); - return null; - }); + if (file) { + fileResponse = uploadModel(localStorage.token, file, selectedOllamaUrlIdx).catch( + (error) => { + toast.error(error); + return null; + } + ); + } } else { - fileResponse = await fetch(`${WEBUI_API_BASE_URL}/utils/download?url=${modelFileUrl}`, { - method: 'GET', - headers: { - ...($user && { Authorization: `Bearer ${localStorage.token}` }) + fileResponse = downloadModel(localStorage.token, modelFileUrl, selectedOllamaUrlIdx).catch( + (error) => { + toast.error(error); + return null; } - }).catch((error) => { - console.log(error); - return null; - }); + ); } if (fileResponse && fileResponse.ok) { @@ -318,7 +313,8 @@ } modelFileUrl = ''; - modelInputFile = ''; + modelUploadInputElement.value = ''; + modelInputFile = null; modelTransferring = false; uploadProgress = null; From bc80f1438c76c5dedd3147f8e1edc34e48eca528 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Fri, 22 Mar 2024 00:10:55 -0700 Subject: [PATCH 10/52] fix: ollama gguf upload --- backend/apps/ollama/main.py | 11 ++++++-- src/lib/apis/ollama/index.ts | 2 ++ .../components/chat/Settings/Models.svelte | 25 +++++++++++-------- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/backend/apps/ollama/main.py b/backend/apps/ollama/main.py index 8bb01ed4..5d66979c 100644 --- a/backend/apps/ollama/main.py +++ b/backend/apps/ollama/main.py @@ -963,6 +963,9 @@ async def download_file_stream( done = current_size == total_size progress = round((current_size / total_size) * 100, 2) + + print(progress) + yield f'data: {{"progress": {progress}, "completed": {current_size}, "total": {total_size}}}\n\n' if done: @@ -986,6 +989,11 @@ async def download_file_stream( raise "Ollama: Could not create blob, Please try again." +# def number_generator(): +# for i in range(1, 101): +# yield f"data: {i}\n" + + # url = "https://huggingface.co/TheBloke/stablelm-zephyr-3b-GGUF/resolve/main/stablelm-zephyr-3b.Q2_K.gguf" @app.post("/models/download") @app.post("/models/download/{url_idx}") @@ -1002,9 +1010,8 @@ async def download_model( if file_name: file_path = f"{UPLOAD_DIR}/{file_name}" - return StreamingResponse( - download_file_stream(url, form_data.url, file_path, file_name) + download_file_stream(url, form_data.url, file_path, file_name), ) else: return None diff --git a/src/lib/apis/ollama/index.ts b/src/lib/apis/ollama/index.ts index a461d71b..c3b37e00 100644 --- a/src/lib/apis/ollama/index.ts +++ b/src/lib/apis/ollama/index.ts @@ -402,6 +402,8 @@ export const downloadModel = async ( { method: 'POST', headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', Authorization: `Bearer ${token}` }, body: JSON.stringify({ diff --git a/src/lib/components/chat/Settings/Models.svelte b/src/lib/components/chat/Settings/Models.svelte index f74e85ff..9b319b95 100644 --- a/src/lib/components/chat/Settings/Models.svelte +++ b/src/lib/components/chat/Settings/Models.svelte @@ -196,7 +196,7 @@ const file = modelInputFile ? modelInputFile[0] : null; if (file) { - fileResponse = uploadModel(localStorage.token, file, selectedOllamaUrlIdx).catch( + fileResponse = await uploadModel(localStorage.token, file, selectedOllamaUrlIdx).catch( (error) => { toast.error(error); return null; @@ -204,12 +204,14 @@ ); } } else { - fileResponse = downloadModel(localStorage.token, modelFileUrl, selectedOllamaUrlIdx).catch( - (error) => { - toast.error(error); - return null; - } - ); + fileResponse = await downloadModel( + localStorage.token, + modelFileUrl, + selectedOllamaUrlIdx + ).catch((error) => { + toast.error(error); + return null; + }); } if (fileResponse && fileResponse.ok) { @@ -313,7 +315,10 @@ } modelFileUrl = ''; - modelUploadInputElement.value = ''; + + if (modelUploadInputElement) { + modelUploadInputElement.value = ''; + } modelInputFile = null; modelTransferring = false; uploadProgress = null; @@ -741,7 +746,7 @@ {#if (modelUploadMode === 'file' && modelInputFile && modelInputFile.length > 0) || (modelUploadMode === 'url' && modelFileUrl !== '')}