diff --git a/.github/workflows/format-backend.yaml b/.github/workflows/format-backend.yaml index 9d767fa5b..e9fd0c6ba 100644 --- a/.github/workflows/format-backend.yaml +++ b/.github/workflows/format-backend.yaml @@ -1,27 +1,39 @@ name: Python CI + on: push: - branches: ['main'] + branches: + - main + - dev pull_request: + branches: + - main + - dev + jobs: build: name: 'Format Backend' - env: - PUBLIC_API_BASE_URL: '' runs-on: ubuntu-latest + strategy: matrix: - node-version: - - latest + python-version: [3.11] + steps: - uses: actions/checkout@v4 - - name: Use Python - uses: actions/setup-python@v4 - - name: Use Bun - uses: oven-sh/setup-bun@v1 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies run: | python -m pip install --upgrade pip - pip install yapf + pip install black + - name: Format backend - run: bun run format:backend + run: black . --exclude "/venv/" + + - name: Check for changes after format + run: git diff --exit-code diff --git a/.github/workflows/format-build-frontend.yaml b/.github/workflows/format-build-frontend.yaml index 65916cf44..121266bf6 100644 --- a/.github/workflows/format-build-frontend.yaml +++ b/.github/workflows/format-build-frontend.yaml @@ -1,22 +1,36 @@ -name: Bun CI +name: Frontend Build + on: push: - branches: ['main'] + branches: + - main + - dev pull_request: + branches: + - main + - dev + jobs: build: name: 'Format & Build Frontend' - env: - PUBLIC_API_BASE_URL: '' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - name: Use Bun - uses: oven-sh/setup-bun@v1 - - run: bun --version - - name: Install frontend dependencies - run: bun install - - name: Format frontend - run: bun run format - - name: Build frontend - run: bun run build + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '20' # Or specify any other version you want to use + + - name: Install Dependencies + run: npm install + + - name: Format Frontend + run: npm run format + + - name: Check for Changes After Format + run: git diff --exit-code + + - name: Build Frontend + run: npm run build diff --git a/CHANGELOG.md b/CHANGELOG.md index da3e289dc..e48f8dc7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.1.116] - 2024-03-31 + +### Added + +- **🔄 Enhanced UI**: Model selector now conveniently located in the navbar, enabling seamless switching between multiple models during conversations. +- **🔍 Improved Model Selector**: Directly pull a model from the selector/Models now display detailed information for better understanding. +- **💬 Webhook Support**: Now compatible with Google Chat and Microsoft Teams. +- **🌐 Localization**: Korean translation (I18n) now available. +- **🌑 Dark Theme**: OLED dark theme introduced for reduced strain during prolonged usage. +- **🏷️ Tag Autocomplete**: Dropdown feature added for effortless chat tagging. + +### Fixed + +- **🔽 Auto-Scrolling**: Addressed OpenAI auto-scrolling issue. +- **🏷️ Tag Validation**: Implemented tag validation to prevent empty string tags. +- **🚫 Model Whitelisting**: Resolved LiteLLM model whitelisting issue. +- **✅ Spelling**: Corrected various spelling issues for improved readability. + ## [0.1.115] - 2024-03-24 ### Added diff --git a/backend/apps/audio/main.py b/backend/apps/audio/main.py index 10cc56716..bb3cd0536 100644 --- a/backend/apps/audio/main.py +++ b/backend/apps/audio/main.py @@ -22,7 +22,13 @@ from utils.utils import ( ) from utils.misc import calculate_sha256 -from config import SRC_LOG_LEVELS, 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"]) diff --git a/backend/apps/images/main.py b/backend/apps/images/main.py index d331fa384..af8cbf7c5 100644 --- a/backend/apps/images/main.py +++ b/backend/apps/images/main.py @@ -325,7 +325,7 @@ def save_url_image(url): return image_id except Exception as e: - print(f"Error saving image: {e}") + log.exception(f"Error saving image: {e}") return None @@ -397,7 +397,7 @@ def generate_image( user.id, app.state.COMFYUI_BASE_URL, ) - print(res) + log.debug(f"res: {res}") images = [] @@ -409,7 +409,7 @@ def generate_image( with open(file_body_path, "w") as f: json.dump(data.model_dump(exclude_none=True), f) - print(images) + log.debug(f"images: {images}") return images else: if form_data.model: diff --git a/backend/apps/images/utils/comfyui.py b/backend/apps/images/utils/comfyui.py index 6a9fef353..393333255 100644 --- a/backend/apps/images/utils/comfyui.py +++ b/backend/apps/images/utils/comfyui.py @@ -4,6 +4,12 @@ import json import urllib.request import urllib.parse import random +import logging + +from config import SRC_LOG_LEVELS + +log = logging.getLogger(__name__) +log.setLevel(SRC_LOG_LEVELS["COMFYUI"]) from pydantic import BaseModel @@ -121,7 +127,7 @@ COMFYUI_DEFAULT_PROMPT = """ def queue_prompt(prompt, client_id, base_url): - print("queue_prompt") + log.info("queue_prompt") p = {"prompt": prompt, "client_id": client_id} data = json.dumps(p).encode("utf-8") req = urllib.request.Request(f"{base_url}/prompt", data=data) @@ -129,7 +135,7 @@ def queue_prompt(prompt, client_id, base_url): def get_image(filename, subfolder, folder_type, base_url): - print("get_image") + log.info("get_image") data = {"filename": filename, "subfolder": subfolder, "type": folder_type} url_values = urllib.parse.urlencode(data) with urllib.request.urlopen(f"{base_url}/view?{url_values}") as response: @@ -137,14 +143,14 @@ def get_image(filename, subfolder, folder_type, base_url): def get_image_url(filename, subfolder, folder_type, base_url): - print("get_image") + log.info("get_image") data = {"filename": filename, "subfolder": subfolder, "type": folder_type} url_values = urllib.parse.urlencode(data) return f"{base_url}/view?{url_values}" def get_history(prompt_id, base_url): - print("get_history") + log.info("get_history") with urllib.request.urlopen(f"{base_url}/history/{prompt_id}") as response: return json.loads(response.read()) @@ -212,15 +218,15 @@ def comfyui_generate_image( try: ws = websocket.WebSocket() ws.connect(f"ws://{host}/ws?clientId={client_id}") - print("WebSocket connection established.") + log.info("WebSocket connection established.") except Exception as e: - print(f"Failed to connect to WebSocket server: {e}") + log.exception(f"Failed to connect to WebSocket server: {e}") return None try: images = get_images(ws, comfyui_prompt, client_id, base_url) except Exception as e: - print(f"Error while receiving images: {e}") + log.exception(f"Error while receiving images: {e}") images = None ws.close() diff --git a/backend/apps/ollama/main.py b/backend/apps/ollama/main.py index f8701c43e..f9ebdb98f 100644 --- a/backend/apps/ollama/main.py +++ b/backend/apps/ollama/main.py @@ -33,7 +33,13 @@ from constants import ERROR_MESSAGES from utils.utils import decode_token, get_current_user, get_admin_user -from config import SRC_LOG_LEVELS, OLLAMA_BASE_URLS, MODEL_FILTER_ENABLED, MODEL_FILTER_LIST, UPLOAD_DIR +from config import ( + SRC_LOG_LEVELS, + OLLAMA_BASE_URLS, + MODEL_FILTER_ENABLED, + MODEL_FILTER_LIST, + UPLOAD_DIR, +) from utils.misc import calculate_sha256 log = logging.getLogger(__name__) @@ -266,7 +272,7 @@ async def pull_model( if request_id in REQUEST_POOL: yield chunk else: - print("User: canceled request") + log.warning("User: canceled request") break finally: if hasattr(r, "close"): @@ -664,7 +670,7 @@ async def generate_completion( else: raise HTTPException( status_code=400, - detail="error_detail", + detail=ERROR_MESSAGES.MODEL_NOT_FOUND(form_data.model), ) url = app.state.OLLAMA_BASE_URLS[url_idx] @@ -770,7 +776,11 @@ async def generate_chat_completion( r = None - log.debug("form_data.model_dump_json(exclude_none=True).encode(): {0} ".format(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 diff --git a/backend/apps/rag/main.py b/backend/apps/rag/main.py index da7bb307d..cb8cbb6c3 100644 --- a/backend/apps/rag/main.py +++ b/backend/apps/rag/main.py @@ -333,7 +333,7 @@ def store_docs_in_vector_db(docs, 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( @@ -346,7 +346,7 @@ def store_docs_in_vector_db(docs, collection_name, overwrite: bool = False) -> b ) return True except Exception as e: - print(e) + log.exception(e) if e.__class__.__name__ == "UniqueConstraintError": return True @@ -575,7 +575,7 @@ def scan_docs_dir(user=Depends(get_admin_user)): ), ) except Exception as e: - print(e) + log.exception(e) pass except Exception as e: diff --git a/backend/apps/web/models/auths.py b/backend/apps/web/models/auths.py index 40efbcf2f..365958555 100644 --- a/backend/apps/web/models/auths.py +++ b/backend/apps/web/models/auths.py @@ -11,6 +11,7 @@ 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"]) diff --git a/backend/apps/web/models/documents.py b/backend/apps/web/models/documents.py index f399e7ae4..91e721a48 100644 --- a/backend/apps/web/models/documents.py +++ b/backend/apps/web/models/documents.py @@ -13,6 +13,7 @@ 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"]) diff --git a/backend/apps/web/models/modelfiles.py b/backend/apps/web/models/modelfiles.py index 51eccfb5e..50439a808 100644 --- a/backend/apps/web/models/modelfiles.py +++ b/backend/apps/web/models/modelfiles.py @@ -64,8 +64,8 @@ class ModelfilesTable: self.db.create_tables([Modelfile]) def insert_new_modelfile( - self, user_id: str, - form_data: ModelfileForm) -> Optional[ModelfileModel]: + self, user_id: str, form_data: ModelfileForm + ) -> Optional[ModelfileModel]: if "tagName" in form_data.modelfile: modelfile = ModelfileModel( **{ @@ -73,7 +73,8 @@ class ModelfilesTable: "tag_name": form_data.modelfile["tagName"], "modelfile": json.dumps(form_data.modelfile), "timestamp": int(time.time()), - }) + } + ) try: result = Modelfile.create(**modelfile.model_dump()) @@ -87,29 +88,28 @@ class ModelfilesTable: else: return None - def get_modelfile_by_tag_name(self, - tag_name: str) -> Optional[ModelfileModel]: + def get_modelfile_by_tag_name(self, tag_name: str) -> Optional[ModelfileModel]: try: modelfile = Modelfile.get(Modelfile.tag_name == tag_name) return ModelfileModel(**model_to_dict(modelfile)) except: return None - def get_modelfiles(self, - skip: int = 0, - limit: int = 50) -> List[ModelfileResponse]: + def get_modelfiles(self, skip: int = 0, limit: int = 50) -> List[ModelfileResponse]: return [ ModelfileResponse( **{ **model_to_dict(modelfile), - "modelfile": - json.loads(modelfile.modelfile), - }) for modelfile in Modelfile.select() + "modelfile": json.loads(modelfile.modelfile), + } + ) + for modelfile in Modelfile.select() # .limit(limit).offset(skip) ] def update_modelfile_by_tag_name( - self, tag_name: str, modelfile: dict) -> Optional[ModelfileModel]: + self, tag_name: str, modelfile: dict + ) -> Optional[ModelfileModel]: try: query = Modelfile.update( modelfile=json.dumps(modelfile), diff --git a/backend/apps/web/models/prompts.py b/backend/apps/web/models/prompts.py index 044a3697c..e6b663c04 100644 --- a/backend/apps/web/models/prompts.py +++ b/backend/apps/web/models/prompts.py @@ -52,8 +52,9 @@ class PromptsTable: self.db = db self.db.create_tables([Prompt]) - def insert_new_prompt(self, user_id: str, - form_data: PromptForm) -> Optional[PromptModel]: + def insert_new_prompt( + self, user_id: str, form_data: PromptForm + ) -> Optional[PromptModel]: prompt = PromptModel( **{ "user_id": user_id, @@ -61,7 +62,8 @@ class PromptsTable: "title": form_data.title, "content": form_data.content, "timestamp": int(time.time()), - }) + } + ) try: result = Prompt.create(**prompt.model_dump()) @@ -81,13 +83,14 @@ class PromptsTable: def get_prompts(self) -> List[PromptModel]: return [ - PromptModel(**model_to_dict(prompt)) for prompt in Prompt.select() + PromptModel(**model_to_dict(prompt)) + for prompt in Prompt.select() # .limit(limit).offset(skip) ] def update_prompt_by_command( - self, command: str, - form_data: PromptForm) -> Optional[PromptModel]: + self, command: str, form_data: PromptForm + ) -> Optional[PromptModel]: try: query = Prompt.update( title=form_data.title, diff --git a/backend/apps/web/models/tags.py b/backend/apps/web/models/tags.py index 476e6693d..196551b7b 100644 --- a/backend/apps/web/models/tags.py +++ b/backend/apps/web/models/tags.py @@ -11,6 +11,7 @@ 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"]) diff --git a/backend/apps/web/routers/chats.py b/backend/apps/web/routers/chats.py index d018b31be..5f8c61b70 100644 --- a/backend/apps/web/routers/chats.py +++ b/backend/apps/web/routers/chats.py @@ -29,6 +29,7 @@ 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"]) diff --git a/backend/apps/web/routers/configs.py b/backend/apps/web/routers/configs.py index b293a398e..0bad55a6a 100644 --- a/backend/apps/web/routers/configs.py +++ b/backend/apps/web/routers/configs.py @@ -10,7 +10,12 @@ import uuid from apps.web.models.users import Users -from utils.utils import get_password_hash, get_current_user, get_admin_user, create_token +from utils.utils import ( + get_password_hash, + get_current_user, + get_admin_user, + create_token, +) from utils.misc import get_gravatar_url, validate_email_format from constants import ERROR_MESSAGES @@ -43,7 +48,6 @@ async def set_global_default_models( return request.app.state.DEFAULT_MODELS - @router.post("/default/suggestions", response_model=List[PromptSuggestion]) async def set_global_default_suggestions( request: Request, diff --git a/backend/apps/web/routers/modelfiles.py b/backend/apps/web/routers/modelfiles.py index 0c5c12166..3cdbf8a74 100644 --- a/backend/apps/web/routers/modelfiles.py +++ b/backend/apps/web/routers/modelfiles.py @@ -24,9 +24,9 @@ router = APIRouter() @router.get("/", response_model=List[ModelfileResponse]) -async def get_modelfiles(skip: int = 0, - limit: int = 50, - user=Depends(get_current_user)): +async def get_modelfiles( + skip: int = 0, limit: int = 50, user=Depends(get_current_user) +): return Modelfiles.get_modelfiles(skip, limit) @@ -36,17 +36,16 @@ async def get_modelfiles(skip: int = 0, @router.post("/create", response_model=Optional[ModelfileResponse]) -async def create_new_modelfile(form_data: ModelfileForm, - user=Depends(get_admin_user)): +async def create_new_modelfile(form_data: ModelfileForm, user=Depends(get_admin_user)): modelfile = Modelfiles.insert_new_modelfile(user.id, form_data) if modelfile: return ModelfileResponse( **{ **modelfile.model_dump(), - "modelfile": - json.loads(modelfile.modelfile), - }) + "modelfile": json.loads(modelfile.modelfile), + } + ) else: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, @@ -60,17 +59,18 @@ async def create_new_modelfile(form_data: ModelfileForm, @router.post("/", response_model=Optional[ModelfileResponse]) -async def get_modelfile_by_tag_name(form_data: ModelfileTagNameForm, - user=Depends(get_current_user)): +async def get_modelfile_by_tag_name( + form_data: ModelfileTagNameForm, user=Depends(get_current_user) +): modelfile = Modelfiles.get_modelfile_by_tag_name(form_data.tag_name) if modelfile: return ModelfileResponse( **{ **modelfile.model_dump(), - "modelfile": - json.loads(modelfile.modelfile), - }) + "modelfile": json.loads(modelfile.modelfile), + } + ) else: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, @@ -84,8 +84,9 @@ async def get_modelfile_by_tag_name(form_data: ModelfileTagNameForm, @router.post("/update", response_model=Optional[ModelfileResponse]) -async def update_modelfile_by_tag_name(form_data: ModelfileUpdateForm, - user=Depends(get_admin_user)): +async def update_modelfile_by_tag_name( + form_data: ModelfileUpdateForm, user=Depends(get_admin_user) +): modelfile = Modelfiles.get_modelfile_by_tag_name(form_data.tag_name) if modelfile: updated_modelfile = { @@ -94,14 +95,15 @@ async def update_modelfile_by_tag_name(form_data: ModelfileUpdateForm, } modelfile = Modelfiles.update_modelfile_by_tag_name( - form_data.tag_name, updated_modelfile) + form_data.tag_name, updated_modelfile + ) return ModelfileResponse( **{ **modelfile.model_dump(), - "modelfile": - json.loads(modelfile.modelfile), - }) + "modelfile": json.loads(modelfile.modelfile), + } + ) else: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, @@ -115,7 +117,8 @@ async def update_modelfile_by_tag_name(form_data: ModelfileUpdateForm, @router.delete("/delete", response_model=bool) -async def delete_modelfile_by_tag_name(form_data: ModelfileTagNameForm, - user=Depends(get_admin_user)): +async def delete_modelfile_by_tag_name( + form_data: ModelfileTagNameForm, user=Depends(get_admin_user) +): result = Modelfiles.delete_modelfile_by_tag_name(form_data.tag_name) return result diff --git a/backend/apps/web/routers/users.py b/backend/apps/web/routers/users.py index 67c136daa..6f1020ec7 100644 --- a/backend/apps/web/routers/users.py +++ b/backend/apps/web/routers/users.py @@ -16,6 +16,7 @@ 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"]) diff --git a/backend/config.py b/backend/config.py index a1f565b23..7dd115a69 100644 --- a/backend/config.py +++ b/backend/config.py @@ -117,7 +117,20 @@ else: 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"] +log_sources = [ + "AUDIO", + "COMFYUI", + "CONFIG", + "DB", + "IMAGES", + "LITELLM", + "MAIN", + "MODELS", + "OLLAMA", + "OPENAI", + "RAG", + "WEBHOOK", +] SRC_LOG_LEVELS = {} @@ -239,7 +252,7 @@ OLLAMA_API_BASE_URL = os.environ.get( ) OLLAMA_BASE_URL = os.environ.get("OLLAMA_BASE_URL", "") -KUBERNETES_SERVICE_HOST = os.environ.get("KUBERNETES_SERVICE_HOST", "") +K8S_FLAG = os.environ.get("K8S_FLAG", "") if OLLAMA_BASE_URL == "" and OLLAMA_API_BASE_URL != "": OLLAMA_BASE_URL = ( @@ -249,9 +262,10 @@ if OLLAMA_BASE_URL == "" and OLLAMA_API_BASE_URL != "": ) if ENV == "prod": - if OLLAMA_BASE_URL == "/ollama" and KUBERNETES_SERVICE_HOST == "": + if OLLAMA_BASE_URL == "/ollama": OLLAMA_BASE_URL = "http://host.docker.internal:11434" - else: + + elif K8S_FLAG: OLLAMA_BASE_URL = "http://ollama-service.open-webui.svc.cluster.local:11434" diff --git a/backend/data/config.json b/backend/data/config.json index cd6687d79..604ffb036 100644 --- a/backend/data/config.json +++ b/backend/data/config.json @@ -1,35 +1,24 @@ { - "version": 0, - "ui": { - "prompt_suggestions": [ - { - "title": [ - "Help me study", - "vocabulary for a college entrance exam" - ], - "content": "Help me study vocabulary: write a sentence for me to fill in the blank, and I'll try to pick the correct option." - }, - { - "title": [ - "Give me ideas", - "for what to do with my kids' art" - ], - "content": "What are 5 creative things I could do with my kids' art? I don't want to throw them away, but it's also so much clutter." - }, - { - "title": [ - "Tell me a fun fact", - "about the Roman Empire" - ], - "content": "Tell me a random fun fact about the Roman Empire" - }, - { - "title": [ - "Show me a code snippet", - "of a website's sticky header" - ], - "content": "Show me a code snippet of a website's sticky header in CSS and JavaScript." - } - ] - } -} \ No newline at end of file + "version": 0, + "ui": { + "default_locale": "en-US", + "prompt_suggestions": [ + { + "title": ["Help me study", "vocabulary for a college entrance exam"], + "content": "Help me study vocabulary: write a sentence for me to fill in the blank, and I'll try to pick the correct option." + }, + { + "title": ["Give me ideas", "for what to do with my kids' art"], + "content": "What are 5 creative things I could do with my kids' art? I don't want to throw them away, but it's also so much clutter." + }, + { + "title": ["Tell me a fun fact", "about the Roman Empire"], + "content": "Tell me a random fun fact about the Roman Empire" + }, + { + "title": ["Show me a code snippet", "of a website's sticky header"], + "content": "Show me a code snippet of a website's sticky header in CSS and JavaScript." + } + ] + } +} diff --git a/backend/main.py b/backend/main.py index 85514e431..892bfb12a 100644 --- a/backend/main.py +++ b/backend/main.py @@ -32,6 +32,7 @@ from utils.utils import get_admin_user from apps.rag.utils import rag_messages from config import ( + CONFIG_DATA, WEBUI_NAME, ENV, VERSION, @@ -49,6 +50,7 @@ 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): try: @@ -88,7 +90,6 @@ class RAGMiddleware(BaseHTTPMiddleware): # Example: Add a new key-value pair or modify existing ones # data["modified"] = True # Example modification if "docs" in data: - data = {**data} data["messages"] = rag_messages( data["docs"], @@ -163,11 +164,15 @@ app.mount("/rag/api/v1", rag_app) @app.get("/api/config") async def get_app_config(): - return { "status": True, "name": WEBUI_NAME, "version": VERSION, + "default_locale": ( + CONFIG_DATA["ui"]["default_locale"] + if "ui" in CONFIG_DATA and "default_locale" in CONFIG_DATA["ui"] + else "en-US" + ), "images": images_app.state.ENABLED, "default_models": webui_app.state.DEFAULT_MODELS, "default_prompt_suggestions": webui_app.state.DEFAULT_PROMPT_SUGGESTIONS, @@ -192,7 +197,6 @@ class ModelFilterConfigForm(BaseModel): async def update_model_filter_config( form_data: ModelFilterConfigForm, user=Depends(get_admin_user) ): - app.state.MODEL_FILTER_ENABLED = form_data.enabled app.state.MODEL_FILTER_LIST = form_data.models @@ -202,6 +206,9 @@ async def update_model_filter_config( openai_app.state.MODEL_FILTER_ENABLED = app.state.MODEL_FILTER_ENABLED openai_app.state.MODEL_FILTER_LIST = app.state.MODEL_FILTER_LIST + litellm_app.state.MODEL_FILTER_ENABLED = app.state.MODEL_FILTER_ENABLED + litellm_app.state.MODEL_FILTER_LIST = app.state.MODEL_FILTER_LIST + return { "enabled": app.state.MODEL_FILTER_ENABLED, "models": app.state.MODEL_FILTER_LIST, @@ -232,7 +239,6 @@ async def update_webhook_url(form_data: UrlForm, user=Depends(get_admin_user)): @app.get("/api/version") async def get_app_config(): - return { "version": VERSION, } @@ -240,7 +246,7 @@ async def get_app_config(): @app.get("/api/changelog") async def get_app_changelog(): - return CHANGELOG + return {key: CHANGELOG[key] for idx, key in enumerate(CHANGELOG) if idx < 5} @app.get("/api/version/updates") diff --git a/backend/utils/webhook.py b/backend/utils/webhook.py index ed626d03e..b6692e53a 100644 --- a/backend/utils/webhook.py +++ b/backend/utils/webhook.py @@ -1,6 +1,12 @@ import json import requests -from config import VERSION, WEBUI_FAVICON_URL, WEBUI_NAME +import logging + +from config import SRC_LOG_LEVELS, VERSION, WEBUI_FAVICON_URL, WEBUI_NAME + +log = logging.getLogger(__name__) +log.setLevel(SRC_LOG_LEVELS["WEBHOOK"]) + def post_webhook(url: str, message: str, event_data: dict) -> bool: try: @@ -38,9 +44,11 @@ def post_webhook(url: str, message: str, event_data: dict) -> bool: else: payload = {**event_data} + log.debug(f"payload: {payload}") r = requests.post(url, json=payload) r.raise_for_status() + log.debug(f"r.text: {r.text}") return True except Exception as e: - print(e) - return False \ No newline at end of file + log.exception(e) + return False diff --git a/kubernetes/helm/templates/ollama-statefulset.yaml b/kubernetes/helm/templates/ollama-statefulset.yaml index a87aeab09..c348b04c0 100644 --- a/kubernetes/helm/templates/ollama-statefulset.yaml +++ b/kubernetes/helm/templates/ollama-statefulset.yaml @@ -88,7 +88,7 @@ spec: resources: requests: storage: {{ .Values.ollama.persistence.size | quote }} - storageClass: {{ .Values.ollama.persistence.storageClass }} + storageClassName: {{ .Values.ollama.persistence.storageClass }} {{- with .Values.ollama.persistence.selector }} selector: {{- toYaml . | nindent 8 }} diff --git a/kubernetes/helm/templates/webui-pvc.yaml b/kubernetes/helm/templates/webui-pvc.yaml index 06b2cc4a4..f06454e7a 100644 --- a/kubernetes/helm/templates/webui-pvc.yaml +++ b/kubernetes/helm/templates/webui-pvc.yaml @@ -17,7 +17,7 @@ spec: resources: requests: storage: {{ .Values.webui.persistence.size }} - storageClass: {{ .Values.webui.persistence.storageClass }} + storageClassName: {{ .Values.webui.persistence.storageClass }} {{- with .Values.webui.persistence.selector }} selector: {{- toYaml . | nindent 4 }} diff --git a/package-lock.json b/package-lock.json index 358313097..bb07683be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "open-webui", - "version": "0.1.114", + "version": "0.1.116", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "open-webui", - "version": "0.1.114", + "version": "0.1.116", "dependencies": { "@sveltejs/adapter-node": "^1.3.1", "async": "^3.2.5", diff --git a/package.json b/package.json index 1ad61223b..ecf140c5b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "open-webui", - "version": "0.1.115", + "version": "0.1.116", "private": true, "scripts": { "dev": "vite dev --host", @@ -13,7 +13,6 @@ "lint:types": "npm run check", "lint:backend": "pylint backend/", "format": "prettier --plugin-search-dir --write '**/*.{js,ts,svelte,css,md,html,json}'", - "format:backend": "yapf --recursive backend -p -i", "i18n:parse": "i18next --config i18next-parser.config.ts && prettier --write 'src/lib/i18n/**/*.{js,json}'" }, "devDependencies": { @@ -59,4 +58,4 @@ "tippy.js": "^6.3.7", "uuid": "^9.0.1" } -} \ No newline at end of file +} diff --git a/src/app.html b/src/app.html index c52cff98c..f731761cf 100644 --- a/src/app.html +++ b/src/app.html @@ -9,7 +9,11 @@ -