From ae97a96379ed85d14e2b70cbd5ede233f1178534 Mon Sep 17 00:00:00 2001 From: Anuraag Jain Date: Sun, 18 Feb 2024 21:29:47 +0200 Subject: [PATCH 01/37] 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/37] 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 d291821bf39f939c7170be7506a1da8633e42a2c Mon Sep 17 00:00:00 2001 From: Anuraag Jain Date: Sat, 23 Mar 2024 20:56:18 +0200 Subject: [PATCH 03/37] refac: delete button UI --- .gitignore | 2 +- .../components/chat/Settings/Models.svelte | 24 ++++++++----------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index 528e1f83..2ccac4d5 100644 --- a/.gitignore +++ b/.gitignore @@ -166,7 +166,7 @@ cython_debug/ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ +.idea/ # Logs logs diff --git a/src/lib/components/chat/Settings/Models.svelte b/src/lib/components/chat/Settings/Models.svelte index 973e3dd2..35386e1e 100644 --- a/src/lib/components/chat/Settings/Models.svelte +++ b/src/lib/components/chat/Settings/Models.svelte @@ -473,8 +473,10 @@ const {reader} = modelDownloadStatus[model]; if(reader){ await reader.cancel(); - toast.success(`${model} download has been canceled`); delete modelDownloadStatus[model]; + await deleteModel(localStorage.token, model); + toast.success(`${model} download has been canceled`); + } } @@ -611,6 +613,7 @@
{model}
+
+
{modelDownloadStatus[model].digest}
From 7e6d49948657348240546b6f5b10bb8f4db47ac1 Mon Sep 17 00:00:00 2001 From: Anuraag Jain Date: Sat, 23 Mar 2024 21:00:01 +0200 Subject: [PATCH 04/37] refac: remove conosle.log --- src/lib/components/chat/Settings/Models.svelte | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib/components/chat/Settings/Models.svelte b/src/lib/components/chat/Settings/Models.svelte index 35386e1e..e2695fb5 100644 --- a/src/lib/components/chat/Settings/Models.svelte +++ b/src/lib/components/chat/Settings/Models.svelte @@ -161,8 +161,6 @@ modelDownloadStatus = {...modelDownloadStatus} - console.log('Cleaned:',modelDownloadStatus); - if (!data.success) { toast.error(data.error); } else { From 642ca5a94a0d9fd683c00cd80cb5272486cb936a Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Sat, 23 Mar 2024 12:46:06 -0700 Subject: [PATCH 05/37] refac: ui --- .../components/chat/Settings/Models.svelte | 61 +++++++++++-------- 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/src/lib/components/chat/Settings/Models.svelte b/src/lib/components/chat/Settings/Models.svelte index e2695fb5..2d0b1d7a 100644 --- a/src/lib/components/chat/Settings/Models.svelte +++ b/src/lib/components/chat/Settings/Models.svelte @@ -159,7 +159,7 @@ // Remove the downloaded model delete modelDownloadStatus[modelName]; - modelDownloadStatus = {...modelDownloadStatus} + modelDownloadStatus = { ...modelDownloadStatus }; if (!data.success) { toast.error(data.error); @@ -467,17 +467,15 @@ ollamaVersion = await getOllamaVersion(localStorage.token).catch((error) => false); liteLLMModelInfo = await getLiteLLMModelInfo(localStorage.token); }); - const deleteModelPull = async(model: string) => { - const {reader} = modelDownloadStatus[model]; - if(reader){ + const deleteModelPull = async (model: string) => { + const { reader } = modelDownloadStatus[model]; + if (reader) { await reader.cancel(); delete modelDownloadStatus[model]; await deleteModel(localStorage.token, model); toast.success(`${model} download has been canceled`); - } - - } + };
@@ -611,25 +609,38 @@
{model}
-
-
- {modelDownloadStatus[model].pullProgress ?? 0}% +
+
+ {modelDownloadStatus[model].pullProgress ?? 0}% +
+
- -
{modelDownloadStatus[model].digest}
From 244f34c24e0a56433c2a0c996994d5271af41159 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Sat, 23 Mar 2024 13:12:23 -0700 Subject: [PATCH 06/37] refac: cancel download --- src/lib/apis/ollama/index.ts | 2 +- .../components/chat/Settings/Models.svelte | 124 ++++++++++++------ src/routes/(app)/+page.svelte | 6 +- src/routes/(app)/c/[id]/+page.svelte | 6 +- src/routes/(app)/playground/+page.svelte | 8 +- 5 files changed, 93 insertions(+), 53 deletions(-) diff --git a/src/lib/apis/ollama/index.ts b/src/lib/apis/ollama/index.ts index 2047fede..ba04e060 100644 --- a/src/lib/apis/ollama/index.ts +++ b/src/lib/apis/ollama/index.ts @@ -271,7 +271,7 @@ export const generateChatCompletion = async (token: string = '', body: object) = return [res, controller]; }; -export const cancelChatCompletion = async (token: string = '', requestId: string) => { +export const cancelOllamaRequest = async (token: string = '', requestId: string) => { let error = null; const res = await fetch(`${OLLAMA_API_BASE_URL}/cancel/${requestId}`, { diff --git a/src/lib/components/chat/Settings/Models.svelte b/src/lib/components/chat/Settings/Models.svelte index 2d0b1d7a..d961b72b 100644 --- a/src/lib/components/chat/Settings/Models.svelte +++ b/src/lib/components/chat/Settings/Models.svelte @@ -7,7 +7,8 @@ deleteModel, getOllamaUrls, getOllamaVersion, - pullModel + pullModel, + cancelOllamaRequest } from '$lib/apis/ollama'; import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants'; import { WEBUI_NAME, models, user } from '$lib/stores'; @@ -364,12 +365,24 @@ for (const line of lines) { if (line !== '') { let data = JSON.parse(line); + console.log(data); if (data.error) { throw data.error; } if (data.detail) { throw data.detail; } + + if (data.id) { + modelDownloadStatus[opts.modelName] = { + ...modelDownloadStatus[opts.modelName], + requestId: data.id, + reader, + done: false + }; + console.log(data); + } + if (data.status) { if (data.digest) { let downloadProgress = 0; @@ -379,12 +392,17 @@ downloadProgress = 100; } modelDownloadStatus[opts.modelName] = { - reader, + ...modelDownloadStatus[opts.modelName], pullProgress: downloadProgress, digest: data.digest }; } else { toast.success(data.status); + + modelDownloadStatus[opts.modelName] = { + ...modelDownloadStatus[opts.modelName], + done: data.status === 'success' + }; } } } @@ -397,7 +415,14 @@ opts.callback({ success: false, error, modelName: opts.modelName }); } } - opts.callback({ success: true, modelName: opts.modelName }); + + console.log(modelDownloadStatus[opts.modelName]); + + if (modelDownloadStatus[opts.modelName].done) { + opts.callback({ success: true, modelName: opts.modelName }); + } else { + opts.callback({ success: false, error: 'Download canceled', modelName: opts.modelName }); + } } }; @@ -467,10 +492,13 @@ ollamaVersion = await getOllamaVersion(localStorage.token).catch((error) => false); liteLLMModelInfo = await getLiteLLMModelInfo(localStorage.token); }); - const deleteModelPull = async (model: string) => { - const { reader } = modelDownloadStatus[model]; + + const cancelModelPullHandler = async (model: string) => { + const { reader, requestId } = modelDownloadStatus[model]; if (reader) { await reader.cancel(); + + await cancelOllamaRequest(localStorage.token, requestId); delete modelDownloadStatus[model]; await deleteModel(localStorage.token, model); toast.success(`${model} download has been canceled`); @@ -606,46 +634,58 @@ {#if Object.keys(modelDownloadStatus).length > 0} {#each Object.keys(modelDownloadStatus) as model} -
-
{model}
-
-
-
- {modelDownloadStatus[model].pullProgress ?? 0}% + {#if 'pullProgress' in modelDownloadStatus[model]} +
+
{model}
+
+
+
+
+ {modelDownloadStatus[model].pullProgress ?? 0}% +
+
+ + + +
- -
-
- {modelDownloadStatus[model].digest} + {#if 'digest' in modelDownloadStatus[model]} +
+ {modelDownloadStatus[model].digest} +
+ {/if}
-
+ {/if} {/each} {/if}
diff --git a/src/routes/(app)/+page.svelte b/src/routes/(app)/+page.svelte index 0fca312a..417ddccd 100644 --- a/src/routes/(app)/+page.svelte +++ b/src/routes/(app)/+page.svelte @@ -19,7 +19,7 @@ } from '$lib/stores'; import { copyToClipboard, splitStream } from '$lib/utils'; - import { generateChatCompletion, cancelChatCompletion, generateTitle } from '$lib/apis/ollama'; + import { generateChatCompletion, cancelOllamaRequest, generateTitle } from '$lib/apis/ollama'; import { addTagById, createNewChat, @@ -104,7 +104,7 @@ const initNewChat = async () => { if (currentRequestId !== null) { - await cancelChatCompletion(localStorage.token, currentRequestId); + await cancelOllamaRequest(localStorage.token, currentRequestId); currentRequestId = null; } window.history.replaceState(history.state, '', `/`); @@ -372,7 +372,7 @@ if (stopResponseFlag) { controller.abort('User: Stop Response'); - await cancelChatCompletion(localStorage.token, currentRequestId); + await cancelOllamaRequest(localStorage.token, currentRequestId); } currentRequestId = null; diff --git a/src/routes/(app)/c/[id]/+page.svelte b/src/routes/(app)/c/[id]/+page.svelte index faa15b4b..836fc90a 100644 --- a/src/routes/(app)/c/[id]/+page.svelte +++ b/src/routes/(app)/c/[id]/+page.svelte @@ -19,7 +19,7 @@ } from '$lib/stores'; import { copyToClipboard, splitStream, convertMessagesToHistory } from '$lib/utils'; - import { generateChatCompletion, generateTitle, cancelChatCompletion } from '$lib/apis/ollama'; + import { generateChatCompletion, generateTitle, cancelOllamaRequest } from '$lib/apis/ollama'; import { addTagById, createNewChat, @@ -382,7 +382,7 @@ if (stopResponseFlag) { controller.abort('User: Stop Response'); - await cancelChatCompletion(localStorage.token, currentRequestId); + await cancelOllamaRequest(localStorage.token, currentRequestId); } currentRequestId = null; @@ -843,7 +843,7 @@ shareEnabled={messages.length > 0} initNewChat={async () => { if (currentRequestId !== null) { - await cancelChatCompletion(localStorage.token, currentRequestId); + await cancelOllamaRequest(localStorage.token, currentRequestId); currentRequestId = null; } diff --git a/src/routes/(app)/playground/+page.svelte b/src/routes/(app)/playground/+page.svelte index 737eff22..d8e9320d 100644 --- a/src/routes/(app)/playground/+page.svelte +++ b/src/routes/(app)/playground/+page.svelte @@ -13,7 +13,7 @@ } from '$lib/constants'; import { WEBUI_NAME, config, user, models, settings } from '$lib/stores'; - import { cancelChatCompletion, generateChatCompletion } from '$lib/apis/ollama'; + import { cancelOllamaRequest, generateChatCompletion } from '$lib/apis/ollama'; import { generateOpenAIChatCompletion } from '$lib/apis/openai'; import { splitStream } from '$lib/utils'; @@ -52,7 +52,7 @@ // const cancelHandler = async () => { // if (currentRequestId) { - // const res = await cancelChatCompletion(localStorage.token, currentRequestId); + // const res = await cancelOllamaRequest(localStorage.token, currentRequestId); // currentRequestId = null; // loading = false; // } @@ -95,7 +95,7 @@ const { value, done } = await reader.read(); if (done || stopResponseFlag) { if (stopResponseFlag) { - await cancelChatCompletion(localStorage.token, currentRequestId); + await cancelOllamaRequest(localStorage.token, currentRequestId); } currentRequestId = null; @@ -181,7 +181,7 @@ const { value, done } = await reader.read(); if (done || stopResponseFlag) { if (stopResponseFlag) { - await cancelChatCompletion(localStorage.token, currentRequestId); + await cancelOllamaRequest(localStorage.token, currentRequestId); } currentRequestId = null; From e008738f30081e4697a1b3c3278e2e51d47539ad Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Sat, 23 Mar 2024 13:12:54 -0700 Subject: [PATCH 07/37] feat: cancel download from backend --- backend/apps/ollama/main.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/backend/apps/ollama/main.py b/backend/apps/ollama/main.py index 6f56f3cf..2283774e 100644 --- a/backend/apps/ollama/main.py +++ b/backend/apps/ollama/main.py @@ -234,11 +234,26 @@ async def pull_model( def get_request(): nonlocal url nonlocal r + + request_id = str(uuid.uuid4()) try: + REQUEST_POOL.append(request_id) def stream_content(): - for chunk in r.iter_content(chunk_size=8192): - yield chunk + try: + yield json.dumps({"id": request_id, "done": False}) + "\n" + + for chunk in r.iter_content(chunk_size=8192): + if request_id in REQUEST_POOL: + yield chunk + else: + print("User: canceled request") + break + finally: + if hasattr(r, "close"): + r.close() + if request_id in REQUEST_POOL: + REQUEST_POOL.remove(request_id) r = requests.request( method="POST", @@ -259,6 +274,7 @@ async def pull_model( try: return await run_in_threadpool(get_request) + except Exception as e: print(e) error_detail = "Open WebUI: Server Connection Error" From c2d6d3230b0ed178d5cccc9cffe6a8edb73c1a29 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Sat, 23 Mar 2024 20:50:57 -0700 Subject: [PATCH 08/37] refac: reset vector db --- src/lib/components/chat/Settings/Chats.svelte | 33 ------ .../documents/Settings/General.svelte | 100 +++++++++++++++++- 2 files changed, 99 insertions(+), 34 deletions(-) diff --git a/src/lib/components/chat/Settings/Chats.svelte b/src/lib/components/chat/Settings/Chats.svelte index ecaf587a..191e64d3 100644 --- a/src/lib/components/chat/Settings/Chats.svelte +++ b/src/lib/components/chat/Settings/Chats.svelte @@ -2,7 +2,6 @@ import fileSaver from 'file-saver'; const { saveAs } = fileSaver; - import { resetVectorDB } from '$lib/apis/rag'; import { chats, user } from '$lib/stores'; import { @@ -330,38 +329,6 @@ {$i18n.t('Export All Chats (All Users)')}
- -
- - {/if}
diff --git a/src/lib/components/documents/Settings/General.svelte b/src/lib/components/documents/Settings/General.svelte index 3f8020e2..f00038de 100644 --- a/src/lib/components/documents/Settings/General.svelte +++ b/src/lib/components/documents/Settings/General.svelte @@ -5,8 +5,10 @@ updateRAGConfig, getQuerySettings, scanDocs, - updateQuerySettings + updateQuerySettings, + resetVectorDB } from '$lib/apis/rag'; + import { documents } from '$lib/stores'; import { onMount, getContext } from 'svelte'; import { toast } from 'svelte-sonner'; @@ -17,6 +19,8 @@ let loading = false; + let showResetConfirm = false; + let chunkSize = 0; let chunkOverlap = 0; let pdfExtractImages = true; @@ -231,6 +235,100 @@ />
+ +
+ + {#if showResetConfirm} +
+
+ + + + + {$i18n.t('Are you sure?')} +
+ +
+ + +
+
+ {:else} + + {/if}
From 1d64d31275c1c81b5c82ab5a3dcef4afd872d0e2 Mon Sep 17 00:00:00 2001 From: Jannik S <69747628+jannikstdl@users.noreply.github.com> Date: Sun, 24 Mar 2024 08:17:38 +0100 Subject: [PATCH 09/37] Updated PVC naming --- kubernetes/manifest/base/webui-pvc.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kubernetes/manifest/base/webui-pvc.yaml b/kubernetes/manifest/base/webui-pvc.yaml index 5c75283a..97fb761d 100644 --- a/kubernetes/manifest/base/webui-pvc.yaml +++ b/kubernetes/manifest/base/webui-pvc.yaml @@ -2,8 +2,8 @@ apiVersion: v1 kind: PersistentVolumeClaim metadata: labels: - app: ollama-webui - name: ollama-webui-pvc + app: open-webui + name: open-webui-pvc namespace: open-webui spec: accessModes: ["ReadWriteOnce"] From c91c95431d0741b69a10d332067f5d9459818fa3 Mon Sep 17 00:00:00 2001 From: Jannik S <69747628+jannikstdl@users.noreply.github.com> Date: Sun, 24 Mar 2024 08:18:28 +0100 Subject: [PATCH 10/37] Changed allocation in webui-deployment.yaml --- kubernetes/manifest/base/webui-deployment.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kubernetes/manifest/base/webui-deployment.yaml b/kubernetes/manifest/base/webui-deployment.yaml index 38efd554..79a0a9a2 100644 --- a/kubernetes/manifest/base/webui-deployment.yaml +++ b/kubernetes/manifest/base/webui-deployment.yaml @@ -35,4 +35,4 @@ spec: volumes: - name: webui-volume persistentVolumeClaim: - claimName: ollama-webui-pvc \ No newline at end of file + claimName: open-webui-pvc \ No newline at end of file From 7e0ea8f77d58b1c059ce57d2c3f82d476cdb772a Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Sun, 24 Mar 2024 00:40:27 -0700 Subject: [PATCH 11/37] feat: RAG text ingestion(store) api --- backend/apps/rag/main.py | 110 ++++++++++++++++++++++++++------------ backend/apps/rag/utils.py | 2 + 2 files changed, 79 insertions(+), 33 deletions(-) diff --git a/backend/apps/rag/main.py b/backend/apps/rag/main.py index 5fc38b4a..0ffc32a8 100644 --- a/backend/apps/rag/main.py +++ b/backend/apps/rag/main.py @@ -111,39 +111,6 @@ class StoreWebForm(CollectionNameForm): url: str -def store_data_in_vector_db(data, collection_name, overwrite: bool = False) -> bool: - text_splitter = RecursiveCharacterTextSplitter( - chunk_size=app.state.CHUNK_SIZE, chunk_overlap=app.state.CHUNK_OVERLAP - ) - docs = text_splitter.split_documents(data) - - texts = [doc.page_content for doc in docs] - metadatas = [doc.metadata for doc in docs] - - try: - if overwrite: - for collection in CHROMA_CLIENT.list_collections(): - if collection_name == collection.name: - print(f"deleting existing collection {collection_name}") - CHROMA_CLIENT.delete_collection(name=collection_name) - - collection = CHROMA_CLIENT.create_collection( - name=collection_name, - embedding_function=app.state.sentence_transformer_ef, - ) - - collection.add( - documents=texts, metadatas=metadatas, ids=[str(uuid.uuid1()) for _ in texts] - ) - return True - except Exception as e: - print(e) - if e.__class__.__name__ == "UniqueConstraintError": - return True - - return False - - @app.get("/") async def get_status(): return { @@ -325,6 +292,56 @@ def store_web(form_data: StoreWebForm, user=Depends(get_current_user)): ) +def store_data_in_vector_db(data, collection_name, overwrite: bool = False) -> bool: + text_splitter = RecursiveCharacterTextSplitter( + chunk_size=app.state.CHUNK_SIZE, + chunk_overlap=app.state.CHUNK_OVERLAP, + add_start_index=True, + ) + docs = text_splitter.split_documents(data) + return store_docs_in_vector_db(docs, collection_name, overwrite) + + +def store_text_in_vector_db( + text, name, collection_name, overwrite: bool = False +) -> bool: + text_splitter = RecursiveCharacterTextSplitter( + chunk_size=app.state.CHUNK_SIZE, + chunk_overlap=app.state.CHUNK_OVERLAP, + add_start_index=True, + ) + docs = text_splitter.create_documents([text], metadatas=[{"name": name}]) + return store_docs_in_vector_db(docs, collection_name, overwrite) + + +def store_docs_in_vector_db(docs, collection_name, overwrite: bool = False) -> bool: + texts = [doc.page_content for doc in docs] + metadatas = [doc.metadata for doc in docs] + + try: + if overwrite: + for collection in CHROMA_CLIENT.list_collections(): + if collection_name == collection.name: + print(f"deleting existing collection {collection_name}") + CHROMA_CLIENT.delete_collection(name=collection_name) + + collection = CHROMA_CLIENT.create_collection( + name=collection_name, + embedding_function=app.state.sentence_transformer_ef, + ) + + collection.add( + documents=texts, metadatas=metadatas, ids=[str(uuid.uuid1()) for _ in texts] + ) + return True + except Exception as e: + print(e) + if e.__class__.__name__ == "UniqueConstraintError": + return True + + return False + + def get_loader(filename: str, file_content_type: str, file_path: str): file_ext = filename.split(".")[-1].lower() known_type = True @@ -460,6 +477,33 @@ def store_doc( ) +class TextRAGForm(BaseModel): + name: str + content: str + collection_name: Optional[str] = None + + +@app.post("/text") +def store_text( + form_data: TextRAGForm, + user=Depends(get_current_user), +): + + collection_name = form_data.collection_name + if collection_name == None: + collection_name = calculate_sha256_string(form_data.content) + + result = store_text_in_vector_db(form_data.content, form_data.name, collection_name) + + if result: + return {"status": True, "collection_name": collection_name} + else: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=ERROR_MESSAGES.DEFAULT(), + ) + + @app.get("/scan") def scan_docs_dir(user=Depends(get_admin_user)): for path in Path(DOCS_DIR).rglob("./**/*"): diff --git a/backend/apps/rag/utils.py b/backend/apps/rag/utils.py index a3537d4d..c5e0e8a8 100644 --- a/backend/apps/rag/utils.py +++ b/backend/apps/rag/utils.py @@ -137,6 +137,8 @@ def rag_messages(docs, messages, template, k, embedding_function): k=k, embedding_function=embedding_function, ) + elif doc["type"] == "text": + context = doc["content"] else: context = query_doc( collection_name=doc["collection_name"], From ff8a55a861836aa27f00a53638de4865e26cef52 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Sun, 24 Mar 2024 00:41:41 -0700 Subject: [PATCH 12/37] refac: rag api --- backend/apps/rag/main.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/backend/apps/rag/main.py b/backend/apps/rag/main.py index 0ffc32a8..a8606b39 100644 --- a/backend/apps/rag/main.py +++ b/backend/apps/rag/main.py @@ -303,14 +303,14 @@ def store_data_in_vector_db(data, collection_name, overwrite: bool = False) -> b def store_text_in_vector_db( - text, name, collection_name, overwrite: bool = False + text, metadata, collection_name, overwrite: bool = False ) -> bool: text_splitter = RecursiveCharacterTextSplitter( chunk_size=app.state.CHUNK_SIZE, chunk_overlap=app.state.CHUNK_OVERLAP, add_start_index=True, ) - docs = text_splitter.create_documents([text], metadatas=[{"name": name}]) + docs = text_splitter.create_documents([text], metadatas=[metadata]) return store_docs_in_vector_db(docs, collection_name, overwrite) @@ -493,7 +493,11 @@ def store_text( if collection_name == None: collection_name = calculate_sha256_string(form_data.content) - result = store_text_in_vector_db(form_data.content, form_data.name, collection_name) + result = store_text_in_vector_db( + form_data.content, + metadata={"name": form_data.name, "created_by": user.id}, + collection_name=collection_name, + ) if result: return {"status": True, "collection_name": collection_name} From b218b02d933663d1f3d771834b78eab726988182 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Sun, 24 Mar 2024 15:28:36 -0700 Subject: [PATCH 13/37] feat: custom model selector --- src/lib/components/chat/ModelSelector.svelte | 43 +++++----- src/lib/components/common/Select.svelte | 84 ++++++++++++++++++++ src/lib/components/icons/Check.svelte | 15 ++++ src/lib/components/icons/ChevronDown.svelte | 15 ++++ src/lib/components/icons/Search.svelte | 19 +++++ src/lib/utils/transitions/index.ts | 48 +++++++++++ 6 files changed, 200 insertions(+), 24 deletions(-) create mode 100644 src/lib/components/common/Select.svelte create mode 100644 src/lib/components/icons/Check.svelte create mode 100644 src/lib/components/icons/ChevronDown.svelte create mode 100644 src/lib/components/icons/Search.svelte create mode 100644 src/lib/utils/transitions/index.ts diff --git a/src/lib/components/chat/ModelSelector.svelte b/src/lib/components/chat/ModelSelector.svelte index df78a1fe..38ed51f7 100644 --- a/src/lib/components/chat/ModelSelector.svelte +++ b/src/lib/components/chat/ModelSelector.svelte @@ -3,6 +3,7 @@ import { models, showSettings, settings, user } from '$lib/stores'; import { onMount, tick, getContext } from 'svelte'; import { toast } from 'svelte-sonner'; + import Select from '../common/Select.svelte'; const i18n = getContext('i18n'); @@ -32,30 +33,24 @@ } -
+
{#each selectedModels as selectedModel, selectedModelIdx} -
- +
+
+
+ +
+ +
+ +
+ {#each filteredItems as item} + + {item.label} + + + + + {:else} + + No results found + + {/each} +
+ + + diff --git a/src/lib/components/icons/Check.svelte b/src/lib/components/icons/Check.svelte new file mode 100644 index 00000000..37eb9d71 --- /dev/null +++ b/src/lib/components/icons/Check.svelte @@ -0,0 +1,15 @@ + + + + + diff --git a/src/lib/components/icons/ChevronDown.svelte b/src/lib/components/icons/ChevronDown.svelte new file mode 100644 index 00000000..16686ea3 --- /dev/null +++ b/src/lib/components/icons/ChevronDown.svelte @@ -0,0 +1,15 @@ + + + + + diff --git a/src/lib/components/icons/Search.svelte b/src/lib/components/icons/Search.svelte new file mode 100644 index 00000000..c2dc5584 --- /dev/null +++ b/src/lib/components/icons/Search.svelte @@ -0,0 +1,19 @@ + + + + + diff --git a/src/lib/utils/transitions/index.ts b/src/lib/utils/transitions/index.ts new file mode 100644 index 00000000..9d919f56 --- /dev/null +++ b/src/lib/utils/transitions/index.ts @@ -0,0 +1,48 @@ +import { cubicOut } from 'svelte/easing'; +import type { TransitionConfig } from 'svelte/transition'; + +type FlyAndScaleParams = { + y?: number; + start?: number; + duration?: number; +}; + +const defaultFlyAndScaleParams = { y: -8, start: 0.95, duration: 200 }; + +export const flyAndScale = (node: Element, params?: FlyAndScaleParams): TransitionConfig => { + const style = getComputedStyle(node); + const transform = style.transform === 'none' ? '' : style.transform; + const withDefaults = { ...defaultFlyAndScaleParams, ...params }; + + const scaleConversion = (valueA: number, scaleA: [number, number], scaleB: [number, number]) => { + const [minA, maxA] = scaleA; + const [minB, maxB] = scaleB; + + const percentage = (valueA - minA) / (maxA - minA); + const valueB = percentage * (maxB - minB) + minB; + + return valueB; + }; + + const styleToString = (style: Record): string => { + return Object.keys(style).reduce((str, key) => { + if (style[key] === undefined) return str; + return str + `${key}:${style[key]};`; + }, ''); + }; + + return { + duration: withDefaults.duration ?? 200, + delay: 0, + css: (t) => { + const y = scaleConversion(t, [0, 1], [withDefaults.y, 0]); + const scale = scaleConversion(t, [0, 1], [withDefaults.start, 1]); + + return styleToString({ + transform: `${transform} translate3d(0, ${y}px, 0) scale(${scale})`, + opacity: t + }); + }, + easing: cubicOut + }; +}; From df21a9254264b11855d5b16196b21869841da9d9 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Sun, 24 Mar 2024 15:31:09 -0700 Subject: [PATCH 14/37] fix: select --- src/lib/components/common/Select.svelte | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/lib/components/common/Select.svelte b/src/lib/components/common/Select.svelte index a0aaf31f..a8563ced 100644 --- a/src/lib/components/common/Select.svelte +++ b/src/lib/components/common/Select.svelte @@ -69,9 +69,12 @@ label={item.label} > {item.label} - - - + + {#if value === item.value} +
+ +
+ {/if} {:else} From 3e0aa296832ff1e3b39347b7658972caf84aaa1e Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Sun, 24 Mar 2024 15:43:03 -0700 Subject: [PATCH 15/37] refac: transition --- src/lib/components/common/Dropdown.svelte | 3 +++ src/lib/components/common/Modal.svelte | 6 ++++-- src/lib/components/layout/Sidebar.svelte | 3 ++- src/lib/components/layout/Sidebar/ChatMenu.svelte | 2 ++ 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/lib/components/common/Dropdown.svelte b/src/lib/components/common/Dropdown.svelte index 89d6b694..e8284a50 100644 --- a/src/lib/components/common/Dropdown.svelte +++ b/src/lib/components/common/Dropdown.svelte @@ -2,6 +2,8 @@ import { DropdownMenu } from 'bits-ui'; import { createEventDispatcher } from 'svelte'; + import { flyAndScale } from '$lib/utils/transitions'; + const dispatch = createEventDispatcher(); @@ -20,6 +22,7 @@ sideOffset={8} side="bottom" align="start" + transition={flyAndScale} >
Profile
diff --git a/src/lib/components/common/Modal.svelte b/src/lib/components/common/Modal.svelte index f223b823..038fa48d 100644 --- a/src/lib/components/common/Modal.svelte +++ b/src/lib/components/common/Modal.svelte @@ -2,6 +2,8 @@ import { onMount } from 'svelte'; import { fade } from 'svelte/transition'; + import { flyAndScale } from '$lib/utils/transitions'; + export let show = true; export let size = 'md'; @@ -41,10 +43,10 @@ }} >