From df09d0830a1680a1028a5df0864d916d6b66d302 Mon Sep 17 00:00:00 2001 From: Jonathan Rohde Date: Tue, 18 Jun 2024 15:03:31 +0200 Subject: [PATCH 001/181] feat(sqlalchemy): Replace peewee with sqlalchemy --- .github/workflows/integration-test.yml | 4 +- backend/alembic.ini | 114 +++++++ backend/apps/ollama/main.py | 7 +- backend/apps/openai/main.py | 4 +- backend/apps/socket/main.py | 4 +- backend/apps/webui/internal/db.py | 64 ++-- backend/apps/webui/internal/wrappers.py | 72 ----- backend/apps/webui/main.py | 6 +- backend/apps/webui/models/auths.py | 77 ++--- backend/apps/webui/models/chats.py | 303 ++++++++---------- backend/apps/webui/models/documents.py | 104 +++--- backend/apps/webui/models/files.py | 62 ++-- backend/apps/webui/models/functions.py | 74 +++-- backend/apps/webui/models/memories.py | 87 +++-- backend/apps/webui/models/models.py | 79 +++-- backend/apps/webui/models/prompts.py | 86 +++-- backend/apps/webui/models/tags.py | 198 +++++++----- backend/apps/webui/models/tools.py | 78 +++-- backend/apps/webui/models/users.py | 181 ++++++----- backend/apps/webui/routers/auths.py | 66 ++-- backend/apps/webui/routers/chats.py | 146 +++++---- backend/apps/webui/routers/documents.py | 40 ++- backend/apps/webui/routers/files.py | 25 +- backend/apps/webui/routers/functions.py | 27 +- backend/apps/webui/routers/memories.py | 29 +- backend/apps/webui/routers/models.py | 35 +- backend/apps/webui/routers/prompts.py | 32 +- backend/apps/webui/routers/tools.py | 35 +- backend/apps/webui/routers/users.py | 69 ++-- backend/apps/webui/routers/utils.py | 8 +- backend/main.py | 56 +++- backend/migrations/README | 4 + backend/migrations/env.py | 93 ++++++ backend/migrations/script.py.mako | 27 ++ .../migrations/versions/22b5ab2667b8_init.py | 188 +++++++++++ backend/requirements.txt | 13 +- backend/test/__init__.py | 0 backend/test/apps/webui/routers/test_auths.py | 209 ++++++++++++ backend/test/apps/webui/routers/test_chats.py | 239 ++++++++++++++ .../test/apps/webui/routers/test_documents.py | 106 ++++++ .../test/apps/webui/routers/test_models.py | 60 ++++ .../test/apps/webui/routers/test_prompts.py | 82 +++++ backend/test/apps/webui/routers/test_users.py | 170 ++++++++++ .../test/util/abstract_integration_test.py | 155 +++++++++ backend/test/util/mock_user.py | 45 +++ backend/utils/utils.py | 15 +- src/lib/apis/models/index.ts | 5 +- 47 files changed, 2580 insertions(+), 1003 deletions(-) create mode 100644 backend/alembic.ini delete mode 100644 backend/apps/webui/internal/wrappers.py create mode 100644 backend/migrations/README create mode 100644 backend/migrations/env.py create mode 100644 backend/migrations/script.py.mako create mode 100644 backend/migrations/versions/22b5ab2667b8_init.py create mode 100644 backend/test/__init__.py create mode 100644 backend/test/apps/webui/routers/test_auths.py create mode 100644 backend/test/apps/webui/routers/test_chats.py create mode 100644 backend/test/apps/webui/routers/test_documents.py create mode 100644 backend/test/apps/webui/routers/test_models.py create mode 100644 backend/test/apps/webui/routers/test_prompts.py create mode 100644 backend/test/apps/webui/routers/test_users.py create mode 100644 backend/test/util/abstract_integration_test.py create mode 100644 backend/test/util/mock_user.py diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index 85810c2ed..c8e7c1672 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -171,7 +171,7 @@ jobs: fi # Check that service will reconnect to postgres when connection will be closed - status_code=$(curl --write-out %{http_code} -s --output /dev/null http://localhost:8081/health) + status_code=$(curl --write-out %{http_code} -s --output /dev/null http://localhost:8081/health/db) if [[ "$status_code" -ne 200 ]] ; then echo "Server has failed before postgres reconnect check" exit 1 @@ -183,7 +183,7 @@ jobs: cur = conn.cursor(); \ cur.execute('SELECT pg_terminate_backend(psa.pid) FROM pg_stat_activity psa WHERE datname = current_database() AND pid <> pg_backend_pid();')" - status_code=$(curl --write-out %{http_code} -s --output /dev/null http://localhost:8081/health) + status_code=$(curl --write-out %{http_code} -s --output /dev/null http://localhost:8081/health/db) if [[ "$status_code" -ne 200 ]] ; then echo "Server has not reconnected to postgres after connection was closed: returned status $status_code" exit 1 diff --git a/backend/alembic.ini b/backend/alembic.ini new file mode 100644 index 000000000..72f2b762b --- /dev/null +++ b/backend/alembic.ini @@ -0,0 +1,114 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts +script_location = migrations + +# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s +# Uncomment the line below if you want the files to be prepended with date and time +# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s + +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. +prepend_sys_path = . + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the python>=3.9 or backports.zoneinfo library. +# Any required deps can installed by adding `alembic[tz]` to the pip requirements +# string value is passed to ZoneInfo() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the +# "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; This defaults +# to migrations/versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "version_path_separator" below. +# version_locations = %(here)s/bar:%(here)s/bat:migrations/versions + +# version path separator; As mentioned above, this is the character used to split +# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. +# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. +# Valid values for version_path_separator are: +# +# version_path_separator = : +# version_path_separator = ; +# version_path_separator = space +version_path_separator = os # Use os.pathsep. Default configuration used for new projects. + +# set to 'true' to search source files recursively +# in each "version_locations" directory +# new in Alembic version 1.10 +# recursive_version_locations = false + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +sqlalchemy.url = REPLACE_WITH_DATABASE_URL + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# lint with attempts to fix using "ruff" - use the exec runner, execute a binary +# hooks = ruff +# ruff.type = exec +# ruff.executable = %(here)s/.venv/bin/ruff +# ruff.options = --fix REVISION_SCRIPT_FILENAME + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/backend/apps/ollama/main.py b/backend/apps/ollama/main.py index 455dc89a5..85bb4c0df 100644 --- a/backend/apps/ollama/main.py +++ b/backend/apps/ollama/main.py @@ -31,6 +31,7 @@ from typing import Optional, List, Union from starlette.background import BackgroundTask +from apps.webui.internal.db import get_db from apps.webui.models.models import Models from apps.webui.models.users import Users from constants import ERROR_MESSAGES @@ -711,6 +712,7 @@ async def generate_chat_completion( form_data: GenerateChatCompletionForm, url_idx: Optional[int] = None, user=Depends(get_verified_user), + db=Depends(get_db), ): log.debug( @@ -724,7 +726,7 @@ async def generate_chat_completion( } model_id = form_data.model - model_info = Models.get_model_by_id(model_id) + model_info = Models.get_model_by_id(db, model_id) if model_info: if model_info.base_model_id: @@ -883,6 +885,7 @@ async def generate_openai_chat_completion( form_data: dict, url_idx: Optional[int] = None, user=Depends(get_verified_user), + db=Depends(get_db), ): form_data = OpenAIChatCompletionForm(**form_data) @@ -891,7 +894,7 @@ async def generate_openai_chat_completion( } model_id = form_data.model - model_info = Models.get_model_by_id(model_id) + model_info = Models.get_model_by_id(db, model_id) if model_info: if model_info.base_model_id: diff --git a/backend/apps/openai/main.py b/backend/apps/openai/main.py index 302dd8d98..bc40bc661 100644 --- a/backend/apps/openai/main.py +++ b/backend/apps/openai/main.py @@ -11,6 +11,7 @@ import logging from pydantic import BaseModel from starlette.background import BackgroundTask +from apps.webui.internal.db import get_db from apps.webui.models.models import Models from apps.webui.models.users import Users from constants import ERROR_MESSAGES @@ -353,12 +354,13 @@ async def generate_chat_completion( form_data: dict, url_idx: Optional[int] = None, user=Depends(get_verified_user), + db=Depends(get_db), ): idx = 0 payload = {**form_data} model_id = form_data.get("model") - model_info = Models.get_model_by_id(model_id) + model_info = Models.get_model_by_id(db, model_id) if model_info: if model_info.base_model_id: diff --git a/backend/apps/socket/main.py b/backend/apps/socket/main.py index 123ff31cd..bbbbccd79 100644 --- a/backend/apps/socket/main.py +++ b/backend/apps/socket/main.py @@ -24,7 +24,9 @@ async def connect(sid, environ, auth): data = decode_token(auth["token"]) if data is not None and "id" in data: - user = Users.get_user_by_id(data["id"]) + from apps.webui.internal.db import SessionLocal + + user = Users.get_user_by_id(SessionLocal(), data["id"]) if user: SESSION_POOL[sid] = user.id diff --git a/backend/apps/webui/internal/db.py b/backend/apps/webui/internal/db.py index 80c30d652..5acf83d5c 100644 --- a/backend/apps/webui/internal/db.py +++ b/backend/apps/webui/internal/db.py @@ -1,18 +1,34 @@ import os import logging import json +from typing import Optional, Any +from typing_extensions import Self -from peewee import * -from peewee_migrate import Router +from sqlalchemy import create_engine, types, Dialect +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker +from sqlalchemy.sql.type_api import _T -from apps.webui.internal.wrappers import register_connection from config import SRC_LOG_LEVELS, DATA_DIR, DATABASE_URL, BACKEND_DIR log = logging.getLogger(__name__) log.setLevel(SRC_LOG_LEVELS["DB"]) -class JSONField(TextField): +class JSONField(types.TypeDecorator): + impl = types.Text + cache_ok = True + + def process_bind_param(self, value: Optional[_T], dialect: Dialect) -> Any: + return json.dumps(value) + + def process_result_value(self, value: Optional[_T], dialect: Dialect) -> Any: + if value is not None: + return json.loads(value) + + def copy(self, **kw: Any) -> Self: + return JSONField(self.impl.length) + def db_value(self, value): return json.dumps(value) @@ -29,26 +45,24 @@ if os.path.exists(f"{DATA_DIR}/ollama.db"): else: pass +SQLALCHEMY_DATABASE_URL = DATABASE_URL +if "sqlite" in SQLALCHEMY_DATABASE_URL: + engine = create_engine( + SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} + ) +else: + engine = create_engine(SQLALCHEMY_DATABASE_URL, pool_pre_ping=True) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) +Base = declarative_base() -# The `register_connection` function encapsulates the logic for setting up -# the database connection based on the connection string, while `connect` -# is a Peewee-specific method to manage the connection state and avoid errors -# when a connection is already open. -try: - DB = register_connection(DATABASE_URL) - log.info(f"Connected to a {DB.__class__.__name__} database.") -except Exception as e: - log.error(f"Failed to initialize the database connection: {e}") - raise -router = Router( - DB, - migrate_dir=BACKEND_DIR / "apps" / "webui" / "internal" / "migrations", - logger=log, -) -router.run() -try: - DB.connect(reuse_if_open=True) -except OperationalError as e: - log.info(f"Failed to connect to database again due to: {e}") - pass +def get_db(): + db = SessionLocal() + try: + yield db + db.commit() + except Exception as e: + db.rollback() + raise e + finally: + db.close() diff --git a/backend/apps/webui/internal/wrappers.py b/backend/apps/webui/internal/wrappers.py deleted file mode 100644 index 2b5551ce2..000000000 --- a/backend/apps/webui/internal/wrappers.py +++ /dev/null @@ -1,72 +0,0 @@ -from contextvars import ContextVar -from peewee import * -from peewee import PostgresqlDatabase, InterfaceError as PeeWeeInterfaceError - -import logging -from playhouse.db_url import connect, parse -from playhouse.shortcuts import ReconnectMixin - -from config import SRC_LOG_LEVELS - -log = logging.getLogger(__name__) -log.setLevel(SRC_LOG_LEVELS["DB"]) - -db_state_default = {"closed": None, "conn": None, "ctx": None, "transactions": None} -db_state = ContextVar("db_state", default=db_state_default.copy()) - - -class PeeweeConnectionState(object): - def __init__(self, **kwargs): - super().__setattr__("_state", db_state) - super().__init__(**kwargs) - - def __setattr__(self, name, value): - self._state.get()[name] = value - - def __getattr__(self, name): - value = self._state.get()[name] - return value - - -class CustomReconnectMixin(ReconnectMixin): - reconnect_errors = ( - # psycopg2 - (OperationalError, "termin"), - (InterfaceError, "closed"), - # peewee - (PeeWeeInterfaceError, "closed"), - ) - - -class ReconnectingPostgresqlDatabase(CustomReconnectMixin, PostgresqlDatabase): - pass - - -def register_connection(db_url): - db = connect(db_url) - if isinstance(db, PostgresqlDatabase): - # Enable autoconnect for SQLite databases, managed by Peewee - db.autoconnect = True - db.reuse_if_open = True - log.info("Connected to PostgreSQL database") - - # Get the connection details - connection = parse(db_url) - - # Use our custom database class that supports reconnection - db = ReconnectingPostgresqlDatabase( - connection["database"], - user=connection["user"], - password=connection["password"], - host=connection["host"], - port=connection["port"], - ) - db.connect(reuse_if_open=True) - elif isinstance(db, SqliteDatabase): - # Enable autoconnect for SQLite databases, managed by Peewee - db.autoconnect = True - db.reuse_if_open = True - log.info("Connected to SQLite database") - else: - raise ValueError("Unsupported database connection") - return db diff --git a/backend/apps/webui/main.py b/backend/apps/webui/main.py index 28b1b4aac..8bef22c05 100644 --- a/backend/apps/webui/main.py +++ b/backend/apps/webui/main.py @@ -3,7 +3,7 @@ from fastapi.routing import APIRoute from fastapi.responses import StreamingResponse from fastapi.middleware.cors import CORSMiddleware from starlette.middleware.sessions import SessionMiddleware - +from sqlalchemy.orm import Session from apps.webui.routers import ( auths, users, @@ -114,8 +114,8 @@ async def get_status(): } -async def get_pipe_models(): - pipes = Functions.get_functions_by_type("pipe", active_only=True) +async def get_pipe_models(db: Session): + pipes = Functions.get_functions_by_type(db, "pipe", active_only=True) pipe_models = [] for pipe in pipes: diff --git a/backend/apps/webui/models/auths.py b/backend/apps/webui/models/auths.py index 9ea38abcb..5ff348dac 100644 --- a/backend/apps/webui/models/auths.py +++ b/backend/apps/webui/models/auths.py @@ -1,14 +1,14 @@ from pydantic import BaseModel -from typing import List, Union, Optional -import time +from typing import Optional import uuid import logging -from peewee import * +from sqlalchemy import String, Column, Boolean +from sqlalchemy.orm import Session from apps.webui.models.users import UserModel, Users from utils.utils import verify_password -from apps.webui.internal.db import DB +from apps.webui.internal.db import Base from config import SRC_LOG_LEVELS @@ -20,14 +20,13 @@ log.setLevel(SRC_LOG_LEVELS["MODELS"]) #################### -class Auth(Model): - id = CharField(unique=True) - email = CharField() - password = TextField() - active = BooleanField() +class Auth(Base): + __tablename__ = "auth" - class Meta: - database = DB + id = Column(String, primary_key=True) + email = Column(String) + password = Column(String) + active = Column(Boolean) class AuthModel(BaseModel): @@ -94,12 +93,10 @@ class AddUserForm(SignupForm): class AuthsTable: - def __init__(self, db): - self.db = db - self.db.create_tables([Auth]) def insert_new_auth( self, + db: Session, email: str, password: str, name: str, @@ -114,24 +111,30 @@ class AuthsTable: auth = AuthModel( **{"id": id, "email": email, "password": password, "active": True} ) - result = Auth.create(**auth.model_dump()) + result = Auth(**auth.model_dump()) + db.add(result) user = Users.insert_new_user( - id, name, email, profile_image_url, role, oauth_sub + db, id, name, email, profile_image_url, role, oauth_sub ) + db.commit() + db.refresh(result) + if result and user: return user else: return None - def authenticate_user(self, email: str, password: str) -> Optional[UserModel]: + def authenticate_user( + self, db: Session, email: str, password: str + ) -> Optional[UserModel]: log.info(f"authenticate_user: {email}") try: - auth = Auth.get(Auth.email == email, Auth.active == True) + auth = db.query(Auth).filter_by(email=email, active=True).first() if auth: if verify_password(password, auth.password): - user = Users.get_user_by_id(auth.id) + user = Users.get_user_by_id(db, auth.id) return user else: return None @@ -140,55 +143,55 @@ class AuthsTable: except: return None - def authenticate_user_by_api_key(self, api_key: str) -> Optional[UserModel]: + def authenticate_user_by_api_key( + self, db: Session, api_key: str + ) -> Optional[UserModel]: log.info(f"authenticate_user_by_api_key: {api_key}") # if no api_key, return None if not api_key: return None try: - user = Users.get_user_by_api_key(api_key) + user = Users.get_user_by_api_key(db, api_key) return user if user else None except: return False - def authenticate_user_by_trusted_header(self, email: str) -> Optional[UserModel]: + def authenticate_user_by_trusted_header( + self, db: Session, email: str + ) -> Optional[UserModel]: log.info(f"authenticate_user_by_trusted_header: {email}") try: - auth = Auth.get(Auth.email == email, Auth.active == True) + auth = db.query(Auth).filter(email=email, active=True).first() if auth: user = Users.get_user_by_id(auth.id) return user except: return None - def update_user_password_by_id(self, id: str, new_password: str) -> bool: + def update_user_password_by_id( + self, db: Session, id: str, new_password: str + ) -> bool: try: - query = Auth.update(password=new_password).where(Auth.id == id) - result = query.execute() - + result = db.query(Auth).filter_by(id=id).update({"password": new_password}) return True if result == 1 else False except: return False - def update_email_by_id(self, id: str, email: str) -> bool: + def update_email_by_id(self, db: Session, id: str, email: str) -> bool: try: - query = Auth.update(email=email).where(Auth.id == id) - result = query.execute() - + result = db.query(Auth).filter_by(id=id).update({"email": email}) return True if result == 1 else False except: return False - def delete_auth_by_id(self, id: str) -> bool: + def delete_auth_by_id(self, db: Session, id: str) -> bool: try: # Delete User - result = Users.delete_user_by_id(id) + result = Users.delete_user_by_id(db, id) if result: - # Delete Auth - query = Auth.delete().where(Auth.id == id) - query.execute() # Remove the rows, return number of rows removed. + db.query(Auth).filter_by(id=id).delete() return True else: @@ -197,4 +200,4 @@ class AuthsTable: return False -Auths = AuthsTable(DB) +Auths = AuthsTable() diff --git a/backend/apps/webui/models/chats.py b/backend/apps/webui/models/chats.py index a6f1ae923..dd92fd0a1 100644 --- a/backend/apps/webui/models/chats.py +++ b/backend/apps/webui/models/chats.py @@ -1,36 +1,39 @@ -from pydantic import BaseModel +from pydantic import BaseModel, ConfigDict from typing import List, Union, Optional -from peewee import * -from playhouse.shortcuts import model_to_dict import json import uuid import time -from apps.webui.internal.db import DB +from sqlalchemy import Column, String, BigInteger, Boolean +from sqlalchemy.orm import Session + +from apps.webui.internal.db import Base + #################### # Chat DB Schema #################### -class Chat(Model): - id = CharField(unique=True) - user_id = CharField() - title = TextField() - chat = TextField() # Save Chat JSON as Text +class Chat(Base): + __tablename__ = "chat" - created_at = BigIntegerField() - updated_at = BigIntegerField() + id = Column(String, primary_key=True) + user_id = Column(String) + title = Column(String) + chat = Column(String) # Save Chat JSON as Text - share_id = CharField(null=True, unique=True) - archived = BooleanField(default=False) + created_at = Column(BigInteger) + updated_at = Column(BigInteger) - class Meta: - database = DB + share_id = Column(String, unique=True, nullable=True) + archived = Column(Boolean, default=False) class ChatModel(BaseModel): + model_config = ConfigDict(from_attributes=True) + id: str user_id: str title: str @@ -75,11 +78,10 @@ class ChatTitleIdResponse(BaseModel): class ChatTable: - def __init__(self, db): - self.db = db - db.create_tables([Chat]) - def insert_new_chat(self, user_id: str, form_data: ChatForm) -> Optional[ChatModel]: + def insert_new_chat( + self, db: Session, user_id: str, form_data: ChatForm + ) -> Optional[ChatModel]: id = str(uuid.uuid4()) chat = ChatModel( **{ @@ -94,29 +96,36 @@ class ChatTable: } ) - result = Chat.create(**chat.model_dump()) - return chat if result else None + result = Chat(**chat.model_dump()) + db.add(result) + db.commit() + db.refresh(result) + return ChatModel.model_validate(result) if result else None - def update_chat_by_id(self, id: str, chat: dict) -> Optional[ChatModel]: + def update_chat_by_id( + self, db: Session, id: str, chat: dict + ) -> Optional[ChatModel]: try: - query = Chat.update( - chat=json.dumps(chat), - title=chat["title"] if "title" in chat else "New Chat", - updated_at=int(time.time()), - ).where(Chat.id == id) - query.execute() + db.query(Chat).filter_by(id=id).update( + { + "chat": json.dumps(chat), + "title": chat["title"] if "title" in chat else "New Chat", + "updated_at": int(time.time()), + } + ) - chat = Chat.get(Chat.id == id) - return ChatModel(**model_to_dict(chat)) + return self.get_chat_by_id(db, id) except: return None - def insert_shared_chat_by_chat_id(self, chat_id: str) -> Optional[ChatModel]: + def insert_shared_chat_by_chat_id( + self, db: Session, chat_id: str + ) -> Optional[ChatModel]: # Get the existing chat to share - chat = Chat.get(Chat.id == chat_id) + chat = db.get(Chat, chat_id) # Check if the chat is already shared if chat.share_id: - return self.get_chat_by_id_and_user_id(chat.share_id, "shared") + return self.get_chat_by_id_and_user_id(db, chat.share_id, "shared") # Create a new chat with the same data, but with a new ID shared_chat = ChatModel( **{ @@ -128,228 +137,196 @@ class ChatTable: "updated_at": int(time.time()), } ) - shared_result = Chat.create(**shared_chat.model_dump()) + shared_result = Chat(**shared_chat.model_dump()) + db.add(shared_result) + db.commit() + db.refresh(shared_result) # Update the original chat with the share_id result = ( - Chat.update(share_id=shared_chat.id).where(Chat.id == chat_id).execute() + db.query(Chat).filter_by(id=chat_id).update({"share_id": shared_chat.id}) ) return shared_chat if (shared_result and result) else None - def update_shared_chat_by_chat_id(self, chat_id: str) -> Optional[ChatModel]: + def update_shared_chat_by_chat_id( + self, db: Session, chat_id: str + ) -> Optional[ChatModel]: try: print("update_shared_chat_by_id") - chat = Chat.get(Chat.id == chat_id) + chat = db.get(Chat, chat_id) print(chat) - query = Chat.update( - title=chat.title, - chat=chat.chat, - ).where(Chat.id == chat.share_id) + db.query(Chat).filter_by(id=chat.share_id).update( + {"title": chat.title, "chat": chat.chat} + ) - query.execute() - - chat = Chat.get(Chat.id == chat.share_id) - return ChatModel(**model_to_dict(chat)) + return self.get_chat_by_id(db, chat.share_id) except: return None - def delete_shared_chat_by_chat_id(self, chat_id: str) -> bool: + def delete_shared_chat_by_chat_id(self, db: Session, chat_id: str) -> bool: try: - query = Chat.delete().where(Chat.user_id == f"shared-{chat_id}") - query.execute() # Remove the rows, return number of rows removed. - + db.query(Chat).filter_by(user_id=f"shared-{chat_id}").delete() return True except: return False def update_chat_share_id_by_id( - self, id: str, share_id: Optional[str] + self, db: Session, id: str, share_id: Optional[str] ) -> Optional[ChatModel]: try: - query = Chat.update( - share_id=share_id, - ).where(Chat.id == id) - query.execute() + db.query(Chat).filter_by(id=id).update({"share_id": share_id}) - chat = Chat.get(Chat.id == id) - return ChatModel(**model_to_dict(chat)) + return self.get_chat_by_id(db, id) except: return None - def toggle_chat_archive_by_id(self, id: str) -> Optional[ChatModel]: + def toggle_chat_archive_by_id(self, db: Session, id: str) -> Optional[ChatModel]: try: - chat = self.get_chat_by_id(id) - query = Chat.update( - archived=(not chat.archived), - ).where(Chat.id == id) + chat = self.get_chat_by_id(db, id) + db.query(Chat).filter_by(id=id).update({"archived": not chat.archived}) - query.execute() - - chat = Chat.get(Chat.id == id) - return ChatModel(**model_to_dict(chat)) + return self.get_chat_by_id(db, id) except: return None - def archive_all_chats_by_user_id(self, user_id: str) -> bool: + def archive_all_chats_by_user_id(self, db: Session, user_id: str) -> bool: try: - chats = self.get_chats_by_user_id(user_id) - for chat in chats: - query = Chat.update( - archived=True, - ).where(Chat.id == chat.id) - - query.execute() + db.query(Chat).filter_by(user_id=user_id).update({"archived": True}) return True except: return False def get_archived_chat_list_by_user_id( - self, user_id: str, skip: int = 0, limit: int = 50 + self, db: Session, user_id: str, skip: int = 0, limit: int = 50 ) -> List[ChatModel]: - return [ - ChatModel(**model_to_dict(chat)) - for chat in Chat.select() - .where(Chat.archived == True) - .where(Chat.user_id == user_id) + all_chats = ( + db.query(Chat) + .filter_by(user_id=user_id, archived=True) .order_by(Chat.updated_at.desc()) - # .limit(limit) - # .offset(skip) - ] + # .limit(limit).offset(skip) + .all() + ) + return [ChatModel.model_validate(chat) for chat in all_chats] def get_chat_list_by_user_id( self, + db: Session, user_id: str, include_archived: bool = False, skip: int = 0, limit: int = 50, ) -> List[ChatModel]: - if include_archived: - return [ - ChatModel(**model_to_dict(chat)) - for chat in Chat.select() - .where(Chat.user_id == user_id) - .order_by(Chat.updated_at.desc()) - # .limit(limit) - # .offset(skip) - ] - else: - return [ - ChatModel(**model_to_dict(chat)) - for chat in Chat.select() - .where(Chat.archived == False) - .where(Chat.user_id == user_id) - .order_by(Chat.updated_at.desc()) - # .limit(limit) - # .offset(skip) - ] + query = db.query(Chat).filter_by(user_id=user_id) + if not include_archived: + query = query.filter_by(archived=False) + all_chats = ( + query.order_by(Chat.updated_at.desc()) + # .limit(limit).offset(skip) + .all() + ) + return [ChatModel.model_validate(chat) for chat in all_chats] def get_chat_list_by_chat_ids( - self, chat_ids: List[str], skip: int = 0, limit: int = 50 + self, db: Session, chat_ids: List[str], skip: int = 0, limit: int = 50 ) -> List[ChatModel]: - return [ - ChatModel(**model_to_dict(chat)) - for chat in Chat.select() - .where(Chat.archived == False) - .where(Chat.id.in_(chat_ids)) + all_chats = ( + db.query(Chat) + .filter(Chat.id.in_(chat_ids)) + .filter_by(archived=False) .order_by(Chat.updated_at.desc()) - ] + .all() + ) + return [ChatModel.model_validate(chat) for chat in all_chats] - def get_chat_by_id(self, id: str) -> Optional[ChatModel]: + def get_chat_by_id(self, db: Session, id: str) -> Optional[ChatModel]: try: - chat = Chat.get(Chat.id == id) - return ChatModel(**model_to_dict(chat)) + chat = db.get(Chat, id) + return ChatModel.model_validate(chat) except: return None - def get_chat_by_share_id(self, id: str) -> Optional[ChatModel]: + def get_chat_by_share_id(self, db: Session, id: str) -> Optional[ChatModel]: try: - chat = Chat.get(Chat.share_id == id) + chat = db.query(Chat).filter_by(share_id=id).first() if chat: - chat = Chat.get(Chat.id == id) - return ChatModel(**model_to_dict(chat)) + return self.get_chat_by_id(db, id) else: return None + except Exception as e: + return None + + def get_chat_by_id_and_user_id( + self, db: Session, id: str, user_id: str + ) -> Optional[ChatModel]: + try: + chat = db.query(Chat).filter_by(id=id, user_id=user_id).first() + return ChatModel.model_validate(chat) except: return None - def get_chat_by_id_and_user_id(self, id: str, user_id: str) -> Optional[ChatModel]: - try: - chat = Chat.get(Chat.id == id, Chat.user_id == user_id) - return ChatModel(**model_to_dict(chat)) - except: - return None - - def get_chats(self, skip: int = 0, limit: int = 50) -> List[ChatModel]: - return [ - ChatModel(**model_to_dict(chat)) - for chat in Chat.select().order_by(Chat.updated_at.desc()) + def get_chats(self, db: Session, skip: int = 0, limit: int = 50) -> List[ChatModel]: + all_chats = ( + db.query(Chat) # .limit(limit).offset(skip) - ] - - def get_chats_by_user_id(self, user_id: str) -> List[ChatModel]: - return [ - ChatModel(**model_to_dict(chat)) - for chat in Chat.select() - .where(Chat.user_id == user_id) .order_by(Chat.updated_at.desc()) - # .limit(limit).offset(skip) - ] + ) + return [ChatModel.model_validate(chat) for chat in all_chats] - def get_archived_chats_by_user_id(self, user_id: str) -> List[ChatModel]: - return [ - ChatModel(**model_to_dict(chat)) - for chat in Chat.select() - .where(Chat.archived == True) - .where(Chat.user_id == user_id) + def get_chats_by_user_id(self, db: Session, user_id: str) -> List[ChatModel]: + all_chats = ( + db.query(Chat).filter_by(user_id=user_id).order_by(Chat.updated_at.desc()) + ) + return [ChatModel.model_validate(chat) for chat in all_chats] + + def get_archived_chats_by_user_id( + self, db: Session, user_id: str + ) -> List[ChatModel]: + all_chats = ( + db.query(Chat) + .filter_by(user_id=user_id, archived=True) .order_by(Chat.updated_at.desc()) - ] + ) + return [ChatModel.model_validate(chat) for chat in all_chats] - def delete_chat_by_id(self, id: str) -> bool: + def delete_chat_by_id(self, db: Session, id: str) -> bool: try: - query = Chat.delete().where((Chat.id == id)) - query.execute() # Remove the rows, return number of rows removed. + db.query(Chat).filter_by(id=id).delete() - return True and self.delete_shared_chat_by_chat_id(id) + return True and self.delete_shared_chat_by_chat_id(db, id) except: return False - def delete_chat_by_id_and_user_id(self, id: str, user_id: str) -> bool: + def delete_chat_by_id_and_user_id(self, db: Session, id: str, user_id: str) -> bool: try: - query = Chat.delete().where((Chat.id == id) & (Chat.user_id == user_id)) - query.execute() # Remove the rows, return number of rows removed. + db.query(Chat).filter_by(id=id, user_id=user_id).delete() - return True and self.delete_shared_chat_by_chat_id(id) + return True and self.delete_shared_chat_by_chat_id(db, id) except: return False - def delete_chats_by_user_id(self, user_id: str) -> bool: + def delete_chats_by_user_id(self, db: Session, user_id: str) -> bool: try: - self.delete_shared_chats_by_user_id(user_id) - - query = Chat.delete().where(Chat.user_id == user_id) - query.execute() # Remove the rows, return number of rows removed. + self.delete_shared_chats_by_user_id(db, user_id) + db.query(Chat).filter_by(user_id=user_id).delete() return True except: return False - def delete_shared_chats_by_user_id(self, user_id: str) -> bool: + def delete_shared_chats_by_user_id(self, db: Session, user_id: str) -> bool: try: - shared_chat_ids = [ - f"shared-{chat.id}" - for chat in Chat.select().where(Chat.user_id == user_id) - ] + chats_by_user = db.query(Chat).filter_by(user_id=user_id).all() + shared_chat_ids = [f"shared-{chat.id}" for chat in chats_by_user] - query = Chat.delete().where(Chat.user_id << shared_chat_ids) - query.execute() # Remove the rows, return number of rows removed. + db.query(Chat).filter(Chat.user_id.in_(shared_chat_ids)).delete() return True except: return False -Chats = ChatTable(DB) +Chats = ChatTable() diff --git a/backend/apps/webui/models/documents.py b/backend/apps/webui/models/documents.py index 3b730535f..b272a5912 100644 --- a/backend/apps/webui/models/documents.py +++ b/backend/apps/webui/models/documents.py @@ -1,14 +1,12 @@ -from pydantic import BaseModel -from peewee import * -from playhouse.shortcuts import model_to_dict -from typing import List, Union, Optional +from pydantic import BaseModel, ConfigDict +from typing import List, Optional import time import logging -from utils.utils import decode_token -from utils.misc import get_gravatar_url +from sqlalchemy import String, Column, BigInteger +from sqlalchemy.orm import Session -from apps.webui.internal.db import DB +from apps.webui.internal.db import Base import json @@ -22,20 +20,21 @@ log.setLevel(SRC_LOG_LEVELS["MODELS"]) #################### -class Document(Model): - collection_name = CharField(unique=True) - name = CharField(unique=True) - title = TextField() - filename = TextField() - content = TextField(null=True) - user_id = CharField() - timestamp = BigIntegerField() +class Document(Base): + __tablename__ = "document" - class Meta: - database = DB + collection_name = Column(String, primary_key=True) + name = Column(String, unique=True) + title = Column(String) + filename = Column(String) + content = Column(String, nullable=True) + user_id = Column(String) + timestamp = Column(BigInteger) class DocumentModel(BaseModel): + model_config = ConfigDict(from_attributes=True) + collection_name: str name: str title: str @@ -72,12 +71,9 @@ class DocumentForm(DocumentUpdateForm): class DocumentsTable: - def __init__(self, db): - self.db = db - self.db.create_tables([Document]) def insert_new_doc( - self, user_id: str, form_data: DocumentForm + self, db: Session, user_id: str, form_data: DocumentForm ) -> Optional[DocumentModel]: document = DocumentModel( **{ @@ -88,73 +84,69 @@ class DocumentsTable: ) try: - result = Document.create(**document.model_dump()) + result = Document(**document.model_dump()) + db.add(result) + db.commit() + db.refresh(result) if result: - return document + return DocumentModel.model_validate(result) else: return None except: return None - def get_doc_by_name(self, name: str) -> Optional[DocumentModel]: + def get_doc_by_name(self, db: Session, name: str) -> Optional[DocumentModel]: try: - document = Document.get(Document.name == name) - return DocumentModel(**model_to_dict(document)) + document = db.query(Document).filter_by(name=name).first() + return DocumentModel.model_validate(document) if document else None except: return None - def get_docs(self) -> List[DocumentModel]: - return [ - DocumentModel(**model_to_dict(doc)) - for doc in Document.select() - # .limit(limit).offset(skip) - ] + def get_docs(self, db: Session) -> List[DocumentModel]: + return [DocumentModel.model_validate(doc) for doc in db.query(Document).all()] def update_doc_by_name( - self, name: str, form_data: DocumentUpdateForm + self, db: Session, name: str, form_data: DocumentUpdateForm ) -> Optional[DocumentModel]: try: - query = Document.update( - title=form_data.title, - name=form_data.name, - timestamp=int(time.time()), - ).where(Document.name == name) - query.execute() - - doc = Document.get(Document.name == form_data.name) - return DocumentModel(**model_to_dict(doc)) + db.query(Document).filter_by(name=name).update( + { + "title": form_data.title, + "name": form_data.name, + "timestamp": int(time.time()), + } + ) + return self.get_doc_by_name(db, form_data.name) except Exception as e: log.exception(e) return None def update_doc_content_by_name( - self, name: str, updated: dict + self, db: Session, name: str, updated: dict ) -> Optional[DocumentModel]: try: - doc = self.get_doc_by_name(name) + doc = self.get_doc_by_name(db, name) doc_content = json.loads(doc.content if doc.content else "{}") doc_content = {**doc_content, **updated} - query = Document.update( - content=json.dumps(doc_content), - timestamp=int(time.time()), - ).where(Document.name == name) - query.execute() + db.query(Document).filter_by(name=name).update( + { + "content": json.dumps(doc_content), + "timestamp": int(time.time()), + } + ) - doc = Document.get(Document.name == name) - return DocumentModel(**model_to_dict(doc)) + return self.get_doc_by_name(db, name) except Exception as e: log.exception(e) return None - def delete_doc_by_name(self, name: str) -> bool: + def delete_doc_by_name(self, db: Session, name: str) -> bool: try: - query = Document.delete().where((Document.name == name)) - query.execute() # Remove the rows, return number of rows removed. - + db.query(Document).filter_by(name=name).delete() return True except: return False -Documents = DocumentsTable(DB) +Documents = DocumentsTable() diff --git a/backend/apps/webui/models/files.py b/backend/apps/webui/models/files.py index 6459ad725..dc9f6be39 100644 --- a/backend/apps/webui/models/files.py +++ b/backend/apps/webui/models/files.py @@ -1,10 +1,12 @@ -from pydantic import BaseModel -from peewee import * -from playhouse.shortcuts import model_to_dict +from pydantic import BaseModel, ConfigDict from typing import List, Union, Optional import time import logging -from apps.webui.internal.db import DB, JSONField + +from sqlalchemy import Column, String, BigInteger +from sqlalchemy.orm import Session + +from apps.webui.internal.db import JSONField, Base import json @@ -18,15 +20,14 @@ log.setLevel(SRC_LOG_LEVELS["MODELS"]) #################### -class File(Model): - id = CharField(unique=True) - user_id = CharField() - filename = TextField() - meta = JSONField() - created_at = BigIntegerField() +class File(Base): + __tablename__ = "file" - class Meta: - database = DB + id = Column(String, primary_key=True) + user_id = Column(String) + filename = Column(String) + meta = Column(JSONField) + created_at = Column(BigInteger) class FileModel(BaseModel): @@ -36,6 +37,7 @@ class FileModel(BaseModel): meta: dict created_at: int # timestamp in epoch + model_config = ConfigDict(from_attributes=True) #################### # Forms @@ -57,11 +59,8 @@ class FileForm(BaseModel): class FilesTable: - def __init__(self, db): - self.db = db - self.db.create_tables([File]) - def insert_new_file(self, user_id: str, form_data: FileForm) -> Optional[FileModel]: + def insert_new_file(self, db: Session, user_id: str, form_data: FileForm) -> Optional[FileModel]: file = FileModel( **{ **form_data.model_dump(), @@ -71,42 +70,41 @@ class FilesTable: ) try: - result = File.create(**file.model_dump()) + result = File(**file.model_dump()) + db.add(result) + db.commit() + db.refresh(result) if result: - return file + return FileModel.model_validate(result) else: return None except Exception as e: print(f"Error creating tool: {e}") return None - def get_file_by_id(self, id: str) -> Optional[FileModel]: + def get_file_by_id(self, db: Session, id: str) -> Optional[FileModel]: try: - file = File.get(File.id == id) - return FileModel(**model_to_dict(file)) + file = db.get(File, id) + return FileModel.model_validate(file) except: return None - def get_files(self) -> List[FileModel]: - return [FileModel(**model_to_dict(file)) for file in File.select()] + def get_files(self, db: Session) -> List[FileModel]: + return [FileModel.model_validate(file) for file in db.query(File).all()] - def delete_file_by_id(self, id: str) -> bool: + def delete_file_by_id(self, db: Session, id: str) -> bool: try: - query = File.delete().where((File.id == id)) - query.execute() # Remove the rows, return number of rows removed. - + db.query(File).filter_by(id=id).delete() return True except: return False - def delete_all_files(self) -> bool: + def delete_all_files(self, db: Session) -> bool: try: - query = File.delete() - query.execute() # Remove the rows, return number of rows removed. - + db.query(File).delete() return True except: return False -Files = FilesTable(DB) +Files = FilesTable() diff --git a/backend/apps/webui/models/functions.py b/backend/apps/webui/models/functions.py index 261987981..88fa24a21 100644 --- a/backend/apps/webui/models/functions.py +++ b/backend/apps/webui/models/functions.py @@ -1,10 +1,12 @@ -from pydantic import BaseModel -from peewee import * -from playhouse.shortcuts import model_to_dict +from pydantic import BaseModel, ConfigDict from typing import List, Union, Optional import time import logging -from apps.webui.internal.db import DB, JSONField + +from sqlalchemy import Column, String, Text, BigInteger, Boolean +from sqlalchemy.orm import Session + +from apps.webui.internal.db import JSONField, Base from apps.webui.models.users import Users import json @@ -21,20 +23,19 @@ log.setLevel(SRC_LOG_LEVELS["MODELS"]) #################### -class Function(Model): - id = CharField(unique=True) - user_id = CharField() - name = TextField() - type = TextField() - content = TextField() - meta = JSONField() - valves = JSONField() - is_active = BooleanField(default=False) - updated_at = BigIntegerField() - created_at = BigIntegerField() +class Function(Base): + __tablename__ = "function" - class Meta: - database = DB + id = Column(String, primary_key=True) + user_id = Column(String) + name = Column(Text) + type = Column(Text) + content = Column(Text) + meta = Column(JSONField) + valves = Column(JSONField) + is_active = Column(Boolean) + updated_at = Column(BigInteger) + created_at = Column(BigInteger) class FunctionMeta(BaseModel): @@ -53,6 +54,8 @@ class FunctionModel(BaseModel): updated_at: int # timestamp in epoch created_at: int # timestamp in epoch + model_config = ConfigDict(from_attributes=True) + #################### # Forms @@ -82,12 +85,9 @@ class FunctionValves(BaseModel): class FunctionsTable: - def __init__(self, db): - self.db = db - self.db.create_tables([Function]) def insert_new_function( - self, user_id: str, type: str, form_data: FunctionForm + self, db: Session, user_id: str, type: str, form_data: FunctionForm ) -> Optional[FunctionModel]: function = FunctionModel( **{ @@ -100,19 +100,22 @@ class FunctionsTable: ) try: - result = Function.create(**function.model_dump()) + result = Function(**function.model_dump()) + db.add(result) + db.commit() + db.refresh(result) if result: - return function + return FunctionModel.model_validate(result) else: return None except Exception as e: print(f"Error creating tool: {e}") return None - def get_function_by_id(self, id: str) -> Optional[FunctionModel]: + def get_function_by_id(self, db: Session, id: str) -> Optional[FunctionModel]: try: - function = Function.get(Function.id == id) - return FunctionModel(**model_to_dict(function)) + function = db.get(Function, id) + return FunctionModel.model_validate(function) except: return None @@ -211,14 +214,11 @@ class FunctionsTable: def update_function_by_id(self, id: str, updated: dict) -> Optional[FunctionModel]: try: - query = Function.update( + db.query(Function).filter_by(id=id).update({ **updated, - updated_at=int(time.time()), - ).where(Function.id == id) - query.execute() - - function = Function.get(Function.id == id) - return FunctionModel(**model_to_dict(function)) + "updated_at": int(time.time()), + }) + return self.get_function_by_id(db, id) except: return None @@ -235,14 +235,12 @@ class FunctionsTable: except: return None - def delete_function_by_id(self, id: str) -> bool: + def delete_function_by_id(self, db: Session, id: str) -> bool: try: - query = Function.delete().where((Function.id == id)) - query.execute() # Remove the rows, return number of rows removed. - + db.query(Function).filter_by(id=id).delete() return True except: return False -Functions = FunctionsTable(DB) +Functions = FunctionsTable() diff --git a/backend/apps/webui/models/memories.py b/backend/apps/webui/models/memories.py index ef63674ab..f5f6d13fb 100644 --- a/backend/apps/webui/models/memories.py +++ b/backend/apps/webui/models/memories.py @@ -1,9 +1,10 @@ -from pydantic import BaseModel -from peewee import * -from playhouse.shortcuts import model_to_dict +from pydantic import BaseModel, ConfigDict from typing import List, Union, Optional -from apps.webui.internal.db import DB +from sqlalchemy import Column, String, BigInteger +from sqlalchemy.orm import Session + +from apps.webui.internal.db import Base from apps.webui.models.chats import Chats import time @@ -14,15 +15,14 @@ import uuid #################### -class Memory(Model): - id = CharField(unique=True) - user_id = CharField() - content = TextField() - updated_at = BigIntegerField() - created_at = BigIntegerField() +class Memory(Base): + __tablename__ = "memory" - class Meta: - database = DB + id = Column(String, primary_key=True) + user_id = Column(String) + content = Column(String) + updated_at = Column(BigInteger) + created_at = Column(BigInteger) class MemoryModel(BaseModel): @@ -32,6 +32,8 @@ class MemoryModel(BaseModel): updated_at: int # timestamp in epoch created_at: int # timestamp in epoch + model_config = ConfigDict(from_attributes=True) + #################### # Forms @@ -39,12 +41,10 @@ class MemoryModel(BaseModel): class MemoriesTable: - def __init__(self, db): - self.db = db - self.db.create_tables([Memory]) def insert_new_memory( self, + db: Session, user_id: str, content: str, ) -> Optional[MemoryModel]: @@ -59,74 +59,73 @@ class MemoriesTable: "updated_at": int(time.time()), } ) - result = Memory.create(**memory.model_dump()) + result = Memory(**memory.dict()) + db.add(result) + db.commit() + db.refresh(result) if result: - return memory + return MemoryModel.model_validate(result) else: return None def update_memory_by_id( self, + db: Session, id: str, content: str, ) -> Optional[MemoryModel]: try: - memory = Memory.get(Memory.id == id) - memory.content = content - memory.updated_at = int(time.time()) - memory.save() - return MemoryModel(**model_to_dict(memory)) + db.query(Memory).filter_by(id=id).update( + {"content": content, "updated_at": int(time.time())} + ) + return self.get_memory_by_id(db, id) except: return None - def get_memories(self) -> List[MemoryModel]: + def get_memories(self, db: Session) -> List[MemoryModel]: try: - memories = Memory.select() - return [MemoryModel(**model_to_dict(memory)) for memory in memories] + memories = db.query(Memory).all() + return [MemoryModel.model_validate(memory) for memory in memories] except: return None - def get_memories_by_user_id(self, user_id: str) -> List[MemoryModel]: + def get_memories_by_user_id(self, db: Session, user_id: str) -> List[MemoryModel]: try: - memories = Memory.select().where(Memory.user_id == user_id) - return [MemoryModel(**model_to_dict(memory)) for memory in memories] + memories = db.query(Memory).filter_by(user_id=user_id).all() + return [MemoryModel.model_validate(memory) for memory in memories] except: return None - def get_memory_by_id(self, id) -> Optional[MemoryModel]: + def get_memory_by_id(self, db: Session, id: str) -> Optional[MemoryModel]: try: - memory = Memory.get(Memory.id == id) - return MemoryModel(**model_to_dict(memory)) + memory = db.get(Memory, id) + return MemoryModel.model_validate(memory) except: return None - def delete_memory_by_id(self, id: str) -> bool: + def delete_memory_by_id(self, db: Session, id: str) -> bool: try: - query = Memory.delete().where(Memory.id == id) - query.execute() # Remove the rows, return number of rows removed. - + db.query(Memory).filter_by(id=id).delete() return True except: return False - def delete_memories_by_user_id(self, user_id: str) -> bool: + def delete_memories_by_user_id(self, db: Session, user_id: str) -> bool: try: - query = Memory.delete().where(Memory.user_id == user_id) - query.execute() - + db.query(Memory).filter_by(user_id=user_id).delete() return True except: return False - def delete_memory_by_id_and_user_id(self, id: str, user_id: str) -> bool: + def delete_memory_by_id_and_user_id( + self, db: Session, id: str, user_id: str + ) -> bool: try: - query = Memory.delete().where(Memory.id == id, Memory.user_id == user_id) - query.execute() - + db.query(Memory).filter_by(id=id, user_id=user_id).delete() return True except: return False -Memories = MemoriesTable(DB) +Memories = MemoriesTable() diff --git a/backend/apps/webui/models/models.py b/backend/apps/webui/models/models.py index 851352398..137333409 100644 --- a/backend/apps/webui/models/models.py +++ b/backend/apps/webui/models/models.py @@ -2,13 +2,11 @@ import json import logging from typing import Optional -import peewee as pw -from peewee import * - -from playhouse.shortcuts import model_to_dict from pydantic import BaseModel, ConfigDict +from sqlalchemy import String, Column, BigInteger +from sqlalchemy.orm import Session -from apps.webui.internal.db import DB, JSONField +from apps.webui.internal.db import Base, JSONField from typing import List, Union, Optional from config import SRC_LOG_LEVELS @@ -46,41 +44,42 @@ class ModelMeta(BaseModel): pass -class Model(pw.Model): - id = pw.TextField(unique=True) +class Model(Base): + __tablename__ = "model" + + id = Column(String, primary_key=True) """ The model's id as used in the API. If set to an existing model, it will override the model. """ - user_id = pw.TextField() + user_id = Column(String) - base_model_id = pw.TextField(null=True) + base_model_id = Column(String, nullable=True) """ An optional pointer to the actual model that should be used when proxying requests. """ - name = pw.TextField() + name = Column(String) """ The human-readable display name of the model. """ - params = JSONField() + params = Column(JSONField) """ Holds a JSON encoded blob of parameters, see `ModelParams`. """ - meta = JSONField() + meta = Column(JSONField) """ Holds a JSON encoded blob of metadata, see `ModelMeta`. """ - updated_at = BigIntegerField() - created_at = BigIntegerField() - - class Meta: - database = DB + updated_at = Column(BigInteger) + created_at = Column(BigInteger) class ModelModel(BaseModel): + model_config = ConfigDict(from_attributes=True) + id: str user_id: str base_model_id: Optional[str] = None @@ -115,15 +114,9 @@ class ModelForm(BaseModel): class ModelsTable: - def __init__( - self, - db: pw.SqliteDatabase | pw.PostgresqlDatabase, - ): - self.db = db - self.db.create_tables([Model]) def insert_new_model( - self, form_data: ModelForm, user_id: str + self, db: Session, form_data: ModelForm, user_id: str ) -> Optional[ModelModel]: model = ModelModel( **{ @@ -134,46 +127,50 @@ class ModelsTable: } ) try: - result = Model.create(**model.model_dump()) + result = Model(**model.dict()) + db.add(result) + db.commit() + db.refresh(result) if result: - return model + return ModelModel.model_validate(result) else: return None except Exception as e: print(e) return None - def get_all_models(self) -> List[ModelModel]: - return [ModelModel(**model_to_dict(model)) for model in Model.select()] + def get_all_models(self, db: Session) -> List[ModelModel]: + return [ModelModel.model_validate(model) for model in db.query(Model).all()] - def get_model_by_id(self, id: str) -> Optional[ModelModel]: + def get_model_by_id(self, db: Session, id: str) -> Optional[ModelModel]: try: - model = Model.get(Model.id == id) - return ModelModel(**model_to_dict(model)) + model = db.get(Model, id) + return ModelModel.model_validate(model) except: return None - def update_model_by_id(self, id: str, model: ModelForm) -> Optional[ModelModel]: + def update_model_by_id( + self, db: Session, id: str, model: ModelForm + ) -> Optional[ModelModel]: try: # update only the fields that are present in the model - query = Model.update(**model.model_dump()).where(Model.id == id) - query.execute() - - model = Model.get(Model.id == id) - return ModelModel(**model_to_dict(model)) + model = db.query(Model).get(id) + model.update(**model.model_dump()) + db.commit() + db.refresh(model) + return ModelModel.model_validate(model) except Exception as e: print(e) return None - def delete_model_by_id(self, id: str) -> bool: + def delete_model_by_id(self, db: Session, id: str) -> bool: try: - query = Model.delete().where(Model.id == id) - query.execute() + db.query(Model).filter_by(id=id).delete() return True except: return False -Models = ModelsTable(DB) +Models = ModelsTable() diff --git a/backend/apps/webui/models/prompts.py b/backend/apps/webui/models/prompts.py index c4ac6be14..21c4de3e1 100644 --- a/backend/apps/webui/models/prompts.py +++ b/backend/apps/webui/models/prompts.py @@ -1,13 +1,11 @@ -from pydantic import BaseModel -from peewee import * -from playhouse.shortcuts import model_to_dict -from typing import List, Union, Optional +from pydantic import BaseModel, ConfigDict +from typing import List, Optional import time -from utils.utils import decode_token -from utils.misc import get_gravatar_url +from sqlalchemy import String, Column, BigInteger +from sqlalchemy.orm import Session -from apps.webui.internal.db import DB +from apps.webui.internal.db import Base import json @@ -16,15 +14,14 @@ import json #################### -class Prompt(Model): - command = CharField(unique=True) - user_id = CharField() - title = TextField() - content = TextField() - timestamp = BigIntegerField() +class Prompt(Base): + __tablename__ = "prompt" - class Meta: - database = DB + command = Column(String, primary_key=True) + user_id = Column(String) + title = Column(String) + content = Column(String) + timestamp = Column(BigInteger) class PromptModel(BaseModel): @@ -34,6 +31,8 @@ class PromptModel(BaseModel): content: str timestamp: int # timestamp in epoch + model_config = ConfigDict(from_attributes=True) + #################### # Forms @@ -48,12 +47,8 @@ class PromptForm(BaseModel): class PromptsTable: - def __init__(self, db): - self.db = db - self.db.create_tables([Prompt]) - def insert_new_prompt( - self, user_id: str, form_data: PromptForm + self, db: Session, user_id: str, form_data: PromptForm ) -> Optional[PromptModel]: prompt = PromptModel( **{ @@ -66,53 +61,48 @@ class PromptsTable: ) try: - result = Prompt.create(**prompt.model_dump()) + result = Prompt(**prompt.dict()) + db.add(result) + db.commit() + db.refresh(result) if result: - return prompt + return PromptModel.model_validate(result) else: return None - except: + except Exception as e: return None - def get_prompt_by_command(self, command: str) -> Optional[PromptModel]: + def get_prompt_by_command(self, db: Session, command: str) -> Optional[PromptModel]: try: - prompt = Prompt.get(Prompt.command == command) - return PromptModel(**model_to_dict(prompt)) + prompt = db.query(Prompt).filter_by(command=command).first() + return PromptModel.model_validate(prompt) except: return None - def get_prompts(self) -> List[PromptModel]: - return [ - PromptModel(**model_to_dict(prompt)) - for prompt in Prompt.select() - # .limit(limit).offset(skip) - ] + def get_prompts(self, db: Session) -> List[PromptModel]: + return [PromptModel.model_validate(prompt) for prompt in db.query(Prompt).all()] def update_prompt_by_command( - self, command: str, form_data: PromptForm + self, db: Session, command: str, form_data: PromptForm ) -> Optional[PromptModel]: try: - query = Prompt.update( - title=form_data.title, - content=form_data.content, - timestamp=int(time.time()), - ).where(Prompt.command == command) - - query.execute() - - prompt = Prompt.get(Prompt.command == command) - return PromptModel(**model_to_dict(prompt)) + db.query(Prompt).filter_by(command=command).update( + { + "title": form_data.title, + "content": form_data.content, + "timestamp": int(time.time()), + } + ) + return self.get_prompt_by_command(db, command) except: return None - def delete_prompt_by_command(self, command: str) -> bool: + def delete_prompt_by_command(self, db: Session, command: str) -> bool: try: - query = Prompt.delete().where((Prompt.command == command)) - query.execute() # Remove the rows, return number of rows removed. - + db.query(Prompt).filter_by(command=command).delete() return True except: return False -Prompts = PromptsTable(DB) +Prompts = PromptsTable() diff --git a/backend/apps/webui/models/tags.py b/backend/apps/webui/models/tags.py index 4c4fa82e6..419425662 100644 --- a/backend/apps/webui/models/tags.py +++ b/backend/apps/webui/models/tags.py @@ -1,14 +1,15 @@ -from pydantic import BaseModel -from typing import List, Union, Optional -from peewee import * -from playhouse.shortcuts import model_to_dict +from pydantic import BaseModel, ConfigDict +from typing import List, Optional import json import uuid import time import logging -from apps.webui.internal.db import DB +from sqlalchemy import String, Column, BigInteger +from sqlalchemy.orm import Session + +from apps.webui.internal.db import Base from config import SRC_LOG_LEVELS @@ -20,25 +21,23 @@ log.setLevel(SRC_LOG_LEVELS["MODELS"]) #################### -class Tag(Model): - id = CharField(unique=True) - name = CharField() - user_id = CharField() - data = TextField(null=True) +class Tag(Base): + __tablename__ = "tag" - class Meta: - database = DB + id = Column(String, primary_key=True) + name = Column(String) + user_id = Column(String) + data = Column(String, nullable=True) -class ChatIdTag(Model): - id = CharField(unique=True) - tag_name = CharField() - chat_id = CharField() - user_id = CharField() - timestamp = BigIntegerField() +class ChatIdTag(Base): + __tablename__ = "chatidtag" - class Meta: - database = DB + id = Column(String, primary_key=True) + tag_name = Column(String) + chat_id = Column(String) + user_id = Column(String) + timestamp = Column(BigInteger) class TagModel(BaseModel): @@ -47,6 +46,8 @@ class TagModel(BaseModel): user_id: str data: Optional[str] = None + model_config = ConfigDict(from_attributes=True) + class ChatIdTagModel(BaseModel): id: str @@ -55,6 +56,8 @@ class ChatIdTagModel(BaseModel): user_id: str timestamp: int + model_config = ConfigDict(from_attributes=True) + #################### # Forms @@ -75,37 +78,39 @@ class ChatTagsResponse(BaseModel): class TagTable: - def __init__(self, db): - self.db = db - db.create_tables([Tag, ChatIdTag]) - def insert_new_tag(self, name: str, user_id: str) -> Optional[TagModel]: + def insert_new_tag( + self, db: Session, name: str, user_id: str + ) -> Optional[TagModel]: id = str(uuid.uuid4()) tag = TagModel(**{"id": id, "user_id": user_id, "name": name}) try: - result = Tag.create(**tag.model_dump()) + result = Tag(**tag.dict()) + db.add(result) + db.commit() + db.refresh(result) if result: - return tag + return TagModel.model_validate(result) else: return None except Exception as e: return None def get_tag_by_name_and_user_id( - self, name: str, user_id: str + self, db: Session, name: str, user_id: str ) -> Optional[TagModel]: try: - tag = Tag.get(Tag.name == name, Tag.user_id == user_id) - return TagModel(**model_to_dict(tag)) + tag = db.query(Tag).filter(name=name, user_id=user_id).first() + return TagModel.model_validate(tag) except Exception as e: return None def add_tag_to_chat( - self, user_id: str, form_data: ChatIdTagForm + self, db: Session, user_id: str, form_data: ChatIdTagForm ) -> Optional[ChatIdTagModel]: - tag = self.get_tag_by_name_and_user_id(form_data.tag_name, user_id) + tag = self.get_tag_by_name_and_user_id(db, form_data.tag_name, user_id) if tag == None: - tag = self.insert_new_tag(form_data.tag_name, user_id) + tag = self.insert_new_tag(db, form_data.tag_name, user_id) id = str(uuid.uuid4()) chatIdTag = ChatIdTagModel( @@ -118,120 +123,135 @@ class TagTable: } ) try: - result = ChatIdTag.create(**chatIdTag.model_dump()) + result = ChatIdTag(**chatIdTag.dict()) + db.add(result) + db.commit() + db.refresh(result) if result: - return chatIdTag + return ChatIdTagModel.model_validate(result) else: return None except: return None - def get_tags_by_user_id(self, user_id: str) -> List[TagModel]: + def get_tags_by_user_id(self, db: Session, user_id: str) -> List[TagModel]: tag_names = [ - ChatIdTagModel(**model_to_dict(chat_id_tag)).tag_name - for chat_id_tag in ChatIdTag.select() - .where(ChatIdTag.user_id == user_id) - .order_by(ChatIdTag.timestamp.desc()) + chat_id_tag.tag_name + for chat_id_tag in ( + db.query(ChatIdTag) + .filter_by(user_id=user_id) + .order_by(ChatIdTag.timestamp.desc()) + .all() + ) ] return [ - TagModel(**model_to_dict(tag)) - for tag in Tag.select() - .where(Tag.user_id == user_id) - .where(Tag.name.in_(tag_names)) + TagModel.model_validate(tag) + for tag in ( + db.query(Tag) + .filter_by(user_id=user_id) + .filter(Tag.name.in_(tag_names)) + .all() + ) ] def get_tags_by_chat_id_and_user_id( - self, chat_id: str, user_id: str + self, db: Session, chat_id: str, user_id: str ) -> List[TagModel]: tag_names = [ - ChatIdTagModel(**model_to_dict(chat_id_tag)).tag_name - for chat_id_tag in ChatIdTag.select() - .where((ChatIdTag.user_id == user_id) & (ChatIdTag.chat_id == chat_id)) - .order_by(ChatIdTag.timestamp.desc()) + chat_id_tag.tag_name + for chat_id_tag in ( + db.query(ChatIdTag) + .filter_by(user_id=user_id, chat_id=chat_id) + .order_by(ChatIdTag.timestamp.desc()) + .all() + ) ] return [ - TagModel(**model_to_dict(tag)) - for tag in Tag.select() - .where(Tag.user_id == user_id) - .where(Tag.name.in_(tag_names)) + TagModel.model_validate(tag) + for tag in ( + db.query(Tag) + .filter_by(user_id=user_id) + .filter(Tag.name.in_(tag_names)) + .all() + ) ] def get_chat_ids_by_tag_name_and_user_id( - self, tag_name: str, user_id: str - ) -> Optional[ChatIdTagModel]: + self, db: Session, tag_name: str, user_id: str + ) -> List[ChatIdTagModel]: return [ - ChatIdTagModel(**model_to_dict(chat_id_tag)) - for chat_id_tag in ChatIdTag.select() - .where((ChatIdTag.user_id == user_id) & (ChatIdTag.tag_name == tag_name)) - .order_by(ChatIdTag.timestamp.desc()) + ChatIdTagModel.model_validate(chat_id_tag) + for chat_id_tag in ( + db.query(ChatIdTag) + .filter_by(user_id=user_id, tag_name=tag_name) + .order_by(ChatIdTag.timestamp.desc()) + .all() + ) ] def count_chat_ids_by_tag_name_and_user_id( - self, tag_name: str, user_id: str + self, db: Session, tag_name: str, user_id: str ) -> int: - return ( - ChatIdTag.select() - .where((ChatIdTag.tag_name == tag_name) & (ChatIdTag.user_id == user_id)) - .count() - ) + return db.query(ChatIdTag).filter_by(tag_name=tag_name, user_id=user_id).count() - def delete_tag_by_tag_name_and_user_id(self, tag_name: str, user_id: str) -> bool: + def delete_tag_by_tag_name_and_user_id( + self, db: Session, tag_name: str, user_id: str + ) -> bool: try: - query = ChatIdTag.delete().where( - (ChatIdTag.tag_name == tag_name) & (ChatIdTag.user_id == user_id) + res = ( + db.query(ChatIdTag) + .filter_by(tag_name=tag_name, user_id=user_id) + .delete() ) - res = query.execute() # Remove the rows, return number of rows removed. log.debug(f"res: {res}") - tag_count = self.count_chat_ids_by_tag_name_and_user_id(tag_name, user_id) + tag_count = self.count_chat_ids_by_tag_name_and_user_id( + db, tag_name, user_id + ) if tag_count == 0: # Remove tag item from Tag col as well - query = Tag.delete().where( - (Tag.name == tag_name) & (Tag.user_id == user_id) - ) - query.execute() # Remove the rows, return number of rows removed. - + db.query(Tag).filter_by(name=tag_name, user_id=user_id).delete() return True except Exception as e: log.error(f"delete_tag: {e}") return False def delete_tag_by_tag_name_and_chat_id_and_user_id( - self, tag_name: str, chat_id: str, user_id: str + self, db: Session, tag_name: str, chat_id: str, user_id: str ) -> bool: try: - query = ChatIdTag.delete().where( - (ChatIdTag.tag_name == tag_name) - & (ChatIdTag.chat_id == chat_id) - & (ChatIdTag.user_id == user_id) + res = ( + db.query(ChatIdTag) + .filter_by(tag_name=tag_name, chat_id=chat_id, user_id=user_id) + .delete() ) - res = query.execute() # Remove the rows, return number of rows removed. log.debug(f"res: {res}") - tag_count = self.count_chat_ids_by_tag_name_and_user_id(tag_name, user_id) + tag_count = self.count_chat_ids_by_tag_name_and_user_id( + db, tag_name, user_id + ) if tag_count == 0: # Remove tag item from Tag col as well - query = Tag.delete().where( - (Tag.name == tag_name) & (Tag.user_id == user_id) - ) - query.execute() # Remove the rows, return number of rows removed. + db.query(Tag).filter_by(name=tag_name, user_id=user_id).delete() return True except Exception as 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: - tags = self.get_tags_by_chat_id_and_user_id(chat_id, user_id) + def delete_tags_by_chat_id_and_user_id( + self, db: Session, chat_id: str, user_id: str + ) -> bool: + tags = self.get_tags_by_chat_id_and_user_id(db, chat_id, user_id) for tag in tags: self.delete_tag_by_tag_name_and_chat_id_and_user_id( - tag.tag_name, chat_id, user_id + db, tag.tag_name, chat_id, user_id ) return True -Tags = TagTable(DB) +Tags = TagTable() diff --git a/backend/apps/webui/models/tools.py b/backend/apps/webui/models/tools.py index 694081df9..b8df2e163 100644 --- a/backend/apps/webui/models/tools.py +++ b/backend/apps/webui/models/tools.py @@ -1,10 +1,11 @@ -from pydantic import BaseModel -from peewee import * -from playhouse.shortcuts import model_to_dict -from typing import List, Union, Optional +from pydantic import BaseModel, ConfigDict +from typing import List, Optional import time import logging -from apps.webui.internal.db import DB, JSONField +from sqlalchemy import String, Column, BigInteger +from sqlalchemy.orm import Session + +from apps.webui.internal.db import Base, JSONField from apps.webui.models.users import Users import json @@ -21,19 +22,18 @@ log.setLevel(SRC_LOG_LEVELS["MODELS"]) #################### -class Tool(Model): - id = CharField(unique=True) - user_id = CharField() - name = TextField() - content = TextField() - specs = JSONField() - meta = JSONField() - valves = JSONField() - updated_at = BigIntegerField() - created_at = BigIntegerField() +class Tool(Base): + __tablename__ = "tool" - class Meta: - database = DB + id = Column(String, primary_key=True) + user_id = Column(String) + name = Column(String) + content = Column(String) + specs = Column(JSONField) + meta = Column(JSONField) + valves = Column(JSONField) + updated_at = Column(BigInteger) + created_at = Column(BigInteger) class ToolMeta(BaseModel): @@ -51,6 +51,8 @@ class ToolModel(BaseModel): updated_at: int # timestamp in epoch created_at: int # timestamp in epoch + model_config = ConfigDict(from_attributes=True) + #################### # Forms @@ -78,12 +80,9 @@ class ToolValves(BaseModel): class ToolsTable: - def __init__(self, db): - self.db = db - self.db.create_tables([Tool]) def insert_new_tool( - self, user_id: str, form_data: ToolForm, specs: List[dict] + self, db: Session, user_id: str, form_data: ToolForm, specs: List[dict] ) -> Optional[ToolModel]: tool = ToolModel( **{ @@ -96,24 +95,27 @@ class ToolsTable: ) try: - result = Tool.create(**tool.model_dump()) + result = Tool(**tool.dict()) + db.add(result) + db.commit() + db.refresh(result) if result: - return tool + return ToolModel.model_validate(result) else: return None except Exception as e: print(f"Error creating tool: {e}") return None - def get_tool_by_id(self, id: str) -> Optional[ToolModel]: + def get_tool_by_id(self, db: Session, id: str) -> Optional[ToolModel]: try: - tool = Tool.get(Tool.id == id) - return ToolModel(**model_to_dict(tool)) + tool = db.get(Tool, id) + return ToolModel.model_validate(tool) except: return None - def get_tools(self) -> List[ToolModel]: - return [ToolModel(**model_to_dict(tool)) for tool in Tool.select()] + def get_tools(self, db: Session) -> List[ToolModel]: + return [ToolModel.model_validate(tool) for tool in db.query(Tool).all()] def get_tool_valves_by_id(self, id: str) -> Optional[dict]: try: @@ -180,25 +182,19 @@ class ToolsTable: def update_tool_by_id(self, id: str, updated: dict) -> Optional[ToolModel]: try: - query = Tool.update( - **updated, - updated_at=int(time.time()), - ).where(Tool.id == id) - query.execute() - - tool = Tool.get(Tool.id == id) - return ToolModel(**model_to_dict(tool)) + db.query(Tool).filter_by(id=id).update( + {**updated, "updated_at": int(time.time())} + ) + return self.get_tool_by_id(db, id) except: return None - def delete_tool_by_id(self, id: str) -> bool: + def delete_tool_by_id(self, db: Session, id: str) -> bool: try: - query = Tool.delete().where((Tool.id == id)) - query.execute() # Remove the rows, return number of rows removed. - + db.query(Tool).filter_by(id=id).delete() return True except: return False -Tools = ToolsTable(DB) +Tools = ToolsTable() diff --git a/backend/apps/webui/models/users.py b/backend/apps/webui/models/users.py index e3e1842b8..7202d2d71 100644 --- a/backend/apps/webui/models/users.py +++ b/backend/apps/webui/models/users.py @@ -1,11 +1,13 @@ -from pydantic import BaseModel, ConfigDict -from peewee import * -from playhouse.shortcuts import model_to_dict +from pydantic import BaseModel, ConfigDict, parse_obj_as from typing import List, Union, Optional import time + +from sqlalchemy import String, Column, BigInteger, Text +from sqlalchemy.orm import Session + from utils.misc import get_gravatar_url -from apps.webui.internal.db import DB, JSONField +from apps.webui.internal.db import Base, JSONField from apps.webui.models.chats import Chats #################### @@ -13,25 +15,24 @@ from apps.webui.models.chats import Chats #################### -class User(Model): - id = CharField(unique=True) - name = CharField() - email = CharField() - role = CharField() - profile_image_url = TextField() +class User(Base): + __tablename__ = "user" - last_active_at = BigIntegerField() - updated_at = BigIntegerField() - created_at = BigIntegerField() + id = Column(String, primary_key=True) + name = Column(String) + email = Column(String) + role = Column(String) + profile_image_url = Column(String) - api_key = CharField(null=True, unique=True) - settings = JSONField(null=True) - info = JSONField(null=True) + last_active_at = Column(BigInteger) + updated_at = Column(BigInteger) + created_at = Column(BigInteger) - oauth_sub = TextField(null=True, unique=True) + api_key = Column(String, nullable=True, unique=True) + settings = Column(JSONField, nullable=True) + info = Column(JSONField, nullable=True) - class Meta: - database = DB + oauth_sub = Column(Text, unique=True) class UserSettings(BaseModel): @@ -41,6 +42,8 @@ class UserSettings(BaseModel): class UserModel(BaseModel): + model_config = ConfigDict(from_attributes=True) + id: str name: str email: str @@ -76,12 +79,10 @@ class UserUpdateForm(BaseModel): class UsersTable: - def __init__(self, db): - self.db = db - self.db.create_tables([User]) def insert_new_user( self, + db: Session, id: str, name: str, email: str, @@ -102,30 +103,33 @@ class UsersTable: "oauth_sub": oauth_sub, } ) - result = User.create(**user.model_dump()) + result = User(**user.model_dump()) + db.add(result) + db.commit() + db.refresh(result) if result: return user else: return None - def get_user_by_id(self, id: str) -> Optional[UserModel]: + def get_user_by_id(self, db: Session, id: str) -> Optional[UserModel]: try: - user = User.get(User.id == id) - return UserModel(**model_to_dict(user)) + user = db.query(User).filter_by(id=id).first() + return UserModel.model_validate(user) + except Exception as e: + return None + + def get_user_by_api_key(self, db: Session, api_key: str) -> Optional[UserModel]: + try: + user = db.query(User).filter_by(api_key=api_key).first() + return UserModel.model_validate(user) except: return None - def get_user_by_api_key(self, api_key: str) -> Optional[UserModel]: + def get_user_by_email(self, db: Session, email: str) -> Optional[UserModel]: try: - user = User.get(User.api_key == api_key) - return UserModel(**model_to_dict(user)) - except: - return None - - def get_user_by_email(self, email: str) -> Optional[UserModel]: - try: - user = User.get(User.email == email) - return UserModel(**model_to_dict(user)) + user = db.query(User).filter_by(email=email).first() + return UserModel.model_validate(user) except: return None @@ -136,88 +140,94 @@ class UsersTable: except: return None - def get_users(self, skip: int = 0, limit: int = 50) -> List[UserModel]: - return [ - UserModel(**model_to_dict(user)) - for user in User.select() - # .limit(limit).offset(skip) - ] + def get_users(self, db: Session, skip: int = 0, limit: int = 50) -> List[UserModel]: + users = ( + db.query(User) + # .offset(skip).limit(limit) + .all() + ) + return [UserModel.model_validate(user) for user in users] - def get_num_users(self) -> Optional[int]: - return User.select().count() + def get_num_users(self, db: Session) -> Optional[int]: + return db.query(User).count() - def get_first_user(self) -> UserModel: + def get_first_user(self, db: Session) -> UserModel: try: - user = User.select().order_by(User.created_at).first() - return UserModel(**model_to_dict(user)) + user = db.query(User).order_by(User.created_at).first() + return UserModel.model_validate(user) except: return None - def update_user_role_by_id(self, id: str, role: str) -> Optional[UserModel]: + def update_user_role_by_id( + self, db: Session, id: str, role: str + ) -> Optional[UserModel]: try: - query = User.update(role=role).where(User.id == id) - query.execute() + db.query(User).filter_by(id=id).update({"role": role}) + db.commit() - user = User.get(User.id == id) - return UserModel(**model_to_dict(user)) + user = db.query(User).filter_by(id=id).first() + return UserModel.model_validate(user) except: return None def update_user_profile_image_url_by_id( - self, id: str, profile_image_url: str + self, db: Session, id: str, profile_image_url: str ) -> Optional[UserModel]: try: - query = User.update(profile_image_url=profile_image_url).where( - User.id == id + db.query(User).filter_by(id=id).update( + {"profile_image_url": profile_image_url} ) - query.execute() + db.commit() - user = User.get(User.id == id) - return UserModel(**model_to_dict(user)) + user = db.query(User).filter_by(id=id).first() + return UserModel.model_validate(user) except: return None - def update_user_last_active_by_id(self, id: str) -> Optional[UserModel]: + def update_user_last_active_by_id( + self, db: Session, id: str + ) -> Optional[UserModel]: try: - query = User.update(last_active_at=int(time.time())).where(User.id == id) - query.execute() + db.query(User).filter_by(id=id).update({"last_active_at": int(time.time())}) - user = User.get(User.id == id) - return UserModel(**model_to_dict(user)) + user = db.query(User).filter_by(id=id).first() + return UserModel.model_validate(user) except: return None def update_user_oauth_sub_by_id( - self, id: str, oauth_sub: str + self, db: Session, id: str, oauth_sub: str ) -> Optional[UserModel]: try: - query = User.update(oauth_sub=oauth_sub).where(User.id == id) - query.execute() + db.query(User).filter_by(id=id).update({"oauth_sub": oauth_sub}) - user = User.get(User.id == id) - return UserModel(**model_to_dict(user)) + user = db.query(User).filter_by(id=id).first() + return UserModel.model_validate(user) except: return None - def update_user_by_id(self, id: str, updated: dict) -> Optional[UserModel]: + def update_user_by_id( + self, db: Session, id: str, updated: dict + ) -> Optional[UserModel]: try: - query = User.update(**updated).where(User.id == id) - query.execute() + db.query(User).filter_by(id=id).update(updated) + db.commit() - user = User.get(User.id == id) - return UserModel(**model_to_dict(user)) - except: + user = db.query(User).filter_by(id=id).first() + return UserModel.model_validate(user) + # return UserModel(**user.dict()) + except Exception as e: return None - def delete_user_by_id(self, id: str) -> bool: + def delete_user_by_id(self, db: Session, id: str) -> bool: try: # Delete User Chats - result = Chats.delete_chats_by_user_id(id) + result = Chats.delete_chats_by_user_id(db, id) if result: # Delete User - query = User.delete().where(User.id == id) - query.execute() # Remove the rows, return number of rows removed. + db.query(User).filter_by(id=id).delete() + db.commit() return True else: @@ -225,21 +235,20 @@ class UsersTable: except: return False - def update_user_api_key_by_id(self, id: str, api_key: str) -> str: + def update_user_api_key_by_id(self, db: Session, id: str, api_key: str) -> str: try: - query = User.update(api_key=api_key).where(User.id == id) - result = query.execute() - + result = db.query(User).filter_by(id=id).update({"api_key": api_key}) + db.commit() return True if result == 1 else False except: return False - def get_user_api_key_by_id(self, id: str) -> Optional[str]: + def get_user_api_key_by_id(self, db: Session, id: str) -> Optional[str]: try: - user = User.get(User.id == id) + user = db.query(User).filter_by(id=id).first() return user.api_key - except: + except Exception as e: return None -Users = UsersTable(DB) +Users = UsersTable() diff --git a/backend/apps/webui/routers/auths.py b/backend/apps/webui/routers/auths.py index 1be79d259..e83ee8cb9 100644 --- a/backend/apps/webui/routers/auths.py +++ b/backend/apps/webui/routers/auths.py @@ -10,6 +10,7 @@ import re import uuid import csv +from apps.webui.internal.db import get_db from apps.webui.models.auths import ( SigninForm, SignupForm, @@ -78,10 +79,13 @@ async def get_session_user( @router.post("/update/profile", response_model=UserResponse) async def update_profile( - form_data: UpdateProfileForm, session_user=Depends(get_current_user) + form_data: UpdateProfileForm, + session_user=Depends(get_current_user), + db=Depends(get_db), ): if session_user: user = Users.update_user_by_id( + db, session_user.id, {"profile_image_url": form_data.profile_image_url, "name": form_data.name}, ) @@ -100,16 +104,18 @@ async def update_profile( @router.post("/update/password", response_model=bool) async def update_password( - form_data: UpdatePasswordForm, session_user=Depends(get_current_user) + form_data: UpdatePasswordForm, + session_user=Depends(get_current_user), + db=Depends(get_db), ): if WEBUI_AUTH_TRUSTED_EMAIL_HEADER: raise HTTPException(400, detail=ERROR_MESSAGES.ACTION_PROHIBITED) if session_user: - user = Auths.authenticate_user(session_user.email, form_data.password) + user = Auths.authenticate_user(db, session_user.email, form_data.password) if user: hashed = get_password_hash(form_data.new_password) - return Auths.update_user_password_by_id(user.id, hashed) + return Auths.update_user_password_by_id(db, user.id, hashed) else: raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_PASSWORD) else: @@ -122,7 +128,7 @@ async def update_password( @router.post("/signin", response_model=SigninResponse) -async def signin(request: Request, response: Response, form_data: SigninForm): +async def signin(request: Request, response: Response, form_data: SigninForm, db=Depends(get_db)): if WEBUI_AUTH_TRUSTED_EMAIL_HEADER: if WEBUI_AUTH_TRUSTED_EMAIL_HEADER not in request.headers: raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_TRUSTED_HEADER) @@ -133,32 +139,34 @@ async def signin(request: Request, response: Response, form_data: SigninForm): trusted_name = request.headers.get( WEBUI_AUTH_TRUSTED_NAME_HEADER, trusted_email ) - if not Users.get_user_by_email(trusted_email.lower()): + if not Users.get_user_by_email(db, trusted_email.lower()): await signup( request, SignupForm( email=trusted_email, password=str(uuid.uuid4()), name=trusted_name ), + db, ) - user = Auths.authenticate_user_by_trusted_header(trusted_email) + user = Auths.authenticate_user_by_trusted_header(db, trusted_email) elif WEBUI_AUTH == False: admin_email = "admin@localhost" admin_password = "admin" - if Users.get_user_by_email(admin_email.lower()): - user = Auths.authenticate_user(admin_email.lower(), admin_password) + if Users.get_user_by_email(db, admin_email.lower()): + user = Auths.authenticate_user(db, admin_email.lower(), admin_password) else: - if Users.get_num_users() != 0: + if Users.get_num_users(db) != 0: raise HTTPException(400, detail=ERROR_MESSAGES.EXISTING_USERS) await signup( request, SignupForm(email=admin_email, password=admin_password, name="User"), + db, ) - user = Auths.authenticate_user(admin_email.lower(), admin_password) + user = Auths.authenticate_user(db, admin_email.lower(), admin_password) else: - user = Auths.authenticate_user(form_data.email.lower(), form_data.password) + user = Auths.authenticate_user(db, form_data.email.lower(), form_data.password) if user: token = create_token( @@ -192,7 +200,7 @@ async def signin(request: Request, response: Response, form_data: SigninForm): @router.post("/signup", response_model=SigninResponse) -async def signup(request: Request, response: Response, form_data: SignupForm): +async def signup(request: Request, response: Response, form_data: SignupForm, db=Depends(get_db)): if not request.app.state.config.ENABLE_SIGNUP and WEBUI_AUTH: raise HTTPException( status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.ACCESS_PROHIBITED @@ -203,17 +211,18 @@ async def signup(request: Request, response: Response, form_data: SignupForm): status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.INVALID_EMAIL_FORMAT ) - if Users.get_user_by_email(form_data.email.lower()): + if Users.get_user_by_email(db, form_data.email.lower()): raise HTTPException(400, detail=ERROR_MESSAGES.EMAIL_TAKEN) try: role = ( "admin" - if Users.get_num_users() == 0 + if Users.get_num_users(db) == 0 else request.app.state.config.DEFAULT_USER_ROLE ) hashed = get_password_hash(form_data.password) user = Auths.insert_new_auth( + db, form_data.email.lower(), hashed, form_data.name, @@ -267,14 +276,16 @@ async def signup(request: Request, response: Response, form_data: SignupForm): @router.post("/add", response_model=SigninResponse) -async def add_user(form_data: AddUserForm, user=Depends(get_admin_user)): +async def add_user( + form_data: AddUserForm, user=Depends(get_admin_user), db=Depends(get_db) +): if not validate_email_format(form_data.email.lower()): raise HTTPException( status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.INVALID_EMAIL_FORMAT ) - if Users.get_user_by_email(form_data.email.lower()): + if Users.get_user_by_email(db, form_data.email.lower()): raise HTTPException(400, detail=ERROR_MESSAGES.EMAIL_TAKEN) try: @@ -282,6 +293,7 @@ async def add_user(form_data: AddUserForm, user=Depends(get_admin_user)): print(form_data) hashed = get_password_hash(form_data.password) user = Auths.insert_new_auth( + db, form_data.email.lower(), hashed, form_data.name, @@ -312,7 +324,9 @@ async def add_user(form_data: AddUserForm, user=Depends(get_admin_user)): @router.get("/admin/details") -async def get_admin_details(request: Request, user=Depends(get_current_user)): +async def get_admin_details( + request: Request, user=Depends(get_current_user), db=Depends(get_db) +): if request.app.state.config.SHOW_ADMIN_DETAILS: admin_email = request.app.state.config.ADMIN_EMAIL admin_name = None @@ -320,11 +334,11 @@ async def get_admin_details(request: Request, user=Depends(get_current_user)): print(admin_email, admin_name) if admin_email: - admin = Users.get_user_by_email(admin_email) + admin = Users.get_user_by_email(db, admin_email) if admin: admin_name = admin.name else: - admin = Users.get_first_user() + admin = Users.get_first_user(db) if admin: admin_email = admin.email admin_name = admin.name @@ -397,9 +411,9 @@ async def update_admin_config( # create api key @router.post("/api_key", response_model=ApiKey) -async def create_api_key_(user=Depends(get_current_user)): +async def create_api_key_(user=Depends(get_current_user), db=Depends(get_db)): api_key = create_api_key() - success = Users.update_user_api_key_by_id(user.id, api_key) + success = Users.update_user_api_key_by_id(db, user.id, api_key) if success: return { "api_key": api_key, @@ -410,15 +424,15 @@ async def create_api_key_(user=Depends(get_current_user)): # delete api key @router.delete("/api_key", response_model=bool) -async def delete_api_key(user=Depends(get_current_user)): - success = Users.update_user_api_key_by_id(user.id, None) +async def delete_api_key(user=Depends(get_current_user), db=Depends(get_db)): + success = Users.update_user_api_key_by_id(db, user.id, None) return success # get api key @router.get("/api_key", response_model=ApiKey) -async def get_api_key(user=Depends(get_current_user)): - api_key = Users.get_user_api_key_by_id(user.id) +async def get_api_key(user=Depends(get_current_user), db=Depends(get_db)): + api_key = Users.get_user_api_key_by_id(db, user.id) if api_key: return { "api_key": api_key, diff --git a/backend/apps/webui/routers/chats.py b/backend/apps/webui/routers/chats.py index 9d1cceaa1..1454d47bd 100644 --- a/backend/apps/webui/routers/chats.py +++ b/backend/apps/webui/routers/chats.py @@ -1,6 +1,8 @@ from fastapi import Depends, Request, HTTPException, status from datetime import datetime, timedelta from typing import List, Union, Optional + +from apps.webui.internal.db import get_db from utils.utils import get_current_user, get_admin_user from fastapi import APIRouter from pydantic import BaseModel @@ -43,9 +45,9 @@ router = APIRouter() @router.get("/", response_model=List[ChatTitleIdResponse]) @router.get("/list", response_model=List[ChatTitleIdResponse]) async def get_session_user_chat_list( - user=Depends(get_current_user), skip: int = 0, limit: int = 50 + user=Depends(get_current_user), skip: int = 0, limit: int = 50, db=Depends(get_db) ): - return Chats.get_chat_list_by_user_id(user.id, skip, limit) + return Chats.get_chat_list_by_user_id(db, user.id, skip, limit) ############################ @@ -54,7 +56,9 @@ async def get_session_user_chat_list( @router.delete("/", response_model=bool) -async def delete_all_user_chats(request: Request, user=Depends(get_current_user)): +async def delete_all_user_chats( + request: Request, user=Depends(get_current_user), db=Depends(get_db) +): if ( user.role == "user" @@ -65,7 +69,7 @@ async def delete_all_user_chats(request: Request, user=Depends(get_current_user) detail=ERROR_MESSAGES.ACCESS_PROHIBITED, ) - result = Chats.delete_chats_by_user_id(user.id) + result = Chats.delete_chats_by_user_id(db, user.id) return result @@ -76,10 +80,14 @@ async def delete_all_user_chats(request: Request, user=Depends(get_current_user) @router.get("/list/user/{user_id}", response_model=List[ChatTitleIdResponse]) async def get_user_chat_list_by_user_id( - user_id: str, user=Depends(get_admin_user), skip: int = 0, limit: int = 50 + user_id: str, + user=Depends(get_admin_user), + skip: int = 0, + limit: int = 50, + db=Depends(get_db), ): return Chats.get_chat_list_by_user_id( - user_id, include_archived=True, skip=skip, limit=limit + db, user_id, include_archived=True, skip=skip, limit=limit ) @@ -89,9 +97,11 @@ async def get_user_chat_list_by_user_id( @router.post("/new", response_model=Optional[ChatResponse]) -async def create_new_chat(form_data: ChatForm, user=Depends(get_current_user)): +async def create_new_chat( + form_data: ChatForm, user=Depends(get_current_user), db=Depends(get_db) +): try: - chat = Chats.insert_new_chat(user.id, form_data) + chat = Chats.insert_new_chat(db, user.id, form_data) return ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)}) except Exception as e: log.exception(e) @@ -106,10 +116,10 @@ async def create_new_chat(form_data: ChatForm, user=Depends(get_current_user)): @router.get("/all", response_model=List[ChatResponse]) -async def get_user_chats(user=Depends(get_current_user)): +async def get_user_chats(user=Depends(get_current_user), db=Depends(get_db)): return [ ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)}) - for chat in Chats.get_chats_by_user_id(user.id) + for chat in Chats.get_chats_by_user_id(db, user.id) ] @@ -119,10 +129,10 @@ async def get_user_chats(user=Depends(get_current_user)): @router.get("/all/archived", response_model=List[ChatResponse]) -async def get_user_chats(user=Depends(get_current_user)): +async def get_user_archived_chats(user=Depends(get_current_user), db=Depends(get_db)): return [ ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)}) - for chat in Chats.get_archived_chats_by_user_id(user.id) + for chat in Chats.get_archived_chats_by_user_id(db, user.id) ] @@ -132,7 +142,7 @@ async def get_user_chats(user=Depends(get_current_user)): @router.get("/all/db", response_model=List[ChatResponse]) -async def get_all_user_chats_in_db(user=Depends(get_admin_user)): +async def get_all_user_chats_in_db(user=Depends(get_admin_user), db=Depends(get_db)): if not ENABLE_ADMIN_EXPORT: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, @@ -140,7 +150,7 @@ async def get_all_user_chats_in_db(user=Depends(get_admin_user)): ) return [ ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)}) - for chat in Chats.get_chats() + for chat in Chats.get_chats(db) ] @@ -151,9 +161,9 @@ async def get_all_user_chats_in_db(user=Depends(get_admin_user)): @router.get("/archived", response_model=List[ChatTitleIdResponse]) async def get_archived_session_user_chat_list( - user=Depends(get_current_user), skip: int = 0, limit: int = 50 + user=Depends(get_current_user), skip: int = 0, limit: int = 50, db=Depends(get_db) ): - return Chats.get_archived_chat_list_by_user_id(user.id, skip, limit) + return Chats.get_archived_chat_list_by_user_id(db, user.id, skip, limit) ############################ @@ -162,8 +172,8 @@ async def get_archived_session_user_chat_list( @router.post("/archive/all", response_model=bool) -async def archive_all_chats(user=Depends(get_current_user)): - return Chats.archive_all_chats_by_user_id(user.id) +async def archive_all_chats(user=Depends(get_current_user), db=Depends(get_db)): + return Chats.archive_all_chats_by_user_id(db, user.id) ############################ @@ -172,16 +182,18 @@ async def archive_all_chats(user=Depends(get_current_user)): @router.get("/share/{share_id}", response_model=Optional[ChatResponse]) -async def get_shared_chat_by_id(share_id: str, user=Depends(get_current_user)): +async def get_shared_chat_by_id( + share_id: str, user=Depends(get_current_user), db=Depends(get_db) +): if user.role == "pending": raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND ) if user.role == "user": - chat = Chats.get_chat_by_share_id(share_id) + chat = Chats.get_chat_by_share_id(db, share_id) elif user.role == "admin": - chat = Chats.get_chat_by_id(share_id) + chat = Chats.get_chat_by_id(db, share_id) if chat: return ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)}) @@ -204,21 +216,23 @@ class TagNameForm(BaseModel): @router.post("/tags", response_model=List[ChatTitleIdResponse]) async def get_user_chat_list_by_tag_name( - form_data: TagNameForm, user=Depends(get_current_user) + form_data: TagNameForm, user=Depends(get_current_user), db=Depends(get_db) ): print(form_data) chat_ids = [ chat_id_tag.chat_id for chat_id_tag in Tags.get_chat_ids_by_tag_name_and_user_id( - form_data.name, user.id + db, form_data.name, user.id ) ] - chats = Chats.get_chat_list_by_chat_ids(chat_ids, form_data.skip, form_data.limit) + chats = Chats.get_chat_list_by_chat_ids( + db, chat_ids, form_data.skip, form_data.limit + ) if len(chats) == 0: - Tags.delete_tag_by_tag_name_and_user_id(form_data.name, user.id) + Tags.delete_tag_by_tag_name_and_user_id(db, form_data.name, user.id) return chats @@ -229,9 +243,9 @@ async def get_user_chat_list_by_tag_name( @router.get("/tags/all", response_model=List[TagModel]) -async def get_all_tags(user=Depends(get_current_user)): +async def get_all_tags(user=Depends(get_current_user), db=Depends(get_db)): try: - tags = Tags.get_tags_by_user_id(user.id) + tags = Tags.get_tags_by_user_id(db, user.id) return tags except Exception as e: log.exception(e) @@ -246,8 +260,8 @@ async def get_all_tags(user=Depends(get_current_user)): @router.get("/{id}", response_model=Optional[ChatResponse]) -async def get_chat_by_id(id: str, user=Depends(get_current_user)): - chat = Chats.get_chat_by_id_and_user_id(id, user.id) +async def get_chat_by_id(id: str, user=Depends(get_current_user), db=Depends(get_db)): + chat = Chats.get_chat_by_id_and_user_id(db, id, user.id) if chat: return ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)}) @@ -264,13 +278,13 @@ async def get_chat_by_id(id: str, user=Depends(get_current_user)): @router.post("/{id}", response_model=Optional[ChatResponse]) async def update_chat_by_id( - id: str, form_data: ChatForm, user=Depends(get_current_user) + id: str, form_data: ChatForm, user=Depends(get_current_user), db=Depends(get_db) ): - chat = Chats.get_chat_by_id_and_user_id(id, user.id) + chat = Chats.get_chat_by_id_and_user_id(db, id, user.id) if chat: updated_chat = {**json.loads(chat.chat), **form_data.chat} - chat = Chats.update_chat_by_id(id, updated_chat) + chat = Chats.update_chat_by_id(db, id, updated_chat) return ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)}) else: raise HTTPException( @@ -285,10 +299,12 @@ async def update_chat_by_id( @router.delete("/{id}", response_model=bool) -async def delete_chat_by_id(request: Request, id: str, user=Depends(get_current_user)): +async def delete_chat_by_id( + request: Request, id: str, user=Depends(get_current_user), db=Depends(get_db) +): if user.role == "admin": - result = Chats.delete_chat_by_id(id) + result = Chats.delete_chat_by_id(db, id) return result else: if not request.app.state.config.USER_PERMISSIONS["chat"]["deletion"]: @@ -297,7 +313,7 @@ async def delete_chat_by_id(request: Request, id: str, user=Depends(get_current_ detail=ERROR_MESSAGES.ACCESS_PROHIBITED, ) - result = Chats.delete_chat_by_id_and_user_id(id, user.id) + result = Chats.delete_chat_by_id_and_user_id(db, id, user.id) return result @@ -307,8 +323,8 @@ async def delete_chat_by_id(request: Request, id: str, user=Depends(get_current_ @router.get("/{id}/clone", response_model=Optional[ChatResponse]) -async def clone_chat_by_id(id: str, user=Depends(get_current_user)): - chat = Chats.get_chat_by_id_and_user_id(id, user.id) +async def clone_chat_by_id(id: str, user=Depends(get_current_user), db=Depends(get_db)): + chat = Chats.get_chat_by_id_and_user_id(db, id, user.id) if chat: chat_body = json.loads(chat.chat) @@ -319,7 +335,7 @@ async def clone_chat_by_id(id: str, user=Depends(get_current_user)): "title": f"Clone of {chat.title}", } - chat = Chats.insert_new_chat(user.id, ChatForm(**{"chat": updated_chat})) + chat = Chats.insert_new_chat(db, user.id, ChatForm(**{"chat": updated_chat})) return ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)}) else: raise HTTPException( @@ -333,10 +349,12 @@ async def clone_chat_by_id(id: str, user=Depends(get_current_user)): @router.get("/{id}/archive", response_model=Optional[ChatResponse]) -async def archive_chat_by_id(id: str, user=Depends(get_current_user)): - chat = Chats.get_chat_by_id_and_user_id(id, user.id) +async def archive_chat_by_id( + id: str, user=Depends(get_current_user), db=Depends(get_db) +): + chat = Chats.get_chat_by_id_and_user_id(db, id, user.id) if chat: - chat = Chats.toggle_chat_archive_by_id(id) + chat = Chats.toggle_chat_archive_by_id(db, id) return ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)}) else: raise HTTPException( @@ -350,16 +368,16 @@ async def archive_chat_by_id(id: str, user=Depends(get_current_user)): @router.post("/{id}/share", response_model=Optional[ChatResponse]) -async def share_chat_by_id(id: str, user=Depends(get_current_user)): - chat = Chats.get_chat_by_id_and_user_id(id, user.id) +async def share_chat_by_id(id: str, user=Depends(get_current_user), db=Depends(get_db)): + chat = Chats.get_chat_by_id_and_user_id(db, id, user.id) if chat: if chat.share_id: - shared_chat = Chats.update_shared_chat_by_chat_id(chat.id) + shared_chat = Chats.update_shared_chat_by_chat_id(db, chat.id) return ChatResponse( **{**shared_chat.model_dump(), "chat": json.loads(shared_chat.chat)} ) - shared_chat = Chats.insert_shared_chat_by_chat_id(chat.id) + shared_chat = Chats.insert_shared_chat_by_chat_id(db, chat.id) if not shared_chat: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, @@ -382,14 +400,16 @@ async def share_chat_by_id(id: str, user=Depends(get_current_user)): @router.delete("/{id}/share", response_model=Optional[bool]) -async def delete_shared_chat_by_id(id: str, user=Depends(get_current_user)): - chat = Chats.get_chat_by_id_and_user_id(id, user.id) +async def delete_shared_chat_by_id( + id: str, user=Depends(get_current_user), db=Depends(get_db) +): + chat = Chats.get_chat_by_id_and_user_id(db, id, user.id) if chat: if not chat.share_id: return False - result = Chats.delete_shared_chat_by_chat_id(id) - update_result = Chats.update_chat_share_id_by_id(id, None) + result = Chats.delete_shared_chat_by_chat_id(db, id) + update_result = Chats.update_chat_share_id_by_id(db, id, None) return result and update_result != None else: @@ -405,8 +425,10 @@ async def delete_shared_chat_by_id(id: str, user=Depends(get_current_user)): @router.get("/{id}/tags", response_model=List[TagModel]) -async def get_chat_tags_by_id(id: str, user=Depends(get_current_user)): - tags = Tags.get_tags_by_chat_id_and_user_id(id, user.id) +async def get_chat_tags_by_id( + id: str, user=Depends(get_current_user), db=Depends(get_db) +): + tags = Tags.get_tags_by_chat_id_and_user_id(db, id, user.id) if tags != None: return tags @@ -423,12 +445,15 @@ async def get_chat_tags_by_id(id: str, user=Depends(get_current_user)): @router.post("/{id}/tags", response_model=Optional[ChatIdTagModel]) async def add_chat_tag_by_id( - id: str, form_data: ChatIdTagForm, user=Depends(get_current_user) + id: str, + form_data: ChatIdTagForm, + user=Depends(get_current_user), + db=Depends(get_db), ): - tags = Tags.get_tags_by_chat_id_and_user_id(id, user.id) + tags = Tags.get_tags_by_chat_id_and_user_id(db, id, user.id) if form_data.tag_name not in tags: - tag = Tags.add_tag_to_chat(user.id, form_data) + tag = Tags.add_tag_to_chat(db, user.id, form_data) if tag: return tag @@ -450,10 +475,13 @@ async def add_chat_tag_by_id( @router.delete("/{id}/tags", response_model=Optional[bool]) async def delete_chat_tag_by_id( - id: str, form_data: ChatIdTagForm, user=Depends(get_current_user) + id: str, + form_data: ChatIdTagForm, + user=Depends(get_current_user), + db=Depends(get_db), ): result = Tags.delete_tag_by_tag_name_and_chat_id_and_user_id( - form_data.tag_name, id, user.id + db, form_data.tag_name, id, user.id ) if result: @@ -470,8 +498,10 @@ async def delete_chat_tag_by_id( @router.delete("/{id}/tags/all", response_model=Optional[bool]) -async def delete_all_chat_tags_by_id(id: str, user=Depends(get_current_user)): - result = Tags.delete_tags_by_chat_id_and_user_id(id, user.id) +async def delete_all_chat_tags_by_id( + id: str, user=Depends(get_current_user), db=Depends(get_db) +): + result = Tags.delete_tags_by_chat_id_and_user_id(db, id, user.id) if result: return result diff --git a/backend/apps/webui/routers/documents.py b/backend/apps/webui/routers/documents.py index 311455390..b9a42352a 100644 --- a/backend/apps/webui/routers/documents.py +++ b/backend/apps/webui/routers/documents.py @@ -6,6 +6,7 @@ from fastapi import APIRouter from pydantic import BaseModel import json +from apps.webui.internal.db import get_db from apps.webui.models.documents import ( Documents, DocumentForm, @@ -25,7 +26,7 @@ router = APIRouter() @router.get("/", response_model=List[DocumentResponse]) -async def get_documents(user=Depends(get_current_user)): +async def get_documents(user=Depends(get_current_user), db=Depends(get_db)): docs = [ DocumentResponse( **{ @@ -33,7 +34,7 @@ async def get_documents(user=Depends(get_current_user)): "content": json.loads(doc.content if doc.content else "{}"), } ) - for doc in Documents.get_docs() + for doc in Documents.get_docs(db) ] return docs @@ -44,10 +45,12 @@ async def get_documents(user=Depends(get_current_user)): @router.post("/create", response_model=Optional[DocumentResponse]) -async def create_new_doc(form_data: DocumentForm, user=Depends(get_admin_user)): - doc = Documents.get_doc_by_name(form_data.name) +async def create_new_doc( + form_data: DocumentForm, user=Depends(get_admin_user), db=Depends(get_db) +): + doc = Documents.get_doc_by_name(db, form_data.name) if doc == None: - doc = Documents.insert_new_doc(user.id, form_data) + doc = Documents.insert_new_doc(db, user.id, form_data) if doc: return DocumentResponse( @@ -74,8 +77,10 @@ async def create_new_doc(form_data: DocumentForm, user=Depends(get_admin_user)): @router.get("/doc", response_model=Optional[DocumentResponse]) -async def get_doc_by_name(name: str, user=Depends(get_current_user)): - doc = Documents.get_doc_by_name(name) +async def get_doc_by_name( + name: str, user=Depends(get_current_user), db=Depends(get_db) +): + doc = Documents.get_doc_by_name(db, name) if doc: return DocumentResponse( @@ -106,8 +111,12 @@ class TagDocumentForm(BaseModel): @router.post("/doc/tags", response_model=Optional[DocumentResponse]) -async def tag_doc_by_name(form_data: TagDocumentForm, user=Depends(get_current_user)): - doc = Documents.update_doc_content_by_name(form_data.name, {"tags": form_data.tags}) +async def tag_doc_by_name( + form_data: TagDocumentForm, user=Depends(get_current_user), db=Depends(get_db) +): + doc = Documents.update_doc_content_by_name( + db, form_data.name, {"tags": form_data.tags} + ) if doc: return DocumentResponse( @@ -130,9 +139,12 @@ async def tag_doc_by_name(form_data: TagDocumentForm, user=Depends(get_current_u @router.post("/doc/update", response_model=Optional[DocumentResponse]) async def update_doc_by_name( - name: str, form_data: DocumentUpdateForm, user=Depends(get_admin_user) + name: str, + form_data: DocumentUpdateForm, + user=Depends(get_admin_user), + db=Depends(get_db), ): - doc = Documents.update_doc_by_name(name, form_data) + doc = Documents.update_doc_by_name(db, name, form_data) if doc: return DocumentResponse( **{ @@ -153,6 +165,8 @@ async def update_doc_by_name( @router.delete("/doc/delete", response_model=bool) -async def delete_doc_by_name(name: str, user=Depends(get_admin_user)): - result = Documents.delete_doc_by_name(name) +async def delete_doc_by_name( + name: str, user=Depends(get_admin_user), db=Depends(get_db) +): + result = Documents.delete_doc_by_name(db, name) return result diff --git a/backend/apps/webui/routers/files.py b/backend/apps/webui/routers/files.py index 3b6d44aa5..2ed119ad0 100644 --- a/backend/apps/webui/routers/files.py +++ b/backend/apps/webui/routers/files.py @@ -20,6 +20,7 @@ from fastapi.responses import StreamingResponse, JSONResponse, FileResponse from pydantic import BaseModel import json +from apps.webui.internal.db import get_db from apps.webui.models.files import ( Files, FileForm, @@ -53,6 +54,7 @@ router = APIRouter() def upload_file( file: UploadFile = File(...), user=Depends(get_verified_user), + db=Depends(get_db) ): log.info(f"file.content_type: {file.content_type}") try: @@ -70,6 +72,7 @@ def upload_file( f.close() file = Files.insert_new_file( + db, user.id, FileForm( **{ @@ -106,8 +109,8 @@ def upload_file( @router.get("/", response_model=List[FileModel]) -async def list_files(user=Depends(get_verified_user)): - files = Files.get_files() +async def list_files(user=Depends(get_verified_user), db=Depends(get_db)): + files = Files.get_files(db) return files @@ -117,8 +120,8 @@ async def list_files(user=Depends(get_verified_user)): @router.delete("/all") -async def delete_all_files(user=Depends(get_admin_user)): - result = Files.delete_all_files() +async def delete_all_files(user=Depends(get_admin_user), db=Depends(get_db)): + result = Files.delete_all_files(db) if result: folder = f"{UPLOAD_DIR}" @@ -154,8 +157,8 @@ async def delete_all_files(user=Depends(get_admin_user)): @router.get("/{id}", response_model=Optional[FileModel]) -async def get_file_by_id(id: str, user=Depends(get_verified_user)): - file = Files.get_file_by_id(id) +async def get_file_by_id(id: str, user=Depends(get_verified_user), db=Depends(get_db)): + file = Files.get_file_by_id(db, id) if file: return file @@ -172,8 +175,8 @@ async def get_file_by_id(id: str, user=Depends(get_verified_user)): @router.get("/{id}/content", response_model=Optional[FileModel]) -async def get_file_content_by_id(id: str, user=Depends(get_verified_user)): - file = Files.get_file_by_id(id) +async def get_file_content_by_id(id: str, user=Depends(get_verified_user), db=Depends(get_db)): + file = Files.get_file_by_id(db, id) if file: file_path = Path(file.meta["path"]) @@ -223,11 +226,11 @@ async def get_file_content_by_id(id: str, user=Depends(get_verified_user)): @router.delete("/{id}") -async def delete_file_by_id(id: str, user=Depends(get_verified_user)): - file = Files.get_file_by_id(id) +async def delete_file_by_id(id: str, user=Depends(get_verified_user), db=Depends(get_db)): + file = Files.get_file_by_id(db, id) if file: - result = Files.delete_file_by_id(id) + result = Files.delete_file_by_id(db, id) if result: return {"message": "File deleted successfully"} else: diff --git a/backend/apps/webui/routers/functions.py b/backend/apps/webui/routers/functions.py index 4c89ca487..f15566702 100644 --- a/backend/apps/webui/routers/functions.py +++ b/backend/apps/webui/routers/functions.py @@ -6,6 +6,7 @@ from fastapi import APIRouter from pydantic import BaseModel import json +from apps.webui.internal.db import get_db from apps.webui.models.functions import ( Functions, FunctionForm, @@ -31,8 +32,8 @@ router = APIRouter() @router.get("/", response_model=List[FunctionResponse]) -async def get_functions(user=Depends(get_verified_user)): - return Functions.get_functions() +async def get_functions(user=Depends(get_verified_user), db=Depends(get_db)): + return Functions.get_functions(db) ############################ @@ -41,8 +42,8 @@ async def get_functions(user=Depends(get_verified_user)): @router.get("/export", response_model=List[FunctionModel]) -async def get_functions(user=Depends(get_admin_user)): - return Functions.get_functions() +async def get_functions(user=Depends(get_admin_user), db=Depends(get_db)): + return Functions.get_functions(db) ############################ @@ -52,7 +53,7 @@ async def get_functions(user=Depends(get_admin_user)): @router.post("/create", response_model=Optional[FunctionResponse]) async def create_new_function( - request: Request, form_data: FunctionForm, user=Depends(get_admin_user) + request: Request, form_data: FunctionForm, user=Depends(get_admin_user), db=Depends(get_db) ): if not form_data.id.isidentifier(): raise HTTPException( @@ -62,7 +63,7 @@ async def create_new_function( form_data.id = form_data.id.lower() - function = Functions.get_function_by_id(form_data.id) + function = Functions.get_function_by_id(db, form_data.id) if function == None: function_path = os.path.join(FUNCTIONS_DIR, f"{form_data.id}.py") try: @@ -77,7 +78,7 @@ async def create_new_function( FUNCTIONS = request.app.state.FUNCTIONS FUNCTIONS[form_data.id] = function_module - function = Functions.insert_new_function(user.id, function_type, form_data) + function = Functions.insert_new_function(db, user.id, function_type, form_data) function_cache_dir = Path(CACHE_DIR) / "functions" / form_data.id function_cache_dir.mkdir(parents=True, exist_ok=True) @@ -108,8 +109,8 @@ async def create_new_function( @router.get("/id/{id}", response_model=Optional[FunctionModel]) -async def get_function_by_id(id: str, user=Depends(get_admin_user)): - function = Functions.get_function_by_id(id) +async def get_function_by_id(id: str, user=Depends(get_admin_user), db=Depends(get_db)): + function = Functions.get_function_by_id(db, id) if function: return function @@ -154,7 +155,7 @@ async def toggle_function_by_id(id: str, user=Depends(get_admin_user)): @router.post("/id/{id}/update", response_model=Optional[FunctionModel]) async def update_function_by_id( - request: Request, id: str, form_data: FunctionForm, user=Depends(get_admin_user) + request: Request, id: str, form_data: FunctionForm, user=Depends(get_admin_user), db=Depends(get_db) ): function_path = os.path.join(FUNCTIONS_DIR, f"{id}.py") @@ -171,7 +172,7 @@ async def update_function_by_id( updated = {**form_data.model_dump(exclude={"id"}), "type": function_type} print(updated) - function = Functions.update_function_by_id(id, updated) + function = Functions.update_function_by_id(db, id, updated) if function: return function @@ -195,9 +196,9 @@ async def update_function_by_id( @router.delete("/id/{id}/delete", response_model=bool) async def delete_function_by_id( - request: Request, id: str, user=Depends(get_admin_user) + request: Request, id: str, user=Depends(get_admin_user), db=Depends(get_db) ): - result = Functions.delete_function_by_id(id) + result = Functions.delete_function_by_id(db, id) if result: FUNCTIONS = request.app.state.FUNCTIONS diff --git a/backend/apps/webui/routers/memories.py b/backend/apps/webui/routers/memories.py index e9ae96173..e7fafa37b 100644 --- a/backend/apps/webui/routers/memories.py +++ b/backend/apps/webui/routers/memories.py @@ -7,6 +7,7 @@ from fastapi import APIRouter from pydantic import BaseModel import logging +from apps.webui.internal.db import get_db from apps.webui.models.memories import Memories, MemoryModel from utils.utils import get_verified_user @@ -31,8 +32,8 @@ async def get_embeddings(request: Request): @router.get("/", response_model=List[MemoryModel]) -async def get_memories(user=Depends(get_verified_user)): - return Memories.get_memories_by_user_id(user.id) +async def get_memories(user=Depends(get_verified_user), db=Depends(get_db)): + return Memories.get_memories_by_user_id(db, user.id) ############################ @@ -50,9 +51,12 @@ class MemoryUpdateModel(BaseModel): @router.post("/add", response_model=Optional[MemoryModel]) async def add_memory( - request: Request, form_data: AddMemoryForm, user=Depends(get_verified_user) + request: Request, + form_data: AddMemoryForm, + user=Depends(get_verified_user), + db=Depends(get_db), ): - memory = Memories.insert_new_memory(user.id, form_data.content) + memory = Memories.insert_new_memory(db, user.id, form_data.content) memory_embedding = request.app.state.EMBEDDING_FUNCTION(memory.content) collection = CHROMA_CLIENT.get_or_create_collection(name=f"user-memory-{user.id}") @@ -72,8 +76,9 @@ async def update_memory_by_id( request: Request, form_data: MemoryUpdateModel, user=Depends(get_verified_user), + db=Depends(get_db), ): - memory = Memories.update_memory_by_id(memory_id, form_data.content) + memory = Memories.update_memory_by_id(db, memory_id, form_data.content) if memory is None: raise HTTPException(status_code=404, detail="Memory not found") @@ -124,12 +129,12 @@ async def query_memory( ############################ @router.get("/reset", response_model=bool) async def reset_memory_from_vector_db( - request: Request, user=Depends(get_verified_user) + request: Request, user=Depends(get_verified_user), db=Depends(get_db) ): CHROMA_CLIENT.delete_collection(f"user-memory-{user.id}") collection = CHROMA_CLIENT.get_or_create_collection(name=f"user-memory-{user.id}") - memories = Memories.get_memories_by_user_id(user.id) + memories = Memories.get_memories_by_user_id(db, user.id) for memory in memories: memory_embedding = request.app.state.EMBEDDING_FUNCTION(memory.content) collection.upsert( @@ -146,8 +151,8 @@ async def reset_memory_from_vector_db( @router.delete("/user", response_model=bool) -async def delete_memory_by_user_id(user=Depends(get_verified_user)): - result = Memories.delete_memories_by_user_id(user.id) +async def delete_memory_by_user_id(user=Depends(get_verified_user), db=Depends(get_db)): + result = Memories.delete_memories_by_user_id(db, user.id) if result: try: @@ -165,8 +170,10 @@ async def delete_memory_by_user_id(user=Depends(get_verified_user)): @router.delete("/{memory_id}", response_model=bool) -async def delete_memory_by_id(memory_id: str, user=Depends(get_verified_user)): - result = Memories.delete_memory_by_id_and_user_id(memory_id, user.id) +async def delete_memory_by_id( + memory_id: str, user=Depends(get_verified_user), db=Depends(get_db) +): + result = Memories.delete_memory_by_id_and_user_id(db, memory_id, user.id) if result: collection = CHROMA_CLIENT.get_or_create_collection( diff --git a/backend/apps/webui/routers/models.py b/backend/apps/webui/routers/models.py index acc1c6b47..f151e8864 100644 --- a/backend/apps/webui/routers/models.py +++ b/backend/apps/webui/routers/models.py @@ -5,6 +5,8 @@ from typing import List, Union, Optional from fastapi import APIRouter from pydantic import BaseModel import json + +from apps.webui.internal.db import get_db from apps.webui.models.models import Models, ModelModel, ModelForm, ModelResponse from utils.utils import get_verified_user, get_admin_user @@ -18,8 +20,8 @@ router = APIRouter() @router.get("/", response_model=List[ModelResponse]) -async def get_models(user=Depends(get_verified_user)): - return Models.get_all_models() +async def get_models(user=Depends(get_verified_user), db=Depends(get_db)): + return Models.get_all_models(db) ############################ @@ -29,7 +31,10 @@ async def get_models(user=Depends(get_verified_user)): @router.post("/add", response_model=Optional[ModelModel]) async def add_new_model( - request: Request, form_data: ModelForm, user=Depends(get_admin_user) + request: Request, + form_data: ModelForm, + user=Depends(get_admin_user), + db=Depends(get_db), ): if form_data.id in request.app.state.MODELS: raise HTTPException( @@ -37,7 +42,7 @@ async def add_new_model( detail=ERROR_MESSAGES.MODEL_ID_TAKEN, ) else: - model = Models.insert_new_model(form_data, user.id) + model = Models.insert_new_model(db, form_data, user.id) if model: return model @@ -53,9 +58,9 @@ async def add_new_model( ############################ -@router.get("/", response_model=Optional[ModelModel]) -async def get_model_by_id(id: str, user=Depends(get_verified_user)): - model = Models.get_model_by_id(id) +@router.get("/{id}", response_model=Optional[ModelModel]) +async def get_model_by_id(id: str, user=Depends(get_verified_user), db=Depends(get_db)): + model = Models.get_model_by_id(db, id) if model: return model @@ -73,15 +78,19 @@ async def get_model_by_id(id: str, user=Depends(get_verified_user)): @router.post("/update", response_model=Optional[ModelModel]) async def update_model_by_id( - request: Request, id: str, form_data: ModelForm, user=Depends(get_admin_user) + request: Request, + id: str, + form_data: ModelForm, + user=Depends(get_admin_user), + db=Depends(get_db), ): - model = Models.get_model_by_id(id) + model = Models.get_model_by_id(db, id) if model: - model = Models.update_model_by_id(id, form_data) + model = Models.update_model_by_id(db, id, form_data) return model else: if form_data.id in request.app.state.MODELS: - model = Models.insert_new_model(form_data, user.id) + model = Models.insert_new_model(db, form_data, user.id) if model: return model else: @@ -102,6 +111,6 @@ async def update_model_by_id( @router.delete("/delete", response_model=bool) -async def delete_model_by_id(id: str, user=Depends(get_admin_user)): - result = Models.delete_model_by_id(id) +async def delete_model_by_id(id: str, user=Depends(get_admin_user), db=Depends(get_db)): + result = Models.delete_model_by_id(db, id) return result diff --git a/backend/apps/webui/routers/prompts.py b/backend/apps/webui/routers/prompts.py index 47d8c7012..c8f173a1e 100644 --- a/backend/apps/webui/routers/prompts.py +++ b/backend/apps/webui/routers/prompts.py @@ -6,6 +6,7 @@ from fastapi import APIRouter from pydantic import BaseModel import json +from apps.webui.internal.db import get_db from apps.webui.models.prompts import Prompts, PromptForm, PromptModel from utils.utils import get_current_user, get_admin_user @@ -19,8 +20,8 @@ router = APIRouter() @router.get("/", response_model=List[PromptModel]) -async def get_prompts(user=Depends(get_current_user)): - return Prompts.get_prompts() +async def get_prompts(user=Depends(get_current_user), db=Depends(get_db)): + return Prompts.get_prompts(db) ############################ @@ -29,10 +30,12 @@ async def get_prompts(user=Depends(get_current_user)): @router.post("/create", response_model=Optional[PromptModel]) -async def create_new_prompt(form_data: PromptForm, user=Depends(get_admin_user)): - prompt = Prompts.get_prompt_by_command(form_data.command) +async def create_new_prompt( + form_data: PromptForm, user=Depends(get_admin_user), db=Depends(get_db) +): + prompt = Prompts.get_prompt_by_command(db, form_data.command) if prompt == None: - prompt = Prompts.insert_new_prompt(user.id, form_data) + prompt = Prompts.insert_new_prompt(db, user.id, form_data) if prompt: return prompt @@ -52,8 +55,10 @@ async def create_new_prompt(form_data: PromptForm, user=Depends(get_admin_user)) @router.get("/command/{command}", response_model=Optional[PromptModel]) -async def get_prompt_by_command(command: str, user=Depends(get_current_user)): - prompt = Prompts.get_prompt_by_command(f"/{command}") +async def get_prompt_by_command( + command: str, user=Depends(get_current_user), db=Depends(get_db) +): + prompt = Prompts.get_prompt_by_command(db, f"/{command}") if prompt: return prompt @@ -71,9 +76,12 @@ async def get_prompt_by_command(command: str, user=Depends(get_current_user)): @router.post("/command/{command}/update", response_model=Optional[PromptModel]) async def update_prompt_by_command( - command: str, form_data: PromptForm, user=Depends(get_admin_user) + command: str, + form_data: PromptForm, + user=Depends(get_admin_user), + db=Depends(get_db), ): - prompt = Prompts.update_prompt_by_command(f"/{command}", form_data) + prompt = Prompts.update_prompt_by_command(db, f"/{command}", form_data) if prompt: return prompt else: @@ -89,6 +97,8 @@ async def update_prompt_by_command( @router.delete("/command/{command}/delete", response_model=bool) -async def delete_prompt_by_command(command: str, user=Depends(get_admin_user)): - result = Prompts.delete_prompt_by_command(f"/{command}") +async def delete_prompt_by_command( + command: str, user=Depends(get_admin_user), db=Depends(get_db) +): + result = Prompts.delete_prompt_by_command(db, f"/{command}") return result diff --git a/backend/apps/webui/routers/tools.py b/backend/apps/webui/routers/tools.py index d20584c22..4eb6d1caf 100644 --- a/backend/apps/webui/routers/tools.py +++ b/backend/apps/webui/routers/tools.py @@ -6,7 +6,7 @@ from fastapi import APIRouter from pydantic import BaseModel import json - +from apps.webui.internal.db import get_db from apps.webui.models.users import Users from apps.webui.models.tools import Tools, ToolForm, ToolModel, ToolResponse from apps.webui.utils import load_toolkit_module_by_id @@ -34,7 +34,7 @@ router = APIRouter() @router.get("/", response_model=List[ToolResponse]) -async def get_toolkits(user=Depends(get_verified_user)): +async def get_toolkits(user=Depends(get_verified_user), db=Depends(get_db)): toolkits = [toolkit for toolkit in Tools.get_tools()] return toolkits @@ -45,8 +45,8 @@ async def get_toolkits(user=Depends(get_verified_user)): @router.get("/export", response_model=List[ToolModel]) -async def get_toolkits(user=Depends(get_admin_user)): - toolkits = [toolkit for toolkit in Tools.get_tools()] +async def get_toolkits(user=Depends(get_admin_user), db=Depends(get_db)): + toolkits = [toolkit for toolkit in Tools.get_tools(db)] return toolkits @@ -57,7 +57,10 @@ async def get_toolkits(user=Depends(get_admin_user)): @router.post("/create", response_model=Optional[ToolResponse]) async def create_new_toolkit( - request: Request, form_data: ToolForm, user=Depends(get_admin_user) + request: Request, + form_data: ToolForm, + user=Depends(get_admin_user), + db=Depends(get_db), ): if not form_data.id.isidentifier(): raise HTTPException( @@ -67,7 +70,7 @@ async def create_new_toolkit( form_data.id = form_data.id.lower() - toolkit = Tools.get_tool_by_id(form_data.id) + toolkit = Tools.get_tool_by_id(db, form_data.id) if toolkit == None: toolkit_path = os.path.join(TOOLS_DIR, f"{form_data.id}.py") try: @@ -81,7 +84,7 @@ async def create_new_toolkit( TOOLS[form_data.id] = toolkit_module specs = get_tools_specs(TOOLS[form_data.id]) - toolkit = Tools.insert_new_tool(user.id, form_data, specs) + toolkit = Tools.insert_new_tool(db, user.id, form_data, specs) tool_cache_dir = Path(CACHE_DIR) / "tools" / form_data.id tool_cache_dir.mkdir(parents=True, exist_ok=True) @@ -112,8 +115,8 @@ async def create_new_toolkit( @router.get("/id/{id}", response_model=Optional[ToolModel]) -async def get_toolkit_by_id(id: str, user=Depends(get_admin_user)): - toolkit = Tools.get_tool_by_id(id) +async def get_toolkit_by_id(id: str, user=Depends(get_admin_user), db=Depends(get_db)): + toolkit = Tools.get_tool_by_id(db, id) if toolkit: return toolkit @@ -131,7 +134,11 @@ async def get_toolkit_by_id(id: str, user=Depends(get_admin_user)): @router.post("/id/{id}/update", response_model=Optional[ToolModel]) async def update_toolkit_by_id( - request: Request, id: str, form_data: ToolForm, user=Depends(get_admin_user) + request: Request, + id: str, + form_data: ToolForm, + user=Depends(get_admin_user), + db=Depends(get_db), ): toolkit_path = os.path.join(TOOLS_DIR, f"{id}.py") @@ -153,7 +160,7 @@ async def update_toolkit_by_id( } print(updated) - toolkit = Tools.update_tool_by_id(id, updated) + toolkit = Tools.update_tool_by_id(db, id, updated) if toolkit: return toolkit @@ -176,8 +183,10 @@ async def update_toolkit_by_id( @router.delete("/id/{id}/delete", response_model=bool) -async def delete_toolkit_by_id(request: Request, id: str, user=Depends(get_admin_user)): - result = Tools.delete_tool_by_id(id) +async def delete_toolkit_by_id( + request: Request, id: str, user=Depends(get_admin_user), db=Depends(get_db) +): + result = Tools.delete_tool_by_id(db, id) if result: TOOLS = request.app.state.TOOLS diff --git a/backend/apps/webui/routers/users.py b/backend/apps/webui/routers/users.py index 270d72a23..46a418fc1 100644 --- a/backend/apps/webui/routers/users.py +++ b/backend/apps/webui/routers/users.py @@ -9,6 +9,7 @@ import time import uuid import logging +from apps.webui.internal.db import get_db from apps.webui.models.users import ( UserModel, UserUpdateForm, @@ -40,8 +41,10 @@ router = APIRouter() @router.get("/", response_model=List[UserModel]) -async def get_users(skip: int = 0, limit: int = 50, user=Depends(get_admin_user)): - return Users.get_users(skip, limit) +async def get_users( + skip: int = 0, limit: int = 50, user=Depends(get_admin_user), db=Depends(get_db) +): + return Users.get_users(db, skip, limit) ############################ @@ -68,10 +71,12 @@ async def update_user_permissions( @router.post("/update/role", response_model=Optional[UserModel]) -async def update_user_role(form_data: UserRoleUpdateForm, user=Depends(get_admin_user)): +async def update_user_role( + form_data: UserRoleUpdateForm, user=Depends(get_admin_user), db=Depends(get_db) +): - if user.id != form_data.id and form_data.id != Users.get_first_user().id: - return Users.update_user_role_by_id(form_data.id, form_data.role) + if user.id != form_data.id and form_data.id != Users.get_first_user(db).id: + return Users.update_user_role_by_id(db, form_data.id, form_data.role) raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, @@ -85,8 +90,10 @@ async def update_user_role(form_data: UserRoleUpdateForm, user=Depends(get_admin @router.get("/user/settings", response_model=Optional[UserSettings]) -async def get_user_settings_by_session_user(user=Depends(get_verified_user)): - user = Users.get_user_by_id(user.id) +async def get_user_settings_by_session_user( + user=Depends(get_verified_user), db=Depends(get_db) +): + user = Users.get_user_by_id(db, user.id) if user: return user.settings else: @@ -103,9 +110,9 @@ async def get_user_settings_by_session_user(user=Depends(get_verified_user)): @router.post("/user/settings/update", response_model=UserSettings) async def update_user_settings_by_session_user( - form_data: UserSettings, user=Depends(get_verified_user) + form_data: UserSettings, user=Depends(get_verified_user), db=Depends(get_db) ): - user = Users.update_user_by_id(user.id, {"settings": form_data.model_dump()}) + user = Users.update_user_by_id(db, user.id, {"settings": form_data.model_dump()}) if user: return user.settings else: @@ -121,8 +128,10 @@ async def update_user_settings_by_session_user( @router.get("/user/info", response_model=Optional[dict]) -async def get_user_info_by_session_user(user=Depends(get_verified_user)): - user = Users.get_user_by_id(user.id) +async def get_user_info_by_session_user( + user=Depends(get_verified_user), db=Depends(get_db) +): + user = Users.get_user_by_id(db, user.id) if user: return user.info else: @@ -138,15 +147,17 @@ async def get_user_info_by_session_user(user=Depends(get_verified_user)): @router.post("/user/info/update", response_model=Optional[dict]) -async def update_user_settings_by_session_user( - form_data: dict, user=Depends(get_verified_user) +async def update_user_info_by_session_user( + form_data: dict, user=Depends(get_verified_user), db=Depends(get_db) ): - user = Users.get_user_by_id(user.id) + user = Users.get_user_by_id(db, user.id) if user: if user.info is None: user.info = {} - user = Users.update_user_by_id(user.id, {"info": {**user.info, **form_data}}) + user = Users.update_user_by_id( + db, user.id, {"info": {**user.info, **form_data}} + ) if user: return user.info else: @@ -172,13 +183,15 @@ class UserResponse(BaseModel): @router.get("/{user_id}", response_model=UserResponse) -async def get_user_by_id(user_id: str, user=Depends(get_verified_user)): +async def get_user_by_id( + user_id: str, user=Depends(get_verified_user), db=Depends(get_db) +): # Check if user_id is a shared chat # If it is, get the user_id from the chat if user_id.startswith("shared-"): chat_id = user_id.replace("shared-", "") - chat = Chats.get_chat_by_id(chat_id) + chat = Chats.get_chat_by_id(db, chat_id) if chat: user_id = chat.user_id else: @@ -187,7 +200,7 @@ async def get_user_by_id(user_id: str, user=Depends(get_verified_user)): detail=ERROR_MESSAGES.USER_NOT_FOUND, ) - user = Users.get_user_by_id(user_id) + user = Users.get_user_by_id(db, user_id) if user: return UserResponse(name=user.name, profile_image_url=user.profile_image_url) @@ -205,13 +218,16 @@ async def get_user_by_id(user_id: str, user=Depends(get_verified_user)): @router.post("/{user_id}/update", response_model=Optional[UserModel]) async def update_user_by_id( - user_id: str, form_data: UserUpdateForm, session_user=Depends(get_admin_user) + user_id: str, + form_data: UserUpdateForm, + session_user=Depends(get_admin_user), + db=Depends(get_db), ): - user = Users.get_user_by_id(user_id) + user = Users.get_user_by_id(db, user_id) if user: if form_data.email.lower() != user.email: - email_user = Users.get_user_by_email(form_data.email.lower()) + email_user = Users.get_user_by_email(db, form_data.email.lower()) if email_user: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, @@ -221,10 +237,11 @@ async def update_user_by_id( if form_data.password: hashed = get_password_hash(form_data.password) log.debug(f"hashed: {hashed}") - Auths.update_user_password_by_id(user_id, hashed) + Auths.update_user_password_by_id(db, user_id, hashed) - Auths.update_email_by_id(user_id, form_data.email.lower()) + Auths.update_email_by_id(db, user_id, form_data.email.lower()) updated_user = Users.update_user_by_id( + db, user_id, { "name": form_data.name, @@ -253,9 +270,11 @@ async def update_user_by_id( @router.delete("/{user_id}", response_model=bool) -async def delete_user_by_id(user_id: str, user=Depends(get_admin_user)): +async def delete_user_by_id( + user_id: str, user=Depends(get_admin_user), db=Depends(get_db) +): if user.id != user_id: - result = Auths.delete_auth_by_id(user_id) + result = Auths.delete_auth_by_id(db, user_id) if result: return True diff --git a/backend/apps/webui/routers/utils.py b/backend/apps/webui/routers/utils.py index 8f6d663b4..780ed6b43 100644 --- a/backend/apps/webui/routers/utils.py +++ b/backend/apps/webui/routers/utils.py @@ -1,6 +1,5 @@ from fastapi import APIRouter, UploadFile, File, Response from fastapi import Depends, HTTPException, status -from peewee import SqliteDatabase from starlette.responses import StreamingResponse, FileResponse from pydantic import BaseModel @@ -10,7 +9,6 @@ import markdown import black -from apps.webui.internal.db import DB from utils.utils import get_admin_user from utils.misc import calculate_sha256, get_gravatar_url @@ -114,13 +112,15 @@ async def download_db(user=Depends(get_admin_user)): status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.ACCESS_PROHIBITED, ) - if not isinstance(DB, SqliteDatabase): + from apps.webui.internal.db import engine + + if engine.name != "sqlite": raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DB_NOT_SQLITE, ) return FileResponse( - DB.database, + engine.url.database, media_type="application/octet-stream", filename="webui.db", ) diff --git a/backend/main.py b/backend/main.py index 52da33155..d80c6a729 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,5 +1,6 @@ import base64 import uuid +import subprocess from contextlib import asynccontextmanager from authlib.integrations.starlette_client import OAuth @@ -27,6 +28,8 @@ from fastapi.responses import JSONResponse from fastapi import HTTPException from fastapi.middleware.wsgi import WSGIMiddleware from fastapi.middleware.cors import CORSMiddleware +from sqlalchemy import text +from sqlalchemy.orm import Session from starlette.exceptions import HTTPException as StarletteHTTPException from starlette.middleware.base import BaseHTTPMiddleware from starlette.middleware.sessions import SessionMiddleware @@ -54,6 +57,7 @@ from apps.webui.main import ( get_pipe_models, generate_function_chat_completion, ) +from apps.webui.internal.db import get_db, SessionLocal from pydantic import BaseModel @@ -124,6 +128,8 @@ from config import ( WEBUI_SESSION_COOKIE_SAME_SITE, WEBUI_SESSION_COOKIE_SECURE, AppConfig, + BACKEND_DIR, + DATABASE_URL, ) from constants import ERROR_MESSAGES, WEBHOOK_MESSAGES from utils.webhook import post_webhook @@ -166,8 +172,19 @@ https://github.com/open-webui/open-webui ) +def run_migrations(): + from alembic.config import Config + from alembic import command + + alembic_cfg = Config(f"{BACKEND_DIR}/alembic.ini") + alembic_cfg.set_main_option("sqlalchemy.url", DATABASE_URL) + alembic_cfg.set_main_option("script_location", f"{BACKEND_DIR}/migrations") + command.upgrade(alembic_cfg, "head") + + @asynccontextmanager async def lifespan(app: FastAPI): + run_migrations() yield @@ -393,6 +410,7 @@ class ChatCompletionMiddleware(BaseHTTPMiddleware): user = get_current_user( request, get_http_authorization_cred(request.headers.get("Authorization")), + SessionLocal(), ) # Flag to skip RAG completions if file_handler is present in tools/functions skip_files = False @@ -736,6 +754,7 @@ class PipelineMiddleware(BaseHTTPMiddleware): user = get_current_user( request, get_http_authorization_cred(request.headers.get("Authorization")), + SessionLocal(), ) try: @@ -781,7 +800,9 @@ app.add_middleware( @app.middleware("http") async def check_url(request: Request, call_next): if len(app.state.MODELS) == 0: - await get_all_models() + db = SessionLocal() + await get_all_models(db) + db.commit() else: pass @@ -815,12 +836,12 @@ app.mount("/api/v1", webui_app) webui_app.state.EMBEDDING_FUNCTION = rag_app.state.EMBEDDING_FUNCTION -async def get_all_models(): +async def get_all_models(db: Session): pipe_models = [] openai_models = [] ollama_models = [] - pipe_models = await get_pipe_models() + pipe_models = await get_pipe_models(db) if app.state.config.ENABLE_OPENAI_API: openai_models = await get_openai_models() @@ -842,7 +863,7 @@ async def get_all_models(): models = pipe_models + openai_models + ollama_models - custom_models = Models.get_all_models() + custom_models = Models.get_all_models(db) for custom_model in custom_models: if custom_model.base_model_id == None: for model in models: @@ -882,8 +903,8 @@ async def get_all_models(): @app.get("/api/models") -async def get_models(user=Depends(get_verified_user)): - models = await get_all_models() +async def get_models(user=Depends(get_verified_user), db=Depends(get_db)): + models = await get_all_models(db) # Filter out filter pipelines models = [ @@ -1584,9 +1605,12 @@ async def get_pipelines(urlIdx: Optional[int] = None, user=Depends(get_admin_use @app.get("/api/pipelines/{pipeline_id}/valves") async def get_pipeline_valves( - urlIdx: Optional[int], pipeline_id: str, user=Depends(get_admin_user) + urlIdx: Optional[int], + pipeline_id: str, + user=Depends(get_admin_user), + db=Depends(get_db), ): - models = await get_all_models() + models = await get_all_models(db) r = None try: @@ -1622,9 +1646,12 @@ async def get_pipeline_valves( @app.get("/api/pipelines/{pipeline_id}/valves/spec") async def get_pipeline_valves_spec( - urlIdx: Optional[int], pipeline_id: str, user=Depends(get_admin_user) + urlIdx: Optional[int], + pipeline_id: str, + user=Depends(get_admin_user), + db=Depends(get_db), ): - models = await get_all_models() + models = await get_all_models(db) r = None try: @@ -1663,8 +1690,9 @@ async def update_pipeline_valves( pipeline_id: str, form_data: dict, user=Depends(get_admin_user), + db=Depends(get_db), ): - models = await get_all_models() + models = await get_all_models(db) r = None try: @@ -2011,6 +2039,12 @@ async def healthcheck(): return {"status": True} +@app.get("/health/db") +async def healthcheck_with_db(db: Session = Depends(get_db)): + result = db.execute(text("SELECT 1;")).all() + return {"status": True} + + app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static") app.mount("/cache", StaticFiles(directory=CACHE_DIR), name="cache") diff --git a/backend/migrations/README b/backend/migrations/README new file mode 100644 index 000000000..f1d93dff9 --- /dev/null +++ b/backend/migrations/README @@ -0,0 +1,4 @@ +Generic single-database configuration. + +Create new migrations with +DATABASE_URL= alembic revision --autogenerate -m "a description" diff --git a/backend/migrations/env.py b/backend/migrations/env.py new file mode 100644 index 000000000..836893bbe --- /dev/null +++ b/backend/migrations/env.py @@ -0,0 +1,93 @@ +import os +from logging.config import fileConfig + +from sqlalchemy import engine_from_config +from sqlalchemy import pool + +from alembic import context + +from apps.webui.models.auths import Auth +from apps.webui.models.chats import Chat +from apps.webui.models.documents import Document +from apps.webui.models.memories import Memory +from apps.webui.models.models import Model +from apps.webui.models.prompts import Prompt +from apps.webui.models.tags import Tag, ChatIdTag +from apps.webui.models.tools import Tool +from apps.webui.models.users import User +from apps.webui.models.files import File +from apps.webui.models.functions import Function + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +if config.config_file_name is not None: + fileConfig(config.config_file_name) + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +target_metadata = Auth.metadata + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + +database_url = os.getenv("DATABASE_URL", None) +if database_url: + config.set_main_option("sqlalchemy.url", database_url) + + +def run_migrations_offline() -> None: + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online() -> None: + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + connectable = engine_from_config( + config.get_section(config.config_ini_section, {}), + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure(connection=connection, target_metadata=target_metadata) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/backend/migrations/script.py.mako b/backend/migrations/script.py.mako new file mode 100644 index 000000000..5f667ccfe --- /dev/null +++ b/backend/migrations/script.py.mako @@ -0,0 +1,27 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +import apps.webui.internal.db +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision: str = ${repr(up_revision)} +down_revision: Union[str, None] = ${repr(down_revision)} +branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} +depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} + + +def upgrade() -> None: + ${upgrades if upgrades else "pass"} + + +def downgrade() -> None: + ${downgrades if downgrades else "pass"} diff --git a/backend/migrations/versions/22b5ab2667b8_init.py b/backend/migrations/versions/22b5ab2667b8_init.py new file mode 100644 index 000000000..af10dc2cf --- /dev/null +++ b/backend/migrations/versions/22b5ab2667b8_init.py @@ -0,0 +1,188 @@ +"""init + +Revision ID: 22b5ab2667b8 +Revises: +Create Date: 2024-06-20 13:22:40.397002 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.engine.reflection import Inspector + +import apps.webui.internal.db + + +# revision identifiers, used by Alembic. +revision: str = "22b5ab2667b8" +down_revision: Union[str, None] = None +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + con = op.get_bind() + inspector = Inspector.from_engine(con) + tables = set(inspector.get_table_names()) + + # ### commands auto generated by Alembic - please adjust! ### + if not "auth" in tables: + op.create_table( + "auth", + sa.Column("id", sa.String(), nullable=False), + sa.Column("email", sa.String(), nullable=True), + sa.Column("password", sa.String(), nullable=True), + sa.Column("active", sa.Boolean(), nullable=True), + sa.PrimaryKeyConstraint("id"), + ) + + if not "chat" in tables: + op.create_table( + "chat", + sa.Column("id", sa.String(), nullable=False), + sa.Column("user_id", sa.String(), nullable=True), + sa.Column("title", sa.String(), nullable=True), + sa.Column("chat", sa.String(), nullable=True), + sa.Column("created_at", sa.BigInteger(), nullable=True), + sa.Column("updated_at", sa.BigInteger(), nullable=True), + sa.Column("share_id", sa.String(), nullable=True), + sa.Column("archived", sa.Boolean(), nullable=True), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("share_id"), + ) + + if not "chatidtag" in tables: + op.create_table( + "chatidtag", + sa.Column("id", sa.String(), nullable=False), + sa.Column("tag_name", sa.String(), nullable=True), + sa.Column("chat_id", sa.String(), nullable=True), + sa.Column("user_id", sa.String(), nullable=True), + sa.Column("timestamp", sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint("id"), + ) + + if not "document" in tables: + op.create_table( + "document", + sa.Column("collection_name", sa.String(), nullable=False), + sa.Column("name", sa.String(), nullable=True), + sa.Column("title", sa.String(), nullable=True), + sa.Column("filename", sa.String(), nullable=True), + sa.Column("content", sa.String(), nullable=True), + sa.Column("user_id", sa.String(), nullable=True), + sa.Column("timestamp", sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint("collection_name"), + sa.UniqueConstraint("name"), + ) + + if not "memory" in tables: + op.create_table( + "memory", + sa.Column("id", sa.String(), nullable=False), + sa.Column("user_id", sa.String(), nullable=True), + sa.Column("content", sa.String(), nullable=True), + sa.Column("updated_at", sa.BigInteger(), nullable=True), + sa.Column("created_at", sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint("id"), + ) + + if not "model" in tables: + op.create_table( + "model", + sa.Column("id", sa.String(), nullable=False), + sa.Column("user_id", sa.String(), nullable=True), + sa.Column("base_model_id", sa.String(), nullable=True), + sa.Column("name", sa.String(), nullable=True), + sa.Column("params", apps.webui.internal.db.JSONField(), nullable=True), + sa.Column("meta", apps.webui.internal.db.JSONField(), nullable=True), + sa.Column("updated_at", sa.BigInteger(), nullable=True), + sa.Column("created_at", sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint("id"), + ) + + if not "prompt" in tables: + op.create_table( + "prompt", + sa.Column("command", sa.String(), nullable=False), + sa.Column("user_id", sa.String(), nullable=True), + sa.Column("title", sa.String(), nullable=True), + sa.Column("content", sa.String(), nullable=True), + sa.Column("timestamp", sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint("command"), + ) + + if not "tag" in tables: + op.create_table( + "tag", + sa.Column("id", sa.String(), nullable=False), + sa.Column("name", sa.String(), nullable=True), + sa.Column("user_id", sa.String(), nullable=True), + sa.Column("data", sa.String(), nullable=True), + sa.PrimaryKeyConstraint("id"), + ) + + if not "tool" in tables: + op.create_table( + "tool", + sa.Column("id", sa.String(), nullable=False), + sa.Column("user_id", sa.String(), nullable=True), + sa.Column("name", sa.String(), nullable=True), + sa.Column("content", sa.String(), nullable=True), + sa.Column("specs", apps.webui.internal.db.JSONField(), nullable=True), + sa.Column("meta", apps.webui.internal.db.JSONField(), nullable=True), + sa.Column("updated_at", sa.BigInteger(), nullable=True), + sa.Column("created_at", sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint("id"), + ) + + if not "user" in tables: + op.create_table( + "user", + sa.Column("id", sa.String(), nullable=False), + sa.Column("name", sa.String(), nullable=True), + sa.Column("email", sa.String(), nullable=True), + sa.Column("role", sa.String(), nullable=True), + sa.Column("profile_image_url", sa.String(), nullable=True), + sa.Column("last_active_at", sa.BigInteger(), nullable=True), + sa.Column("updated_at", sa.BigInteger(), nullable=True), + sa.Column("created_at", sa.BigInteger(), nullable=True), + sa.Column("api_key", sa.String(), nullable=True), + sa.Column("settings", apps.webui.internal.db.JSONField(), nullable=True), + sa.Column("info", apps.webui.internal.db.JSONField(), nullable=True), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("api_key"), + ) + + if not "file" in tables: + op.create_table('file', + sa.Column('id', sa.String(), nullable=False), + sa.Column('user_id', sa.String(), nullable=True), + sa.Column('filename', sa.String(), nullable=True), + sa.Column('meta', apps.webui.internal.db.JSONField(), nullable=True), + sa.Column('created_at', sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + + if not "function" in tables: + op.create_table('function', + sa.Column('id', sa.String(), nullable=False), + sa.Column('user_id', sa.String(), nullable=True), + sa.Column('name', sa.Text(), nullable=True), + sa.Column('type', sa.Text(), nullable=True), + sa.Column('content', sa.Text(), nullable=True), + sa.Column('meta', apps.webui.internal.db.JSONField(), nullable=True), + sa.Column('updated_at', sa.BigInteger(), nullable=True), + sa.Column('created_at', sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + # do nothing as we assume we had previous migrations from peewee-migrate + pass + # ### end Alembic commands ### diff --git a/backend/requirements.txt b/backend/requirements.txt index a36af5497..720809471 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -12,8 +12,10 @@ passlib[bcrypt]==1.7.4 requests==2.32.2 aiohttp==3.9.5 -peewee==3.17.5 -peewee-migrate==1.12.2 +sqlalchemy==2.0.30 +alembic==1.13.1 +# peewee==3.17.5 +# peewee-migrate==1.12.2 psycopg2-binary==2.9.9 PyMySQL==1.1.1 bcrypt==4.1.3 @@ -67,4 +69,9 @@ pytube==15.0.0 extract_msg pydub -duckduckgo-search~=6.1.5 \ No newline at end of file +duckduckgo-search~=6.1.5 + +## Tests +docker~=7.1.0 +pytest~=8.2.1 +pytest-docker~=3.1.1 diff --git a/backend/test/__init__.py b/backend/test/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/test/apps/webui/routers/test_auths.py b/backend/test/apps/webui/routers/test_auths.py new file mode 100644 index 000000000..3450f57c6 --- /dev/null +++ b/backend/test/apps/webui/routers/test_auths.py @@ -0,0 +1,209 @@ +import pytest + +from test.util.abstract_integration_test import AbstractPostgresTest +from test.util.mock_user import mock_webui_user + + +class TestAuths(AbstractPostgresTest): + BASE_PATH = "/api/v1/auths" + + def setup_class(cls): + super().setup_class() + from apps.webui.models.users import Users + from apps.webui.models.auths import Auths + + cls.users = Users + cls.auths = Auths + + def test_get_session_user(self): + with mock_webui_user(): + response = self.fast_api_client.get(self.create_url("")) + assert response.status_code == 200 + assert response.json() == { + "id": "1", + "name": "John Doe", + "email": "john.doe@openwebui.com", + "role": "user", + "profile_image_url": "/user.png", + } + + def test_update_profile(self): + from utils.utils import get_password_hash + + user = self.auths.insert_new_auth( + self.db_session, + email="john.doe@openwebui.com", + password=get_password_hash("old_password"), + name="John Doe", + profile_image_url="/user.png", + role="user", + ) + + with mock_webui_user(id=user.id): + response = self.fast_api_client.post( + self.create_url("/update/profile"), + json={"name": "John Doe 2", "profile_image_url": "/user2.png"}, + ) + assert response.status_code == 200 + db_user = self.users.get_user_by_id(self.db_session, user.id) + assert db_user.name == "John Doe 2" + assert db_user.profile_image_url == "/user2.png" + + def test_update_password(self): + from utils.utils import get_password_hash + + user = self.auths.insert_new_auth( + self.db_session, + email="john.doe@openwebui.com", + password=get_password_hash("old_password"), + name="John Doe", + profile_image_url="/user.png", + role="user", + ) + + with mock_webui_user(id=user.id): + response = self.fast_api_client.post( + self.create_url("/update/password"), + json={"password": "old_password", "new_password": "new_password"}, + ) + assert response.status_code == 200 + + old_auth = self.auths.authenticate_user( + self.db_session, "john.doe@openwebui.com", "old_password" + ) + assert old_auth is None + new_auth = self.auths.authenticate_user( + self.db_session, "john.doe@openwebui.com", "new_password" + ) + assert new_auth is not None + + def test_signin(self): + from utils.utils import get_password_hash + + user = self.auths.insert_new_auth( + self.db_session, + email="john.doe@openwebui.com", + password=get_password_hash("password"), + name="John Doe", + profile_image_url="/user.png", + role="user", + ) + response = self.fast_api_client.post( + self.create_url("/signin"), + json={"email": "john.doe@openwebui.com", "password": "password"}, + ) + assert response.status_code == 200 + data = response.json() + assert data["id"] == user.id + assert data["name"] == "John Doe" + assert data["email"] == "john.doe@openwebui.com" + assert data["role"] == "user" + assert data["profile_image_url"] == "/user.png" + assert data["token"] is not None and len(data["token"]) > 0 + assert data["token_type"] == "Bearer" + + def test_signup(self): + response = self.fast_api_client.post( + self.create_url("/signup"), + json={ + "name": "John Doe", + "email": "john.doe@openwebui.com", + "password": "password", + }, + ) + assert response.status_code == 200 + data = response.json() + assert data["id"] is not None and len(data["id"]) > 0 + assert data["name"] == "John Doe" + assert data["email"] == "john.doe@openwebui.com" + assert data["role"] in ["admin", "user", "pending"] + assert data["profile_image_url"] == "/user.png" + assert data["token"] is not None and len(data["token"]) > 0 + assert data["token_type"] == "Bearer" + + def test_add_user(self): + with mock_webui_user(): + response = self.fast_api_client.post( + self.create_url("/add"), + json={ + "name": "John Doe 2", + "email": "john.doe2@openwebui.com", + "password": "password2", + "role": "admin", + }, + ) + assert response.status_code == 200 + data = response.json() + assert data["id"] is not None and len(data["id"]) > 0 + assert data["name"] == "John Doe 2" + assert data["email"] == "john.doe2@openwebui.com" + assert data["role"] == "admin" + assert data["profile_image_url"] == "/user.png" + assert data["token"] is not None and len(data["token"]) > 0 + assert data["token_type"] == "Bearer" + + def test_get_admin_details(self): + self.auths.insert_new_auth( + self.db_session, + email="john.doe@openwebui.com", + password="password", + name="John Doe", + profile_image_url="/user.png", + role="admin", + ) + with mock_webui_user(): + response = self.fast_api_client.get(self.create_url("/admin/details")) + + assert response.status_code == 200 + assert response.json() == { + "name": "John Doe", + "email": "john.doe@openwebui.com", + } + + def test_create_api_key_(self): + user = self.auths.insert_new_auth( + self.db_session, + email="john.doe@openwebui.com", + password="password", + name="John Doe", + profile_image_url="/user.png", + role="admin", + ) + with mock_webui_user(id=user.id): + response = self.fast_api_client.post(self.create_url("/api_key")) + assert response.status_code == 200 + data = response.json() + assert data["api_key"] is not None + assert len(data["api_key"]) > 0 + + def test_delete_api_key(self): + user = self.auths.insert_new_auth( + self.db_session, + email="john.doe@openwebui.com", + password="password", + name="John Doe", + profile_image_url="/user.png", + role="admin", + ) + self.users.update_user_api_key_by_id(self.db_session, user.id, "abc") + with mock_webui_user(id=user.id): + response = self.fast_api_client.delete(self.create_url("/api_key")) + assert response.status_code == 200 + assert response.json() == True + db_user = self.users.get_user_by_id(self.db_session, user.id) + assert db_user.api_key is None + + def test_get_api_key(self): + user = self.auths.insert_new_auth( + self.db_session, + email="john.doe@openwebui.com", + password="password", + name="John Doe", + profile_image_url="/user.png", + role="admin", + ) + self.users.update_user_api_key_by_id(self.db_session, user.id, "abc") + with mock_webui_user(id=user.id): + response = self.fast_api_client.get(self.create_url("/api_key")) + assert response.status_code == 200 + assert response.json() == {"api_key": "abc"} diff --git a/backend/test/apps/webui/routers/test_chats.py b/backend/test/apps/webui/routers/test_chats.py new file mode 100644 index 000000000..2d1145c06 --- /dev/null +++ b/backend/test/apps/webui/routers/test_chats.py @@ -0,0 +1,239 @@ +import uuid + +from test.util.abstract_integration_test import AbstractPostgresTest +from test.util.mock_user import mock_webui_user + + +class TestChats(AbstractPostgresTest): + + BASE_PATH = "/api/v1/chats" + + def setup_class(cls): + super().setup_class() + + def setup_method(self): + super().setup_method() + from apps.webui.models.chats import ChatForm + from apps.webui.models.chats import Chats + + self.chats = Chats + self.chats.insert_new_chat( + self.db_session, + "2", + ChatForm( + **{ + "chat": { + "name": "chat1", + "description": "chat1 description", + "tags": ["tag1", "tag2"], + "history": {"currentId": "1", "messages": []}, + } + } + ), + ) + + def test_get_session_user_chat_list(self): + with mock_webui_user(id="2"): + response = self.fast_api_client.get(self.create_url("/")) + assert response.status_code == 200 + first_chat = response.json()[0] + assert first_chat["id"] is not None + assert first_chat["title"] == "New Chat" + assert first_chat["created_at"] is not None + assert first_chat["updated_at"] is not None + + def test_delete_all_user_chats(self): + with mock_webui_user(id="2"): + response = self.fast_api_client.delete(self.create_url("/")) + assert response.status_code == 200 + assert len(self.chats.get_chats(self.db_session)) == 0 + + def test_get_user_chat_list_by_user_id(self): + with mock_webui_user(id="3"): + response = self.fast_api_client.get(self.create_url("/list/user/2")) + assert response.status_code == 200 + first_chat = response.json()[0] + assert first_chat["id"] is not None + assert first_chat["title"] == "New Chat" + assert first_chat["created_at"] is not None + assert first_chat["updated_at"] is not None + + def test_create_new_chat(self): + with mock_webui_user(id="2"): + response = self.fast_api_client.post( + self.create_url("/new"), + json={ + "chat": { + "name": "chat2", + "description": "chat2 description", + "tags": ["tag1", "tag2"], + } + }, + ) + assert response.status_code == 200 + data = response.json() + assert data["archived"] is False + assert data["chat"] == { + "name": "chat2", + "description": "chat2 description", + "tags": ["tag1", "tag2"], + } + assert data["user_id"] == "2" + assert data["id"] is not None + assert data["share_id"] is None + assert data["title"] == "New Chat" + assert data["updated_at"] is not None + assert data["created_at"] is not None + assert len(self.chats.get_chats(self.db_session)) == 2 + + def test_get_user_chats(self): + self.test_get_session_user_chat_list() + + def test_get_user_archived_chats(self): + self.chats.archive_all_chats_by_user_id(self.db_session, "2") + self.db_session.commit() + with mock_webui_user(id="2"): + response = self.fast_api_client.get(self.create_url("/all/archived")) + assert response.status_code == 200 + first_chat = response.json()[0] + assert first_chat["id"] is not None + assert first_chat["title"] == "New Chat" + assert first_chat["created_at"] is not None + assert first_chat["updated_at"] is not None + + def test_get_all_user_chats_in_db(self): + with mock_webui_user(id="4"): + response = self.fast_api_client.get(self.create_url("/all/db")) + assert response.status_code == 200 + assert len(response.json()) == 1 + + def test_get_archived_session_user_chat_list(self): + self.test_get_user_archived_chats() + + def test_archive_all_chats(self): + with mock_webui_user(id="2"): + response = self.fast_api_client.post(self.create_url("/archive/all")) + assert response.status_code == 200 + assert len(self.chats.get_archived_chats_by_user_id(self.db_session, "2")) == 1 + + def test_get_shared_chat_by_id(self): + chat_id = self.chats.get_chats(self.db_session)[0].id + self.chats.update_chat_share_id_by_id(self.db_session, chat_id, chat_id) + self.db_session.commit() + with mock_webui_user(id="2"): + response = self.fast_api_client.get(self.create_url(f"/share/{chat_id}")) + assert response.status_code == 200 + data = response.json() + assert data["id"] == chat_id + assert data["chat"] == { + "name": "chat1", + "description": "chat1 description", + "tags": ["tag1", "tag2"], + "history": {"currentId": "1", "messages": []}, + } + assert data["id"] == chat_id + assert data["share_id"] == chat_id + assert data["title"] == "New Chat" + + def test_get_chat_by_id(self): + chat_id = self.chats.get_chats(self.db_session)[0].id + with mock_webui_user(id="2"): + response = self.fast_api_client.get(self.create_url(f"/{chat_id}")) + assert response.status_code == 200 + data = response.json() + assert data["id"] == chat_id + assert data["chat"] == { + "name": "chat1", + "description": "chat1 description", + "tags": ["tag1", "tag2"], + "history": {"currentId": "1", "messages": []}, + } + assert data["share_id"] is None + assert data["title"] == "New Chat" + assert data["user_id"] == "2" + + def test_update_chat_by_id(self): + chat_id = self.chats.get_chats(self.db_session)[0].id + with mock_webui_user(id="2"): + response = self.fast_api_client.post( + self.create_url(f"/{chat_id}"), + json={ + "chat": { + "name": "chat2", + "description": "chat2 description", + "tags": ["tag2", "tag4"], + "title": "Just another title", + } + }, + ) + assert response.status_code == 200 + data = response.json() + assert data["id"] == chat_id + assert data["chat"] == { + "name": "chat2", + "title": "Just another title", + "description": "chat2 description", + "tags": ["tag2", "tag4"], + "history": {"currentId": "1", "messages": []}, + } + assert data["share_id"] is None + assert data["title"] == "Just another title" + assert data["user_id"] == "2" + + def test_delete_chat_by_id(self): + chat_id = self.chats.get_chats(self.db_session)[0].id + with mock_webui_user(id="2"): + response = self.fast_api_client.delete(self.create_url(f"/{chat_id}")) + assert response.status_code == 200 + assert response.json() is True + + def test_clone_chat_by_id(self): + chat_id = self.chats.get_chats(self.db_session)[0].id + with mock_webui_user(id="2"): + response = self.fast_api_client.get(self.create_url(f"/{chat_id}/clone")) + + assert response.status_code == 200 + data = response.json() + assert data["id"] != chat_id + assert data["chat"] == { + "branchPointMessageId": "1", + "description": "chat1 description", + "history": {"currentId": "1", "messages": []}, + "name": "chat1", + "originalChatId": chat_id, + "tags": ["tag1", "tag2"], + "title": "Clone of New Chat", + } + assert data["share_id"] is None + assert data["title"] == "Clone of New Chat" + assert data["user_id"] == "2" + + def test_archive_chat_by_id(self): + chat_id = self.chats.get_chats(self.db_session)[0].id + with mock_webui_user(id="2"): + response = self.fast_api_client.get(self.create_url(f"/{chat_id}/archive")) + assert response.status_code == 200 + + chat = self.chats.get_chat_by_id(self.db_session, chat_id) + assert chat.archived is True + + def test_share_chat_by_id(self): + chat_id = self.chats.get_chats(self.db_session)[0].id + with mock_webui_user(id="2"): + response = self.fast_api_client.post(self.create_url(f"/{chat_id}/share")) + assert response.status_code == 200 + + chat = self.chats.get_chat_by_id(self.db_session, chat_id) + assert chat.share_id is not None + + def test_delete_shared_chat_by_id(self): + chat_id = self.chats.get_chats(self.db_session)[0].id + share_id = str(uuid.uuid4()) + self.chats.update_chat_share_id_by_id(self.db_session, chat_id, share_id) + self.db_session.commit() + with mock_webui_user(id="2"): + response = self.fast_api_client.delete(self.create_url(f"/{chat_id}/share")) + assert response.status_code + + chat = self.chats.get_chat_by_id(self.db_session, chat_id) + assert chat.share_id is None diff --git a/backend/test/apps/webui/routers/test_documents.py b/backend/test/apps/webui/routers/test_documents.py new file mode 100644 index 000000000..53ef3d2aa --- /dev/null +++ b/backend/test/apps/webui/routers/test_documents.py @@ -0,0 +1,106 @@ +from test.util.abstract_integration_test import AbstractPostgresTest +from test.util.mock_user import mock_webui_user + + +class TestDocuments(AbstractPostgresTest): + + BASE_PATH = "/api/v1/documents" + + def setup_class(cls): + super().setup_class() + from apps.webui.models.documents import Documents + + cls.documents = Documents + + def test_documents(self): + # Empty database + assert len(self.documents.get_docs(self.db_session)) == 0 + with mock_webui_user(id="2"): + response = self.fast_api_client.get(self.create_url("/")) + assert response.status_code == 200 + assert len(response.json()) == 0 + + # Create a new document + with mock_webui_user(id="2"): + response = self.fast_api_client.post( + self.create_url("/create"), + json={ + "name": "doc_name", + "title": "doc title", + "collection_name": "custom collection", + "filename": "doc_name.pdf", + "content": "", + }, + ) + assert response.status_code == 200 + assert response.json()["name"] == "doc_name" + assert len(self.documents.get_docs(self.db_session)) == 1 + + # Get the document + with mock_webui_user(id="2"): + response = self.fast_api_client.get(self.create_url("/doc?name=doc_name")) + assert response.status_code == 200 + data = response.json() + assert data["collection_name"] == "custom collection" + assert data["name"] == "doc_name" + assert data["title"] == "doc title" + assert data["filename"] == "doc_name.pdf" + assert data["content"] == {} + + # Create another document + with mock_webui_user(id="2"): + response = self.fast_api_client.post( + self.create_url("/create"), + json={ + "name": "doc_name 2", + "title": "doc title 2", + "collection_name": "custom collection 2", + "filename": "doc_name2.pdf", + "content": "", + }, + ) + assert response.status_code == 200 + assert response.json()["name"] == "doc_name 2" + assert len(self.documents.get_docs(self.db_session)) == 2 + + # Get all documents + with mock_webui_user(id="2"): + response = self.fast_api_client.get(self.create_url("/")) + assert response.status_code == 200 + assert len(response.json()) == 2 + + # Update the first document + with mock_webui_user(id="2"): + response = self.fast_api_client.post( + self.create_url("/doc/update?name=doc_name"), + json={"name": "doc_name rework", "title": "updated title"}, + ) + assert response.status_code == 200 + data = response.json() + assert data["name"] == "doc_name rework" + assert data["title"] == "updated title" + + # Tag the first document + with mock_webui_user(id="2"): + response = self.fast_api_client.post( + self.create_url("/doc/tags"), + json={ + "name": "doc_name rework", + "tags": [{"name": "testing-tag"}, {"name": "another-tag"}], + }, + ) + assert response.status_code == 200 + data = response.json() + assert data["name"] == "doc_name rework" + assert data["content"] == { + "tags": [{"name": "testing-tag"}, {"name": "another-tag"}] + } + assert len(self.documents.get_docs(self.db_session)) == 2 + + # Delete the first document + with mock_webui_user(id="2"): + response = self.fast_api_client.delete( + self.create_url("/doc/delete?name=doc_name rework") + ) + assert response.status_code == 200 + assert len(self.documents.get_docs(self.db_session)) == 1 diff --git a/backend/test/apps/webui/routers/test_models.py b/backend/test/apps/webui/routers/test_models.py new file mode 100644 index 000000000..991c83bee --- /dev/null +++ b/backend/test/apps/webui/routers/test_models.py @@ -0,0 +1,60 @@ +from test.util.abstract_integration_test import AbstractPostgresTest +from test.util.mock_user import mock_webui_user + + +class TestModels(AbstractPostgresTest): + + BASE_PATH = "/api/v1/models" + + def setup_class(cls): + super().setup_class() + from apps.webui.models.models import Model + + cls.models = Model + + def test_models(self): + with mock_webui_user(id="2"): + response = self.fast_api_client.get(self.create_url("/")) + assert response.status_code == 200 + assert len(response.json()) == 0 + + with mock_webui_user(id="2"): + response = self.fast_api_client.post( + self.create_url("/add"), + json={ + "id": "my-model", + "base_model_id": "base-model-id", + "name": "Hello World", + "meta": { + "profile_image_url": "/favicon.png", + "description": "description", + "capabilities": None, + "model_config": {}, + }, + "params": {}, + }, + ) + assert response.status_code == 200 + + with mock_webui_user(id="2"): + response = self.fast_api_client.get(self.create_url("/")) + assert response.status_code == 200 + assert len(response.json()) == 1 + + with mock_webui_user(id="2"): + response = self.fast_api_client.get(self.create_url("/my-model")) + assert response.status_code == 200 + data = response.json() + assert data["id"] == "my-model" + assert data["name"] == "Hello World" + + with mock_webui_user(id="2"): + response = self.fast_api_client.delete( + self.create_url("/delete?id=my-model") + ) + assert response.status_code == 200 + + with mock_webui_user(id="2"): + response = self.fast_api_client.get(self.create_url("/")) + assert response.status_code == 200 + assert len(response.json()) == 0 diff --git a/backend/test/apps/webui/routers/test_prompts.py b/backend/test/apps/webui/routers/test_prompts.py new file mode 100644 index 000000000..cd2fcec87 --- /dev/null +++ b/backend/test/apps/webui/routers/test_prompts.py @@ -0,0 +1,82 @@ +from test.util.abstract_integration_test import AbstractPostgresTest +from test.util.mock_user import mock_webui_user + + +class TestPrompts(AbstractPostgresTest): + + BASE_PATH = "/api/v1/prompts" + + def test_prompts(self): + # Get all prompts + with mock_webui_user(id="2"): + response = self.fast_api_client.get(self.create_url("/")) + assert response.status_code == 200 + assert len(response.json()) == 0 + + # Create a two new prompts + with mock_webui_user(id="2"): + response = self.fast_api_client.post( + self.create_url("/create"), + json={ + "command": "/my-command", + "title": "Hello World", + "content": "description", + }, + ) + assert response.status_code == 200 + with mock_webui_user(id="3"): + response = self.fast_api_client.post( + self.create_url("/create"), + json={ + "command": "/my-command2", + "title": "Hello World 2", + "content": "description 2", + }, + ) + assert response.status_code == 200 + + # Get all prompts + with mock_webui_user(id="2"): + response = self.fast_api_client.get(self.create_url("/")) + assert response.status_code == 200 + assert len(response.json()) == 2 + + # Get prompt by command + with mock_webui_user(id="2"): + response = self.fast_api_client.get(self.create_url("/command/my-command")) + assert response.status_code == 200 + data = response.json() + assert data["command"] == "/my-command" + assert data["title"] == "Hello World" + assert data["content"] == "description" + assert data["user_id"] == "2" + + # Update prompt + with mock_webui_user(id="2"): + response = self.fast_api_client.post( + self.create_url("/command/my-command2/update"), + json={ + "command": "irrelevant for request", + "title": "Hello World Updated", + "content": "description Updated", + }, + ) + assert response.status_code == 200 + data = response.json() + assert data["command"] == "/my-command2" + assert data["title"] == "Hello World Updated" + assert data["content"] == "description Updated" + assert data["user_id"] == "3" + + # Delete prompt + with mock_webui_user(id="2"): + response = self.fast_api_client.delete( + self.create_url("/command/my-command/delete") + ) + assert response.status_code == 200 + + # Get all prompts + with mock_webui_user(id="2"): + response = self.fast_api_client.get(self.create_url("/")) + assert response.status_code == 200 + assert len(response.json()) == 1 diff --git a/backend/test/apps/webui/routers/test_users.py b/backend/test/apps/webui/routers/test_users.py new file mode 100644 index 000000000..35b662304 --- /dev/null +++ b/backend/test/apps/webui/routers/test_users.py @@ -0,0 +1,170 @@ +from test.util.abstract_integration_test import AbstractPostgresTest +from test.util.mock_user import mock_webui_user + + +def _get_user_by_id(data, param): + return next((item for item in data if item["id"] == param), None) + + +def _assert_user(data, id, **kwargs): + user = _get_user_by_id(data, id) + assert user is not None + comparison_data = { + "name": f"user {id}", + "email": f"user{id}@openwebui.com", + "profile_image_url": f"/user{id}.png", + "role": "user", + **kwargs, + } + for key, value in comparison_data.items(): + assert user[key] == value + + +class TestUsers(AbstractPostgresTest): + + BASE_PATH = "/api/v1/users" + + def setup_class(cls): + super().setup_class() + from apps.webui.models.users import Users + + cls.users = Users + + def setup_method(self): + super().setup_method() + self.users.insert_new_user( + self.db_session, + id="1", + name="user 1", + email="user1@openwebui.com", + profile_image_url="/user1.png", + role="user", + ) + self.users.insert_new_user( + self.db_session, + id="2", + name="user 2", + email="user2@openwebui.com", + profile_image_url="/user2.png", + role="user", + ) + + def test_users(self): + # Get all users + with mock_webui_user(id="3"): + response = self.fast_api_client.get(self.create_url("")) + assert response.status_code == 200 + assert len(response.json()) == 2 + data = response.json() + _assert_user(data, "1") + _assert_user(data, "2") + + # update role + with mock_webui_user(id="3"): + response = self.fast_api_client.post( + self.create_url("/update/role"), json={"id": "2", "role": "admin"} + ) + assert response.status_code == 200 + _assert_user([response.json()], "2", role="admin") + + # Get all users + with mock_webui_user(id="3"): + response = self.fast_api_client.get(self.create_url("")) + assert response.status_code == 200 + assert len(response.json()) == 2 + data = response.json() + _assert_user(data, "1") + _assert_user(data, "2", role="admin") + + # Get (empty) user settings + with mock_webui_user(id="2"): + response = self.fast_api_client.get(self.create_url("/user/settings")) + assert response.status_code == 200 + assert response.json() is None + + # Update user settings + with mock_webui_user(id="2"): + response = self.fast_api_client.post( + self.create_url("/user/settings/update"), + json={ + "ui": {"attr1": "value1", "attr2": "value2"}, + "model_config": {"attr3": "value3", "attr4": "value4"}, + }, + ) + assert response.status_code == 200 + + # Get user settings + with mock_webui_user(id="2"): + response = self.fast_api_client.get(self.create_url("/user/settings")) + assert response.status_code == 200 + assert response.json() == { + "ui": {"attr1": "value1", "attr2": "value2"}, + "model_config": {"attr3": "value3", "attr4": "value4"}, + } + + # Get (empty) user info + with mock_webui_user(id="1"): + response = self.fast_api_client.get(self.create_url("/user/info")) + assert response.status_code == 200 + assert response.json() is None + + # Update user info + with mock_webui_user(id="1"): + response = self.fast_api_client.post( + self.create_url("/user/info/update"), + json={"attr1": "value1", "attr2": "value2"}, + ) + assert response.status_code == 200 + + # Get user info + with mock_webui_user(id="1"): + response = self.fast_api_client.get(self.create_url("/user/info")) + assert response.status_code == 200 + assert response.json() == {"attr1": "value1", "attr2": "value2"} + + # Get user by id + with mock_webui_user(id="1"): + response = self.fast_api_client.get(self.create_url("/2")) + assert response.status_code == 200 + assert response.json() == {"name": "user 2", "profile_image_url": "/user2.png"} + + # Update user by id + with mock_webui_user(id="1"): + response = self.fast_api_client.post( + self.create_url("/2/update"), + json={ + "name": "user 2 updated", + "email": "user2-updated@openwebui.com", + "profile_image_url": "/user2-updated.png", + }, + ) + assert response.status_code == 200 + + # Get all users + with mock_webui_user(id="3"): + response = self.fast_api_client.get(self.create_url("")) + assert response.status_code == 200 + assert len(response.json()) == 2 + data = response.json() + _assert_user(data, "1") + _assert_user( + data, + "2", + role="admin", + name="user 2 updated", + email="user2-updated@openwebui.com", + profile_image_url="/user2-updated.png", + ) + + # Delete user by id + with mock_webui_user(id="1"): + response = self.fast_api_client.delete(self.create_url("/2")) + assert response.status_code == 200 + + # Get all users + with mock_webui_user(id="3"): + response = self.fast_api_client.get(self.create_url("")) + assert response.status_code == 200 + assert len(response.json()) == 1 + data = response.json() + _assert_user(data, "1") diff --git a/backend/test/util/abstract_integration_test.py b/backend/test/util/abstract_integration_test.py new file mode 100644 index 000000000..9cbf42d47 --- /dev/null +++ b/backend/test/util/abstract_integration_test.py @@ -0,0 +1,155 @@ +import logging +import os +import time + +import docker +import pytest +from docker import DockerClient +from pytest_docker.plugin import get_docker_ip +from fastapi.testclient import TestClient +from sqlalchemy import text, create_engine + +log = logging.getLogger(__name__) + + +def get_fast_api_client(): + from main import app + + with TestClient(app) as c: + return c + + +class AbstractIntegrationTest: + BASE_PATH = None + + def create_url(self, path): + if self.BASE_PATH is None: + raise Exception("BASE_PATH is not set") + parts = self.BASE_PATH.split("/") + parts = [part.strip() for part in parts if part.strip() != ""] + path_parts = path.split("/") + path_parts = [part.strip() for part in path_parts if part.strip() != ""] + return "/".join(parts + path_parts) + + @classmethod + def setup_class(cls): + pass + + def setup_method(self): + pass + + @classmethod + def teardown_class(cls): + pass + + def teardown_method(self): + pass + + +class AbstractPostgresTest(AbstractIntegrationTest): + DOCKER_CONTAINER_NAME = "postgres-test-container-will-get-deleted" + docker_client: DockerClient + + def get_db(self): + from apps.webui.internal.db import SessionLocal + + return SessionLocal() + + @classmethod + def _create_db_url(cls, env_vars_postgres: dict) -> str: + host = get_docker_ip() + user = env_vars_postgres["POSTGRES_USER"] + pw = env_vars_postgres["POSTGRES_PASSWORD"] + port = 8081 + db = env_vars_postgres["POSTGRES_DB"] + return f"postgresql://{user}:{pw}@{host}:{port}/{db}" + + @classmethod + def setup_class(cls): + super().setup_class() + try: + env_vars_postgres = { + "POSTGRES_USER": "user", + "POSTGRES_PASSWORD": "example", + "POSTGRES_DB": "openwebui", + } + cls.docker_client = docker.from_env() + cls.docker_client.containers.run( + "postgres:16.2", + detach=True, + environment=env_vars_postgres, + name=cls.DOCKER_CONTAINER_NAME, + ports={5432: ("0.0.0.0", 8081)}, + command="postgres -c log_statement=all", + ) + time.sleep(0.5) + + database_url = cls._create_db_url(env_vars_postgres) + os.environ["DATABASE_URL"] = database_url + retries = 10 + db = None + while retries > 0: + try: + from config import BACKEND_DIR + db = create_engine(database_url, pool_pre_ping=True) + db = db.connect() + log.info("postgres is ready!") + break + except Exception as e: + log.warning(e) + time.sleep(3) + retries -= 1 + + if db: + # import must be after setting env! + cls.fast_api_client = get_fast_api_client() + db.close() + else: + raise Exception("Could not connect to Postgres") + except Exception as ex: + log.error(ex) + cls.teardown_class() + pytest.fail(f"Could not setup test environment: {ex}") + + def _check_db_connection(self): + retries = 10 + while retries > 0: + try: + self.db_session.execute(text("SELECT 1")) + self.db_session.commit() + break + except Exception as e: + self.db_session.rollback() + log.warning(e) + time.sleep(3) + retries -= 1 + + def setup_method(self): + super().setup_method() + self.db_session = self.get_db() + self._check_db_connection() + + @classmethod + def teardown_class(cls) -> None: + super().teardown_class() + cls.docker_client.containers.get(cls.DOCKER_CONTAINER_NAME).remove(force=True) + + def teardown_method(self): + # rollback everything not yet committed + self.db_session.commit() + + # truncate all tables + tables = [ + "auth", + "chat", + "chatidtag", + "document", + "memory", + "model", + "prompt", + "tag", + '"user"', + ] + for table in tables: + self.db_session.execute(text(f"TRUNCATE TABLE {table}")) + self.db_session.commit() diff --git a/backend/test/util/mock_user.py b/backend/test/util/mock_user.py new file mode 100644 index 000000000..8d0300d3f --- /dev/null +++ b/backend/test/util/mock_user.py @@ -0,0 +1,45 @@ +from contextlib import contextmanager + +from fastapi import FastAPI + + +@contextmanager +def mock_webui_user(**kwargs): + from apps.webui.main import app + + with mock_user(app, **kwargs): + yield + + +@contextmanager +def mock_user(app: FastAPI, **kwargs): + from utils.utils import ( + get_current_user, + get_verified_user, + get_admin_user, + get_current_user_by_api_key, + ) + from apps.webui.models.users import User + + def create_user(): + user_parameters = { + "id": "1", + "name": "John Doe", + "email": "john.doe@openwebui.com", + "role": "user", + "profile_image_url": "/user.png", + "last_active_at": 1627351200, + "updated_at": 1627351200, + "created_at": 162735120, + **kwargs, + } + return User(**user_parameters) + + app.dependency_overrides = { + get_current_user: create_user, + get_verified_user: create_user, + get_admin_user: create_user, + get_current_user_by_api_key: create_user, + } + yield + app.dependency_overrides = {} diff --git a/backend/utils/utils.py b/backend/utils/utils.py index 8c3c899bd..f1225ec0e 100644 --- a/backend/utils/utils.py +++ b/backend/utils/utils.py @@ -1,6 +1,8 @@ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from fastapi import HTTPException, status, Depends, Request +from sqlalchemy.orm import Session +from apps.webui.internal.db import get_db from apps.webui.models.users import Users from pydantic import BaseModel @@ -77,6 +79,7 @@ def get_http_authorization_cred(auth_header: str): def get_current_user( request: Request, auth_token: HTTPAuthorizationCredentials = Depends(bearer_security), + db=Depends(get_db), ): token = None @@ -91,19 +94,19 @@ def get_current_user( # auth by api key if token.startswith("sk-"): - return get_current_user_by_api_key(token) + return get_current_user_by_api_key(db, token) # auth by jwt token data = decode_token(token) if data != None and "id" in data: - user = Users.get_user_by_id(data["id"]) + user = Users.get_user_by_id(db, data["id"]) if user is None: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.INVALID_TOKEN, ) else: - Users.update_user_last_active_by_id(user.id) + Users.update_user_last_active_by_id(db, user.id) return user else: raise HTTPException( @@ -112,8 +115,8 @@ def get_current_user( ) -def get_current_user_by_api_key(api_key: str): - user = Users.get_user_by_api_key(api_key) +def get_current_user_by_api_key(db: Session, api_key: str): + user = Users.get_user_by_api_key(db, api_key) if user is None: raise HTTPException( @@ -121,7 +124,7 @@ def get_current_user_by_api_key(api_key: str): detail=ERROR_MESSAGES.INVALID_TOKEN, ) else: - Users.update_user_last_active_by_id(user.id) + Users.update_user_last_active_by_id(db, user.id) return user diff --git a/src/lib/apis/models/index.ts b/src/lib/apis/models/index.ts index 9faa358d3..17d11d816 100644 --- a/src/lib/apis/models/index.ts +++ b/src/lib/apis/models/index.ts @@ -63,10 +63,7 @@ export const getModelInfos = async (token: string = '') => { export const getModelById = async (token: string, id: string) => { let error = null; - const searchParams = new URLSearchParams(); - searchParams.append('id', id); - - const res = await fetch(`${WEBUI_API_BASE_URL}/models?${searchParams.toString()}`, { + const res = await fetch(`${WEBUI_API_BASE_URL}/models/${id}`, { method: 'GET', headers: { Accept: 'application/json', From bee835cb65a8b3feba6824d2e6c9378b95f6e990 Mon Sep 17 00:00:00 2001 From: Jonathan Rohde Date: Fri, 21 Jun 2024 14:58:57 +0200 Subject: [PATCH 002/181] feat(sqlalchemy): remove session reference from router --- backend/apps/ollama/main.py | 7 +- backend/apps/openai/main.py | 4 +- backend/apps/webui/internal/db.py | 9 +- backend/apps/webui/main.py | 4 +- backend/apps/webui/models/auths.py | 158 ++++---- backend/apps/webui/models/chats.py | 336 ++++++++++-------- backend/apps/webui/models/documents.py | 83 +++-- backend/apps/webui/models/files.py | 45 ++- backend/apps/webui/models/functions.py | 117 +++--- backend/apps/webui/models/memories.py | 63 ++-- backend/apps/webui/models/models.py | 55 +-- backend/apps/webui/models/prompts.py | 102 +++--- backend/apps/webui/models/tags.py | 217 +++++------ backend/apps/webui/models/tools.py | 70 ++-- backend/apps/webui/models/users.py | 309 ++++++++-------- backend/apps/webui/routers/auths.py | 60 ++-- backend/apps/webui/routers/chats.py | 116 +++--- backend/apps/webui/routers/documents.py | 26 +- backend/apps/webui/routers/files.py | 27 +- backend/apps/webui/routers/functions.py | 27 +- backend/apps/webui/routers/memories.py | 23 +- backend/apps/webui/routers/models.py | 23 +- backend/apps/webui/routers/prompts.py | 22 +- backend/apps/webui/routers/tools.py | 23 +- backend/apps/webui/routers/users.py | 49 ++- backend/main.py | 31 +- .../migrations/versions/22b5ab2667b8_init.py | 188 ---------- .../migrations/versions/ba76b0bae648_init.py | 161 +++++++++ backend/test/apps/webui/routers/test_auths.py | 19 +- backend/test/apps/webui/routers/test_chats.py | 38 +- .../test/apps/webui/routers/test_documents.py | 10 +- .../test/apps/webui/routers/test_prompts.py | 10 + backend/test/apps/webui/routers/test_users.py | 2 - backend/utils/utils.py | 8 +- 34 files changed, 1231 insertions(+), 1211 deletions(-) delete mode 100644 backend/migrations/versions/22b5ab2667b8_init.py create mode 100644 backend/migrations/versions/ba76b0bae648_init.py diff --git a/backend/apps/ollama/main.py b/backend/apps/ollama/main.py index 85bb4c0df..455dc89a5 100644 --- a/backend/apps/ollama/main.py +++ b/backend/apps/ollama/main.py @@ -31,7 +31,6 @@ from typing import Optional, List, Union from starlette.background import BackgroundTask -from apps.webui.internal.db import get_db from apps.webui.models.models import Models from apps.webui.models.users import Users from constants import ERROR_MESSAGES @@ -712,7 +711,6 @@ async def generate_chat_completion( form_data: GenerateChatCompletionForm, url_idx: Optional[int] = None, user=Depends(get_verified_user), - db=Depends(get_db), ): log.debug( @@ -726,7 +724,7 @@ async def generate_chat_completion( } model_id = form_data.model - model_info = Models.get_model_by_id(db, model_id) + model_info = Models.get_model_by_id(model_id) if model_info: if model_info.base_model_id: @@ -885,7 +883,6 @@ async def generate_openai_chat_completion( form_data: dict, url_idx: Optional[int] = None, user=Depends(get_verified_user), - db=Depends(get_db), ): form_data = OpenAIChatCompletionForm(**form_data) @@ -894,7 +891,7 @@ async def generate_openai_chat_completion( } model_id = form_data.model - model_info = Models.get_model_by_id(db, model_id) + model_info = Models.get_model_by_id(model_id) if model_info: if model_info.base_model_id: diff --git a/backend/apps/openai/main.py b/backend/apps/openai/main.py index bc40bc661..302dd8d98 100644 --- a/backend/apps/openai/main.py +++ b/backend/apps/openai/main.py @@ -11,7 +11,6 @@ import logging from pydantic import BaseModel from starlette.background import BackgroundTask -from apps.webui.internal.db import get_db from apps.webui.models.models import Models from apps.webui.models.users import Users from constants import ERROR_MESSAGES @@ -354,13 +353,12 @@ async def generate_chat_completion( form_data: dict, url_idx: Optional[int] = None, user=Depends(get_verified_user), - db=Depends(get_db), ): idx = 0 payload = {**form_data} model_id = form_data.get("model") - model_info = Models.get_model_by_id(db, model_id) + model_info = Models.get_model_by_id(model_id) if model_info: if model_info.base_model_id: diff --git a/backend/apps/webui/internal/db.py b/backend/apps/webui/internal/db.py index 5acf83d5c..3c37bb09b 100644 --- a/backend/apps/webui/internal/db.py +++ b/backend/apps/webui/internal/db.py @@ -1,6 +1,7 @@ import os import logging import json +from contextlib import contextmanager from typing import Optional, Any from typing_extensions import Self @@ -52,11 +53,12 @@ if "sqlite" in SQLALCHEMY_DATABASE_URL: ) else: engine = create_engine(SQLALCHEMY_DATABASE_URL, pool_pre_ping=True) -SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine, expire_on_commit=False) Base = declarative_base() -def get_db(): +@contextmanager +def get_session(): db = SessionLocal() try: yield db @@ -64,5 +66,4 @@ def get_db(): except Exception as e: db.rollback() raise e - finally: - db.close() + diff --git a/backend/apps/webui/main.py b/backend/apps/webui/main.py index 8bef22c05..1ba8a080e 100644 --- a/backend/apps/webui/main.py +++ b/backend/apps/webui/main.py @@ -114,8 +114,8 @@ async def get_status(): } -async def get_pipe_models(db: Session): - pipes = Functions.get_functions_by_type(db, "pipe", active_only=True) +async def get_pipe_models(): + pipes = Functions.get_functions_by_type("pipe", active_only=True) pipe_models = [] for pipe in pipes: diff --git a/backend/apps/webui/models/auths.py b/backend/apps/webui/models/auths.py index 5ff348dac..fd2934bb1 100644 --- a/backend/apps/webui/models/auths.py +++ b/backend/apps/webui/models/auths.py @@ -8,7 +8,7 @@ from sqlalchemy.orm import Session from apps.webui.models.users import UserModel, Users from utils.utils import verify_password -from apps.webui.internal.db import Base +from apps.webui.internal.db import Base, get_session from config import SRC_LOG_LEVELS @@ -96,7 +96,6 @@ class AuthsTable: def insert_new_auth( self, - db: Session, email: str, password: str, name: str, @@ -104,100 +103,107 @@ class AuthsTable: role: str = "pending", oauth_sub: Optional[str] = None, ) -> Optional[UserModel]: - log.info("insert_new_auth") + with get_session() as db: + log.info("insert_new_auth") - id = str(uuid.uuid4()) + id = str(uuid.uuid4()) - auth = AuthModel( - **{"id": id, "email": email, "password": password, "active": True} - ) - result = Auth(**auth.model_dump()) - db.add(result) + auth = AuthModel( + **{"id": id, "email": email, "password": password, "active": True} + ) + result = Auth(**auth.model_dump()) + db.add(result) - user = Users.insert_new_user( - db, id, name, email, profile_image_url, role, oauth_sub - ) + user = Users.insert_new_user( + id, name, email, profile_image_url, role, oauth_sub + ) - db.commit() - db.refresh(result) + db.commit() + db.refresh(result) - if result and user: - return user - else: - return None - - def authenticate_user( - self, db: Session, email: str, password: str - ) -> Optional[UserModel]: - log.info(f"authenticate_user: {email}") - try: - auth = db.query(Auth).filter_by(email=email, active=True).first() - if auth: - if verify_password(password, auth.password): - user = Users.get_user_by_id(db, auth.id) - return user - else: - return None + if result and user: + return user else: return None - except: - return None + + def authenticate_user( + self, email: str, password: str + ) -> Optional[UserModel]: + log.info(f"authenticate_user: {email}") + with get_session() as db: + try: + auth = db.query(Auth).filter_by(email=email, active=True).first() + if auth: + if verify_password(password, auth.password): + user = Users.get_user_by_id(auth.id) + return user + else: + return None + else: + return None + except: + return None def authenticate_user_by_api_key( - self, db: Session, api_key: str + self, api_key: str ) -> Optional[UserModel]: log.info(f"authenticate_user_by_api_key: {api_key}") - # if no api_key, return None - if not api_key: - return None + with get_session() as db: + # if no api_key, return None + if not api_key: + return None - try: - user = Users.get_user_by_api_key(db, api_key) - return user if user else None - except: - return False + try: + user = Users.get_user_by_api_key(api_key) + return user if user else None + except: + return False def authenticate_user_by_trusted_header( - self, db: Session, email: str + self, email: str ) -> Optional[UserModel]: log.info(f"authenticate_user_by_trusted_header: {email}") - try: - auth = db.query(Auth).filter(email=email, active=True).first() - if auth: - user = Users.get_user_by_id(auth.id) - return user - except: - return None + with get_session() as db: + try: + auth = db.query(Auth).filter(email=email, active=True).first() + if auth: + user = Users.get_user_by_id(auth.id) + return user + except: + return None def update_user_password_by_id( - self, db: Session, id: str, new_password: str + self, id: str, new_password: str ) -> bool: - try: - result = db.query(Auth).filter_by(id=id).update({"password": new_password}) - return True if result == 1 else False - except: - return False - - def update_email_by_id(self, db: Session, id: str, email: str) -> bool: - try: - result = db.query(Auth).filter_by(id=id).update({"email": email}) - return True if result == 1 else False - except: - return False - - def delete_auth_by_id(self, db: Session, id: str) -> bool: - try: - # Delete User - result = Users.delete_user_by_id(db, id) - - if result: - db.query(Auth).filter_by(id=id).delete() - - return True - else: + with get_session() as db: + try: + result = db.query(Auth).filter_by(id=id).update({"password": new_password}) + return True if result == 1 else False + except: + return False + + def update_email_by_id(self, id: str, email: str) -> bool: + with get_session() as db: + try: + result = db.query(Auth).filter_by(id=id).update({"email": email}) + return True if result == 1 else False + except: + return False + + def delete_auth_by_id(self, id: str) -> bool: + with get_session() as db: + try: + # Delete User + result = Users.delete_user_by_id(id) + + if result: + db.query(Auth).filter_by(id=id).delete() + + return True + else: + return False + except: return False - except: - return False Auths = AuthsTable() diff --git a/backend/apps/webui/models/chats.py b/backend/apps/webui/models/chats.py index dd92fd0a1..d71ffd992 100644 --- a/backend/apps/webui/models/chats.py +++ b/backend/apps/webui/models/chats.py @@ -8,7 +8,7 @@ import time from sqlalchemy import Column, String, BigInteger, Boolean from sqlalchemy.orm import Session -from apps.webui.internal.db import Base +from apps.webui.internal.db import Base, get_session #################### @@ -80,249 +80,269 @@ class ChatTitleIdResponse(BaseModel): class ChatTable: def insert_new_chat( - self, db: Session, user_id: str, form_data: ChatForm + self, user_id: str, form_data: ChatForm ) -> Optional[ChatModel]: - id = str(uuid.uuid4()) - chat = ChatModel( - **{ - "id": id, - "user_id": user_id, - "title": ( - form_data.chat["title"] if "title" in form_data.chat else "New Chat" - ), - "chat": json.dumps(form_data.chat), - "created_at": int(time.time()), - "updated_at": int(time.time()), - } - ) - - result = Chat(**chat.model_dump()) - db.add(result) - db.commit() - db.refresh(result) - return ChatModel.model_validate(result) if result else None - - def update_chat_by_id( - self, db: Session, id: str, chat: dict - ) -> Optional[ChatModel]: - try: - db.query(Chat).filter_by(id=id).update( - { - "chat": json.dumps(chat), - "title": chat["title"] if "title" in chat else "New Chat", + with get_session() as db: + id = str(uuid.uuid4()) + chat = ChatModel( + **{ + "id": id, + "user_id": user_id, + "title": ( + form_data.chat["title"] if "title" in form_data.chat else "New Chat" + ), + "chat": json.dumps(form_data.chat), + "created_at": int(time.time()), "updated_at": int(time.time()), } ) - return self.get_chat_by_id(db, id) - except: - return None + result = Chat(**chat.model_dump()) + db.add(result) + db.commit() + db.refresh(result) + return ChatModel.model_validate(result) if result else None + + def update_chat_by_id( + self, id: str, chat: dict + ) -> Optional[ChatModel]: + with get_session() as db: + try: + chat_obj = db.get(Chat, id) + chat_obj.chat = json.dumps(chat) + chat_obj.title = chat["title"] if "title" in chat else "New Chat" + chat_obj.updated_at = int(time.time()) + db.commit() + db.refresh(chat_obj) + + return ChatModel.model_validate(chat_obj) + except Exception as e: + return None def insert_shared_chat_by_chat_id( - self, db: Session, chat_id: str + self, chat_id: str ) -> Optional[ChatModel]: - # Get the existing chat to share - chat = db.get(Chat, chat_id) - # Check if the chat is already shared - if chat.share_id: - return self.get_chat_by_id_and_user_id(db, chat.share_id, "shared") - # Create a new chat with the same data, but with a new ID - shared_chat = ChatModel( - **{ - "id": str(uuid.uuid4()), - "user_id": f"shared-{chat_id}", - "title": chat.title, - "chat": chat.chat, - "created_at": chat.created_at, - "updated_at": int(time.time()), - } - ) - shared_result = Chat(**shared_chat.model_dump()) - db.add(shared_result) - db.commit() - db.refresh(shared_result) - # Update the original chat with the share_id - result = ( - db.query(Chat).filter_by(id=chat_id).update({"share_id": shared_chat.id}) - ) - - return shared_chat if (shared_result and result) else None - - def update_shared_chat_by_chat_id( - self, db: Session, chat_id: str - ) -> Optional[ChatModel]: - try: - print("update_shared_chat_by_id") + with get_session() as db: + # Get the existing chat to share chat = db.get(Chat, chat_id) - print(chat) - - db.query(Chat).filter_by(id=chat.share_id).update( - {"title": chat.title, "chat": chat.chat} + # Check if the chat is already shared + if chat.share_id: + return self.get_chat_by_id_and_user_id(chat.share_id, "shared") + # Create a new chat with the same data, but with a new ID + shared_chat = ChatModel( + **{ + "id": str(uuid.uuid4()), + "user_id": f"shared-{chat_id}", + "title": chat.title, + "chat": chat.chat, + "created_at": chat.created_at, + "updated_at": int(time.time()), + } + ) + shared_result = Chat(**shared_chat.model_dump()) + db.add(shared_result) + db.commit() + db.refresh(shared_result) + # Update the original chat with the share_id + result = ( + db.query(Chat).filter_by(id=chat_id).update({"share_id": shared_chat.id}) ) - return self.get_chat_by_id(db, chat.share_id) - except: - return None + return shared_chat if (shared_result and result) else None - def delete_shared_chat_by_chat_id(self, db: Session, chat_id: str) -> bool: + def update_shared_chat_by_chat_id( + self, chat_id: str + ) -> Optional[ChatModel]: + with get_session() as db: + try: + print("update_shared_chat_by_id") + chat = db.get(Chat, chat_id) + print(chat) + chat.title = chat.title + chat.chat = chat.chat + db.commit() + db.refresh(chat) + + return self.get_chat_by_id(chat.share_id) + except: + return None + + def delete_shared_chat_by_chat_id(self, chat_id: str) -> bool: try: - db.query(Chat).filter_by(user_id=f"shared-{chat_id}").delete() + with get_session() as db: + db.query(Chat).filter_by(user_id=f"shared-{chat_id}").delete() return True except: return False def update_chat_share_id_by_id( - self, db: Session, id: str, share_id: Optional[str] + self, id: str, share_id: Optional[str] ) -> Optional[ChatModel]: try: - db.query(Chat).filter_by(id=id).update({"share_id": share_id}) - - return self.get_chat_by_id(db, id) + with get_session() as db: + chat = db.get(Chat, id) + chat.share_id = share_id + db.commit() + db.refresh(chat) + return chat except: return None - def toggle_chat_archive_by_id(self, db: Session, id: str) -> Optional[ChatModel]: + def toggle_chat_archive_by_id(self, id: str) -> Optional[ChatModel]: try: - chat = self.get_chat_by_id(db, id) - db.query(Chat).filter_by(id=id).update({"archived": not chat.archived}) + with get_session() as db: + chat = self.get_chat_by_id(id) + db.query(Chat).filter_by(id=id).update({"archived": not chat.archived}) - return self.get_chat_by_id(db, id) + return self.get_chat_by_id(id) except: return None - def archive_all_chats_by_user_id(self, db: Session, user_id: str) -> bool: + def archive_all_chats_by_user_id(self, user_id: str) -> bool: try: - db.query(Chat).filter_by(user_id=user_id).update({"archived": True}) + with get_session() as db: + db.query(Chat).filter_by(user_id=user_id).update({"archived": True}) return True except: return False def get_archived_chat_list_by_user_id( - self, db: Session, user_id: str, skip: int = 0, limit: int = 50 + self, user_id: str, skip: int = 0, limit: int = 50 ) -> List[ChatModel]: - all_chats = ( - db.query(Chat) - .filter_by(user_id=user_id, archived=True) - .order_by(Chat.updated_at.desc()) - # .limit(limit).offset(skip) - .all() - ) - return [ChatModel.model_validate(chat) for chat in all_chats] + with get_session() as db: + all_chats = ( + db.query(Chat) + .filter_by(user_id=user_id, archived=True) + .order_by(Chat.updated_at.desc()) + # .limit(limit).offset(skip) + .all() + ) + return [ChatModel.model_validate(chat) for chat in all_chats] def get_chat_list_by_user_id( self, - db: Session, user_id: str, include_archived: bool = False, skip: int = 0, limit: int = 50, ) -> List[ChatModel]: - query = db.query(Chat).filter_by(user_id=user_id) - if not include_archived: - query = query.filter_by(archived=False) - all_chats = ( - query.order_by(Chat.updated_at.desc()) - # .limit(limit).offset(skip) - .all() - ) - return [ChatModel.model_validate(chat) for chat in all_chats] + with get_session() as db: + query = db.query(Chat).filter_by(user_id=user_id) + if not include_archived: + query = query.filter_by(archived=False) + all_chats = ( + query.order_by(Chat.updated_at.desc()) + # .limit(limit).offset(skip) + .all() + ) + return [ChatModel.model_validate(chat) for chat in all_chats] def get_chat_list_by_chat_ids( - self, db: Session, chat_ids: List[str], skip: int = 0, limit: int = 50 + self, chat_ids: List[str], skip: int = 0, limit: int = 50 ) -> List[ChatModel]: - all_chats = ( - db.query(Chat) - .filter(Chat.id.in_(chat_ids)) - .filter_by(archived=False) - .order_by(Chat.updated_at.desc()) - .all() - ) - return [ChatModel.model_validate(chat) for chat in all_chats] + with get_session() as db: + all_chats = ( + db.query(Chat) + .filter(Chat.id.in_(chat_ids)) + .filter_by(archived=False) + .order_by(Chat.updated_at.desc()) + .all() + ) + return [ChatModel.model_validate(chat) for chat in all_chats] - def get_chat_by_id(self, db: Session, id: str) -> Optional[ChatModel]: + def get_chat_by_id(self, id: str) -> Optional[ChatModel]: try: - chat = db.get(Chat, id) - return ChatModel.model_validate(chat) + with get_session() as db: + chat = db.get(Chat, id) + return ChatModel.model_validate(chat) except: return None - def get_chat_by_share_id(self, db: Session, id: str) -> Optional[ChatModel]: + def get_chat_by_share_id(self, id: str) -> Optional[ChatModel]: try: - chat = db.query(Chat).filter_by(share_id=id).first() + with get_session() as db: + chat = db.query(Chat).filter_by(share_id=id).first() - if chat: - return self.get_chat_by_id(db, id) - else: - return None + if chat: + return self.get_chat_by_id(id) + else: + return None except Exception as e: return None def get_chat_by_id_and_user_id( - self, db: Session, id: str, user_id: str + self, id: str, user_id: str ) -> Optional[ChatModel]: try: - chat = db.query(Chat).filter_by(id=id, user_id=user_id).first() - return ChatModel.model_validate(chat) + with get_session() as db: + chat = db.query(Chat).filter_by(id=id, user_id=user_id).first() + return ChatModel.model_validate(chat) except: return None - def get_chats(self, db: Session, skip: int = 0, limit: int = 50) -> List[ChatModel]: - all_chats = ( - db.query(Chat) - # .limit(limit).offset(skip) - .order_by(Chat.updated_at.desc()) - ) - return [ChatModel.model_validate(chat) for chat in all_chats] + def get_chats(self, skip: int = 0, limit: int = 50) -> List[ChatModel]: + with get_session() as db: + all_chats = ( + db.query(Chat) + # .limit(limit).offset(skip) + .order_by(Chat.updated_at.desc()) + ) + return [ChatModel.model_validate(chat) for chat in all_chats] - def get_chats_by_user_id(self, db: Session, user_id: str) -> List[ChatModel]: - all_chats = ( - db.query(Chat).filter_by(user_id=user_id).order_by(Chat.updated_at.desc()) - ) - return [ChatModel.model_validate(chat) for chat in all_chats] + def get_chats_by_user_id(self, user_id: str) -> List[ChatModel]: + with get_session() as db: + all_chats = ( + db.query(Chat).filter_by(user_id=user_id).order_by(Chat.updated_at.desc()) + ) + return [ChatModel.model_validate(chat) for chat in all_chats] def get_archived_chats_by_user_id( - self, db: Session, user_id: str + self, user_id: str ) -> List[ChatModel]: - all_chats = ( - db.query(Chat) - .filter_by(user_id=user_id, archived=True) - .order_by(Chat.updated_at.desc()) - ) - return [ChatModel.model_validate(chat) for chat in all_chats] + with get_session() as db: + all_chats = ( + db.query(Chat) + .filter_by(user_id=user_id, archived=True) + .order_by(Chat.updated_at.desc()) + ) + return [ChatModel.model_validate(chat) for chat in all_chats] - def delete_chat_by_id(self, db: Session, id: str) -> bool: + def delete_chat_by_id(self, id: str) -> bool: try: - db.query(Chat).filter_by(id=id).delete() + with get_session() as db: + db.query(Chat).filter_by(id=id).delete() - return True and self.delete_shared_chat_by_chat_id(db, id) + return True and self.delete_shared_chat_by_chat_id(id) except: return False - def delete_chat_by_id_and_user_id(self, db: Session, id: str, user_id: str) -> bool: + def delete_chat_by_id_and_user_id(self, id: str, user_id: str) -> bool: try: - db.query(Chat).filter_by(id=id, user_id=user_id).delete() + with get_session() as db: + db.query(Chat).filter_by(id=id, user_id=user_id).delete() - return True and self.delete_shared_chat_by_chat_id(db, id) + return True and self.delete_shared_chat_by_chat_id(id) except: return False - def delete_chats_by_user_id(self, db: Session, user_id: str) -> bool: + def delete_chats_by_user_id(self, user_id: str) -> bool: try: + with get_session() as db: + self.delete_shared_chats_by_user_id(user_id) - self.delete_shared_chats_by_user_id(db, user_id) - - db.query(Chat).filter_by(user_id=user_id).delete() + db.query(Chat).filter_by(user_id=user_id).delete() return True except: return False - def delete_shared_chats_by_user_id(self, db: Session, user_id: str) -> bool: + def delete_shared_chats_by_user_id(self, user_id: str) -> bool: try: - chats_by_user = db.query(Chat).filter_by(user_id=user_id).all() - shared_chat_ids = [f"shared-{chat.id}" for chat in chats_by_user] + with get_session() as db: + chats_by_user = db.query(Chat).filter_by(user_id=user_id).all() + shared_chat_ids = [f"shared-{chat.id}" for chat in chats_by_user] - db.query(Chat).filter(Chat.user_id.in_(shared_chat_ids)).delete() + db.query(Chat).filter(Chat.user_id.in_(shared_chat_ids)).delete() return True except: diff --git a/backend/apps/webui/models/documents.py b/backend/apps/webui/models/documents.py index b272a5912..6348967db 100644 --- a/backend/apps/webui/models/documents.py +++ b/backend/apps/webui/models/documents.py @@ -6,7 +6,7 @@ import logging from sqlalchemy import String, Column, BigInteger from sqlalchemy.orm import Session -from apps.webui.internal.db import Base +from apps.webui.internal.db import Base, get_session import json @@ -73,7 +73,7 @@ class DocumentForm(DocumentUpdateForm): class DocumentsTable: def insert_new_doc( - self, db: Session, user_id: str, form_data: DocumentForm + self, user_id: str, form_data: DocumentForm ) -> Optional[DocumentModel]: document = DocumentModel( **{ @@ -84,66 +84,73 @@ class DocumentsTable: ) try: - result = Document(**document.model_dump()) - db.add(result) - db.commit() - db.refresh(result) - if result: - return DocumentModel.model_validate(result) - else: - return None + with get_session() as db: + result = Document(**document.model_dump()) + db.add(result) + db.commit() + db.refresh(result) + if result: + return DocumentModel.model_validate(result) + else: + return None except: return None - def get_doc_by_name(self, db: Session, name: str) -> Optional[DocumentModel]: + def get_doc_by_name(self, name: str) -> Optional[DocumentModel]: try: - document = db.query(Document).filter_by(name=name).first() - return DocumentModel.model_validate(document) if document else None + with get_session() as db: + document = db.query(Document).filter_by(name=name).first() + return DocumentModel.model_validate(document) if document else None except: return None - def get_docs(self, db: Session) -> List[DocumentModel]: - return [DocumentModel.model_validate(doc) for doc in db.query(Document).all()] + def get_docs(self) -> List[DocumentModel]: + with get_session() as db: + return [DocumentModel.model_validate(doc) for doc in db.query(Document).all()] def update_doc_by_name( - self, db: Session, name: str, form_data: DocumentUpdateForm + self, name: str, form_data: DocumentUpdateForm ) -> Optional[DocumentModel]: try: - db.query(Document).filter_by(name=name).update( - { - "title": form_data.title, - "name": form_data.name, - "timestamp": int(time.time()), - } - ) - return self.get_doc_by_name(db, form_data.name) + with get_session() as db: + db.query(Document).filter_by(name=name).update( + { + "title": form_data.title, + "name": form_data.name, + "timestamp": int(time.time()), + } + ) + db.commit() + return self.get_doc_by_name(form_data.name) except Exception as e: log.exception(e) return None def update_doc_content_by_name( - self, db: Session, name: str, updated: dict + self, name: str, updated: dict ) -> Optional[DocumentModel]: try: - doc = self.get_doc_by_name(db, name) - doc_content = json.loads(doc.content if doc.content else "{}") - doc_content = {**doc_content, **updated} + with get_session() as db: + doc = self.get_doc_by_name(name) + doc_content = json.loads(doc.content if doc.content else "{}") + doc_content = {**doc_content, **updated} - db.query(Document).filter_by(name=name).update( - { - "content": json.dumps(doc_content), - "timestamp": int(time.time()), - } - ) - - return self.get_doc_by_name(db, name) + db.query(Document).filter_by(name=name).update( + { + "content": json.dumps(doc_content), + "timestamp": int(time.time()), + } + ) + db.commit() + return self.get_doc_by_name(name) except Exception as e: log.exception(e) return None - def delete_doc_by_name(self, db: Session, name: str) -> bool: + def delete_doc_by_name(self, name: str) -> bool: try: - db.query(Document).filter_by(name=name).delete() + with get_session() as db: + db.query(Document).filter_by(name=name).delete() return True except: return False diff --git a/backend/apps/webui/models/files.py b/backend/apps/webui/models/files.py index dc9f6be39..d2565db3d 100644 --- a/backend/apps/webui/models/files.py +++ b/backend/apps/webui/models/files.py @@ -6,7 +6,7 @@ import logging from sqlalchemy import Column, String, BigInteger from sqlalchemy.orm import Session -from apps.webui.internal.db import JSONField, Base +from apps.webui.internal.db import JSONField, Base, get_session import json @@ -60,7 +60,7 @@ class FileForm(BaseModel): class FilesTable: - def insert_new_file(self, db: Session, user_id: str, form_data: FileForm) -> Optional[FileModel]: + def insert_new_file(self, user_id: str, form_data: FileForm) -> Optional[FileModel]: file = FileModel( **{ **form_data.model_dump(), @@ -70,38 +70,45 @@ class FilesTable: ) try: - result = File(**file.model_dump()) - db.add(result) - db.commit() - db.refresh(result) - if result: - return FileModel.model_validate(result) - else: - return None + with get_session() as db: + result = File(**file.model_dump()) + db.add(result) + db.commit() + db.refresh(result) + if result: + return FileModel.model_validate(result) + else: + return None except Exception as e: print(f"Error creating tool: {e}") return None - def get_file_by_id(self, db: Session, id: str) -> Optional[FileModel]: + def get_file_by_id(self, id: str) -> Optional[FileModel]: try: - file = db.get(File, id) - return FileModel.model_validate(file) + with get_session() as db: + file = db.get(File, id) + return FileModel.model_validate(file) except: return None - def get_files(self, db: Session) -> List[FileModel]: - return [FileModel.model_validate(file) for file in db.query(File).all()] + def get_files(self) -> List[FileModel]: + with get_session() as db: + return [FileModel.model_validate(file) for file in db.query(File).all()] - def delete_file_by_id(self, db: Session, id: str) -> bool: + def delete_file_by_id(self, id: str) -> bool: try: - db.query(File).filter_by(id=id).delete() + with get_session() as db: + db.query(File).filter_by(id=id).delete() + db.commit() return True except: return False - def delete_all_files(self, db: Session) -> bool: + def delete_all_files(self) -> bool: try: - db.query(File).delete() + with get_session() as db: + db.query(File).delete() + db.commit() return True except: return False diff --git a/backend/apps/webui/models/functions.py b/backend/apps/webui/models/functions.py index 88fa24a21..417e52329 100644 --- a/backend/apps/webui/models/functions.py +++ b/backend/apps/webui/models/functions.py @@ -6,7 +6,7 @@ import logging from sqlalchemy import Column, String, Text, BigInteger, Boolean from sqlalchemy.orm import Session -from apps.webui.internal.db import JSONField, Base +from apps.webui.internal.db import JSONField, Base, get_session from apps.webui.models.users import Users import json @@ -87,7 +87,7 @@ class FunctionValves(BaseModel): class FunctionsTable: def insert_new_function( - self, db: Session, user_id: str, type: str, form_data: FunctionForm + self, user_id: str, type: str, form_data: FunctionForm ) -> Optional[FunctionModel]: function = FunctionModel( **{ @@ -100,57 +100,64 @@ class FunctionsTable: ) try: - result = Function(**function.model_dump()) - db.add(result) - db.commit() - db.refresh(result) - if result: - return FunctionModel.model_validate(result) - else: - return None + with get_session() as db: + result = Function(**function.model_dump()) + db.add(result) + db.commit() + db.refresh(result) + if result: + return FunctionModel.model_validate(result) + else: + return None except Exception as e: print(f"Error creating tool: {e}") return None - def get_function_by_id(self, db: Session, id: str) -> Optional[FunctionModel]: + def get_function_by_id(self, id: str) -> Optional[FunctionModel]: try: - function = db.get(Function, id) - return FunctionModel.model_validate(function) + with get_session() as db: + function = db.get(Function, id) + return FunctionModel.model_validate(function) except: return None def get_functions(self, active_only=False) -> List[FunctionModel]: if active_only: - return [ - FunctionModel(**model_to_dict(function)) - for function in Function.select().where(Function.is_active == True) - ] + with get_session() as db: + return [ + FunctionModel.model_validate(function) + for function in db.query(Function).filter_by(is_active=True).all() + ] else: - return [ - FunctionModel(**model_to_dict(function)) - for function in Function.select() - ] + with get_session() as db: + return [ + FunctionModel.model_validate(function) + for function in db.query(Function).all() + ] def get_functions_by_type( self, type: str, active_only=False ) -> List[FunctionModel]: if active_only: - return [ - FunctionModel(**model_to_dict(function)) - for function in Function.select().where( - Function.type == type, Function.is_active == True - ) - ] + with get_session() as db: + return [ + FunctionModel.model_validate(function) + for function in db.query(Function).filter_by( + type=type, is_active=True + ).all() + ] else: - return [ - FunctionModel(**model_to_dict(function)) - for function in Function.select().where(Function.type == type) - ] + with get_session() as db: + return [ + FunctionModel.model_validate(function) + for function in db.query(Function).filter_by(type=type).all() + ] def get_function_valves_by_id(self, id: str) -> Optional[dict]: try: - function = Function.get(Function.id == id) - return function.valves if function.valves else {} + with get_session() as db: + function = db.get(Function, id) + return function.valves if function.valves else {} except Exception as e: print(f"An error occurred: {e}") return None @@ -159,14 +166,12 @@ class FunctionsTable: self, id: str, valves: dict ) -> Optional[FunctionValves]: try: - query = Function.update( - **{"valves": valves}, - updated_at=int(time.time()), - ).where(Function.id == id) - query.execute() - - function = Function.get(Function.id == id) - return FunctionValves(**model_to_dict(function)) + with get_session() as db: + db.query(Function).filter_by(id=id).update( + {"valves": valves, "updated_at": int(time.time())} + ) + db.commit() + return self.get_function_by_id(id) except: return None @@ -214,30 +219,32 @@ class FunctionsTable: def update_function_by_id(self, id: str, updated: dict) -> Optional[FunctionModel]: try: - db.query(Function).filter_by(id=id).update({ - **updated, - "updated_at": int(time.time()), - }) - return self.get_function_by_id(db, id) + with get_session() as db: + db.query(Function).filter_by(id=id).update({ + **updated, + "updated_at": int(time.time()), + }) + db.commit() + return self.get_function_by_id(id) except: return None def deactivate_all_functions(self) -> Optional[bool]: try: - query = Function.update( - **{"is_active": False}, - updated_at=int(time.time()), - ) - - query.execute() - + with get_session() as db: + db.query(Function).update({ + "is_active": False, + "updated_at": int(time.time()), + }) + db.commit() return True except: return None - def delete_function_by_id(self, db: Session, id: str) -> bool: + def delete_function_by_id(self, id: str) -> bool: try: - db.query(Function).filter_by(id=id).delete() + with get_session() as db: + db.query(Function).filter_by(id=id).delete() return True except: return False diff --git a/backend/apps/webui/models/memories.py b/backend/apps/webui/models/memories.py index f5f6d13fb..941da5b26 100644 --- a/backend/apps/webui/models/memories.py +++ b/backend/apps/webui/models/memories.py @@ -4,7 +4,7 @@ from typing import List, Union, Optional from sqlalchemy import Column, String, BigInteger from sqlalchemy.orm import Session -from apps.webui.internal.db import Base +from apps.webui.internal.db import Base, get_session from apps.webui.models.chats import Chats import time @@ -44,7 +44,6 @@ class MemoriesTable: def insert_new_memory( self, - db: Session, user_id: str, content: str, ) -> Optional[MemoryModel]: @@ -59,53 +58,59 @@ class MemoriesTable: "updated_at": int(time.time()), } ) - result = Memory(**memory.dict()) - db.add(result) - db.commit() - db.refresh(result) - if result: - return MemoryModel.model_validate(result) - else: - return None + with get_session() as db: + result = Memory(**memory.model_dump()) + db.add(result) + db.commit() + db.refresh(result) + if result: + return MemoryModel.model_validate(result) + else: + return None def update_memory_by_id( self, - db: Session, id: str, content: str, ) -> Optional[MemoryModel]: try: - db.query(Memory).filter_by(id=id).update( - {"content": content, "updated_at": int(time.time())} - ) - return self.get_memory_by_id(db, id) + with get_session() as db: + db.query(Memory).filter_by(id=id).update( + {"content": content, "updated_at": int(time.time())} + ) + db.commit() + return self.get_memory_by_id(id) except: return None - def get_memories(self, db: Session) -> List[MemoryModel]: + def get_memories(self) -> List[MemoryModel]: try: - memories = db.query(Memory).all() - return [MemoryModel.model_validate(memory) for memory in memories] + with get_session() as db: + memories = db.query(Memory).all() + return [MemoryModel.model_validate(memory) for memory in memories] except: return None - def get_memories_by_user_id(self, db: Session, user_id: str) -> List[MemoryModel]: + def get_memories_by_user_id(self, user_id: str) -> List[MemoryModel]: try: - memories = db.query(Memory).filter_by(user_id=user_id).all() - return [MemoryModel.model_validate(memory) for memory in memories] + with get_session() as db: + memories = db.query(Memory).filter_by(user_id=user_id).all() + return [MemoryModel.model_validate(memory) for memory in memories] except: return None - def get_memory_by_id(self, db: Session, id: str) -> Optional[MemoryModel]: + def get_memory_by_id(self, id: str) -> Optional[MemoryModel]: try: - memory = db.get(Memory, id) - return MemoryModel.model_validate(memory) + with get_session() as db: + memory = db.get(Memory, id) + return MemoryModel.model_validate(memory) except: return None - def delete_memory_by_id(self, db: Session, id: str) -> bool: + def delete_memory_by_id(self, id: str) -> bool: try: - db.query(Memory).filter_by(id=id).delete() + with get_session() as db: + db.query(Memory).filter_by(id=id).delete() return True except: @@ -113,7 +118,8 @@ class MemoriesTable: def delete_memories_by_user_id(self, db: Session, user_id: str) -> bool: try: - db.query(Memory).filter_by(user_id=user_id).delete() + with get_session() as db: + db.query(Memory).filter_by(user_id=user_id).delete() return True except: return False @@ -122,7 +128,8 @@ class MemoriesTable: self, db: Session, id: str, user_id: str ) -> bool: try: - db.query(Memory).filter_by(id=id, user_id=user_id).delete() + with get_session() as db: + db.query(Memory).filter_by(id=id, user_id=user_id).delete() return True except: return False diff --git a/backend/apps/webui/models/models.py b/backend/apps/webui/models/models.py index 137333409..7641ee5a0 100644 --- a/backend/apps/webui/models/models.py +++ b/backend/apps/webui/models/models.py @@ -6,7 +6,7 @@ from pydantic import BaseModel, ConfigDict from sqlalchemy import String, Column, BigInteger from sqlalchemy.orm import Session -from apps.webui.internal.db import Base, JSONField +from apps.webui.internal.db import Base, JSONField, get_session from typing import List, Union, Optional from config import SRC_LOG_LEVELS @@ -78,8 +78,6 @@ class Model(Base): class ModelModel(BaseModel): - model_config = ConfigDict(from_attributes=True) - id: str user_id: str base_model_id: Optional[str] = None @@ -91,6 +89,8 @@ class ModelModel(BaseModel): updated_at: int # timestamp in epoch created_at: int # timestamp in epoch + model_config = ConfigDict(from_attributes=True) + #################### # Forms @@ -116,7 +116,7 @@ class ModelForm(BaseModel): class ModelsTable: def insert_new_model( - self, db: Session, form_data: ModelForm, user_id: str + self, form_data: ModelForm, user_id: str ) -> Optional[ModelModel]: model = ModelModel( **{ @@ -127,47 +127,52 @@ class ModelsTable: } ) try: - result = Model(**model.dict()) - db.add(result) - db.commit() - db.refresh(result) + with get_session() as db: + result = Model(**model.model_dump()) + db.add(result) + db.commit() + db.refresh(result) - if result: - return ModelModel.model_validate(result) - else: - return None + if result: + return ModelModel.model_validate(result) + else: + return None except Exception as e: print(e) return None - def get_all_models(self, db: Session) -> List[ModelModel]: - return [ModelModel.model_validate(model) for model in db.query(Model).all()] + def get_all_models(self) -> List[ModelModel]: + with get_session() as db: + return [ModelModel.model_validate(model) for model in db.query(Model).all()] - def get_model_by_id(self, db: Session, id: str) -> Optional[ModelModel]: + def get_model_by_id(self, id: str) -> Optional[ModelModel]: try: - model = db.get(Model, id) - return ModelModel.model_validate(model) + with get_session() as db: + model = db.get(Model, id) + return ModelModel.model_validate(model) except: return None def update_model_by_id( - self, db: Session, id: str, model: ModelForm + self, id: str, model: ModelForm ) -> Optional[ModelModel]: try: # update only the fields that are present in the model - model = db.query(Model).get(id) - model.update(**model.model_dump()) - db.commit() - db.refresh(model) - return ModelModel.model_validate(model) + with get_session() as db: + model = db.query(Model).get(id) + model.update(**model.model_dump()) + db.commit() + db.refresh(model) + return ModelModel.model_validate(model) except Exception as e: print(e) return None - def delete_model_by_id(self, db: Session, id: str) -> bool: + def delete_model_by_id(self, id: str) -> bool: try: - db.query(Model).filter_by(id=id).delete() + with get_session() as db: + db.query(Model).filter_by(id=id).delete() return True except: return False diff --git a/backend/apps/webui/models/prompts.py b/backend/apps/webui/models/prompts.py index 21c4de3e1..2157153d8 100644 --- a/backend/apps/webui/models/prompts.py +++ b/backend/apps/webui/models/prompts.py @@ -5,7 +5,7 @@ import time from sqlalchemy import String, Column, BigInteger from sqlalchemy.orm import Session -from apps.webui.internal.db import Base +from apps.webui.internal.db import Base, get_session import json @@ -48,61 +48,65 @@ class PromptForm(BaseModel): class PromptsTable: def insert_new_prompt( - self, db: Session, user_id: str, form_data: PromptForm + self, user_id: str, form_data: PromptForm ) -> Optional[PromptModel]: - prompt = PromptModel( - **{ - "user_id": user_id, - "command": form_data.command, - "title": form_data.title, - "content": form_data.content, - "timestamp": int(time.time()), - } - ) - - try: - result = Prompt(**prompt.dict()) - db.add(result) - db.commit() - db.refresh(result) - if result: - return PromptModel.model_validate(result) - else: - return None - except Exception as e: - return None - - def get_prompt_by_command(self, db: Session, command: str) -> Optional[PromptModel]: - try: - prompt = db.query(Prompt).filter_by(command=command).first() - return PromptModel.model_validate(prompt) - except: - return None - - def get_prompts(self, db: Session) -> List[PromptModel]: - return [PromptModel.model_validate(prompt) for prompt in db.query(Prompt).all()] - - def update_prompt_by_command( - self, db: Session, command: str, form_data: PromptForm - ) -> Optional[PromptModel]: - try: - db.query(Prompt).filter_by(command=command).update( - { + with get_session() as db: + prompt = PromptModel( + **{ + "user_id": user_id, + "command": form_data.command, "title": form_data.title, "content": form_data.content, "timestamp": int(time.time()), } ) - return self.get_prompt_by_command(db, command) - except: - return None - def delete_prompt_by_command(self, db: Session, command: str) -> bool: - try: - db.query(Prompt).filter_by(command=command).delete() - return True - except: - return False + try: + result = Prompt(**prompt.dict()) + db.add(result) + db.commit() + db.refresh(result) + if result: + return PromptModel.model_validate(result) + else: + return None + except Exception as e: + return None + + def get_prompt_by_command(self, command: str) -> Optional[PromptModel]: + with get_session() as db: + try: + prompt = db.query(Prompt).filter_by(command=command).first() + return PromptModel.model_validate(prompt) + except: + return None + + def get_prompts(self) -> List[PromptModel]: + with get_session() as db: + return [PromptModel.model_validate(prompt) for prompt in db.query(Prompt).all()] + + def update_prompt_by_command( + self, command: str, form_data: PromptForm + ) -> Optional[PromptModel]: + with get_session() as db: + try: + prompt = db.query(Prompt).filter_by(command=command).first() + prompt.title = form_data.title + prompt.content = form_data.content + prompt.timestamp = int(time.time()) + db.commit() + return prompt + # return self.get_prompt_by_command(command) + except: + return None + + def delete_prompt_by_command(self, command: str) -> bool: + with get_session() as db: + try: + db.query(Prompt).filter_by(command=command).delete() + return True + except: + return False Prompts = PromptsTable() diff --git a/backend/apps/webui/models/tags.py b/backend/apps/webui/models/tags.py index 419425662..5ad176c37 100644 --- a/backend/apps/webui/models/tags.py +++ b/backend/apps/webui/models/tags.py @@ -9,7 +9,7 @@ import logging from sqlalchemy import String, Column, BigInteger from sqlalchemy.orm import Session -from apps.webui.internal.db import Base +from apps.webui.internal.db import Base, get_session from config import SRC_LOG_LEVELS @@ -80,37 +80,39 @@ class ChatTagsResponse(BaseModel): class TagTable: def insert_new_tag( - self, db: Session, name: str, user_id: str + self, name: str, user_id: str ) -> Optional[TagModel]: id = str(uuid.uuid4()) tag = TagModel(**{"id": id, "user_id": user_id, "name": name}) try: - result = Tag(**tag.dict()) - db.add(result) - db.commit() - db.refresh(result) - if result: - return TagModel.model_validate(result) - else: - return None + with get_session() as db: + result = Tag(**tag.model_dump()) + db.add(result) + db.commit() + db.refresh(result) + if result: + return TagModel.model_validate(result) + else: + return None except Exception as e: return None def get_tag_by_name_and_user_id( - self, db: Session, name: str, user_id: str + self, name: str, user_id: str ) -> Optional[TagModel]: try: - tag = db.query(Tag).filter(name=name, user_id=user_id).first() - return TagModel.model_validate(tag) + with get_session() as db: + tag = db.query(Tag).filter(name=name, user_id=user_id).first() + return TagModel.model_validate(tag) except Exception as e: return None def add_tag_to_chat( - self, db: Session, user_id: str, form_data: ChatIdTagForm + self, user_id: str, form_data: ChatIdTagForm ) -> Optional[ChatIdTagModel]: - tag = self.get_tag_by_name_and_user_id(db, form_data.tag_name, user_id) + tag = self.get_tag_by_name_and_user_id(form_data.tag_name, user_id) if tag == None: - tag = self.insert_new_tag(db, form_data.tag_name, user_id) + tag = self.insert_new_tag(form_data.tag_name, user_id) id = str(uuid.uuid4()) chatIdTag = ChatIdTagModel( @@ -123,118 +125,127 @@ class TagTable: } ) try: - result = ChatIdTag(**chatIdTag.dict()) - db.add(result) - db.commit() - db.refresh(result) - if result: - return ChatIdTagModel.model_validate(result) - else: - return None + with get_session() as db: + result = ChatIdTag(**chatIdTag.model_dump()) + db.add(result) + db.commit() + db.refresh(result) + if result: + return ChatIdTagModel.model_validate(result) + else: + return None except: return None - def get_tags_by_user_id(self, db: Session, user_id: str) -> List[TagModel]: - tag_names = [ - chat_id_tag.tag_name - for chat_id_tag in ( - db.query(ChatIdTag) - .filter_by(user_id=user_id) - .order_by(ChatIdTag.timestamp.desc()) - .all() - ) - ] + def get_tags_by_user_id(self, user_id: str) -> List[TagModel]: + with get_session() as db: + tag_names = [ + chat_id_tag.tag_name + for chat_id_tag in ( + db.query(ChatIdTag) + .filter_by(user_id=user_id) + .order_by(ChatIdTag.timestamp.desc()) + .all() + ) + ] - return [ - TagModel.model_validate(tag) - for tag in ( - db.query(Tag) - .filter_by(user_id=user_id) - .filter(Tag.name.in_(tag_names)) - .all() - ) - ] + return [ + TagModel.model_validate(tag) + for tag in ( + db.query(Tag) + .filter_by(user_id=user_id) + .filter(Tag.name.in_(tag_names)) + .all() + ) + ] def get_tags_by_chat_id_and_user_id( - self, db: Session, chat_id: str, user_id: str + self, chat_id: str, user_id: str ) -> List[TagModel]: - tag_names = [ - chat_id_tag.tag_name - for chat_id_tag in ( - db.query(ChatIdTag) - .filter_by(user_id=user_id, chat_id=chat_id) - .order_by(ChatIdTag.timestamp.desc()) - .all() - ) - ] + with get_session() as db: + tag_names = [ + chat_id_tag.tag_name + for chat_id_tag in ( + db.query(ChatIdTag) + .filter_by(user_id=user_id, chat_id=chat_id) + .order_by(ChatIdTag.timestamp.desc()) + .all() + ) + ] - return [ - TagModel.model_validate(tag) - for tag in ( - db.query(Tag) - .filter_by(user_id=user_id) - .filter(Tag.name.in_(tag_names)) - .all() - ) - ] + return [ + TagModel.model_validate(tag) + for tag in ( + db.query(Tag) + .filter_by(user_id=user_id) + .filter(Tag.name.in_(tag_names)) + .all() + ) + ] def get_chat_ids_by_tag_name_and_user_id( - self, db: Session, tag_name: str, user_id: str + self, tag_name: str, user_id: str ) -> List[ChatIdTagModel]: - return [ - ChatIdTagModel.model_validate(chat_id_tag) - for chat_id_tag in ( - db.query(ChatIdTag) - .filter_by(user_id=user_id, tag_name=tag_name) - .order_by(ChatIdTag.timestamp.desc()) - .all() - ) - ] + with get_session() as db: + return [ + ChatIdTagModel.model_validate(chat_id_tag) + for chat_id_tag in ( + db.query(ChatIdTag) + .filter_by(user_id=user_id, tag_name=tag_name) + .order_by(ChatIdTag.timestamp.desc()) + .all() + ) + ] def count_chat_ids_by_tag_name_and_user_id( - self, db: Session, tag_name: str, user_id: str + self, tag_name: str, user_id: str ) -> int: - return db.query(ChatIdTag).filter_by(tag_name=tag_name, user_id=user_id).count() + with get_session() as db: + return db.query(ChatIdTag).filter_by(tag_name=tag_name, user_id=user_id).count() def delete_tag_by_tag_name_and_user_id( - self, db: Session, tag_name: str, user_id: str + self, tag_name: str, user_id: str ) -> bool: try: - res = ( - db.query(ChatIdTag) - .filter_by(tag_name=tag_name, user_id=user_id) - .delete() - ) - log.debug(f"res: {res}") + with get_session() as db: + res = ( + db.query(ChatIdTag) + .filter_by(tag_name=tag_name, user_id=user_id) + .delete() + ) + log.debug(f"res: {res}") + db.commit() - tag_count = self.count_chat_ids_by_tag_name_and_user_id( - db, tag_name, user_id - ) - if tag_count == 0: - # Remove tag item from Tag col as well - db.query(Tag).filter_by(name=tag_name, user_id=user_id).delete() + tag_count = self.count_chat_ids_by_tag_name_and_user_id( + tag_name, user_id + ) + if tag_count == 0: + # Remove tag item from Tag col as well + db.query(Tag).filter_by(name=tag_name, user_id=user_id).delete() return True except Exception as e: log.error(f"delete_tag: {e}") return False def delete_tag_by_tag_name_and_chat_id_and_user_id( - self, db: Session, tag_name: str, chat_id: str, user_id: str + self, tag_name: str, chat_id: str, user_id: str ) -> bool: try: - res = ( - db.query(ChatIdTag) - .filter_by(tag_name=tag_name, chat_id=chat_id, user_id=user_id) - .delete() - ) - log.debug(f"res: {res}") + with get_session() as db: + res = ( + db.query(ChatIdTag) + .filter_by(tag_name=tag_name, chat_id=chat_id, user_id=user_id) + .delete() + ) + log.debug(f"res: {res}") + db.commit() - tag_count = self.count_chat_ids_by_tag_name_and_user_id( - db, tag_name, user_id - ) - if tag_count == 0: - # Remove tag item from Tag col as well - db.query(Tag).filter_by(name=tag_name, user_id=user_id).delete() + tag_count = self.count_chat_ids_by_tag_name_and_user_id( + tag_name, user_id + ) + if tag_count == 0: + # Remove tag item from Tag col as well + db.query(Tag).filter_by(name=tag_name, user_id=user_id).delete() return True except Exception as e: @@ -242,13 +253,13 @@ class TagTable: return False def delete_tags_by_chat_id_and_user_id( - self, db: Session, chat_id: str, user_id: str + self, chat_id: str, user_id: str ) -> bool: - tags = self.get_tags_by_chat_id_and_user_id(db, chat_id, user_id) + tags = self.get_tags_by_chat_id_and_user_id(chat_id, user_id) for tag in tags: self.delete_tag_by_tag_name_and_chat_id_and_user_id( - db, tag.tag_name, chat_id, user_id + tag.tag_name, chat_id, user_id ) return True diff --git a/backend/apps/webui/models/tools.py b/backend/apps/webui/models/tools.py index b8df2e163..534a4e3e8 100644 --- a/backend/apps/webui/models/tools.py +++ b/backend/apps/webui/models/tools.py @@ -5,7 +5,7 @@ import logging from sqlalchemy import String, Column, BigInteger from sqlalchemy.orm import Session -from apps.webui.internal.db import Base, JSONField +from apps.webui.internal.db import Base, JSONField, get_session from apps.webui.models.users import Users import json @@ -82,7 +82,7 @@ class ToolValves(BaseModel): class ToolsTable: def insert_new_tool( - self, db: Session, user_id: str, form_data: ToolForm, specs: List[dict] + self, user_id: str, form_data: ToolForm, specs: List[dict] ) -> Optional[ToolModel]: tool = ToolModel( **{ @@ -95,46 +95,48 @@ class ToolsTable: ) try: - result = Tool(**tool.dict()) - db.add(result) - db.commit() - db.refresh(result) - if result: - return ToolModel.model_validate(result) - else: - return None + with get_session() as db: + result = Tool(**tool.model_dump()) + db.add(result) + db.commit() + db.refresh(result) + if result: + return ToolModel.model_validate(result) + else: + return None except Exception as e: print(f"Error creating tool: {e}") return None - def get_tool_by_id(self, db: Session, id: str) -> Optional[ToolModel]: + def get_tool_by_id(self, id: str) -> Optional[ToolModel]: try: - tool = db.get(Tool, id) - return ToolModel.model_validate(tool) + with get_session() as db: + tool = db.get(Tool, id) + return ToolModel.model_validate(tool) except: return None - def get_tools(self, db: Session) -> List[ToolModel]: - return [ToolModel.model_validate(tool) for tool in db.query(Tool).all()] + def get_tools(self) -> List[ToolModel]: + with get_session() as db: + return [ToolModel.model_validate(tool) for tool in db.query(Tool).all()] def get_tool_valves_by_id(self, id: str) -> Optional[dict]: try: - tool = Tool.get(Tool.id == id) - return tool.valves if tool.valves else {} + with get_session() as db: + tool = db.get(Tool, id) + return tool.valves if tool.valves else {} except Exception as e: print(f"An error occurred: {e}") return None def update_tool_valves_by_id(self, id: str, valves: dict) -> Optional[ToolValves]: try: - query = Tool.update( - **{"valves": valves}, - updated_at=int(time.time()), - ).where(Tool.id == id) - query.execute() - - tool = Tool.get(Tool.id == id) - return ToolValves(**model_to_dict(tool)) + with get_session() as db: + db.query(Tool).filter_by(id=id).update( + {"valves": valves, "updated_at": int(time.time())} + ) + db.commit() + return self.get_tool_by_id(id) except: return None @@ -172,8 +174,7 @@ class ToolsTable: user_settings["tools"]["valves"][id] = valves # Update the user settings in the database - query = Users.update_user_by_id(user_id, {"settings": user_settings}) - query.execute() + Users.update_user_by_id(user_id, {"settings": user_settings}) return user_settings["tools"]["valves"][id] except Exception as e: @@ -182,16 +183,19 @@ class ToolsTable: def update_tool_by_id(self, id: str, updated: dict) -> Optional[ToolModel]: try: - db.query(Tool).filter_by(id=id).update( - {**updated, "updated_at": int(time.time())} - ) - return self.get_tool_by_id(db, id) + with get_session() as db: + db.query(Tool).filter_by(id=id).update( + {**updated, "updated_at": int(time.time())} + ) + db.commit() + return self.get_tool_by_id(id) except: return None - def delete_tool_by_id(self, db: Session, id: str) -> bool: + def delete_tool_by_id(self, id: str) -> bool: try: - db.query(Tool).filter_by(id=id).delete() + with get_session() as db: + db.query(Tool).filter_by(id=id).delete() return True except: return False diff --git a/backend/apps/webui/models/users.py b/backend/apps/webui/models/users.py index 7202d2d71..bef15185b 100644 --- a/backend/apps/webui/models/users.py +++ b/backend/apps/webui/models/users.py @@ -7,7 +7,7 @@ from sqlalchemy.orm import Session from utils.misc import get_gravatar_url -from apps.webui.internal.db import Base, JSONField +from apps.webui.internal.db import Base, JSONField, get_session from apps.webui.models.chats import Chats #################### @@ -42,8 +42,6 @@ class UserSettings(BaseModel): class UserModel(BaseModel): - model_config = ConfigDict(from_attributes=True) - id: str name: str email: str @@ -60,6 +58,8 @@ class UserModel(BaseModel): oauth_sub: Optional[str] = None + model_config = ConfigDict(from_attributes=True) + #################### # Forms @@ -82,7 +82,6 @@ class UsersTable: def insert_new_user( self, - db: Session, id: str, name: str, email: str, @@ -90,165 +89,181 @@ class UsersTable: role: str = "pending", oauth_sub: Optional[str] = None, ) -> Optional[UserModel]: - user = UserModel( - **{ - "id": id, - "name": name, - "email": email, - "role": role, - "profile_image_url": profile_image_url, - "last_active_at": int(time.time()), - "created_at": int(time.time()), - "updated_at": int(time.time()), - "oauth_sub": oauth_sub, - } - ) - result = User(**user.model_dump()) - db.add(result) - db.commit() - db.refresh(result) - if result: - return user - else: - return None + with get_session() as db: + user = UserModel( + **{ + "id": id, + "name": name, + "email": email, + "role": role, + "profile_image_url": profile_image_url, + "last_active_at": int(time.time()), + "created_at": int(time.time()), + "updated_at": int(time.time()), + "oauth_sub": oauth_sub, + } + ) + result = User(**user.model_dump()) + db.add(result) + db.commit() + db.refresh(result) + if result: + return user + else: + return None - def get_user_by_id(self, db: Session, id: str) -> Optional[UserModel]: - try: - user = db.query(User).filter_by(id=id).first() - return UserModel.model_validate(user) - except Exception as e: - return None + def get_user_by_id(self, id: str) -> Optional[UserModel]: + with get_session() as db: + try: + user = db.query(User).filter_by(id=id).first() + return UserModel.model_validate(user) + except Exception as e: + return None - def get_user_by_api_key(self, db: Session, api_key: str) -> Optional[UserModel]: - try: - user = db.query(User).filter_by(api_key=api_key).first() - return UserModel.model_validate(user) - except: - return None + def get_user_by_api_key(self, api_key: str) -> Optional[UserModel]: + with get_session() as db: + try: + user = db.query(User).filter_by(api_key=api_key).first() + return UserModel.model_validate(user) + except: + return None - def get_user_by_email(self, db: Session, email: str) -> Optional[UserModel]: - try: - user = db.query(User).filter_by(email=email).first() - return UserModel.model_validate(user) - except: - return None + def get_user_by_email(self, email: str) -> Optional[UserModel]: + with get_session() as db: + try: + user = db.query(User).filter_by(email=email).first() + return UserModel.model_validate(user) + except: + return None def get_user_by_oauth_sub(self, sub: str) -> Optional[UserModel]: - try: - user = User.get(User.oauth_sub == sub) - return UserModel(**model_to_dict(user)) - except: - return None + with get_session() as db: + try: + user = db.query(User).filter_by(oauth_sub=sub).first() + return UserModel.model_validate(user) + except: + return None - def get_users(self, db: Session, skip: int = 0, limit: int = 50) -> List[UserModel]: - users = ( - db.query(User) - # .offset(skip).limit(limit) - .all() - ) - return [UserModel.model_validate(user) for user in users] + def get_users(self, skip: int = 0, limit: int = 50) -> List[UserModel]: + with get_session() as db: + users = ( + db.query(User) + # .offset(skip).limit(limit) + .all() + ) + return [UserModel.model_validate(user) for user in users] - def get_num_users(self, db: Session) -> Optional[int]: - return db.query(User).count() + def get_num_users(self) -> Optional[int]: + with get_session() as db: + return db.query(User).count() - def get_first_user(self, db: Session) -> UserModel: - try: - user = db.query(User).order_by(User.created_at).first() - return UserModel.model_validate(user) - except: - return None + def get_first_user(self) -> UserModel: + with get_session() as db: + try: + user = db.query(User).order_by(User.created_at).first() + return UserModel.model_validate(user) + except: + return None def update_user_role_by_id( - self, db: Session, id: str, role: str + self, id: str, role: str ) -> Optional[UserModel]: - try: - db.query(User).filter_by(id=id).update({"role": role}) - db.commit() - - user = db.query(User).filter_by(id=id).first() - return UserModel.model_validate(user) - except: - return None - - def update_user_profile_image_url_by_id( - self, db: Session, id: str, profile_image_url: str - ) -> Optional[UserModel]: - try: - db.query(User).filter_by(id=id).update( - {"profile_image_url": profile_image_url} - ) - db.commit() - - user = db.query(User).filter_by(id=id).first() - return UserModel.model_validate(user) - except: - return None - - def update_user_last_active_by_id( - self, db: Session, id: str - ) -> Optional[UserModel]: - try: - db.query(User).filter_by(id=id).update({"last_active_at": int(time.time())}) - - user = db.query(User).filter_by(id=id).first() - return UserModel.model_validate(user) - except: - return None - - def update_user_oauth_sub_by_id( - self, db: Session, id: str, oauth_sub: str - ) -> Optional[UserModel]: - try: - db.query(User).filter_by(id=id).update({"oauth_sub": oauth_sub}) - - user = db.query(User).filter_by(id=id).first() - return UserModel.model_validate(user) - except: - return None - - def update_user_by_id( - self, db: Session, id: str, updated: dict - ) -> Optional[UserModel]: - try: - db.query(User).filter_by(id=id).update(updated) - db.commit() - - user = db.query(User).filter_by(id=id).first() - return UserModel.model_validate(user) - # return UserModel(**user.dict()) - except Exception as e: - return None - - def delete_user_by_id(self, db: Session, id: str) -> bool: - try: - # Delete User Chats - result = Chats.delete_chats_by_user_id(db, id) - - if result: - # Delete User - db.query(User).filter_by(id=id).delete() + with get_session() as db: + try: + db.query(User).filter_by(id=id).update({"role": role}) db.commit() - return True - else: + user = db.query(User).filter_by(id=id).first() + return UserModel.model_validate(user) + except: + return None + + def update_user_profile_image_url_by_id( + self, id: str, profile_image_url: str + ) -> Optional[UserModel]: + with get_session() as db: + try: + db.query(User).filter_by(id=id).update( + {"profile_image_url": profile_image_url} + ) + db.commit() + + user = db.query(User).filter_by(id=id).first() + return UserModel.model_validate(user) + except: + return None + + def update_user_last_active_by_id( + self, id: str + ) -> Optional[UserModel]: + with get_session() as db: + try: + db.query(User).filter_by(id=id).update({"last_active_at": int(time.time())}) + + user = db.query(User).filter_by(id=id).first() + return UserModel.model_validate(user) + except: + return None + + def update_user_oauth_sub_by_id( + self, id: str, oauth_sub: str + ) -> Optional[UserModel]: + with get_session() as db: + try: + db.query(User).filter_by(id=id).update({"oauth_sub": oauth_sub}) + + user = db.query(User).filter_by(id=id).first() + return UserModel.model_validate(user) + except: + return None + + def update_user_by_id( + self, id: str, updated: dict + ) -> Optional[UserModel]: + with get_session() as db: + try: + db.query(User).filter_by(id=id).update(updated) + db.commit() + + user = db.query(User).filter_by(id=id).first() + return UserModel.model_validate(user) + # return UserModel(**user.dict()) + except Exception as e: + return None + + def delete_user_by_id(self, id: str) -> bool: + with get_session() as db: + try: + # Delete User Chats + result = Chats.delete_chats_by_user_id(id) + + if result: + # Delete User + db.query(User).filter_by(id=id).delete() + db.commit() + + return True + else: + return False + except: return False - except: - return False - def update_user_api_key_by_id(self, db: Session, id: str, api_key: str) -> str: - try: - result = db.query(User).filter_by(id=id).update({"api_key": api_key}) - db.commit() - return True if result == 1 else False - except: - return False + def update_user_api_key_by_id(self, id: str, api_key: str) -> str: + with get_session() as db: + try: + result = db.query(User).filter_by(id=id).update({"api_key": api_key}) + db.commit() + return True if result == 1 else False + except: + return False - def get_user_api_key_by_id(self, db: Session, id: str) -> Optional[str]: - try: - user = db.query(User).filter_by(id=id).first() - return user.api_key - except Exception as e: - return None + def get_user_api_key_by_id(self, id: str) -> Optional[str]: + with get_session() as db: + try: + user = db.query(User).filter_by(id=id).first() + return user.api_key + except Exception as e: + return None Users = UsersTable() diff --git a/backend/apps/webui/routers/auths.py b/backend/apps/webui/routers/auths.py index e83ee8cb9..f32b074b1 100644 --- a/backend/apps/webui/routers/auths.py +++ b/backend/apps/webui/routers/auths.py @@ -10,7 +10,6 @@ import re import uuid import csv -from apps.webui.internal.db import get_db from apps.webui.models.auths import ( SigninForm, SignupForm, @@ -80,12 +79,10 @@ async def get_session_user( @router.post("/update/profile", response_model=UserResponse) async def update_profile( form_data: UpdateProfileForm, - session_user=Depends(get_current_user), - db=Depends(get_db), + session_user=Depends(get_current_user) ): if session_user: user = Users.update_user_by_id( - db, session_user.id, {"profile_image_url": form_data.profile_image_url, "name": form_data.name}, ) @@ -105,17 +102,16 @@ async def update_profile( @router.post("/update/password", response_model=bool) async def update_password( form_data: UpdatePasswordForm, - session_user=Depends(get_current_user), - db=Depends(get_db), + session_user=Depends(get_current_user) ): if WEBUI_AUTH_TRUSTED_EMAIL_HEADER: raise HTTPException(400, detail=ERROR_MESSAGES.ACTION_PROHIBITED) if session_user: - user = Auths.authenticate_user(db, session_user.email, form_data.password) + user = Auths.authenticate_user(session_user.email, form_data.password) if user: hashed = get_password_hash(form_data.new_password) - return Auths.update_user_password_by_id(db, user.id, hashed) + return Auths.update_user_password_by_id(user.id, hashed) else: raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_PASSWORD) else: @@ -128,7 +124,7 @@ async def update_password( @router.post("/signin", response_model=SigninResponse) -async def signin(request: Request, response: Response, form_data: SigninForm, db=Depends(get_db)): +async def signin(request: Request, response: Response, form_data: SigninForm): if WEBUI_AUTH_TRUSTED_EMAIL_HEADER: if WEBUI_AUTH_TRUSTED_EMAIL_HEADER not in request.headers: raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_TRUSTED_HEADER) @@ -139,34 +135,32 @@ async def signin(request: Request, response: Response, form_data: SigninForm, db trusted_name = request.headers.get( WEBUI_AUTH_TRUSTED_NAME_HEADER, trusted_email ) - if not Users.get_user_by_email(db, trusted_email.lower()): + if not Users.get_user_by_email(trusted_email.lower()): await signup( request, SignupForm( email=trusted_email, password=str(uuid.uuid4()), name=trusted_name ), - db, ) - user = Auths.authenticate_user_by_trusted_header(db, trusted_email) + user = Auths.authenticate_user_by_trusted_header(trusted_email) elif WEBUI_AUTH == False: admin_email = "admin@localhost" admin_password = "admin" - if Users.get_user_by_email(db, admin_email.lower()): - user = Auths.authenticate_user(db, admin_email.lower(), admin_password) + if Users.get_user_by_email(admin_email.lower()): + user = Auths.authenticate_user(admin_email.lower(), admin_password) else: - if Users.get_num_users(db) != 0: + if Users.get_num_users() != 0: raise HTTPException(400, detail=ERROR_MESSAGES.EXISTING_USERS) await signup( request, SignupForm(email=admin_email, password=admin_password, name="User"), - db, ) - user = Auths.authenticate_user(db, admin_email.lower(), admin_password) + user = Auths.authenticate_user(admin_email.lower(), admin_password) else: - user = Auths.authenticate_user(db, form_data.email.lower(), form_data.password) + user = Auths.authenticate_user(form_data.email.lower(), form_data.password) if user: token = create_token( @@ -200,7 +194,7 @@ async def signin(request: Request, response: Response, form_data: SigninForm, db @router.post("/signup", response_model=SigninResponse) -async def signup(request: Request, response: Response, form_data: SignupForm, db=Depends(get_db)): +async def signup(request: Request, response: Response, form_data: SignupForm): if not request.app.state.config.ENABLE_SIGNUP and WEBUI_AUTH: raise HTTPException( status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.ACCESS_PROHIBITED @@ -211,18 +205,17 @@ async def signup(request: Request, response: Response, form_data: SignupForm, db status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.INVALID_EMAIL_FORMAT ) - if Users.get_user_by_email(db, form_data.email.lower()): + if Users.get_user_by_email(form_data.email.lower()): raise HTTPException(400, detail=ERROR_MESSAGES.EMAIL_TAKEN) try: role = ( "admin" - if Users.get_num_users(db) == 0 + if Users.get_num_users() == 0 else request.app.state.config.DEFAULT_USER_ROLE ) hashed = get_password_hash(form_data.password) user = Auths.insert_new_auth( - db, form_data.email.lower(), hashed, form_data.name, @@ -277,7 +270,7 @@ async def signup(request: Request, response: Response, form_data: SignupForm, db @router.post("/add", response_model=SigninResponse) async def add_user( - form_data: AddUserForm, user=Depends(get_admin_user), db=Depends(get_db) + form_data: AddUserForm, user=Depends(get_admin_user) ): if not validate_email_format(form_data.email.lower()): @@ -285,7 +278,7 @@ async def add_user( status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.INVALID_EMAIL_FORMAT ) - if Users.get_user_by_email(db, form_data.email.lower()): + if Users.get_user_by_email(form_data.email.lower()): raise HTTPException(400, detail=ERROR_MESSAGES.EMAIL_TAKEN) try: @@ -293,7 +286,6 @@ async def add_user( print(form_data) hashed = get_password_hash(form_data.password) user = Auths.insert_new_auth( - db, form_data.email.lower(), hashed, form_data.name, @@ -325,7 +317,7 @@ async def add_user( @router.get("/admin/details") async def get_admin_details( - request: Request, user=Depends(get_current_user), db=Depends(get_db) + request: Request, user=Depends(get_current_user) ): if request.app.state.config.SHOW_ADMIN_DETAILS: admin_email = request.app.state.config.ADMIN_EMAIL @@ -334,11 +326,11 @@ async def get_admin_details( print(admin_email, admin_name) if admin_email: - admin = Users.get_user_by_email(db, admin_email) + admin = Users.get_user_by_email(admin_email) if admin: admin_name = admin.name else: - admin = Users.get_first_user(db) + admin = Users.get_first_user() if admin: admin_email = admin.email admin_name = admin.name @@ -411,9 +403,9 @@ async def update_admin_config( # create api key @router.post("/api_key", response_model=ApiKey) -async def create_api_key_(user=Depends(get_current_user), db=Depends(get_db)): +async def create_api_key_(user=Depends(get_current_user)): api_key = create_api_key() - success = Users.update_user_api_key_by_id(db, user.id, api_key) + success = Users.update_user_api_key_by_id(user.id, api_key) if success: return { "api_key": api_key, @@ -424,15 +416,15 @@ async def create_api_key_(user=Depends(get_current_user), db=Depends(get_db)): # delete api key @router.delete("/api_key", response_model=bool) -async def delete_api_key(user=Depends(get_current_user), db=Depends(get_db)): - success = Users.update_user_api_key_by_id(db, user.id, None) +async def delete_api_key(user=Depends(get_current_user)): + success = Users.update_user_api_key_by_id(user.id, None) return success # get api key @router.get("/api_key", response_model=ApiKey) -async def get_api_key(user=Depends(get_current_user), db=Depends(get_db)): - api_key = Users.get_user_api_key_by_id(db, user.id) +async def get_api_key(user=Depends(get_current_user)): + api_key = Users.get_user_api_key_by_id(user.id) if api_key: return { "api_key": api_key, diff --git a/backend/apps/webui/routers/chats.py b/backend/apps/webui/routers/chats.py index 1454d47bd..8b2b9987a 100644 --- a/backend/apps/webui/routers/chats.py +++ b/backend/apps/webui/routers/chats.py @@ -2,7 +2,6 @@ from fastapi import Depends, Request, HTTPException, status from datetime import datetime, timedelta from typing import List, Union, Optional -from apps.webui.internal.db import get_db from utils.utils import get_current_user, get_admin_user from fastapi import APIRouter from pydantic import BaseModel @@ -45,9 +44,9 @@ router = APIRouter() @router.get("/", response_model=List[ChatTitleIdResponse]) @router.get("/list", response_model=List[ChatTitleIdResponse]) async def get_session_user_chat_list( - user=Depends(get_current_user), skip: int = 0, limit: int = 50, db=Depends(get_db) + user=Depends(get_current_user), skip: int = 0, limit: int = 50 ): - return Chats.get_chat_list_by_user_id(db, user.id, skip, limit) + return Chats.get_chat_list_by_user_id(user.id, skip, limit) ############################ @@ -57,7 +56,7 @@ async def get_session_user_chat_list( @router.delete("/", response_model=bool) async def delete_all_user_chats( - request: Request, user=Depends(get_current_user), db=Depends(get_db) + request: Request, user=Depends(get_current_user) ): if ( @@ -69,7 +68,7 @@ async def delete_all_user_chats( detail=ERROR_MESSAGES.ACCESS_PROHIBITED, ) - result = Chats.delete_chats_by_user_id(db, user.id) + result = Chats.delete_chats_by_user_id(user.id) return result @@ -84,10 +83,9 @@ async def get_user_chat_list_by_user_id( user=Depends(get_admin_user), skip: int = 0, limit: int = 50, - db=Depends(get_db), ): return Chats.get_chat_list_by_user_id( - db, user_id, include_archived=True, skip=skip, limit=limit + user_id, include_archived=True, skip=skip, limit=limit ) @@ -98,10 +96,10 @@ async def get_user_chat_list_by_user_id( @router.post("/new", response_model=Optional[ChatResponse]) async def create_new_chat( - form_data: ChatForm, user=Depends(get_current_user), db=Depends(get_db) + form_data: ChatForm, user=Depends(get_current_user) ): try: - chat = Chats.insert_new_chat(db, user.id, form_data) + chat = Chats.insert_new_chat(user.id, form_data) return ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)}) except Exception as e: log.exception(e) @@ -116,10 +114,10 @@ async def create_new_chat( @router.get("/all", response_model=List[ChatResponse]) -async def get_user_chats(user=Depends(get_current_user), db=Depends(get_db)): +async def get_user_chats(user=Depends(get_current_user)): return [ ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)}) - for chat in Chats.get_chats_by_user_id(db, user.id) + for chat in Chats.get_chats_by_user_id(user.id) ] @@ -129,10 +127,10 @@ async def get_user_chats(user=Depends(get_current_user), db=Depends(get_db)): @router.get("/all/archived", response_model=List[ChatResponse]) -async def get_user_archived_chats(user=Depends(get_current_user), db=Depends(get_db)): +async def get_user_archived_chats(user=Depends(get_current_user)): return [ ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)}) - for chat in Chats.get_archived_chats_by_user_id(db, user.id) + for chat in Chats.get_archived_chats_by_user_id(user.id) ] @@ -142,7 +140,7 @@ async def get_user_archived_chats(user=Depends(get_current_user), db=Depends(get @router.get("/all/db", response_model=List[ChatResponse]) -async def get_all_user_chats_in_db(user=Depends(get_admin_user), db=Depends(get_db)): +async def get_all_user_chats_in_db(user=Depends(get_admin_user)): if not ENABLE_ADMIN_EXPORT: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, @@ -150,7 +148,7 @@ async def get_all_user_chats_in_db(user=Depends(get_admin_user), db=Depends(get_ ) return [ ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)}) - for chat in Chats.get_chats(db) + for chat in Chats.get_chats() ] @@ -161,9 +159,9 @@ async def get_all_user_chats_in_db(user=Depends(get_admin_user), db=Depends(get_ @router.get("/archived", response_model=List[ChatTitleIdResponse]) async def get_archived_session_user_chat_list( - user=Depends(get_current_user), skip: int = 0, limit: int = 50, db=Depends(get_db) + user=Depends(get_current_user), skip: int = 0, limit: int = 50 ): - return Chats.get_archived_chat_list_by_user_id(db, user.id, skip, limit) + return Chats.get_archived_chat_list_by_user_id(user.id, skip, limit) ############################ @@ -172,8 +170,8 @@ async def get_archived_session_user_chat_list( @router.post("/archive/all", response_model=bool) -async def archive_all_chats(user=Depends(get_current_user), db=Depends(get_db)): - return Chats.archive_all_chats_by_user_id(db, user.id) +async def archive_all_chats(user=Depends(get_current_user)): + return Chats.archive_all_chats_by_user_id(user.id) ############################ @@ -183,7 +181,7 @@ async def archive_all_chats(user=Depends(get_current_user), db=Depends(get_db)): @router.get("/share/{share_id}", response_model=Optional[ChatResponse]) async def get_shared_chat_by_id( - share_id: str, user=Depends(get_current_user), db=Depends(get_db) + share_id: str, user=Depends(get_current_user) ): if user.role == "pending": raise HTTPException( @@ -191,9 +189,9 @@ async def get_shared_chat_by_id( ) if user.role == "user": - chat = Chats.get_chat_by_share_id(db, share_id) + chat = Chats.get_chat_by_share_id(share_id) elif user.role == "admin": - chat = Chats.get_chat_by_id(db, share_id) + chat = Chats.get_chat_by_id(share_id) if chat: return ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)}) @@ -216,23 +214,23 @@ class TagNameForm(BaseModel): @router.post("/tags", response_model=List[ChatTitleIdResponse]) async def get_user_chat_list_by_tag_name( - form_data: TagNameForm, user=Depends(get_current_user), db=Depends(get_db) + form_data: TagNameForm, user=Depends(get_current_user) ): print(form_data) chat_ids = [ chat_id_tag.chat_id for chat_id_tag in Tags.get_chat_ids_by_tag_name_and_user_id( - db, form_data.name, user.id + form_data.name, user.id ) ] chats = Chats.get_chat_list_by_chat_ids( - db, chat_ids, form_data.skip, form_data.limit + chat_ids, form_data.skip, form_data.limit ) if len(chats) == 0: - Tags.delete_tag_by_tag_name_and_user_id(db, form_data.name, user.id) + Tags.delete_tag_by_tag_name_and_user_id(form_data.name, user.id) return chats @@ -243,9 +241,9 @@ async def get_user_chat_list_by_tag_name( @router.get("/tags/all", response_model=List[TagModel]) -async def get_all_tags(user=Depends(get_current_user), db=Depends(get_db)): +async def get_all_tags(user=Depends(get_current_user)): try: - tags = Tags.get_tags_by_user_id(db, user.id) + tags = Tags.get_tags_by_user_id(user.id) return tags except Exception as e: log.exception(e) @@ -260,8 +258,8 @@ async def get_all_tags(user=Depends(get_current_user), db=Depends(get_db)): @router.get("/{id}", response_model=Optional[ChatResponse]) -async def get_chat_by_id(id: str, user=Depends(get_current_user), db=Depends(get_db)): - chat = Chats.get_chat_by_id_and_user_id(db, id, user.id) +async def get_chat_by_id(id: str, user=Depends(get_current_user)): + chat = Chats.get_chat_by_id_and_user_id(id, user.id) if chat: return ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)}) @@ -278,13 +276,13 @@ async def get_chat_by_id(id: str, user=Depends(get_current_user), db=Depends(get @router.post("/{id}", response_model=Optional[ChatResponse]) async def update_chat_by_id( - id: str, form_data: ChatForm, user=Depends(get_current_user), db=Depends(get_db) + id: str, form_data: ChatForm, user=Depends(get_current_user) ): - chat = Chats.get_chat_by_id_and_user_id(db, id, user.id) + chat = Chats.get_chat_by_id_and_user_id(id, user.id) if chat: updated_chat = {**json.loads(chat.chat), **form_data.chat} - chat = Chats.update_chat_by_id(db, id, updated_chat) + chat = Chats.update_chat_by_id(id, updated_chat) return ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)}) else: raise HTTPException( @@ -300,11 +298,11 @@ async def update_chat_by_id( @router.delete("/{id}", response_model=bool) async def delete_chat_by_id( - request: Request, id: str, user=Depends(get_current_user), db=Depends(get_db) + request: Request, id: str, user=Depends(get_current_user) ): if user.role == "admin": - result = Chats.delete_chat_by_id(db, id) + result = Chats.delete_chat_by_id(id) return result else: if not request.app.state.config.USER_PERMISSIONS["chat"]["deletion"]: @@ -313,7 +311,7 @@ async def delete_chat_by_id( detail=ERROR_MESSAGES.ACCESS_PROHIBITED, ) - result = Chats.delete_chat_by_id_and_user_id(db, id, user.id) + result = Chats.delete_chat_by_id_and_user_id(id, user.id) return result @@ -323,8 +321,8 @@ async def delete_chat_by_id( @router.get("/{id}/clone", response_model=Optional[ChatResponse]) -async def clone_chat_by_id(id: str, user=Depends(get_current_user), db=Depends(get_db)): - chat = Chats.get_chat_by_id_and_user_id(db, id, user.id) +async def clone_chat_by_id(id: str, user=Depends(get_current_user)): + chat = Chats.get_chat_by_id_and_user_id(id, user.id) if chat: chat_body = json.loads(chat.chat) @@ -335,7 +333,7 @@ async def clone_chat_by_id(id: str, user=Depends(get_current_user), db=Depends(g "title": f"Clone of {chat.title}", } - chat = Chats.insert_new_chat(db, user.id, ChatForm(**{"chat": updated_chat})) + chat = Chats.insert_new_chat(user.id, ChatForm(**{"chat": updated_chat})) return ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)}) else: raise HTTPException( @@ -350,11 +348,11 @@ async def clone_chat_by_id(id: str, user=Depends(get_current_user), db=Depends(g @router.get("/{id}/archive", response_model=Optional[ChatResponse]) async def archive_chat_by_id( - id: str, user=Depends(get_current_user), db=Depends(get_db) + id: str, user=Depends(get_current_user) ): - chat = Chats.get_chat_by_id_and_user_id(db, id, user.id) + chat = Chats.get_chat_by_id_and_user_id(id, user.id) if chat: - chat = Chats.toggle_chat_archive_by_id(db, id) + chat = Chats.toggle_chat_archive_by_id(id) return ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)}) else: raise HTTPException( @@ -368,16 +366,16 @@ async def archive_chat_by_id( @router.post("/{id}/share", response_model=Optional[ChatResponse]) -async def share_chat_by_id(id: str, user=Depends(get_current_user), db=Depends(get_db)): - chat = Chats.get_chat_by_id_and_user_id(db, id, user.id) +async def share_chat_by_id(id: str, user=Depends(get_current_user)): + chat = Chats.get_chat_by_id_and_user_id(id, user.id) if chat: if chat.share_id: - shared_chat = Chats.update_shared_chat_by_chat_id(db, chat.id) + shared_chat = Chats.update_shared_chat_by_chat_id(chat.id) return ChatResponse( **{**shared_chat.model_dump(), "chat": json.loads(shared_chat.chat)} ) - shared_chat = Chats.insert_shared_chat_by_chat_id(db, chat.id) + shared_chat = Chats.insert_shared_chat_by_chat_id(chat.id) if not shared_chat: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, @@ -401,15 +399,15 @@ async def share_chat_by_id(id: str, user=Depends(get_current_user), db=Depends(g @router.delete("/{id}/share", response_model=Optional[bool]) async def delete_shared_chat_by_id( - id: str, user=Depends(get_current_user), db=Depends(get_db) + id: str, user=Depends(get_current_user) ): - chat = Chats.get_chat_by_id_and_user_id(db, id, user.id) + chat = Chats.get_chat_by_id_and_user_id(id, user.id) if chat: if not chat.share_id: return False - result = Chats.delete_shared_chat_by_chat_id(db, id) - update_result = Chats.update_chat_share_id_by_id(db, id, None) + result = Chats.delete_shared_chat_by_chat_id(id) + update_result = Chats.update_chat_share_id_by_id(id, None) return result and update_result != None else: @@ -426,9 +424,9 @@ async def delete_shared_chat_by_id( @router.get("/{id}/tags", response_model=List[TagModel]) async def get_chat_tags_by_id( - id: str, user=Depends(get_current_user), db=Depends(get_db) + id: str, user=Depends(get_current_user) ): - tags = Tags.get_tags_by_chat_id_and_user_id(db, id, user.id) + tags = Tags.get_tags_by_chat_id_and_user_id(id, user.id) if tags != None: return tags @@ -447,13 +445,12 @@ async def get_chat_tags_by_id( async def add_chat_tag_by_id( id: str, form_data: ChatIdTagForm, - user=Depends(get_current_user), - db=Depends(get_db), + user=Depends(get_current_user) ): - tags = Tags.get_tags_by_chat_id_and_user_id(db, id, user.id) + tags = Tags.get_tags_by_chat_id_and_user_id(id, user.id) if form_data.tag_name not in tags: - tag = Tags.add_tag_to_chat(db, user.id, form_data) + tag = Tags.add_tag_to_chat(user.id, form_data) if tag: return tag @@ -478,10 +475,9 @@ async def delete_chat_tag_by_id( id: str, form_data: ChatIdTagForm, user=Depends(get_current_user), - db=Depends(get_db), ): result = Tags.delete_tag_by_tag_name_and_chat_id_and_user_id( - db, form_data.tag_name, id, user.id + form_data.tag_name, id, user.id ) if result: @@ -499,9 +495,9 @@ async def delete_chat_tag_by_id( @router.delete("/{id}/tags/all", response_model=Optional[bool]) async def delete_all_chat_tags_by_id( - id: str, user=Depends(get_current_user), db=Depends(get_db) + id: str, user=Depends(get_current_user) ): - result = Tags.delete_tags_by_chat_id_and_user_id(db, id, user.id) + result = Tags.delete_tags_by_chat_id_and_user_id(id, user.id) if result: return result diff --git a/backend/apps/webui/routers/documents.py b/backend/apps/webui/routers/documents.py index b9a42352a..f358e033c 100644 --- a/backend/apps/webui/routers/documents.py +++ b/backend/apps/webui/routers/documents.py @@ -6,7 +6,6 @@ from fastapi import APIRouter from pydantic import BaseModel import json -from apps.webui.internal.db import get_db from apps.webui.models.documents import ( Documents, DocumentForm, @@ -26,7 +25,7 @@ router = APIRouter() @router.get("/", response_model=List[DocumentResponse]) -async def get_documents(user=Depends(get_current_user), db=Depends(get_db)): +async def get_documents(user=Depends(get_current_user)): docs = [ DocumentResponse( **{ @@ -34,7 +33,7 @@ async def get_documents(user=Depends(get_current_user), db=Depends(get_db)): "content": json.loads(doc.content if doc.content else "{}"), } ) - for doc in Documents.get_docs(db) + for doc in Documents.get_docs() ] return docs @@ -46,11 +45,11 @@ async def get_documents(user=Depends(get_current_user), db=Depends(get_db)): @router.post("/create", response_model=Optional[DocumentResponse]) async def create_new_doc( - form_data: DocumentForm, user=Depends(get_admin_user), db=Depends(get_db) + form_data: DocumentForm, user=Depends(get_admin_user) ): - doc = Documents.get_doc_by_name(db, form_data.name) + doc = Documents.get_doc_by_name(form_data.name) if doc == None: - doc = Documents.insert_new_doc(db, user.id, form_data) + doc = Documents.insert_new_doc(user.id, form_data) if doc: return DocumentResponse( @@ -78,9 +77,9 @@ async def create_new_doc( @router.get("/doc", response_model=Optional[DocumentResponse]) async def get_doc_by_name( - name: str, user=Depends(get_current_user), db=Depends(get_db) + name: str, user=Depends(get_current_user) ): - doc = Documents.get_doc_by_name(db, name) + doc = Documents.get_doc_by_name(name) if doc: return DocumentResponse( @@ -112,10 +111,10 @@ class TagDocumentForm(BaseModel): @router.post("/doc/tags", response_model=Optional[DocumentResponse]) async def tag_doc_by_name( - form_data: TagDocumentForm, user=Depends(get_current_user), db=Depends(get_db) + form_data: TagDocumentForm, user=Depends(get_current_user) ): doc = Documents.update_doc_content_by_name( - db, form_data.name, {"tags": form_data.tags} + form_data.name, {"tags": form_data.tags} ) if doc: @@ -142,9 +141,8 @@ async def update_doc_by_name( name: str, form_data: DocumentUpdateForm, user=Depends(get_admin_user), - db=Depends(get_db), ): - doc = Documents.update_doc_by_name(db, name, form_data) + doc = Documents.update_doc_by_name(name, form_data) if doc: return DocumentResponse( **{ @@ -166,7 +164,7 @@ async def update_doc_by_name( @router.delete("/doc/delete", response_model=bool) async def delete_doc_by_name( - name: str, user=Depends(get_admin_user), db=Depends(get_db) + name: str, user=Depends(get_admin_user) ): - result = Documents.delete_doc_by_name(db, name) + result = Documents.delete_doc_by_name(name) return result diff --git a/backend/apps/webui/routers/files.py b/backend/apps/webui/routers/files.py index 2ed119ad0..e98d1da58 100644 --- a/backend/apps/webui/routers/files.py +++ b/backend/apps/webui/routers/files.py @@ -20,7 +20,6 @@ from fastapi.responses import StreamingResponse, JSONResponse, FileResponse from pydantic import BaseModel import json -from apps.webui.internal.db import get_db from apps.webui.models.files import ( Files, FileForm, @@ -53,8 +52,7 @@ router = APIRouter() @router.post("/") def upload_file( file: UploadFile = File(...), - user=Depends(get_verified_user), - db=Depends(get_db) + user=Depends(get_verified_user) ): log.info(f"file.content_type: {file.content_type}") try: @@ -72,7 +70,6 @@ def upload_file( f.close() file = Files.insert_new_file( - db, user.id, FileForm( **{ @@ -109,8 +106,8 @@ def upload_file( @router.get("/", response_model=List[FileModel]) -async def list_files(user=Depends(get_verified_user), db=Depends(get_db)): - files = Files.get_files(db) +async def list_files(user=Depends(get_verified_user)): + files = Files.get_files() return files @@ -120,8 +117,8 @@ async def list_files(user=Depends(get_verified_user), db=Depends(get_db)): @router.delete("/all") -async def delete_all_files(user=Depends(get_admin_user), db=Depends(get_db)): - result = Files.delete_all_files(db) +async def delete_all_files(user=Depends(get_admin_user)): + result = Files.delete_all_files() if result: folder = f"{UPLOAD_DIR}" @@ -157,8 +154,8 @@ async def delete_all_files(user=Depends(get_admin_user), db=Depends(get_db)): @router.get("/{id}", response_model=Optional[FileModel]) -async def get_file_by_id(id: str, user=Depends(get_verified_user), db=Depends(get_db)): - file = Files.get_file_by_id(db, id) +async def get_file_by_id(id: str, user=Depends(get_verified_user)): + file = Files.get_file_by_id(id) if file: return file @@ -175,8 +172,8 @@ async def get_file_by_id(id: str, user=Depends(get_verified_user), db=Depends(ge @router.get("/{id}/content", response_model=Optional[FileModel]) -async def get_file_content_by_id(id: str, user=Depends(get_verified_user), db=Depends(get_db)): - file = Files.get_file_by_id(db, id) +async def get_file_content_by_id(id: str, user=Depends(get_verified_user)): + file = Files.get_file_by_id(id) if file: file_path = Path(file.meta["path"]) @@ -226,11 +223,11 @@ async def get_file_content_by_id(id: str, user=Depends(get_verified_user)): @router.delete("/{id}") -async def delete_file_by_id(id: str, user=Depends(get_verified_user), db=Depends(get_db)): - file = Files.get_file_by_id(db, id) +async def delete_file_by_id(id: str, user=Depends(get_verified_user)): + file = Files.get_file_by_id(id) if file: - result = Files.delete_file_by_id(db, id) + result = Files.delete_file_by_id(id) if result: return {"message": "File deleted successfully"} else: diff --git a/backend/apps/webui/routers/functions.py b/backend/apps/webui/routers/functions.py index f15566702..4c89ca487 100644 --- a/backend/apps/webui/routers/functions.py +++ b/backend/apps/webui/routers/functions.py @@ -6,7 +6,6 @@ from fastapi import APIRouter from pydantic import BaseModel import json -from apps.webui.internal.db import get_db from apps.webui.models.functions import ( Functions, FunctionForm, @@ -32,8 +31,8 @@ router = APIRouter() @router.get("/", response_model=List[FunctionResponse]) -async def get_functions(user=Depends(get_verified_user), db=Depends(get_db)): - return Functions.get_functions(db) +async def get_functions(user=Depends(get_verified_user)): + return Functions.get_functions() ############################ @@ -42,8 +41,8 @@ async def get_functions(user=Depends(get_verified_user), db=Depends(get_db)): @router.get("/export", response_model=List[FunctionModel]) -async def get_functions(user=Depends(get_admin_user), db=Depends(get_db)): - return Functions.get_functions(db) +async def get_functions(user=Depends(get_admin_user)): + return Functions.get_functions() ############################ @@ -53,7 +52,7 @@ async def get_functions(user=Depends(get_admin_user), db=Depends(get_db)): @router.post("/create", response_model=Optional[FunctionResponse]) async def create_new_function( - request: Request, form_data: FunctionForm, user=Depends(get_admin_user), db=Depends(get_db) + request: Request, form_data: FunctionForm, user=Depends(get_admin_user) ): if not form_data.id.isidentifier(): raise HTTPException( @@ -63,7 +62,7 @@ async def create_new_function( form_data.id = form_data.id.lower() - function = Functions.get_function_by_id(db, form_data.id) + function = Functions.get_function_by_id(form_data.id) if function == None: function_path = os.path.join(FUNCTIONS_DIR, f"{form_data.id}.py") try: @@ -78,7 +77,7 @@ async def create_new_function( FUNCTIONS = request.app.state.FUNCTIONS FUNCTIONS[form_data.id] = function_module - function = Functions.insert_new_function(db, user.id, function_type, form_data) + function = Functions.insert_new_function(user.id, function_type, form_data) function_cache_dir = Path(CACHE_DIR) / "functions" / form_data.id function_cache_dir.mkdir(parents=True, exist_ok=True) @@ -109,8 +108,8 @@ async def create_new_function( @router.get("/id/{id}", response_model=Optional[FunctionModel]) -async def get_function_by_id(id: str, user=Depends(get_admin_user), db=Depends(get_db)): - function = Functions.get_function_by_id(db, id) +async def get_function_by_id(id: str, user=Depends(get_admin_user)): + function = Functions.get_function_by_id(id) if function: return function @@ -155,7 +154,7 @@ async def toggle_function_by_id(id: str, user=Depends(get_admin_user)): @router.post("/id/{id}/update", response_model=Optional[FunctionModel]) async def update_function_by_id( - request: Request, id: str, form_data: FunctionForm, user=Depends(get_admin_user), db=Depends(get_db) + request: Request, id: str, form_data: FunctionForm, user=Depends(get_admin_user) ): function_path = os.path.join(FUNCTIONS_DIR, f"{id}.py") @@ -172,7 +171,7 @@ async def update_function_by_id( updated = {**form_data.model_dump(exclude={"id"}), "type": function_type} print(updated) - function = Functions.update_function_by_id(db, id, updated) + function = Functions.update_function_by_id(id, updated) if function: return function @@ -196,9 +195,9 @@ async def update_function_by_id( @router.delete("/id/{id}/delete", response_model=bool) async def delete_function_by_id( - request: Request, id: str, user=Depends(get_admin_user), db=Depends(get_db) + request: Request, id: str, user=Depends(get_admin_user) ): - result = Functions.delete_function_by_id(db, id) + result = Functions.delete_function_by_id(id) if result: FUNCTIONS = request.app.state.FUNCTIONS diff --git a/backend/apps/webui/routers/memories.py b/backend/apps/webui/routers/memories.py index e7fafa37b..d6b2d0fcb 100644 --- a/backend/apps/webui/routers/memories.py +++ b/backend/apps/webui/routers/memories.py @@ -7,7 +7,6 @@ from fastapi import APIRouter from pydantic import BaseModel import logging -from apps.webui.internal.db import get_db from apps.webui.models.memories import Memories, MemoryModel from utils.utils import get_verified_user @@ -32,8 +31,8 @@ async def get_embeddings(request: Request): @router.get("/", response_model=List[MemoryModel]) -async def get_memories(user=Depends(get_verified_user), db=Depends(get_db)): - return Memories.get_memories_by_user_id(db, user.id) +async def get_memories(user=Depends(get_verified_user)): + return Memories.get_memories_by_user_id(user.id) ############################ @@ -54,9 +53,8 @@ async def add_memory( request: Request, form_data: AddMemoryForm, user=Depends(get_verified_user), - db=Depends(get_db), ): - memory = Memories.insert_new_memory(db, user.id, form_data.content) + memory = Memories.insert_new_memory(user.id, form_data.content) memory_embedding = request.app.state.EMBEDDING_FUNCTION(memory.content) collection = CHROMA_CLIENT.get_or_create_collection(name=f"user-memory-{user.id}") @@ -76,9 +74,8 @@ async def update_memory_by_id( request: Request, form_data: MemoryUpdateModel, user=Depends(get_verified_user), - db=Depends(get_db), ): - memory = Memories.update_memory_by_id(db, memory_id, form_data.content) + memory = Memories.update_memory_by_id(memory_id, form_data.content) if memory is None: raise HTTPException(status_code=404, detail="Memory not found") @@ -129,12 +126,12 @@ async def query_memory( ############################ @router.get("/reset", response_model=bool) async def reset_memory_from_vector_db( - request: Request, user=Depends(get_verified_user), db=Depends(get_db) + request: Request, user=Depends(get_verified_user) ): CHROMA_CLIENT.delete_collection(f"user-memory-{user.id}") collection = CHROMA_CLIENT.get_or_create_collection(name=f"user-memory-{user.id}") - memories = Memories.get_memories_by_user_id(db, user.id) + memories = Memories.get_memories_by_user_id(user.id) for memory in memories: memory_embedding = request.app.state.EMBEDDING_FUNCTION(memory.content) collection.upsert( @@ -151,8 +148,8 @@ async def reset_memory_from_vector_db( @router.delete("/user", response_model=bool) -async def delete_memory_by_user_id(user=Depends(get_verified_user), db=Depends(get_db)): - result = Memories.delete_memories_by_user_id(db, user.id) +async def delete_memory_by_user_id(user=Depends(get_verified_user)): + result = Memories.delete_memories_by_user_id(user.id) if result: try: @@ -171,9 +168,9 @@ async def delete_memory_by_user_id(user=Depends(get_verified_user), db=Depends(g @router.delete("/{memory_id}", response_model=bool) async def delete_memory_by_id( - memory_id: str, user=Depends(get_verified_user), db=Depends(get_db) + memory_id: str, user=Depends(get_verified_user) ): - result = Memories.delete_memory_by_id_and_user_id(db, memory_id, user.id) + result = Memories.delete_memory_by_id_and_user_id(memory_id, user.id) if result: collection = CHROMA_CLIENT.get_or_create_collection( diff --git a/backend/apps/webui/routers/models.py b/backend/apps/webui/routers/models.py index f151e8864..eaf459d73 100644 --- a/backend/apps/webui/routers/models.py +++ b/backend/apps/webui/routers/models.py @@ -6,7 +6,6 @@ from fastapi import APIRouter from pydantic import BaseModel import json -from apps.webui.internal.db import get_db from apps.webui.models.models import Models, ModelModel, ModelForm, ModelResponse from utils.utils import get_verified_user, get_admin_user @@ -20,8 +19,8 @@ router = APIRouter() @router.get("/", response_model=List[ModelResponse]) -async def get_models(user=Depends(get_verified_user), db=Depends(get_db)): - return Models.get_all_models(db) +async def get_models(user=Depends(get_verified_user)): + return Models.get_all_models() ############################ @@ -34,7 +33,6 @@ async def add_new_model( request: Request, form_data: ModelForm, user=Depends(get_admin_user), - db=Depends(get_db), ): if form_data.id in request.app.state.MODELS: raise HTTPException( @@ -42,7 +40,7 @@ async def add_new_model( detail=ERROR_MESSAGES.MODEL_ID_TAKEN, ) else: - model = Models.insert_new_model(db, form_data, user.id) + model = Models.insert_new_model(form_data, user.id) if model: return model @@ -59,8 +57,8 @@ async def add_new_model( @router.get("/{id}", response_model=Optional[ModelModel]) -async def get_model_by_id(id: str, user=Depends(get_verified_user), db=Depends(get_db)): - model = Models.get_model_by_id(db, id) +async def get_model_by_id(id: str, user=Depends(get_verified_user)): + model = Models.get_model_by_id(id) if model: return model @@ -82,15 +80,14 @@ async def update_model_by_id( id: str, form_data: ModelForm, user=Depends(get_admin_user), - db=Depends(get_db), ): - model = Models.get_model_by_id(db, id) + model = Models.get_model_by_id(id) if model: - model = Models.update_model_by_id(db, id, form_data) + model = Models.update_model_by_id(id, form_data) return model else: if form_data.id in request.app.state.MODELS: - model = Models.insert_new_model(db, form_data, user.id) + model = Models.insert_new_model(form_data, user.id) if model: return model else: @@ -111,6 +108,6 @@ async def update_model_by_id( @router.delete("/delete", response_model=bool) -async def delete_model_by_id(id: str, user=Depends(get_admin_user), db=Depends(get_db)): - result = Models.delete_model_by_id(db, id) +async def delete_model_by_id(id: str, user=Depends(get_admin_user)): + result = Models.delete_model_by_id(id) return result diff --git a/backend/apps/webui/routers/prompts.py b/backend/apps/webui/routers/prompts.py index c8f173a1e..3912b1028 100644 --- a/backend/apps/webui/routers/prompts.py +++ b/backend/apps/webui/routers/prompts.py @@ -6,7 +6,6 @@ from fastapi import APIRouter from pydantic import BaseModel import json -from apps.webui.internal.db import get_db from apps.webui.models.prompts import Prompts, PromptForm, PromptModel from utils.utils import get_current_user, get_admin_user @@ -20,8 +19,8 @@ router = APIRouter() @router.get("/", response_model=List[PromptModel]) -async def get_prompts(user=Depends(get_current_user), db=Depends(get_db)): - return Prompts.get_prompts(db) +async def get_prompts(user=Depends(get_current_user)): + return Prompts.get_prompts() ############################ @@ -31,11 +30,11 @@ async def get_prompts(user=Depends(get_current_user), db=Depends(get_db)): @router.post("/create", response_model=Optional[PromptModel]) async def create_new_prompt( - form_data: PromptForm, user=Depends(get_admin_user), db=Depends(get_db) + form_data: PromptForm, user=Depends(get_admin_user) ): - prompt = Prompts.get_prompt_by_command(db, form_data.command) + prompt = Prompts.get_prompt_by_command(form_data.command) if prompt == None: - prompt = Prompts.insert_new_prompt(db, user.id, form_data) + prompt = Prompts.insert_new_prompt(user.id, form_data) if prompt: return prompt @@ -56,9 +55,9 @@ async def create_new_prompt( @router.get("/command/{command}", response_model=Optional[PromptModel]) async def get_prompt_by_command( - command: str, user=Depends(get_current_user), db=Depends(get_db) + command: str, user=Depends(get_current_user) ): - prompt = Prompts.get_prompt_by_command(db, f"/{command}") + prompt = Prompts.get_prompt_by_command(f"/{command}") if prompt: return prompt @@ -79,9 +78,8 @@ async def update_prompt_by_command( command: str, form_data: PromptForm, user=Depends(get_admin_user), - db=Depends(get_db), ): - prompt = Prompts.update_prompt_by_command(db, f"/{command}", form_data) + prompt = Prompts.update_prompt_by_command(f"/{command}", form_data) if prompt: return prompt else: @@ -98,7 +96,7 @@ async def update_prompt_by_command( @router.delete("/command/{command}/delete", response_model=bool) async def delete_prompt_by_command( - command: str, user=Depends(get_admin_user), db=Depends(get_db) + command: str, user=Depends(get_admin_user) ): - result = Prompts.delete_prompt_by_command(db, f"/{command}") + result = Prompts.delete_prompt_by_command(f"/{command}") return result diff --git a/backend/apps/webui/routers/tools.py b/backend/apps/webui/routers/tools.py index 4eb6d1caf..82a09477d 100644 --- a/backend/apps/webui/routers/tools.py +++ b/backend/apps/webui/routers/tools.py @@ -6,7 +6,6 @@ from fastapi import APIRouter from pydantic import BaseModel import json -from apps.webui.internal.db import get_db from apps.webui.models.users import Users from apps.webui.models.tools import Tools, ToolForm, ToolModel, ToolResponse from apps.webui.utils import load_toolkit_module_by_id @@ -34,7 +33,7 @@ router = APIRouter() @router.get("/", response_model=List[ToolResponse]) -async def get_toolkits(user=Depends(get_verified_user), db=Depends(get_db)): +async def get_toolkits(user=Depends(get_verified_user)): toolkits = [toolkit for toolkit in Tools.get_tools()] return toolkits @@ -45,8 +44,8 @@ async def get_toolkits(user=Depends(get_verified_user), db=Depends(get_db)): @router.get("/export", response_model=List[ToolModel]) -async def get_toolkits(user=Depends(get_admin_user), db=Depends(get_db)): - toolkits = [toolkit for toolkit in Tools.get_tools(db)] +async def get_toolkits(user=Depends(get_admin_user)): + toolkits = [toolkit for toolkit in Tools.get_tools()] return toolkits @@ -60,7 +59,6 @@ async def create_new_toolkit( request: Request, form_data: ToolForm, user=Depends(get_admin_user), - db=Depends(get_db), ): if not form_data.id.isidentifier(): raise HTTPException( @@ -70,7 +68,7 @@ async def create_new_toolkit( form_data.id = form_data.id.lower() - toolkit = Tools.get_tool_by_id(db, form_data.id) + toolkit = Tools.get_tool_by_id(form_data.id) if toolkit == None: toolkit_path = os.path.join(TOOLS_DIR, f"{form_data.id}.py") try: @@ -84,7 +82,7 @@ async def create_new_toolkit( TOOLS[form_data.id] = toolkit_module specs = get_tools_specs(TOOLS[form_data.id]) - toolkit = Tools.insert_new_tool(db, user.id, form_data, specs) + toolkit = Tools.insert_new_tool(user.id, form_data, specs) tool_cache_dir = Path(CACHE_DIR) / "tools" / form_data.id tool_cache_dir.mkdir(parents=True, exist_ok=True) @@ -115,8 +113,8 @@ async def create_new_toolkit( @router.get("/id/{id}", response_model=Optional[ToolModel]) -async def get_toolkit_by_id(id: str, user=Depends(get_admin_user), db=Depends(get_db)): - toolkit = Tools.get_tool_by_id(db, id) +async def get_toolkit_by_id(id: str, user=Depends(get_admin_user)): + toolkit = Tools.get_tool_by_id(id) if toolkit: return toolkit @@ -138,7 +136,6 @@ async def update_toolkit_by_id( id: str, form_data: ToolForm, user=Depends(get_admin_user), - db=Depends(get_db), ): toolkit_path = os.path.join(TOOLS_DIR, f"{id}.py") @@ -160,7 +157,7 @@ async def update_toolkit_by_id( } print(updated) - toolkit = Tools.update_tool_by_id(db, id, updated) + toolkit = Tools.update_tool_by_id(id, updated) if toolkit: return toolkit @@ -184,9 +181,9 @@ async def update_toolkit_by_id( @router.delete("/id/{id}/delete", response_model=bool) async def delete_toolkit_by_id( - request: Request, id: str, user=Depends(get_admin_user), db=Depends(get_db) + request: Request, id: str, user=Depends(get_admin_user) ): - result = Tools.delete_tool_by_id(db, id) + result = Tools.delete_tool_by_id(id) if result: TOOLS = request.app.state.TOOLS diff --git a/backend/apps/webui/routers/users.py b/backend/apps/webui/routers/users.py index 46a418fc1..8a38d5b9f 100644 --- a/backend/apps/webui/routers/users.py +++ b/backend/apps/webui/routers/users.py @@ -9,7 +9,6 @@ import time import uuid import logging -from apps.webui.internal.db import get_db from apps.webui.models.users import ( UserModel, UserUpdateForm, @@ -42,9 +41,9 @@ router = APIRouter() @router.get("/", response_model=List[UserModel]) async def get_users( - skip: int = 0, limit: int = 50, user=Depends(get_admin_user), db=Depends(get_db) + skip: int = 0, limit: int = 50, user=Depends(get_admin_user) ): - return Users.get_users(db, skip, limit) + return Users.get_users(skip, limit) ############################ @@ -72,11 +71,11 @@ async def update_user_permissions( @router.post("/update/role", response_model=Optional[UserModel]) async def update_user_role( - form_data: UserRoleUpdateForm, user=Depends(get_admin_user), db=Depends(get_db) + form_data: UserRoleUpdateForm, user=Depends(get_admin_user) ): - if user.id != form_data.id and form_data.id != Users.get_first_user(db).id: - return Users.update_user_role_by_id(db, form_data.id, form_data.role) + if user.id != form_data.id and form_data.id != Users.get_first_user().id: + return Users.update_user_role_by_id(form_data.id, form_data.role) raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, @@ -91,9 +90,9 @@ async def update_user_role( @router.get("/user/settings", response_model=Optional[UserSettings]) async def get_user_settings_by_session_user( - user=Depends(get_verified_user), db=Depends(get_db) + user=Depends(get_verified_user) ): - user = Users.get_user_by_id(db, user.id) + user = Users.get_user_by_id(user.id) if user: return user.settings else: @@ -110,9 +109,9 @@ async def get_user_settings_by_session_user( @router.post("/user/settings/update", response_model=UserSettings) async def update_user_settings_by_session_user( - form_data: UserSettings, user=Depends(get_verified_user), db=Depends(get_db) + form_data: UserSettings, user=Depends(get_verified_user) ): - user = Users.update_user_by_id(db, user.id, {"settings": form_data.model_dump()}) + user = Users.update_user_by_id(user.id, {"settings": form_data.model_dump()}) if user: return user.settings else: @@ -129,9 +128,9 @@ async def update_user_settings_by_session_user( @router.get("/user/info", response_model=Optional[dict]) async def get_user_info_by_session_user( - user=Depends(get_verified_user), db=Depends(get_db) + user=Depends(get_verified_user) ): - user = Users.get_user_by_id(db, user.id) + user = Users.get_user_by_id(user.id) if user: return user.info else: @@ -148,15 +147,15 @@ async def get_user_info_by_session_user( @router.post("/user/info/update", response_model=Optional[dict]) async def update_user_info_by_session_user( - form_data: dict, user=Depends(get_verified_user), db=Depends(get_db) + form_data: dict, user=Depends(get_verified_user) ): - user = Users.get_user_by_id(db, user.id) + user = Users.get_user_by_id(user.id) if user: if user.info is None: user.info = {} user = Users.update_user_by_id( - db, user.id, {"info": {**user.info, **form_data}} + user.id, {"info": {**user.info, **form_data}} ) if user: return user.info @@ -184,14 +183,14 @@ class UserResponse(BaseModel): @router.get("/{user_id}", response_model=UserResponse) async def get_user_by_id( - user_id: str, user=Depends(get_verified_user), db=Depends(get_db) + user_id: str, user=Depends(get_verified_user) ): # Check if user_id is a shared chat # If it is, get the user_id from the chat if user_id.startswith("shared-"): chat_id = user_id.replace("shared-", "") - chat = Chats.get_chat_by_id(db, chat_id) + chat = Chats.get_chat_by_id(chat_id) if chat: user_id = chat.user_id else: @@ -200,7 +199,7 @@ async def get_user_by_id( detail=ERROR_MESSAGES.USER_NOT_FOUND, ) - user = Users.get_user_by_id(db, user_id) + user = Users.get_user_by_id(user_id) if user: return UserResponse(name=user.name, profile_image_url=user.profile_image_url) @@ -221,13 +220,12 @@ async def update_user_by_id( user_id: str, form_data: UserUpdateForm, session_user=Depends(get_admin_user), - db=Depends(get_db), ): - user = Users.get_user_by_id(db, user_id) + user = Users.get_user_by_id(user_id) if user: if form_data.email.lower() != user.email: - email_user = Users.get_user_by_email(db, form_data.email.lower()) + email_user = Users.get_user_by_email(form_data.email.lower()) if email_user: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, @@ -237,11 +235,10 @@ async def update_user_by_id( if form_data.password: hashed = get_password_hash(form_data.password) log.debug(f"hashed: {hashed}") - Auths.update_user_password_by_id(db, user_id, hashed) + Auths.update_user_password_by_id(user_id, hashed) - Auths.update_email_by_id(db, user_id, form_data.email.lower()) + Auths.update_email_by_id(user_id, form_data.email.lower()) updated_user = Users.update_user_by_id( - db, user_id, { "name": form_data.name, @@ -271,10 +268,10 @@ async def update_user_by_id( @router.delete("/{user_id}", response_model=bool) async def delete_user_by_id( - user_id: str, user=Depends(get_admin_user), db=Depends(get_db) + user_id: str, user=Depends(get_admin_user) ): if user.id != user_id: - result = Auths.delete_auth_by_id(db, user_id) + result = Auths.delete_auth_by_id(user_id) if result: return True diff --git a/backend/main.py b/backend/main.py index d80c6a729..6e44045f2 100644 --- a/backend/main.py +++ b/backend/main.py @@ -57,7 +57,7 @@ from apps.webui.main import ( get_pipe_models, generate_function_chat_completion, ) -from apps.webui.internal.db import get_db, SessionLocal +from apps.webui.internal.db import get_session, SessionLocal from pydantic import BaseModel @@ -410,7 +410,6 @@ class ChatCompletionMiddleware(BaseHTTPMiddleware): user = get_current_user( request, get_http_authorization_cred(request.headers.get("Authorization")), - SessionLocal(), ) # Flag to skip RAG completions if file_handler is present in tools/functions skip_files = False @@ -800,9 +799,7 @@ app.add_middleware( @app.middleware("http") async def check_url(request: Request, call_next): if len(app.state.MODELS) == 0: - db = SessionLocal() - await get_all_models(db) - db.commit() + await get_all_models() else: pass @@ -836,12 +833,12 @@ app.mount("/api/v1", webui_app) webui_app.state.EMBEDDING_FUNCTION = rag_app.state.EMBEDDING_FUNCTION -async def get_all_models(db: Session): +async def get_all_models(): pipe_models = [] openai_models = [] ollama_models = [] - pipe_models = await get_pipe_models(db) + pipe_models = await get_pipe_models() if app.state.config.ENABLE_OPENAI_API: openai_models = await get_openai_models() @@ -863,7 +860,7 @@ async def get_all_models(db: Session): models = pipe_models + openai_models + ollama_models - custom_models = Models.get_all_models(db) + custom_models = Models.get_all_models() for custom_model in custom_models: if custom_model.base_model_id == None: for model in models: @@ -903,8 +900,8 @@ async def get_all_models(db: Session): @app.get("/api/models") -async def get_models(user=Depends(get_verified_user), db=Depends(get_db)): - models = await get_all_models(db) +async def get_models(user=Depends(get_verified_user)): + models = await get_all_models() # Filter out filter pipelines models = [ @@ -1608,9 +1605,8 @@ async def get_pipeline_valves( urlIdx: Optional[int], pipeline_id: str, user=Depends(get_admin_user), - db=Depends(get_db), ): - models = await get_all_models(db) + models = await get_all_models() r = None try: @@ -1649,9 +1645,8 @@ async def get_pipeline_valves_spec( urlIdx: Optional[int], pipeline_id: str, user=Depends(get_admin_user), - db=Depends(get_db), ): - models = await get_all_models(db) + models = await get_all_models() r = None try: @@ -1690,9 +1685,8 @@ async def update_pipeline_valves( pipeline_id: str, form_data: dict, user=Depends(get_admin_user), - db=Depends(get_db), ): - models = await get_all_models(db) + models = await get_all_models() r = None try: @@ -2040,8 +2034,9 @@ async def healthcheck(): @app.get("/health/db") -async def healthcheck_with_db(db: Session = Depends(get_db)): - result = db.execute(text("SELECT 1;")).all() +async def healthcheck_with_db(): + with get_session() as db: + result = db.execute(text("SELECT 1;")).all() return {"status": True} diff --git a/backend/migrations/versions/22b5ab2667b8_init.py b/backend/migrations/versions/22b5ab2667b8_init.py deleted file mode 100644 index af10dc2cf..000000000 --- a/backend/migrations/versions/22b5ab2667b8_init.py +++ /dev/null @@ -1,188 +0,0 @@ -"""init - -Revision ID: 22b5ab2667b8 -Revises: -Create Date: 2024-06-20 13:22:40.397002 - -""" - -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa -from sqlalchemy.engine.reflection import Inspector - -import apps.webui.internal.db - - -# revision identifiers, used by Alembic. -revision: str = "22b5ab2667b8" -down_revision: Union[str, None] = None -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - con = op.get_bind() - inspector = Inspector.from_engine(con) - tables = set(inspector.get_table_names()) - - # ### commands auto generated by Alembic - please adjust! ### - if not "auth" in tables: - op.create_table( - "auth", - sa.Column("id", sa.String(), nullable=False), - sa.Column("email", sa.String(), nullable=True), - sa.Column("password", sa.String(), nullable=True), - sa.Column("active", sa.Boolean(), nullable=True), - sa.PrimaryKeyConstraint("id"), - ) - - if not "chat" in tables: - op.create_table( - "chat", - sa.Column("id", sa.String(), nullable=False), - sa.Column("user_id", sa.String(), nullable=True), - sa.Column("title", sa.String(), nullable=True), - sa.Column("chat", sa.String(), nullable=True), - sa.Column("created_at", sa.BigInteger(), nullable=True), - sa.Column("updated_at", sa.BigInteger(), nullable=True), - sa.Column("share_id", sa.String(), nullable=True), - sa.Column("archived", sa.Boolean(), nullable=True), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint("share_id"), - ) - - if not "chatidtag" in tables: - op.create_table( - "chatidtag", - sa.Column("id", sa.String(), nullable=False), - sa.Column("tag_name", sa.String(), nullable=True), - sa.Column("chat_id", sa.String(), nullable=True), - sa.Column("user_id", sa.String(), nullable=True), - sa.Column("timestamp", sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint("id"), - ) - - if not "document" in tables: - op.create_table( - "document", - sa.Column("collection_name", sa.String(), nullable=False), - sa.Column("name", sa.String(), nullable=True), - sa.Column("title", sa.String(), nullable=True), - sa.Column("filename", sa.String(), nullable=True), - sa.Column("content", sa.String(), nullable=True), - sa.Column("user_id", sa.String(), nullable=True), - sa.Column("timestamp", sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint("collection_name"), - sa.UniqueConstraint("name"), - ) - - if not "memory" in tables: - op.create_table( - "memory", - sa.Column("id", sa.String(), nullable=False), - sa.Column("user_id", sa.String(), nullable=True), - sa.Column("content", sa.String(), nullable=True), - sa.Column("updated_at", sa.BigInteger(), nullable=True), - sa.Column("created_at", sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint("id"), - ) - - if not "model" in tables: - op.create_table( - "model", - sa.Column("id", sa.String(), nullable=False), - sa.Column("user_id", sa.String(), nullable=True), - sa.Column("base_model_id", sa.String(), nullable=True), - sa.Column("name", sa.String(), nullable=True), - sa.Column("params", apps.webui.internal.db.JSONField(), nullable=True), - sa.Column("meta", apps.webui.internal.db.JSONField(), nullable=True), - sa.Column("updated_at", sa.BigInteger(), nullable=True), - sa.Column("created_at", sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint("id"), - ) - - if not "prompt" in tables: - op.create_table( - "prompt", - sa.Column("command", sa.String(), nullable=False), - sa.Column("user_id", sa.String(), nullable=True), - sa.Column("title", sa.String(), nullable=True), - sa.Column("content", sa.String(), nullable=True), - sa.Column("timestamp", sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint("command"), - ) - - if not "tag" in tables: - op.create_table( - "tag", - sa.Column("id", sa.String(), nullable=False), - sa.Column("name", sa.String(), nullable=True), - sa.Column("user_id", sa.String(), nullable=True), - sa.Column("data", sa.String(), nullable=True), - sa.PrimaryKeyConstraint("id"), - ) - - if not "tool" in tables: - op.create_table( - "tool", - sa.Column("id", sa.String(), nullable=False), - sa.Column("user_id", sa.String(), nullable=True), - sa.Column("name", sa.String(), nullable=True), - sa.Column("content", sa.String(), nullable=True), - sa.Column("specs", apps.webui.internal.db.JSONField(), nullable=True), - sa.Column("meta", apps.webui.internal.db.JSONField(), nullable=True), - sa.Column("updated_at", sa.BigInteger(), nullable=True), - sa.Column("created_at", sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint("id"), - ) - - if not "user" in tables: - op.create_table( - "user", - sa.Column("id", sa.String(), nullable=False), - sa.Column("name", sa.String(), nullable=True), - sa.Column("email", sa.String(), nullable=True), - sa.Column("role", sa.String(), nullable=True), - sa.Column("profile_image_url", sa.String(), nullable=True), - sa.Column("last_active_at", sa.BigInteger(), nullable=True), - sa.Column("updated_at", sa.BigInteger(), nullable=True), - sa.Column("created_at", sa.BigInteger(), nullable=True), - sa.Column("api_key", sa.String(), nullable=True), - sa.Column("settings", apps.webui.internal.db.JSONField(), nullable=True), - sa.Column("info", apps.webui.internal.db.JSONField(), nullable=True), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint("api_key"), - ) - - if not "file" in tables: - op.create_table('file', - sa.Column('id', sa.String(), nullable=False), - sa.Column('user_id', sa.String(), nullable=True), - sa.Column('filename', sa.String(), nullable=True), - sa.Column('meta', apps.webui.internal.db.JSONField(), nullable=True), - sa.Column('created_at', sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint('id') - ) - - if not "function" in tables: - op.create_table('function', - sa.Column('id', sa.String(), nullable=False), - sa.Column('user_id', sa.String(), nullable=True), - sa.Column('name', sa.Text(), nullable=True), - sa.Column('type', sa.Text(), nullable=True), - sa.Column('content', sa.Text(), nullable=True), - sa.Column('meta', apps.webui.internal.db.JSONField(), nullable=True), - sa.Column('updated_at', sa.BigInteger(), nullable=True), - sa.Column('created_at', sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint('id') - ) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - # do nothing as we assume we had previous migrations from peewee-migrate - pass - # ### end Alembic commands ### diff --git a/backend/migrations/versions/ba76b0bae648_init.py b/backend/migrations/versions/ba76b0bae648_init.py new file mode 100644 index 000000000..b1250662f --- /dev/null +++ b/backend/migrations/versions/ba76b0bae648_init.py @@ -0,0 +1,161 @@ +"""init + +Revision ID: ba76b0bae648 +Revises: +Create Date: 2024-06-24 09:09:11.636336 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +import apps.webui.internal.db + + +# revision identifiers, used by Alembic. +revision: str = 'ba76b0bae648' +down_revision: Union[str, None] = None +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('auth', + sa.Column('id', sa.String(), nullable=False), + sa.Column('email', sa.String(), nullable=True), + sa.Column('password', sa.String(), nullable=True), + sa.Column('active', sa.Boolean(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('chat', + sa.Column('id', sa.String(), nullable=False), + sa.Column('user_id', sa.String(), nullable=True), + sa.Column('title', sa.String(), nullable=True), + sa.Column('chat', sa.String(), nullable=True), + sa.Column('created_at', sa.BigInteger(), nullable=True), + sa.Column('updated_at', sa.BigInteger(), nullable=True), + sa.Column('share_id', sa.String(), nullable=True), + sa.Column('archived', sa.Boolean(), nullable=True), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('share_id') + ) + op.create_table('chatidtag', + sa.Column('id', sa.String(), nullable=False), + sa.Column('tag_name', sa.String(), nullable=True), + sa.Column('chat_id', sa.String(), nullable=True), + sa.Column('user_id', sa.String(), nullable=True), + sa.Column('timestamp', sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('document', + sa.Column('collection_name', sa.String(), nullable=False), + sa.Column('name', sa.String(), nullable=True), + sa.Column('title', sa.String(), nullable=True), + sa.Column('filename', sa.String(), nullable=True), + sa.Column('content', sa.String(), nullable=True), + sa.Column('user_id', sa.String(), nullable=True), + sa.Column('timestamp', sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint('collection_name'), + sa.UniqueConstraint('name') + ) + op.create_table('file', + sa.Column('id', sa.String(), nullable=False), + sa.Column('user_id', sa.String(), nullable=True), + sa.Column('filename', sa.String(), nullable=True), + sa.Column('meta', apps.webui.internal.db.JSONField(), nullable=True), + sa.Column('created_at', sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('function', + sa.Column('id', sa.String(), nullable=False), + sa.Column('user_id', sa.String(), nullable=True), + sa.Column('name', sa.Text(), nullable=True), + sa.Column('type', sa.Text(), nullable=True), + sa.Column('content', sa.Text(), nullable=True), + sa.Column('meta', apps.webui.internal.db.JSONField(), nullable=True), + sa.Column('valves', apps.webui.internal.db.JSONField(), nullable=True), + sa.Column('is_active', sa.Boolean(), nullable=True), + sa.Column('updated_at', sa.BigInteger(), nullable=True), + sa.Column('created_at', sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('memory', + sa.Column('id', sa.String(), nullable=False), + sa.Column('user_id', sa.String(), nullable=True), + sa.Column('content', sa.String(), nullable=True), + sa.Column('updated_at', sa.BigInteger(), nullable=True), + sa.Column('created_at', sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('model', + sa.Column('id', sa.String(), nullable=False), + sa.Column('user_id', sa.String(), nullable=True), + sa.Column('base_model_id', sa.String(), nullable=True), + sa.Column('name', sa.String(), nullable=True), + sa.Column('params', apps.webui.internal.db.JSONField(), nullable=True), + sa.Column('meta', apps.webui.internal.db.JSONField(), nullable=True), + sa.Column('updated_at', sa.BigInteger(), nullable=True), + sa.Column('created_at', sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('prompt', + sa.Column('command', sa.String(), nullable=False), + sa.Column('user_id', sa.String(), nullable=True), + sa.Column('title', sa.String(), nullable=True), + sa.Column('content', sa.String(), nullable=True), + sa.Column('timestamp', sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint('command') + ) + op.create_table('tag', + sa.Column('id', sa.String(), nullable=False), + sa.Column('name', sa.String(), nullable=True), + sa.Column('user_id', sa.String(), nullable=True), + sa.Column('data', sa.String(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('tool', + sa.Column('id', sa.String(), nullable=False), + sa.Column('user_id', sa.String(), nullable=True), + sa.Column('name', sa.String(), nullable=True), + sa.Column('content', sa.String(), nullable=True), + sa.Column('specs', apps.webui.internal.db.JSONField(), nullable=True), + sa.Column('meta', apps.webui.internal.db.JSONField(), nullable=True), + sa.Column('valves', apps.webui.internal.db.JSONField(), nullable=True), + sa.Column('updated_at', sa.BigInteger(), nullable=True), + sa.Column('created_at', sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('user', + sa.Column('id', sa.String(), nullable=False), + sa.Column('name', sa.String(), nullable=True), + sa.Column('email', sa.String(), nullable=True), + sa.Column('role', sa.String(), nullable=True), + sa.Column('profile_image_url', sa.String(), nullable=True), + sa.Column('last_active_at', sa.BigInteger(), nullable=True), + sa.Column('updated_at', sa.BigInteger(), nullable=True), + sa.Column('created_at', sa.BigInteger(), nullable=True), + sa.Column('api_key', sa.String(), nullable=True), + sa.Column('settings', apps.webui.internal.db.JSONField(), nullable=True), + sa.Column('info', apps.webui.internal.db.JSONField(), nullable=True), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('api_key') + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('user') + op.drop_table('tool') + op.drop_table('tag') + op.drop_table('prompt') + op.drop_table('model') + op.drop_table('memory') + op.drop_table('function') + op.drop_table('file') + op.drop_table('document') + op.drop_table('chatidtag') + op.drop_table('chat') + op.drop_table('auth') + # ### end Alembic commands ### diff --git a/backend/test/apps/webui/routers/test_auths.py b/backend/test/apps/webui/routers/test_auths.py index 3450f57c6..3a8695a69 100644 --- a/backend/test/apps/webui/routers/test_auths.py +++ b/backend/test/apps/webui/routers/test_auths.py @@ -31,7 +31,6 @@ class TestAuths(AbstractPostgresTest): from utils.utils import get_password_hash user = self.auths.insert_new_auth( - self.db_session, email="john.doe@openwebui.com", password=get_password_hash("old_password"), name="John Doe", @@ -45,7 +44,7 @@ class TestAuths(AbstractPostgresTest): json={"name": "John Doe 2", "profile_image_url": "/user2.png"}, ) assert response.status_code == 200 - db_user = self.users.get_user_by_id(self.db_session, user.id) + db_user = self.users.get_user_by_id(user.id) assert db_user.name == "John Doe 2" assert db_user.profile_image_url == "/user2.png" @@ -53,7 +52,6 @@ class TestAuths(AbstractPostgresTest): from utils.utils import get_password_hash user = self.auths.insert_new_auth( - self.db_session, email="john.doe@openwebui.com", password=get_password_hash("old_password"), name="John Doe", @@ -69,11 +67,11 @@ class TestAuths(AbstractPostgresTest): assert response.status_code == 200 old_auth = self.auths.authenticate_user( - self.db_session, "john.doe@openwebui.com", "old_password" + "john.doe@openwebui.com", "old_password" ) assert old_auth is None new_auth = self.auths.authenticate_user( - self.db_session, "john.doe@openwebui.com", "new_password" + "john.doe@openwebui.com", "new_password" ) assert new_auth is not None @@ -81,7 +79,6 @@ class TestAuths(AbstractPostgresTest): from utils.utils import get_password_hash user = self.auths.insert_new_auth( - self.db_session, email="john.doe@openwebui.com", password=get_password_hash("password"), name="John Doe", @@ -144,7 +141,6 @@ class TestAuths(AbstractPostgresTest): def test_get_admin_details(self): self.auths.insert_new_auth( - self.db_session, email="john.doe@openwebui.com", password="password", name="John Doe", @@ -162,7 +158,6 @@ class TestAuths(AbstractPostgresTest): def test_create_api_key_(self): user = self.auths.insert_new_auth( - self.db_session, email="john.doe@openwebui.com", password="password", name="John Doe", @@ -178,31 +173,29 @@ class TestAuths(AbstractPostgresTest): def test_delete_api_key(self): user = self.auths.insert_new_auth( - self.db_session, email="john.doe@openwebui.com", password="password", name="John Doe", profile_image_url="/user.png", role="admin", ) - self.users.update_user_api_key_by_id(self.db_session, user.id, "abc") + self.users.update_user_api_key_by_id(user.id, "abc") with mock_webui_user(id=user.id): response = self.fast_api_client.delete(self.create_url("/api_key")) assert response.status_code == 200 assert response.json() == True - db_user = self.users.get_user_by_id(self.db_session, user.id) + db_user = self.users.get_user_by_id(user.id) assert db_user.api_key is None def test_get_api_key(self): user = self.auths.insert_new_auth( - self.db_session, email="john.doe@openwebui.com", password="password", name="John Doe", profile_image_url="/user.png", role="admin", ) - self.users.update_user_api_key_by_id(self.db_session, user.id, "abc") + self.users.update_user_api_key_by_id(user.id, "abc") with mock_webui_user(id=user.id): response = self.fast_api_client.get(self.create_url("/api_key")) assert response.status_code == 200 diff --git a/backend/test/apps/webui/routers/test_chats.py b/backend/test/apps/webui/routers/test_chats.py index 2d1145c06..ea4518eaf 100644 --- a/backend/test/apps/webui/routers/test_chats.py +++ b/backend/test/apps/webui/routers/test_chats.py @@ -18,7 +18,6 @@ class TestChats(AbstractPostgresTest): self.chats = Chats self.chats.insert_new_chat( - self.db_session, "2", ChatForm( **{ @@ -46,7 +45,7 @@ class TestChats(AbstractPostgresTest): with mock_webui_user(id="2"): response = self.fast_api_client.delete(self.create_url("/")) assert response.status_code == 200 - assert len(self.chats.get_chats(self.db_session)) == 0 + assert len(self.chats.get_chats()) == 0 def test_get_user_chat_list_by_user_id(self): with mock_webui_user(id="3"): @@ -84,14 +83,13 @@ class TestChats(AbstractPostgresTest): assert data["title"] == "New Chat" assert data["updated_at"] is not None assert data["created_at"] is not None - assert len(self.chats.get_chats(self.db_session)) == 2 + assert len(self.chats.get_chats()) == 2 def test_get_user_chats(self): self.test_get_session_user_chat_list() def test_get_user_archived_chats(self): - self.chats.archive_all_chats_by_user_id(self.db_session, "2") - self.db_session.commit() + self.chats.archive_all_chats_by_user_id("2") with mock_webui_user(id="2"): response = self.fast_api_client.get(self.create_url("/all/archived")) assert response.status_code == 200 @@ -114,12 +112,11 @@ class TestChats(AbstractPostgresTest): with mock_webui_user(id="2"): response = self.fast_api_client.post(self.create_url("/archive/all")) assert response.status_code == 200 - assert len(self.chats.get_archived_chats_by_user_id(self.db_session, "2")) == 1 + assert len(self.chats.get_archived_chats_by_user_id("2")) == 1 def test_get_shared_chat_by_id(self): - chat_id = self.chats.get_chats(self.db_session)[0].id - self.chats.update_chat_share_id_by_id(self.db_session, chat_id, chat_id) - self.db_session.commit() + chat_id = self.chats.get_chats()[0].id + self.chats.update_chat_share_id_by_id(chat_id, chat_id) with mock_webui_user(id="2"): response = self.fast_api_client.get(self.create_url(f"/share/{chat_id}")) assert response.status_code == 200 @@ -136,7 +133,7 @@ class TestChats(AbstractPostgresTest): assert data["title"] == "New Chat" def test_get_chat_by_id(self): - chat_id = self.chats.get_chats(self.db_session)[0].id + chat_id = self.chats.get_chats()[0].id with mock_webui_user(id="2"): response = self.fast_api_client.get(self.create_url(f"/{chat_id}")) assert response.status_code == 200 @@ -153,7 +150,7 @@ class TestChats(AbstractPostgresTest): assert data["user_id"] == "2" def test_update_chat_by_id(self): - chat_id = self.chats.get_chats(self.db_session)[0].id + chat_id = self.chats.get_chats()[0].id with mock_webui_user(id="2"): response = self.fast_api_client.post( self.create_url(f"/{chat_id}"), @@ -181,14 +178,14 @@ class TestChats(AbstractPostgresTest): assert data["user_id"] == "2" def test_delete_chat_by_id(self): - chat_id = self.chats.get_chats(self.db_session)[0].id + chat_id = self.chats.get_chats()[0].id with mock_webui_user(id="2"): response = self.fast_api_client.delete(self.create_url(f"/{chat_id}")) assert response.status_code == 200 assert response.json() is True def test_clone_chat_by_id(self): - chat_id = self.chats.get_chats(self.db_session)[0].id + chat_id = self.chats.get_chats()[0].id with mock_webui_user(id="2"): response = self.fast_api_client.get(self.create_url(f"/{chat_id}/clone")) @@ -209,31 +206,30 @@ class TestChats(AbstractPostgresTest): assert data["user_id"] == "2" def test_archive_chat_by_id(self): - chat_id = self.chats.get_chats(self.db_session)[0].id + chat_id = self.chats.get_chats()[0].id with mock_webui_user(id="2"): response = self.fast_api_client.get(self.create_url(f"/{chat_id}/archive")) assert response.status_code == 200 - chat = self.chats.get_chat_by_id(self.db_session, chat_id) + chat = self.chats.get_chat_by_id(chat_id) assert chat.archived is True def test_share_chat_by_id(self): - chat_id = self.chats.get_chats(self.db_session)[0].id + chat_id = self.chats.get_chats()[0].id with mock_webui_user(id="2"): response = self.fast_api_client.post(self.create_url(f"/{chat_id}/share")) assert response.status_code == 200 - chat = self.chats.get_chat_by_id(self.db_session, chat_id) + chat = self.chats.get_chat_by_id(chat_id) assert chat.share_id is not None def test_delete_shared_chat_by_id(self): - chat_id = self.chats.get_chats(self.db_session)[0].id + chat_id = self.chats.get_chats()[0].id share_id = str(uuid.uuid4()) - self.chats.update_chat_share_id_by_id(self.db_session, chat_id, share_id) - self.db_session.commit() + self.chats.update_chat_share_id_by_id(chat_id, share_id) with mock_webui_user(id="2"): response = self.fast_api_client.delete(self.create_url(f"/{chat_id}/share")) assert response.status_code - chat = self.chats.get_chat_by_id(self.db_session, chat_id) + chat = self.chats.get_chat_by_id(chat_id) assert chat.share_id is None diff --git a/backend/test/apps/webui/routers/test_documents.py b/backend/test/apps/webui/routers/test_documents.py index 53ef3d2aa..14ca339fd 100644 --- a/backend/test/apps/webui/routers/test_documents.py +++ b/backend/test/apps/webui/routers/test_documents.py @@ -14,7 +14,7 @@ class TestDocuments(AbstractPostgresTest): def test_documents(self): # Empty database - assert len(self.documents.get_docs(self.db_session)) == 0 + assert len(self.documents.get_docs()) == 0 with mock_webui_user(id="2"): response = self.fast_api_client.get(self.create_url("/")) assert response.status_code == 200 @@ -34,7 +34,7 @@ class TestDocuments(AbstractPostgresTest): ) assert response.status_code == 200 assert response.json()["name"] == "doc_name" - assert len(self.documents.get_docs(self.db_session)) == 1 + assert len(self.documents.get_docs()) == 1 # Get the document with mock_webui_user(id="2"): @@ -61,7 +61,7 @@ class TestDocuments(AbstractPostgresTest): ) assert response.status_code == 200 assert response.json()["name"] == "doc_name 2" - assert len(self.documents.get_docs(self.db_session)) == 2 + assert len(self.documents.get_docs()) == 2 # Get all documents with mock_webui_user(id="2"): @@ -95,7 +95,7 @@ class TestDocuments(AbstractPostgresTest): assert data["content"] == { "tags": [{"name": "testing-tag"}, {"name": "another-tag"}] } - assert len(self.documents.get_docs(self.db_session)) == 2 + assert len(self.documents.get_docs()) == 2 # Delete the first document with mock_webui_user(id="2"): @@ -103,4 +103,4 @@ class TestDocuments(AbstractPostgresTest): self.create_url("/doc/delete?name=doc_name rework") ) assert response.status_code == 200 - assert len(self.documents.get_docs(self.db_session)) == 1 + assert len(self.documents.get_docs()) == 1 diff --git a/backend/test/apps/webui/routers/test_prompts.py b/backend/test/apps/webui/routers/test_prompts.py index cd2fcec87..9f47be992 100644 --- a/backend/test/apps/webui/routers/test_prompts.py +++ b/backend/test/apps/webui/routers/test_prompts.py @@ -68,6 +68,16 @@ class TestPrompts(AbstractPostgresTest): assert data["content"] == "description Updated" assert data["user_id"] == "3" + # Get prompt by command + with mock_webui_user(id="2"): + response = self.fast_api_client.get(self.create_url("/command/my-command2")) + assert response.status_code == 200 + data = response.json() + assert data["command"] == "/my-command2" + assert data["title"] == "Hello World Updated" + assert data["content"] == "description Updated" + assert data["user_id"] == "3" + # Delete prompt with mock_webui_user(id="2"): response = self.fast_api_client.delete( diff --git a/backend/test/apps/webui/routers/test_users.py b/backend/test/apps/webui/routers/test_users.py index 35b662304..9736b4d32 100644 --- a/backend/test/apps/webui/routers/test_users.py +++ b/backend/test/apps/webui/routers/test_users.py @@ -33,7 +33,6 @@ class TestUsers(AbstractPostgresTest): def setup_method(self): super().setup_method() self.users.insert_new_user( - self.db_session, id="1", name="user 1", email="user1@openwebui.com", @@ -41,7 +40,6 @@ class TestUsers(AbstractPostgresTest): role="user", ) self.users.insert_new_user( - self.db_session, id="2", name="user 2", email="user2@openwebui.com", diff --git a/backend/utils/utils.py b/backend/utils/utils.py index f1225ec0e..6409fc7aa 100644 --- a/backend/utils/utils.py +++ b/backend/utils/utils.py @@ -2,7 +2,6 @@ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from fastapi import HTTPException, status, Depends, Request from sqlalchemy.orm import Session -from apps.webui.internal.db import get_db from apps.webui.models.users import Users from pydantic import BaseModel @@ -79,7 +78,6 @@ def get_http_authorization_cred(auth_header: str): def get_current_user( request: Request, auth_token: HTTPAuthorizationCredentials = Depends(bearer_security), - db=Depends(get_db), ): token = None @@ -94,19 +92,19 @@ def get_current_user( # auth by api key if token.startswith("sk-"): - return get_current_user_by_api_key(db, token) + return get_current_user_by_api_key(token) # auth by jwt token data = decode_token(token) if data != None and "id" in data: - user = Users.get_user_by_id(db, data["id"]) + user = Users.get_user_by_id(data["id"]) if user is None: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.INVALID_TOKEN, ) else: - Users.update_user_last_active_by_id(db, user.id) + Users.update_user_last_active_by_id(user.id) return user else: raise HTTPException( From 070d9083d5dd515c32bd7bf60aeecc56f5bc059c Mon Sep 17 00:00:00 2001 From: Jonathan Rohde Date: Mon, 24 Jun 2024 09:50:14 +0200 Subject: [PATCH 003/181] feat(sqlalchemy): use subprocess to do migrations --- backend/alembic.ini | 2 +- backend/main.py | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/backend/alembic.ini b/backend/alembic.ini index 72f2b762b..4eff85f0c 100644 --- a/backend/alembic.ini +++ b/backend/alembic.ini @@ -58,7 +58,7 @@ version_path_separator = os # Use os.pathsep. Default configuration used for ne # are written from script.py.mako # output_encoding = utf-8 -sqlalchemy.url = REPLACE_WITH_DATABASE_URL +# sqlalchemy.url = REPLACE_WITH_DATABASE_URL [post_write_hooks] diff --git a/backend/main.py b/backend/main.py index 6e44045f2..8892d9bc7 100644 --- a/backend/main.py +++ b/backend/main.py @@ -173,13 +173,11 @@ https://github.com/open-webui/open-webui def run_migrations(): - from alembic.config import Config - from alembic import command - - alembic_cfg = Config(f"{BACKEND_DIR}/alembic.ini") - alembic_cfg.set_main_option("sqlalchemy.url", DATABASE_URL) - alembic_cfg.set_main_option("script_location", f"{BACKEND_DIR}/migrations") - command.upgrade(alembic_cfg, "head") + env = os.environ.copy() + env["DATABASE_URL"] = DATABASE_URL + migration_task = subprocess.run(["alembic", f"-c{BACKEND_DIR}/alembic.ini", "upgrade", "head"], env=env) + if migration_task.returncode > 0: + raise ValueError("Error running migrations") @asynccontextmanager From 320e658595918241c9bdab4f302017039d1ae694 Mon Sep 17 00:00:00 2001 From: Jonathan Rohde Date: Mon, 24 Jun 2024 09:56:42 +0200 Subject: [PATCH 004/181] feat(sqlalchemy): cleanup fixes --- backend/apps/socket/main.py | 4 +--- backend/main.py | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/backend/apps/socket/main.py b/backend/apps/socket/main.py index bbbbccd79..123ff31cd 100644 --- a/backend/apps/socket/main.py +++ b/backend/apps/socket/main.py @@ -24,9 +24,7 @@ async def connect(sid, environ, auth): data = decode_token(auth["token"]) if data is not None and "id" in data: - from apps.webui.internal.db import SessionLocal - - user = Users.get_user_by_id(SessionLocal(), data["id"]) + user = Users.get_user_by_id(data["id"]) if user: SESSION_POOL[sid] = user.id diff --git a/backend/main.py b/backend/main.py index 8892d9bc7..2c4d5ecfd 100644 --- a/backend/main.py +++ b/backend/main.py @@ -751,7 +751,6 @@ class PipelineMiddleware(BaseHTTPMiddleware): user = get_current_user( request, get_http_authorization_cred(request.headers.get("Authorization")), - SessionLocal(), ) try: From c134eab27a929cbf678a60356a4c8f6c2e718201 Mon Sep 17 00:00:00 2001 From: Jonathan Rohde Date: Mon, 24 Jun 2024 09:57:08 +0200 Subject: [PATCH 005/181] feat(sqlalchemy): format backend --- backend/apps/webui/internal/db.py | 5 +- backend/apps/webui/models/auths.py | 20 +- backend/apps/webui/models/chats.py | 36 ++- backend/apps/webui/models/documents.py | 4 +- backend/apps/webui/models/files.py | 1 + backend/apps/webui/models/functions.py | 26 +- backend/apps/webui/models/models.py | 4 +- backend/apps/webui/models/prompts.py | 4 +- backend/apps/webui/models/tags.py | 18 +- backend/apps/webui/models/users.py | 16 +- backend/apps/webui/routers/auths.py | 14 +- backend/apps/webui/routers/chats.py | 40 +-- backend/apps/webui/routers/documents.py | 20 +- backend/apps/webui/routers/files.py | 5 +- backend/apps/webui/routers/memories.py | 4 +- backend/apps/webui/routers/prompts.py | 12 +- backend/apps/webui/routers/tools.py | 4 +- backend/apps/webui/routers/users.py | 28 +- backend/main.py | 4 +- .../migrations/versions/ba76b0bae648_init.py | 255 +++++++++--------- .../test/util/abstract_integration_test.py | 1 + 21 files changed, 232 insertions(+), 289 deletions(-) diff --git a/backend/apps/webui/internal/db.py b/backend/apps/webui/internal/db.py index 3c37bb09b..6fd541f4e 100644 --- a/backend/apps/webui/internal/db.py +++ b/backend/apps/webui/internal/db.py @@ -53,7 +53,9 @@ if "sqlite" in SQLALCHEMY_DATABASE_URL: ) else: engine = create_engine(SQLALCHEMY_DATABASE_URL, pool_pre_ping=True) -SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine, expire_on_commit=False) +SessionLocal = sessionmaker( + autocommit=False, autoflush=False, bind=engine, expire_on_commit=False +) Base = declarative_base() @@ -66,4 +68,3 @@ def get_session(): except Exception as e: db.rollback() raise e - diff --git a/backend/apps/webui/models/auths.py b/backend/apps/webui/models/auths.py index fd2934bb1..9f10e0fdd 100644 --- a/backend/apps/webui/models/auths.py +++ b/backend/apps/webui/models/auths.py @@ -126,9 +126,7 @@ class AuthsTable: else: return None - def authenticate_user( - self, email: str, password: str - ) -> Optional[UserModel]: + def authenticate_user(self, email: str, password: str) -> Optional[UserModel]: log.info(f"authenticate_user: {email}") with get_session() as db: try: @@ -144,9 +142,7 @@ class AuthsTable: except: return None - def authenticate_user_by_api_key( - self, api_key: str - ) -> Optional[UserModel]: + def authenticate_user_by_api_key(self, api_key: str) -> Optional[UserModel]: log.info(f"authenticate_user_by_api_key: {api_key}") with get_session() as db: # if no api_key, return None @@ -159,9 +155,7 @@ class AuthsTable: except: return False - def authenticate_user_by_trusted_header( - self, email: str - ) -> Optional[UserModel]: + def authenticate_user_by_trusted_header(self, email: str) -> Optional[UserModel]: log.info(f"authenticate_user_by_trusted_header: {email}") with get_session() as db: try: @@ -172,12 +166,12 @@ class AuthsTable: except: return None - def update_user_password_by_id( - self, id: str, new_password: str - ) -> bool: + def update_user_password_by_id(self, id: str, new_password: str) -> bool: with get_session() as db: try: - result = db.query(Auth).filter_by(id=id).update({"password": new_password}) + result = ( + db.query(Auth).filter_by(id=id).update({"password": new_password}) + ) return True if result == 1 else False except: return False diff --git a/backend/apps/webui/models/chats.py b/backend/apps/webui/models/chats.py index d71ffd992..b0c983ade 100644 --- a/backend/apps/webui/models/chats.py +++ b/backend/apps/webui/models/chats.py @@ -79,9 +79,7 @@ class ChatTitleIdResponse(BaseModel): class ChatTable: - def insert_new_chat( - self, user_id: str, form_data: ChatForm - ) -> Optional[ChatModel]: + def insert_new_chat(self, user_id: str, form_data: ChatForm) -> Optional[ChatModel]: with get_session() as db: id = str(uuid.uuid4()) chat = ChatModel( @@ -89,7 +87,9 @@ class ChatTable: "id": id, "user_id": user_id, "title": ( - form_data.chat["title"] if "title" in form_data.chat else "New Chat" + form_data.chat["title"] + if "title" in form_data.chat + else "New Chat" ), "chat": json.dumps(form_data.chat), "created_at": int(time.time()), @@ -103,9 +103,7 @@ class ChatTable: db.refresh(result) return ChatModel.model_validate(result) if result else None - def update_chat_by_id( - self, id: str, chat: dict - ) -> Optional[ChatModel]: + def update_chat_by_id(self, id: str, chat: dict) -> Optional[ChatModel]: with get_session() as db: try: chat_obj = db.get(Chat, id) @@ -119,9 +117,7 @@ class ChatTable: except Exception as e: return None - def insert_shared_chat_by_chat_id( - self, chat_id: str - ) -> Optional[ChatModel]: + def insert_shared_chat_by_chat_id(self, chat_id: str) -> Optional[ChatModel]: with get_session() as db: # Get the existing chat to share chat = db.get(Chat, chat_id) @@ -145,14 +141,14 @@ class ChatTable: db.refresh(shared_result) # Update the original chat with the share_id result = ( - db.query(Chat).filter_by(id=chat_id).update({"share_id": shared_chat.id}) + db.query(Chat) + .filter_by(id=chat_id) + .update({"share_id": shared_chat.id}) ) return shared_chat if (shared_result and result) else None - def update_shared_chat_by_chat_id( - self, chat_id: str - ) -> Optional[ChatModel]: + def update_shared_chat_by_chat_id(self, chat_id: str) -> Optional[ChatModel]: with get_session() as db: try: print("update_shared_chat_by_id") @@ -271,9 +267,7 @@ class ChatTable: except Exception as e: return None - def get_chat_by_id_and_user_id( - self, id: str, user_id: str - ) -> Optional[ChatModel]: + def get_chat_by_id_and_user_id(self, id: str, user_id: str) -> Optional[ChatModel]: try: with get_session() as db: chat = db.query(Chat).filter_by(id=id, user_id=user_id).first() @@ -293,13 +287,13 @@ class ChatTable: def get_chats_by_user_id(self, user_id: str) -> List[ChatModel]: with get_session() as db: all_chats = ( - db.query(Chat).filter_by(user_id=user_id).order_by(Chat.updated_at.desc()) + db.query(Chat) + .filter_by(user_id=user_id) + .order_by(Chat.updated_at.desc()) ) return [ChatModel.model_validate(chat) for chat in all_chats] - def get_archived_chats_by_user_id( - self, user_id: str - ) -> List[ChatModel]: + def get_archived_chats_by_user_id(self, user_id: str) -> List[ChatModel]: with get_session() as db: all_chats = ( db.query(Chat) diff --git a/backend/apps/webui/models/documents.py b/backend/apps/webui/models/documents.py index 6348967db..897f182be 100644 --- a/backend/apps/webui/models/documents.py +++ b/backend/apps/webui/models/documents.py @@ -106,7 +106,9 @@ class DocumentsTable: def get_docs(self) -> List[DocumentModel]: with get_session() as db: - return [DocumentModel.model_validate(doc) for doc in db.query(Document).all()] + return [ + DocumentModel.model_validate(doc) for doc in db.query(Document).all() + ] def update_doc_by_name( self, name: str, form_data: DocumentUpdateForm diff --git a/backend/apps/webui/models/files.py b/backend/apps/webui/models/files.py index d2565db3d..b7196d604 100644 --- a/backend/apps/webui/models/files.py +++ b/backend/apps/webui/models/files.py @@ -39,6 +39,7 @@ class FileModel(BaseModel): model_config = ConfigDict(from_attributes=True) + #################### # Forms #################### diff --git a/backend/apps/webui/models/functions.py b/backend/apps/webui/models/functions.py index 417e52329..2343c9139 100644 --- a/backend/apps/webui/models/functions.py +++ b/backend/apps/webui/models/functions.py @@ -142,9 +142,9 @@ class FunctionsTable: with get_session() as db: return [ FunctionModel.model_validate(function) - for function in db.query(Function).filter_by( - type=type, is_active=True - ).all() + for function in db.query(Function) + .filter_by(type=type, is_active=True) + .all() ] else: with get_session() as db: @@ -220,10 +220,12 @@ class FunctionsTable: def update_function_by_id(self, id: str, updated: dict) -> Optional[FunctionModel]: try: with get_session() as db: - db.query(Function).filter_by(id=id).update({ - **updated, - "updated_at": int(time.time()), - }) + db.query(Function).filter_by(id=id).update( + { + **updated, + "updated_at": int(time.time()), + } + ) db.commit() return self.get_function_by_id(id) except: @@ -232,10 +234,12 @@ class FunctionsTable: def deactivate_all_functions(self) -> Optional[bool]: try: with get_session() as db: - db.query(Function).update({ - "is_active": False, - "updated_at": int(time.time()), - }) + db.query(Function).update( + { + "is_active": False, + "updated_at": int(time.time()), + } + ) db.commit() return True except: diff --git a/backend/apps/webui/models/models.py b/backend/apps/webui/models/models.py index 7641ee5a0..86b4fa49b 100644 --- a/backend/apps/webui/models/models.py +++ b/backend/apps/webui/models/models.py @@ -153,9 +153,7 @@ class ModelsTable: except: return None - def update_model_by_id( - self, id: str, model: ModelForm - ) -> Optional[ModelModel]: + def update_model_by_id(self, id: str, model: ModelForm) -> Optional[ModelModel]: try: # update only the fields that are present in the model with get_session() as db: diff --git a/backend/apps/webui/models/prompts.py b/backend/apps/webui/models/prompts.py index 2157153d8..029fd5e1b 100644 --- a/backend/apps/webui/models/prompts.py +++ b/backend/apps/webui/models/prompts.py @@ -83,7 +83,9 @@ class PromptsTable: def get_prompts(self) -> List[PromptModel]: with get_session() as db: - return [PromptModel.model_validate(prompt) for prompt in db.query(Prompt).all()] + return [ + PromptModel.model_validate(prompt) for prompt in db.query(Prompt).all() + ] def update_prompt_by_command( self, command: str, form_data: PromptForm diff --git a/backend/apps/webui/models/tags.py b/backend/apps/webui/models/tags.py index 5ad176c37..dfe63688e 100644 --- a/backend/apps/webui/models/tags.py +++ b/backend/apps/webui/models/tags.py @@ -79,9 +79,7 @@ class ChatTagsResponse(BaseModel): class TagTable: - def insert_new_tag( - self, name: str, user_id: str - ) -> Optional[TagModel]: + def insert_new_tag(self, name: str, user_id: str) -> Optional[TagModel]: id = str(uuid.uuid4()) tag = TagModel(**{"id": id, "user_id": user_id, "name": name}) try: @@ -201,11 +199,13 @@ class TagTable: self, tag_name: str, user_id: str ) -> int: with get_session() as db: - return db.query(ChatIdTag).filter_by(tag_name=tag_name, user_id=user_id).count() + return ( + db.query(ChatIdTag) + .filter_by(tag_name=tag_name, user_id=user_id) + .count() + ) - def delete_tag_by_tag_name_and_user_id( - self, tag_name: str, user_id: str - ) -> bool: + def delete_tag_by_tag_name_and_user_id(self, tag_name: str, user_id: str) -> bool: try: with get_session() as db: res = ( @@ -252,9 +252,7 @@ class TagTable: 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: + def delete_tags_by_chat_id_and_user_id(self, chat_id: str, user_id: str) -> bool: tags = self.get_tags_by_chat_id_and_user_id(chat_id, user_id) for tag in tags: diff --git a/backend/apps/webui/models/users.py b/backend/apps/webui/models/users.py index bef15185b..796892927 100644 --- a/backend/apps/webui/models/users.py +++ b/backend/apps/webui/models/users.py @@ -165,9 +165,7 @@ class UsersTable: except: return None - def update_user_role_by_id( - self, id: str, role: str - ) -> Optional[UserModel]: + def update_user_role_by_id(self, id: str, role: str) -> Optional[UserModel]: with get_session() as db: try: db.query(User).filter_by(id=id).update({"role": role}) @@ -193,12 +191,12 @@ class UsersTable: except: return None - def update_user_last_active_by_id( - self, id: str - ) -> Optional[UserModel]: + def update_user_last_active_by_id(self, id: str) -> Optional[UserModel]: with get_session() as db: try: - db.query(User).filter_by(id=id).update({"last_active_at": int(time.time())}) + db.query(User).filter_by(id=id).update( + {"last_active_at": int(time.time())} + ) user = db.query(User).filter_by(id=id).first() return UserModel.model_validate(user) @@ -217,9 +215,7 @@ class UsersTable: except: return None - def update_user_by_id( - self, id: str, updated: dict - ) -> Optional[UserModel]: + def update_user_by_id(self, id: str, updated: dict) -> Optional[UserModel]: with get_session() as db: try: db.query(User).filter_by(id=id).update(updated) diff --git a/backend/apps/webui/routers/auths.py b/backend/apps/webui/routers/auths.py index f32b074b1..1be79d259 100644 --- a/backend/apps/webui/routers/auths.py +++ b/backend/apps/webui/routers/auths.py @@ -78,8 +78,7 @@ async def get_session_user( @router.post("/update/profile", response_model=UserResponse) async def update_profile( - form_data: UpdateProfileForm, - session_user=Depends(get_current_user) + form_data: UpdateProfileForm, session_user=Depends(get_current_user) ): if session_user: user = Users.update_user_by_id( @@ -101,8 +100,7 @@ async def update_profile( @router.post("/update/password", response_model=bool) async def update_password( - form_data: UpdatePasswordForm, - session_user=Depends(get_current_user) + form_data: UpdatePasswordForm, session_user=Depends(get_current_user) ): if WEBUI_AUTH_TRUSTED_EMAIL_HEADER: raise HTTPException(400, detail=ERROR_MESSAGES.ACTION_PROHIBITED) @@ -269,9 +267,7 @@ async def signup(request: Request, response: Response, form_data: SignupForm): @router.post("/add", response_model=SigninResponse) -async def add_user( - form_data: AddUserForm, user=Depends(get_admin_user) -): +async def add_user(form_data: AddUserForm, user=Depends(get_admin_user)): if not validate_email_format(form_data.email.lower()): raise HTTPException( @@ -316,9 +312,7 @@ async def add_user( @router.get("/admin/details") -async def get_admin_details( - request: Request, user=Depends(get_current_user) -): +async def get_admin_details(request: Request, user=Depends(get_current_user)): if request.app.state.config.SHOW_ADMIN_DETAILS: admin_email = request.app.state.config.ADMIN_EMAIL admin_name = None diff --git a/backend/apps/webui/routers/chats.py b/backend/apps/webui/routers/chats.py index 8b2b9987a..3070483f3 100644 --- a/backend/apps/webui/routers/chats.py +++ b/backend/apps/webui/routers/chats.py @@ -55,9 +55,7 @@ async def get_session_user_chat_list( @router.delete("/", response_model=bool) -async def delete_all_user_chats( - request: Request, user=Depends(get_current_user) -): +async def delete_all_user_chats(request: Request, user=Depends(get_current_user)): if ( user.role == "user" @@ -95,9 +93,7 @@ async def get_user_chat_list_by_user_id( @router.post("/new", response_model=Optional[ChatResponse]) -async def create_new_chat( - form_data: ChatForm, user=Depends(get_current_user) -): +async def create_new_chat(form_data: ChatForm, user=Depends(get_current_user)): try: chat = Chats.insert_new_chat(user.id, form_data) return ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)}) @@ -180,9 +176,7 @@ async def archive_all_chats(user=Depends(get_current_user)): @router.get("/share/{share_id}", response_model=Optional[ChatResponse]) -async def get_shared_chat_by_id( - share_id: str, user=Depends(get_current_user) -): +async def get_shared_chat_by_id(share_id: str, user=Depends(get_current_user)): if user.role == "pending": raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND @@ -225,9 +219,7 @@ async def get_user_chat_list_by_tag_name( ) ] - chats = Chats.get_chat_list_by_chat_ids( - chat_ids, form_data.skip, form_data.limit - ) + chats = Chats.get_chat_list_by_chat_ids(chat_ids, form_data.skip, form_data.limit) if len(chats) == 0: Tags.delete_tag_by_tag_name_and_user_id(form_data.name, user.id) @@ -297,9 +289,7 @@ async def update_chat_by_id( @router.delete("/{id}", response_model=bool) -async def delete_chat_by_id( - request: Request, id: str, user=Depends(get_current_user) -): +async def delete_chat_by_id(request: Request, id: str, user=Depends(get_current_user)): if user.role == "admin": result = Chats.delete_chat_by_id(id) @@ -347,9 +337,7 @@ async def clone_chat_by_id(id: str, user=Depends(get_current_user)): @router.get("/{id}/archive", response_model=Optional[ChatResponse]) -async def archive_chat_by_id( - id: str, user=Depends(get_current_user) -): +async def archive_chat_by_id(id: str, user=Depends(get_current_user)): chat = Chats.get_chat_by_id_and_user_id(id, user.id) if chat: chat = Chats.toggle_chat_archive_by_id(id) @@ -398,9 +386,7 @@ async def share_chat_by_id(id: str, user=Depends(get_current_user)): @router.delete("/{id}/share", response_model=Optional[bool]) -async def delete_shared_chat_by_id( - id: str, user=Depends(get_current_user) -): +async def delete_shared_chat_by_id(id: str, user=Depends(get_current_user)): chat = Chats.get_chat_by_id_and_user_id(id, user.id) if chat: if not chat.share_id: @@ -423,9 +409,7 @@ async def delete_shared_chat_by_id( @router.get("/{id}/tags", response_model=List[TagModel]) -async def get_chat_tags_by_id( - id: str, user=Depends(get_current_user) -): +async def get_chat_tags_by_id(id: str, user=Depends(get_current_user)): tags = Tags.get_tags_by_chat_id_and_user_id(id, user.id) if tags != None: @@ -443,9 +427,7 @@ async def get_chat_tags_by_id( @router.post("/{id}/tags", response_model=Optional[ChatIdTagModel]) async def add_chat_tag_by_id( - id: str, - form_data: ChatIdTagForm, - user=Depends(get_current_user) + id: str, form_data: ChatIdTagForm, user=Depends(get_current_user) ): tags = Tags.get_tags_by_chat_id_and_user_id(id, user.id) @@ -494,9 +476,7 @@ async def delete_chat_tag_by_id( @router.delete("/{id}/tags/all", response_model=Optional[bool]) -async def delete_all_chat_tags_by_id( - id: str, user=Depends(get_current_user) -): +async def delete_all_chat_tags_by_id(id: str, user=Depends(get_current_user)): result = Tags.delete_tags_by_chat_id_and_user_id(id, user.id) if result: diff --git a/backend/apps/webui/routers/documents.py b/backend/apps/webui/routers/documents.py index f358e033c..4e1111c07 100644 --- a/backend/apps/webui/routers/documents.py +++ b/backend/apps/webui/routers/documents.py @@ -44,9 +44,7 @@ async def get_documents(user=Depends(get_current_user)): @router.post("/create", response_model=Optional[DocumentResponse]) -async def create_new_doc( - form_data: DocumentForm, user=Depends(get_admin_user) -): +async def create_new_doc(form_data: DocumentForm, user=Depends(get_admin_user)): doc = Documents.get_doc_by_name(form_data.name) if doc == None: doc = Documents.insert_new_doc(user.id, form_data) @@ -76,9 +74,7 @@ async def create_new_doc( @router.get("/doc", response_model=Optional[DocumentResponse]) -async def get_doc_by_name( - name: str, user=Depends(get_current_user) -): +async def get_doc_by_name(name: str, user=Depends(get_current_user)): doc = Documents.get_doc_by_name(name) if doc: @@ -110,12 +106,8 @@ class TagDocumentForm(BaseModel): @router.post("/doc/tags", response_model=Optional[DocumentResponse]) -async def tag_doc_by_name( - form_data: TagDocumentForm, user=Depends(get_current_user) -): - doc = Documents.update_doc_content_by_name( - form_data.name, {"tags": form_data.tags} - ) +async def tag_doc_by_name(form_data: TagDocumentForm, user=Depends(get_current_user)): + doc = Documents.update_doc_content_by_name(form_data.name, {"tags": form_data.tags}) if doc: return DocumentResponse( @@ -163,8 +155,6 @@ async def update_doc_by_name( @router.delete("/doc/delete", response_model=bool) -async def delete_doc_by_name( - name: str, user=Depends(get_admin_user) -): +async def delete_doc_by_name(name: str, user=Depends(get_admin_user)): result = Documents.delete_doc_by_name(name) return result diff --git a/backend/apps/webui/routers/files.py b/backend/apps/webui/routers/files.py index e98d1da58..fffe0743c 100644 --- a/backend/apps/webui/routers/files.py +++ b/backend/apps/webui/routers/files.py @@ -50,10 +50,7 @@ router = APIRouter() @router.post("/") -def upload_file( - file: UploadFile = File(...), - user=Depends(get_verified_user) -): +def upload_file(file: UploadFile = File(...), user=Depends(get_verified_user)): log.info(f"file.content_type: {file.content_type}") try: unsanitized_filename = file.filename diff --git a/backend/apps/webui/routers/memories.py b/backend/apps/webui/routers/memories.py index d6b2d0fcb..2c473ebe8 100644 --- a/backend/apps/webui/routers/memories.py +++ b/backend/apps/webui/routers/memories.py @@ -167,9 +167,7 @@ async def delete_memory_by_user_id(user=Depends(get_verified_user)): @router.delete("/{memory_id}", response_model=bool) -async def delete_memory_by_id( - memory_id: str, user=Depends(get_verified_user) -): +async def delete_memory_by_id(memory_id: str, user=Depends(get_verified_user)): result = Memories.delete_memory_by_id_and_user_id(memory_id, user.id) if result: diff --git a/backend/apps/webui/routers/prompts.py b/backend/apps/webui/routers/prompts.py index 3912b1028..0cbf3d366 100644 --- a/backend/apps/webui/routers/prompts.py +++ b/backend/apps/webui/routers/prompts.py @@ -29,9 +29,7 @@ async def get_prompts(user=Depends(get_current_user)): @router.post("/create", response_model=Optional[PromptModel]) -async def create_new_prompt( - form_data: PromptForm, user=Depends(get_admin_user) -): +async def create_new_prompt(form_data: PromptForm, user=Depends(get_admin_user)): prompt = Prompts.get_prompt_by_command(form_data.command) if prompt == None: prompt = Prompts.insert_new_prompt(user.id, form_data) @@ -54,9 +52,7 @@ async def create_new_prompt( @router.get("/command/{command}", response_model=Optional[PromptModel]) -async def get_prompt_by_command( - command: str, user=Depends(get_current_user) -): +async def get_prompt_by_command(command: str, user=Depends(get_current_user)): prompt = Prompts.get_prompt_by_command(f"/{command}") if prompt: @@ -95,8 +91,6 @@ async def update_prompt_by_command( @router.delete("/command/{command}/delete", response_model=bool) -async def delete_prompt_by_command( - command: str, user=Depends(get_admin_user) -): +async def delete_prompt_by_command(command: str, user=Depends(get_admin_user)): result = Prompts.delete_prompt_by_command(f"/{command}") return result diff --git a/backend/apps/webui/routers/tools.py b/backend/apps/webui/routers/tools.py index 82a09477d..ea9db8180 100644 --- a/backend/apps/webui/routers/tools.py +++ b/backend/apps/webui/routers/tools.py @@ -180,9 +180,7 @@ async def update_toolkit_by_id( @router.delete("/id/{id}/delete", response_model=bool) -async def delete_toolkit_by_id( - request: Request, id: str, user=Depends(get_admin_user) -): +async def delete_toolkit_by_id(request: Request, id: str, user=Depends(get_admin_user)): result = Tools.delete_tool_by_id(id) if result: diff --git a/backend/apps/webui/routers/users.py b/backend/apps/webui/routers/users.py index 8a38d5b9f..9627f0b06 100644 --- a/backend/apps/webui/routers/users.py +++ b/backend/apps/webui/routers/users.py @@ -40,9 +40,7 @@ router = APIRouter() @router.get("/", response_model=List[UserModel]) -async def get_users( - skip: int = 0, limit: int = 50, user=Depends(get_admin_user) -): +async def get_users(skip: int = 0, limit: int = 50, user=Depends(get_admin_user)): return Users.get_users(skip, limit) @@ -70,9 +68,7 @@ async def update_user_permissions( @router.post("/update/role", response_model=Optional[UserModel]) -async def update_user_role( - form_data: UserRoleUpdateForm, user=Depends(get_admin_user) -): +async def update_user_role(form_data: UserRoleUpdateForm, user=Depends(get_admin_user)): if user.id != form_data.id and form_data.id != Users.get_first_user().id: return Users.update_user_role_by_id(form_data.id, form_data.role) @@ -89,9 +85,7 @@ async def update_user_role( @router.get("/user/settings", response_model=Optional[UserSettings]) -async def get_user_settings_by_session_user( - user=Depends(get_verified_user) -): +async def get_user_settings_by_session_user(user=Depends(get_verified_user)): user = Users.get_user_by_id(user.id) if user: return user.settings @@ -127,9 +121,7 @@ async def update_user_settings_by_session_user( @router.get("/user/info", response_model=Optional[dict]) -async def get_user_info_by_session_user( - user=Depends(get_verified_user) -): +async def get_user_info_by_session_user(user=Depends(get_verified_user)): user = Users.get_user_by_id(user.id) if user: return user.info @@ -154,9 +146,7 @@ async def update_user_info_by_session_user( if user.info is None: user.info = {} - user = Users.update_user_by_id( - user.id, {"info": {**user.info, **form_data}} - ) + user = Users.update_user_by_id(user.id, {"info": {**user.info, **form_data}}) if user: return user.info else: @@ -182,9 +172,7 @@ class UserResponse(BaseModel): @router.get("/{user_id}", response_model=UserResponse) -async def get_user_by_id( - user_id: str, user=Depends(get_verified_user) -): +async def get_user_by_id(user_id: str, user=Depends(get_verified_user)): # Check if user_id is a shared chat # If it is, get the user_id from the chat @@ -267,9 +255,7 @@ async def update_user_by_id( @router.delete("/{user_id}", response_model=bool) -async def delete_user_by_id( - user_id: str, user=Depends(get_admin_user) -): +async def delete_user_by_id(user_id: str, user=Depends(get_admin_user)): if user.id != user_id: result = Auths.delete_auth_by_id(user_id) diff --git a/backend/main.py b/backend/main.py index 2c4d5ecfd..f35095bf1 100644 --- a/backend/main.py +++ b/backend/main.py @@ -175,7 +175,9 @@ https://github.com/open-webui/open-webui def run_migrations(): env = os.environ.copy() env["DATABASE_URL"] = DATABASE_URL - migration_task = subprocess.run(["alembic", f"-c{BACKEND_DIR}/alembic.ini", "upgrade", "head"], env=env) + migration_task = subprocess.run( + ["alembic", f"-c{BACKEND_DIR}/alembic.ini", "upgrade", "head"], env=env + ) if migration_task.returncode > 0: raise ValueError("Error running migrations") diff --git a/backend/migrations/versions/ba76b0bae648_init.py b/backend/migrations/versions/ba76b0bae648_init.py index b1250662f..c491ed46c 100644 --- a/backend/migrations/versions/ba76b0bae648_init.py +++ b/backend/migrations/versions/ba76b0bae648_init.py @@ -5,6 +5,7 @@ Revises: Create Date: 2024-06-24 09:09:11.636336 """ + from typing import Sequence, Union from alembic import op @@ -13,7 +14,7 @@ import apps.webui.internal.db # revision identifiers, used by Alembic. -revision: str = 'ba76b0bae648' +revision: str = "ba76b0bae648" down_revision: Union[str, None] = None branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None @@ -21,141 +22,153 @@ depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### - op.create_table('auth', - sa.Column('id', sa.String(), nullable=False), - sa.Column('email', sa.String(), nullable=True), - sa.Column('password', sa.String(), nullable=True), - sa.Column('active', sa.Boolean(), nullable=True), - sa.PrimaryKeyConstraint('id') + op.create_table( + "auth", + sa.Column("id", sa.String(), nullable=False), + sa.Column("email", sa.String(), nullable=True), + sa.Column("password", sa.String(), nullable=True), + sa.Column("active", sa.Boolean(), nullable=True), + sa.PrimaryKeyConstraint("id"), ) - op.create_table('chat', - sa.Column('id', sa.String(), nullable=False), - sa.Column('user_id', sa.String(), nullable=True), - sa.Column('title', sa.String(), nullable=True), - sa.Column('chat', sa.String(), nullable=True), - sa.Column('created_at', sa.BigInteger(), nullable=True), - sa.Column('updated_at', sa.BigInteger(), nullable=True), - sa.Column('share_id', sa.String(), nullable=True), - sa.Column('archived', sa.Boolean(), nullable=True), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('share_id') + op.create_table( + "chat", + sa.Column("id", sa.String(), nullable=False), + sa.Column("user_id", sa.String(), nullable=True), + sa.Column("title", sa.String(), nullable=True), + sa.Column("chat", sa.String(), nullable=True), + sa.Column("created_at", sa.BigInteger(), nullable=True), + sa.Column("updated_at", sa.BigInteger(), nullable=True), + sa.Column("share_id", sa.String(), nullable=True), + sa.Column("archived", sa.Boolean(), nullable=True), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("share_id"), ) - op.create_table('chatidtag', - sa.Column('id', sa.String(), nullable=False), - sa.Column('tag_name', sa.String(), nullable=True), - sa.Column('chat_id', sa.String(), nullable=True), - sa.Column('user_id', sa.String(), nullable=True), - sa.Column('timestamp', sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint('id') + op.create_table( + "chatidtag", + sa.Column("id", sa.String(), nullable=False), + sa.Column("tag_name", sa.String(), nullable=True), + sa.Column("chat_id", sa.String(), nullable=True), + sa.Column("user_id", sa.String(), nullable=True), + sa.Column("timestamp", sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint("id"), ) - op.create_table('document', - sa.Column('collection_name', sa.String(), nullable=False), - sa.Column('name', sa.String(), nullable=True), - sa.Column('title', sa.String(), nullable=True), - sa.Column('filename', sa.String(), nullable=True), - sa.Column('content', sa.String(), nullable=True), - sa.Column('user_id', sa.String(), nullable=True), - sa.Column('timestamp', sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint('collection_name'), - sa.UniqueConstraint('name') + op.create_table( + "document", + sa.Column("collection_name", sa.String(), nullable=False), + sa.Column("name", sa.String(), nullable=True), + sa.Column("title", sa.String(), nullable=True), + sa.Column("filename", sa.String(), nullable=True), + sa.Column("content", sa.String(), nullable=True), + sa.Column("user_id", sa.String(), nullable=True), + sa.Column("timestamp", sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint("collection_name"), + sa.UniqueConstraint("name"), ) - op.create_table('file', - sa.Column('id', sa.String(), nullable=False), - sa.Column('user_id', sa.String(), nullable=True), - sa.Column('filename', sa.String(), nullable=True), - sa.Column('meta', apps.webui.internal.db.JSONField(), nullable=True), - sa.Column('created_at', sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint('id') + op.create_table( + "file", + sa.Column("id", sa.String(), nullable=False), + sa.Column("user_id", sa.String(), nullable=True), + sa.Column("filename", sa.String(), nullable=True), + sa.Column("meta", apps.webui.internal.db.JSONField(), nullable=True), + sa.Column("created_at", sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint("id"), ) - op.create_table('function', - sa.Column('id', sa.String(), nullable=False), - sa.Column('user_id', sa.String(), nullable=True), - sa.Column('name', sa.Text(), nullable=True), - sa.Column('type', sa.Text(), nullable=True), - sa.Column('content', sa.Text(), nullable=True), - sa.Column('meta', apps.webui.internal.db.JSONField(), nullable=True), - sa.Column('valves', apps.webui.internal.db.JSONField(), nullable=True), - sa.Column('is_active', sa.Boolean(), nullable=True), - sa.Column('updated_at', sa.BigInteger(), nullable=True), - sa.Column('created_at', sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint('id') + op.create_table( + "function", + sa.Column("id", sa.String(), nullable=False), + sa.Column("user_id", sa.String(), nullable=True), + sa.Column("name", sa.Text(), nullable=True), + sa.Column("type", sa.Text(), nullable=True), + sa.Column("content", sa.Text(), nullable=True), + sa.Column("meta", apps.webui.internal.db.JSONField(), nullable=True), + sa.Column("valves", apps.webui.internal.db.JSONField(), nullable=True), + sa.Column("is_active", sa.Boolean(), nullable=True), + sa.Column("updated_at", sa.BigInteger(), nullable=True), + sa.Column("created_at", sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint("id"), ) - op.create_table('memory', - sa.Column('id', sa.String(), nullable=False), - sa.Column('user_id', sa.String(), nullable=True), - sa.Column('content', sa.String(), nullable=True), - sa.Column('updated_at', sa.BigInteger(), nullable=True), - sa.Column('created_at', sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint('id') + op.create_table( + "memory", + sa.Column("id", sa.String(), nullable=False), + sa.Column("user_id", sa.String(), nullable=True), + sa.Column("content", sa.String(), nullable=True), + sa.Column("updated_at", sa.BigInteger(), nullable=True), + sa.Column("created_at", sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint("id"), ) - op.create_table('model', - sa.Column('id', sa.String(), nullable=False), - sa.Column('user_id', sa.String(), nullable=True), - sa.Column('base_model_id', sa.String(), nullable=True), - sa.Column('name', sa.String(), nullable=True), - sa.Column('params', apps.webui.internal.db.JSONField(), nullable=True), - sa.Column('meta', apps.webui.internal.db.JSONField(), nullable=True), - sa.Column('updated_at', sa.BigInteger(), nullable=True), - sa.Column('created_at', sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint('id') + op.create_table( + "model", + sa.Column("id", sa.String(), nullable=False), + sa.Column("user_id", sa.String(), nullable=True), + sa.Column("base_model_id", sa.String(), nullable=True), + sa.Column("name", sa.String(), nullable=True), + sa.Column("params", apps.webui.internal.db.JSONField(), nullable=True), + sa.Column("meta", apps.webui.internal.db.JSONField(), nullable=True), + sa.Column("updated_at", sa.BigInteger(), nullable=True), + sa.Column("created_at", sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint("id"), ) - op.create_table('prompt', - sa.Column('command', sa.String(), nullable=False), - sa.Column('user_id', sa.String(), nullable=True), - sa.Column('title', sa.String(), nullable=True), - sa.Column('content', sa.String(), nullable=True), - sa.Column('timestamp', sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint('command') + op.create_table( + "prompt", + sa.Column("command", sa.String(), nullable=False), + sa.Column("user_id", sa.String(), nullable=True), + sa.Column("title", sa.String(), nullable=True), + sa.Column("content", sa.String(), nullable=True), + sa.Column("timestamp", sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint("command"), ) - op.create_table('tag', - sa.Column('id', sa.String(), nullable=False), - sa.Column('name', sa.String(), nullable=True), - sa.Column('user_id', sa.String(), nullable=True), - sa.Column('data', sa.String(), nullable=True), - sa.PrimaryKeyConstraint('id') + op.create_table( + "tag", + sa.Column("id", sa.String(), nullable=False), + sa.Column("name", sa.String(), nullable=True), + sa.Column("user_id", sa.String(), nullable=True), + sa.Column("data", sa.String(), nullable=True), + sa.PrimaryKeyConstraint("id"), ) - op.create_table('tool', - sa.Column('id', sa.String(), nullable=False), - sa.Column('user_id', sa.String(), nullable=True), - sa.Column('name', sa.String(), nullable=True), - sa.Column('content', sa.String(), nullable=True), - sa.Column('specs', apps.webui.internal.db.JSONField(), nullable=True), - sa.Column('meta', apps.webui.internal.db.JSONField(), nullable=True), - sa.Column('valves', apps.webui.internal.db.JSONField(), nullable=True), - sa.Column('updated_at', sa.BigInteger(), nullable=True), - sa.Column('created_at', sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint('id') + op.create_table( + "tool", + sa.Column("id", sa.String(), nullable=False), + sa.Column("user_id", sa.String(), nullable=True), + sa.Column("name", sa.String(), nullable=True), + sa.Column("content", sa.String(), nullable=True), + sa.Column("specs", apps.webui.internal.db.JSONField(), nullable=True), + sa.Column("meta", apps.webui.internal.db.JSONField(), nullable=True), + sa.Column("valves", apps.webui.internal.db.JSONField(), nullable=True), + sa.Column("updated_at", sa.BigInteger(), nullable=True), + sa.Column("created_at", sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint("id"), ) - op.create_table('user', - sa.Column('id', sa.String(), nullable=False), - sa.Column('name', sa.String(), nullable=True), - sa.Column('email', sa.String(), nullable=True), - sa.Column('role', sa.String(), nullable=True), - sa.Column('profile_image_url', sa.String(), nullable=True), - sa.Column('last_active_at', sa.BigInteger(), nullable=True), - sa.Column('updated_at', sa.BigInteger(), nullable=True), - sa.Column('created_at', sa.BigInteger(), nullable=True), - sa.Column('api_key', sa.String(), nullable=True), - sa.Column('settings', apps.webui.internal.db.JSONField(), nullable=True), - sa.Column('info', apps.webui.internal.db.JSONField(), nullable=True), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('api_key') + op.create_table( + "user", + sa.Column("id", sa.String(), nullable=False), + sa.Column("name", sa.String(), nullable=True), + sa.Column("email", sa.String(), nullable=True), + sa.Column("role", sa.String(), nullable=True), + sa.Column("profile_image_url", sa.String(), nullable=True), + sa.Column("last_active_at", sa.BigInteger(), nullable=True), + sa.Column("updated_at", sa.BigInteger(), nullable=True), + sa.Column("created_at", sa.BigInteger(), nullable=True), + sa.Column("api_key", sa.String(), nullable=True), + sa.Column("settings", apps.webui.internal.db.JSONField(), nullable=True), + sa.Column("info", apps.webui.internal.db.JSONField(), nullable=True), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("api_key"), ) # ### end Alembic commands ### def downgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('user') - op.drop_table('tool') - op.drop_table('tag') - op.drop_table('prompt') - op.drop_table('model') - op.drop_table('memory') - op.drop_table('function') - op.drop_table('file') - op.drop_table('document') - op.drop_table('chatidtag') - op.drop_table('chat') - op.drop_table('auth') + op.drop_table("user") + op.drop_table("tool") + op.drop_table("tag") + op.drop_table("prompt") + op.drop_table("model") + op.drop_table("memory") + op.drop_table("function") + op.drop_table("file") + op.drop_table("document") + op.drop_table("chatidtag") + op.drop_table("chat") + op.drop_table("auth") # ### end Alembic commands ### diff --git a/backend/test/util/abstract_integration_test.py b/backend/test/util/abstract_integration_test.py index 9cbf42d47..781fbfff8 100644 --- a/backend/test/util/abstract_integration_test.py +++ b/backend/test/util/abstract_integration_test.py @@ -91,6 +91,7 @@ class AbstractPostgresTest(AbstractIntegrationTest): while retries > 0: try: from config import BACKEND_DIR + db = create_engine(database_url, pool_pre_ping=True) db = db.connect() log.info("postgres is ready!") From eb01e8d2755a73f7c8db121d7b69b36bee1cae22 Mon Sep 17 00:00:00 2001 From: Jonathan Rohde Date: Mon, 24 Jun 2024 10:54:18 +0200 Subject: [PATCH 006/181] feat(sqlalchemy): use scoped session --- backend/apps/webui/internal/db.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/apps/webui/internal/db.py b/backend/apps/webui/internal/db.py index 6fd541f4e..b9bfc8aff 100644 --- a/backend/apps/webui/internal/db.py +++ b/backend/apps/webui/internal/db.py @@ -7,7 +7,7 @@ from typing_extensions import Self from sqlalchemy import create_engine, types, Dialect from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm import sessionmaker, scoped_session from sqlalchemy.sql.type_api import _T from config import SRC_LOG_LEVELS, DATA_DIR, DATABASE_URL, BACKEND_DIR @@ -61,10 +61,10 @@ Base = declarative_base() @contextmanager def get_session(): - db = SessionLocal() + session = scoped_session(SessionLocal) try: - yield db - db.commit() + yield session + session.commit() except Exception as e: - db.rollback() + session.rollback() raise e From da403f3e3cf9ce700da2fdb477e0bdfc4794d37d Mon Sep 17 00:00:00 2001 From: Jonathan Rohde Date: Mon, 24 Jun 2024 13:06:15 +0200 Subject: [PATCH 007/181] feat(sqlalchemy): use session factory instead of context manager --- backend/apps/webui/internal/db.py | 12 +- backend/apps/webui/models/auths.py | 131 ++++---- backend/apps/webui/models/chats.py | 292 ++++++++---------- backend/apps/webui/models/documents.py | 77 +++-- backend/apps/webui/models/files.py | 36 +-- backend/apps/webui/models/functions.py | 117 ++++--- backend/apps/webui/models/memories.py | 60 ++-- backend/apps/webui/models/models.py | 42 ++- backend/apps/webui/models/prompts.py | 91 +++--- backend/apps/webui/models/tags.py | 200 ++++++------ backend/apps/webui/models/tools.py | 59 ++-- backend/apps/webui/models/users.py | 245 +++++++-------- backend/main.py | 14 +- backend/test/apps/webui/routers/test_chats.py | 2 + .../test/util/abstract_integration_test.py | 21 +- 15 files changed, 640 insertions(+), 759 deletions(-) diff --git a/backend/apps/webui/internal/db.py b/backend/apps/webui/internal/db.py index b9bfc8aff..320ab3e07 100644 --- a/backend/apps/webui/internal/db.py +++ b/backend/apps/webui/internal/db.py @@ -57,14 +57,4 @@ SessionLocal = sessionmaker( autocommit=False, autoflush=False, bind=engine, expire_on_commit=False ) Base = declarative_base() - - -@contextmanager -def get_session(): - session = scoped_session(SessionLocal) - try: - yield session - session.commit() - except Exception as e: - session.rollback() - raise e +Session = scoped_session(SessionLocal) diff --git a/backend/apps/webui/models/auths.py b/backend/apps/webui/models/auths.py index 9f10e0fdd..1858b2c0d 100644 --- a/backend/apps/webui/models/auths.py +++ b/backend/apps/webui/models/auths.py @@ -3,12 +3,11 @@ from typing import Optional import uuid import logging from sqlalchemy import String, Column, Boolean -from sqlalchemy.orm import Session from apps.webui.models.users import UserModel, Users from utils.utils import verify_password -from apps.webui.internal.db import Base, get_session +from apps.webui.internal.db import Base, Session from config import SRC_LOG_LEVELS @@ -103,101 +102,93 @@ class AuthsTable: role: str = "pending", oauth_sub: Optional[str] = None, ) -> Optional[UserModel]: - with get_session() as db: - log.info("insert_new_auth") + log.info("insert_new_auth") - id = str(uuid.uuid4()) + id = str(uuid.uuid4()) - auth = AuthModel( - **{"id": id, "email": email, "password": password, "active": True} - ) - result = Auth(**auth.model_dump()) - db.add(result) + auth = AuthModel( + **{"id": id, "email": email, "password": password, "active": True} + ) + result = Auth(**auth.model_dump()) + Session.add(result) - user = Users.insert_new_user( - id, name, email, profile_image_url, role, oauth_sub - ) + user = Users.insert_new_user( + id, name, email, profile_image_url, role, oauth_sub) - db.commit() - db.refresh(result) + Session.commit() + Session.refresh(result) - if result and user: - return user - else: - return None + if result and user: + return user + else: + return None def authenticate_user(self, email: str, password: str) -> Optional[UserModel]: log.info(f"authenticate_user: {email}") - with get_session() as db: - try: - auth = db.query(Auth).filter_by(email=email, active=True).first() - if auth: - if verify_password(password, auth.password): - user = Users.get_user_by_id(auth.id) - return user - else: - return None + try: + auth = Session.query(Auth).filter_by(email=email, active=True).first() + if auth: + if verify_password(password, auth.password): + user = Users.get_user_by_id(auth.id) + return user else: return None - except: + else: return None + except: + return None def authenticate_user_by_api_key(self, api_key: str) -> Optional[UserModel]: log.info(f"authenticate_user_by_api_key: {api_key}") - with get_session() as db: - # if no api_key, return None - if not api_key: - return None + # if no api_key, return None + if not api_key: + return None - try: - user = Users.get_user_by_api_key(api_key) - return user if user else None - except: - return False + try: + user = Users.get_user_by_api_key(api_key) + return user if user else None + except: + return False def authenticate_user_by_trusted_header(self, email: str) -> Optional[UserModel]: log.info(f"authenticate_user_by_trusted_header: {email}") - with get_session() as db: - try: - auth = db.query(Auth).filter(email=email, active=True).first() - if auth: - user = Users.get_user_by_id(auth.id) - return user - except: - return None + try: + auth = Session.query(Auth).filter(email=email, active=True).first() + if auth: + user = Users.get_user_by_id(auth.id) + return user + except: + return None def update_user_password_by_id(self, id: str, new_password: str) -> bool: - with get_session() as db: - try: - result = ( - db.query(Auth).filter_by(id=id).update({"password": new_password}) - ) - return True if result == 1 else False - except: - return False + try: + result = ( + Session.query(Auth).filter_by(id=id).update({"password": new_password}) + ) + return True if result == 1 else False + except: + return False def update_email_by_id(self, id: str, email: str) -> bool: - with get_session() as db: - try: - result = db.query(Auth).filter_by(id=id).update({"email": email}) - return True if result == 1 else False - except: - return False + try: + result = Session.query(Auth).filter_by(id=id).update({"email": email}) + return True if result == 1 else False + except: + return False def delete_auth_by_id(self, id: str) -> bool: - with get_session() as db: - try: - # Delete User - result = Users.delete_user_by_id(id) + try: + # Delete User + result = Users.delete_user_by_id(id) - if result: - db.query(Auth).filter_by(id=id).delete() + if result: + Session.query(Auth).filter_by(id=id).delete() - return True - else: - return False - except: + return True + else: return False + except: + return False Auths = AuthsTable() diff --git a/backend/apps/webui/models/chats.py b/backend/apps/webui/models/chats.py index b0c983ade..abf5b544c 100644 --- a/backend/apps/webui/models/chats.py +++ b/backend/apps/webui/models/chats.py @@ -6,9 +6,8 @@ import uuid import time from sqlalchemy import Column, String, BigInteger, Boolean -from sqlalchemy.orm import Session -from apps.webui.internal.db import Base, get_session +from apps.webui.internal.db import Base, Session #################### @@ -80,93 +79,88 @@ class ChatTitleIdResponse(BaseModel): class ChatTable: def insert_new_chat(self, user_id: str, form_data: ChatForm) -> Optional[ChatModel]: - with get_session() as db: - id = str(uuid.uuid4()) - chat = ChatModel( - **{ - "id": id, - "user_id": user_id, - "title": ( - form_data.chat["title"] - if "title" in form_data.chat - else "New Chat" - ), - "chat": json.dumps(form_data.chat), - "created_at": int(time.time()), - "updated_at": int(time.time()), - } - ) + id = str(uuid.uuid4()) + chat = ChatModel( + **{ + "id": id, + "user_id": user_id, + "title": ( + form_data.chat["title"] + if "title" in form_data.chat + else "New Chat" + ), + "chat": json.dumps(form_data.chat), + "created_at": int(time.time()), + "updated_at": int(time.time()), + } + ) - result = Chat(**chat.model_dump()) - db.add(result) - db.commit() - db.refresh(result) - return ChatModel.model_validate(result) if result else None + result = Chat(**chat.model_dump()) + Session.add(result) + Session.commit() + Session.refresh(result) + return ChatModel.model_validate(result) if result else None def update_chat_by_id(self, id: str, chat: dict) -> Optional[ChatModel]: - with get_session() as db: - try: - chat_obj = db.get(Chat, id) - chat_obj.chat = json.dumps(chat) - chat_obj.title = chat["title"] if "title" in chat else "New Chat" - chat_obj.updated_at = int(time.time()) - db.commit() - db.refresh(chat_obj) + try: + chat_obj = Session.get(Chat, id) + chat_obj.chat = json.dumps(chat) + chat_obj.title = chat["title"] if "title" in chat else "New Chat" + chat_obj.updated_at = int(time.time()) + Session.commit() + Session.refresh(chat_obj) - return ChatModel.model_validate(chat_obj) - except Exception as e: - return None + return ChatModel.model_validate(chat_obj) + except Exception as e: + return None def insert_shared_chat_by_chat_id(self, chat_id: str) -> Optional[ChatModel]: - with get_session() as db: - # Get the existing chat to share - chat = db.get(Chat, chat_id) - # Check if the chat is already shared - if chat.share_id: - return self.get_chat_by_id_and_user_id(chat.share_id, "shared") - # Create a new chat with the same data, but with a new ID - shared_chat = ChatModel( - **{ - "id": str(uuid.uuid4()), - "user_id": f"shared-{chat_id}", - "title": chat.title, - "chat": chat.chat, - "created_at": chat.created_at, - "updated_at": int(time.time()), - } - ) - shared_result = Chat(**shared_chat.model_dump()) - db.add(shared_result) - db.commit() - db.refresh(shared_result) - # Update the original chat with the share_id - result = ( - db.query(Chat) - .filter_by(id=chat_id) - .update({"share_id": shared_chat.id}) - ) + # Get the existing chat to share + chat = Session.get(Chat, chat_id) + # Check if the chat is already shared + if chat.share_id: + return self.get_chat_by_id_and_user_id(chat.share_id, "shared") + # Create a new chat with the same data, but with a new ID + shared_chat = ChatModel( + **{ + "id": str(uuid.uuid4()), + "user_id": f"shared-{chat_id}", + "title": chat.title, + "chat": chat.chat, + "created_at": chat.created_at, + "updated_at": int(time.time()), + } + ) + shared_result = Chat(**shared_chat.model_dump()) + Session.add(shared_result) + Session.commit() + Session.refresh(shared_result) + # Update the original chat with the share_id + result = ( + Session.query(Chat) + .filter_by(id=chat_id) + .update({"share_id": shared_chat.id}) + ) - return shared_chat if (shared_result and result) else None + return shared_chat if (shared_result and result) else None def update_shared_chat_by_chat_id(self, chat_id: str) -> Optional[ChatModel]: - with get_session() as db: - try: - print("update_shared_chat_by_id") - chat = db.get(Chat, chat_id) - print(chat) - chat.title = chat.title - chat.chat = chat.chat - db.commit() - db.refresh(chat) + try: + print("update_shared_chat_by_id") + chat = Session.get(Chat, chat_id) + print(chat) + chat.title = chat.title + chat.chat = chat.chat + Session.commit() + Session.refresh(chat) - return self.get_chat_by_id(chat.share_id) - except: - return None + return self.get_chat_by_id(chat.share_id) + except: + return None def delete_shared_chat_by_chat_id(self, chat_id: str) -> bool: try: - with get_session() as db: - db.query(Chat).filter_by(user_id=f"shared-{chat_id}").delete() + Session.query(Chat).filter_by(user_id=f"shared-{chat_id}").delete() return True except: return False @@ -175,30 +169,27 @@ class ChatTable: self, id: str, share_id: Optional[str] ) -> Optional[ChatModel]: try: - with get_session() as db: - chat = db.get(Chat, id) - chat.share_id = share_id - db.commit() - db.refresh(chat) - return chat + chat = Session.get(Chat, id) + chat.share_id = share_id + Session.commit() + Session.refresh(chat) + return ChatModel.model_validate(chat) except: return None def toggle_chat_archive_by_id(self, id: str) -> Optional[ChatModel]: try: - with get_session() as db: - chat = self.get_chat_by_id(id) - db.query(Chat).filter_by(id=id).update({"archived": not chat.archived}) - - return self.get_chat_by_id(id) + chat = Session.get(Chat, id) + chat.archived = not chat.archived + Session.commit() + Session.refresh(chat) + return ChatModel.model_validate(chat) except: return None def archive_all_chats_by_user_id(self, user_id: str) -> bool: try: - with get_session() as db: - db.query(Chat).filter_by(user_id=user_id).update({"archived": True}) - + Session.query(Chat).filter_by(user_id=user_id).update({"archived": True}) return True except: return False @@ -206,9 +197,8 @@ class ChatTable: def get_archived_chat_list_by_user_id( self, user_id: str, skip: int = 0, limit: int = 50 ) -> List[ChatModel]: - with get_session() as db: all_chats = ( - db.query(Chat) + Session.query(Chat) .filter_by(user_id=user_id, archived=True) .order_by(Chat.updated_at.desc()) # .limit(limit).offset(skip) @@ -223,120 +213,108 @@ class ChatTable: skip: int = 0, limit: int = 50, ) -> List[ChatModel]: - with get_session() as db: - query = db.query(Chat).filter_by(user_id=user_id) - if not include_archived: - query = query.filter_by(archived=False) - all_chats = ( - query.order_by(Chat.updated_at.desc()) - # .limit(limit).offset(skip) - .all() - ) - return [ChatModel.model_validate(chat) for chat in all_chats] + query = Session.query(Chat).filter_by(user_id=user_id) + if not include_archived: + query = query.filter_by(archived=False) + all_chats = ( + query.order_by(Chat.updated_at.desc()) + # .limit(limit).offset(skip) + .all() + ) + return [ChatModel.model_validate(chat) for chat in all_chats] def get_chat_list_by_chat_ids( self, chat_ids: List[str], skip: int = 0, limit: int = 50 ) -> List[ChatModel]: - with get_session() as db: - all_chats = ( - db.query(Chat) - .filter(Chat.id.in_(chat_ids)) - .filter_by(archived=False) - .order_by(Chat.updated_at.desc()) - .all() - ) - return [ChatModel.model_validate(chat) for chat in all_chats] + all_chats = ( + Session.query(Chat) + .filter(Chat.id.in_(chat_ids)) + .filter_by(archived=False) + .order_by(Chat.updated_at.desc()) + .all() + ) + return [ChatModel.model_validate(chat) for chat in all_chats] def get_chat_by_id(self, id: str) -> Optional[ChatModel]: try: - with get_session() as db: - chat = db.get(Chat, id) - return ChatModel.model_validate(chat) + chat = Session.get(Chat, id) + return ChatModel.model_validate(chat) except: return None def get_chat_by_share_id(self, id: str) -> Optional[ChatModel]: try: - with get_session() as db: - chat = db.query(Chat).filter_by(share_id=id).first() + chat = Session.query(Chat).filter_by(share_id=id).first() - if chat: - return self.get_chat_by_id(id) - else: - return None + if chat: + return self.get_chat_by_id(id) + else: + return None except Exception as e: return None def get_chat_by_id_and_user_id(self, id: str, user_id: str) -> Optional[ChatModel]: try: - with get_session() as db: - chat = db.query(Chat).filter_by(id=id, user_id=user_id).first() - return ChatModel.model_validate(chat) + chat = Session.query(Chat).filter_by(id=id, user_id=user_id).first() + return ChatModel.model_validate(chat) except: return None def get_chats(self, skip: int = 0, limit: int = 50) -> List[ChatModel]: - with get_session() as db: - all_chats = ( - db.query(Chat) - # .limit(limit).offset(skip) - .order_by(Chat.updated_at.desc()) - ) - return [ChatModel.model_validate(chat) for chat in all_chats] + all_chats = ( + Session.query(Chat) + # .limit(limit).offset(skip) + .order_by(Chat.updated_at.desc()) + ) + return [ChatModel.model_validate(chat) for chat in all_chats] def get_chats_by_user_id(self, user_id: str) -> List[ChatModel]: - with get_session() as db: - all_chats = ( - db.query(Chat) - .filter_by(user_id=user_id) - .order_by(Chat.updated_at.desc()) - ) - return [ChatModel.model_validate(chat) for chat in all_chats] + all_chats = ( + Session.query(Chat) + .filter_by(user_id=user_id) + .order_by(Chat.updated_at.desc()) + ) + return [ChatModel.model_validate(chat) for chat in all_chats] def get_archived_chats_by_user_id(self, user_id: str) -> List[ChatModel]: - with get_session() as db: - all_chats = ( - db.query(Chat) - .filter_by(user_id=user_id, archived=True) - .order_by(Chat.updated_at.desc()) - ) - return [ChatModel.model_validate(chat) for chat in all_chats] + all_chats = ( + Session.query(Chat) + .filter_by(user_id=user_id, archived=True) + .order_by(Chat.updated_at.desc()) + ) + return [ChatModel.model_validate(chat) for chat in all_chats] def delete_chat_by_id(self, id: str) -> bool: try: - with get_session() as db: - db.query(Chat).filter_by(id=id).delete() + Session.query(Chat).filter_by(id=id).delete() - return True and self.delete_shared_chat_by_chat_id(id) + return True and self.delete_shared_chat_by_chat_id(id) except: return False def delete_chat_by_id_and_user_id(self, id: str, user_id: str) -> bool: try: - with get_session() as db: - db.query(Chat).filter_by(id=id, user_id=user_id).delete() + Session.query(Chat).filter_by(id=id, user_id=user_id).delete() - return True and self.delete_shared_chat_by_chat_id(id) + return True and self.delete_shared_chat_by_chat_id(id) except: return False def delete_chats_by_user_id(self, user_id: str) -> bool: try: - with get_session() as db: - self.delete_shared_chats_by_user_id(user_id) + self.delete_shared_chats_by_user_id(user_id) - db.query(Chat).filter_by(user_id=user_id).delete() + Session.query(Chat).filter_by(user_id=user_id).delete() return True except: return False def delete_shared_chats_by_user_id(self, user_id: str) -> bool: try: - with get_session() as db: - chats_by_user = db.query(Chat).filter_by(user_id=user_id).all() - shared_chat_ids = [f"shared-{chat.id}" for chat in chats_by_user] + chats_by_user = Session.query(Chat).filter_by(user_id=user_id).all() + shared_chat_ids = [f"shared-{chat.id}" for chat in chats_by_user] - db.query(Chat).filter(Chat.user_id.in_(shared_chat_ids)).delete() + Session.query(Chat).filter(Chat.user_id.in_(shared_chat_ids)).delete() return True except: diff --git a/backend/apps/webui/models/documents.py b/backend/apps/webui/models/documents.py index 897f182be..f8e7153c5 100644 --- a/backend/apps/webui/models/documents.py +++ b/backend/apps/webui/models/documents.py @@ -4,9 +4,8 @@ import time import logging from sqlalchemy import String, Column, BigInteger -from sqlalchemy.orm import Session -from apps.webui.internal.db import Base, get_session +from apps.webui.internal.db import Base, Session import json @@ -84,46 +83,42 @@ class DocumentsTable: ) try: - with get_session() as db: - result = Document(**document.model_dump()) - db.add(result) - db.commit() - db.refresh(result) - if result: - return DocumentModel.model_validate(result) - else: - return None + result = Document(**document.model_dump()) + Session.add(result) + Session.commit() + Session.refresh(result) + if result: + return DocumentModel.model_validate(result) + else: + return None except: return None def get_doc_by_name(self, name: str) -> Optional[DocumentModel]: try: - with get_session() as db: - document = db.query(Document).filter_by(name=name).first() - return DocumentModel.model_validate(document) if document else None + document = Session.query(Document).filter_by(name=name).first() + return DocumentModel.model_validate(document) if document else None except: return None def get_docs(self) -> List[DocumentModel]: - with get_session() as db: - return [ - DocumentModel.model_validate(doc) for doc in db.query(Document).all() - ] + return [ + DocumentModel.model_validate(doc) for doc in Session.query(Document).all() + ] def update_doc_by_name( self, name: str, form_data: DocumentUpdateForm ) -> Optional[DocumentModel]: try: - with get_session() as db: - db.query(Document).filter_by(name=name).update( - { - "title": form_data.title, - "name": form_data.name, - "timestamp": int(time.time()), - } - ) - db.commit() - return self.get_doc_by_name(form_data.name) + Session.query(Document).filter_by(name=name).update( + { + "title": form_data.title, + "name": form_data.name, + "timestamp": int(time.time()), + } + ) + Session.commit() + return self.get_doc_by_name(form_data.name) except Exception as e: log.exception(e) return None @@ -132,27 +127,25 @@ class DocumentsTable: self, name: str, updated: dict ) -> Optional[DocumentModel]: try: - with get_session() as db: - doc = self.get_doc_by_name(name) - doc_content = json.loads(doc.content if doc.content else "{}") - doc_content = {**doc_content, **updated} + doc = self.get_doc_by_name(name) + doc_content = json.loads(doc.content if doc.content else "{}") + doc_content = {**doc_content, **updated} - db.query(Document).filter_by(name=name).update( - { - "content": json.dumps(doc_content), - "timestamp": int(time.time()), - } - ) - db.commit() - return self.get_doc_by_name(name) + Session.query(Document).filter_by(name=name).update( + { + "content": json.dumps(doc_content), + "timestamp": int(time.time()), + } + ) + Session.commit() + return self.get_doc_by_name(name) except Exception as e: log.exception(e) return None def delete_doc_by_name(self, name: str) -> bool: try: - with get_session() as db: - db.query(Document).filter_by(name=name).delete() + Session.query(Document).filter_by(name=name).delete() return True except: return False diff --git a/backend/apps/webui/models/files.py b/backend/apps/webui/models/files.py index b7196d604..7664bf4f1 100644 --- a/backend/apps/webui/models/files.py +++ b/backend/apps/webui/models/files.py @@ -4,9 +4,8 @@ import time import logging from sqlalchemy import Column, String, BigInteger -from sqlalchemy.orm import Session -from apps.webui.internal.db import JSONField, Base, get_session +from apps.webui.internal.db import JSONField, Base, Session import json @@ -71,45 +70,38 @@ class FilesTable: ) try: - with get_session() as db: - result = File(**file.model_dump()) - db.add(result) - db.commit() - db.refresh(result) - if result: - return FileModel.model_validate(result) - else: - return None + result = File(**file.model_dump()) + Session.add(result) + Session.commit() + Session.refresh(result) + if result: + return FileModel.model_validate(result) + else: + return None except Exception as e: print(f"Error creating tool: {e}") return None def get_file_by_id(self, id: str) -> Optional[FileModel]: try: - with get_session() as db: - file = db.get(File, id) - return FileModel.model_validate(file) + file = Session.get(File, id) + return FileModel.model_validate(file) except: return None def get_files(self) -> List[FileModel]: - with get_session() as db: - return [FileModel.model_validate(file) for file in db.query(File).all()] + return [FileModel.model_validate(file) for file in Session.query(File).all()] def delete_file_by_id(self, id: str) -> bool: try: - with get_session() as db: - db.query(File).filter_by(id=id).delete() - db.commit() + Session.query(File).filter_by(id=id).delete() return True except: return False def delete_all_files(self) -> bool: try: - with get_session() as db: - db.query(File).delete() - db.commit() + Session.query(File).delete() return True except: return False diff --git a/backend/apps/webui/models/functions.py b/backend/apps/webui/models/functions.py index 2343c9139..b78ac9708 100644 --- a/backend/apps/webui/models/functions.py +++ b/backend/apps/webui/models/functions.py @@ -4,9 +4,8 @@ import time import logging from sqlalchemy import Column, String, Text, BigInteger, Boolean -from sqlalchemy.orm import Session -from apps.webui.internal.db import JSONField, Base, get_session +from apps.webui.internal.db import JSONField, Base, Session from apps.webui.models.users import Users import json @@ -100,64 +99,57 @@ class FunctionsTable: ) try: - with get_session() as db: - result = Function(**function.model_dump()) - db.add(result) - db.commit() - db.refresh(result) - if result: - return FunctionModel.model_validate(result) - else: - return None + result = Function(**function.model_dump()) + Session.add(result) + Session.commit() + Session.refresh(result) + if result: + return FunctionModel.model_validate(result) + else: + return None except Exception as e: print(f"Error creating tool: {e}") return None def get_function_by_id(self, id: str) -> Optional[FunctionModel]: try: - with get_session() as db: - function = db.get(Function, id) - return FunctionModel.model_validate(function) + function = Session.get(Function, id) + return FunctionModel.model_validate(function) except: return None def get_functions(self, active_only=False) -> List[FunctionModel]: if active_only: - with get_session() as db: - return [ - FunctionModel.model_validate(function) - for function in db.query(Function).filter_by(is_active=True).all() - ] + return [ + FunctionModel.model_validate(function) + for function in Session.query(Function).filter_by(is_active=True).all() + ] else: - with get_session() as db: - return [ - FunctionModel.model_validate(function) - for function in db.query(Function).all() - ] + return [ + FunctionModel.model_validate(function) + for function in Session.query(Function).all() + ] def get_functions_by_type( self, type: str, active_only=False ) -> List[FunctionModel]: if active_only: - with get_session() as db: - return [ - FunctionModel.model_validate(function) - for function in db.query(Function) - .filter_by(type=type, is_active=True) - .all() - ] + return [ + FunctionModel.model_validate(function) + for function in Session.query(Function) + .filter_by(type=type, is_active=True) + .all() + ] else: - with get_session() as db: - return [ - FunctionModel.model_validate(function) - for function in db.query(Function).filter_by(type=type).all() - ] + return [ + FunctionModel.model_validate(function) + for function in Session.query(Function).filter_by(type=type).all() + ] def get_function_valves_by_id(self, id: str) -> Optional[dict]: try: - with get_session() as db: - function = db.get(Function, id) - return function.valves if function.valves else {} + function = Session.get(Function, id) + return function.valves if function.valves else {} except Exception as e: print(f"An error occurred: {e}") return None @@ -166,12 +158,12 @@ class FunctionsTable: self, id: str, valves: dict ) -> Optional[FunctionValves]: try: - with get_session() as db: - db.query(Function).filter_by(id=id).update( - {"valves": valves, "updated_at": int(time.time())} - ) - db.commit() - return self.get_function_by_id(id) + function = Session.get(Function, id) + function.valves = valves + function.updated_at = int(time.time()) + Session.commit() + Session.refresh(function) + return self.get_function_by_id(id) except: return None @@ -219,36 +211,33 @@ class FunctionsTable: def update_function_by_id(self, id: str, updated: dict) -> Optional[FunctionModel]: try: - with get_session() as db: - db.query(Function).filter_by(id=id).update( - { - **updated, - "updated_at": int(time.time()), - } - ) - db.commit() - return self.get_function_by_id(id) + Session.query(Function).filter_by(id=id).update( + { + **updated, + "updated_at": int(time.time()), + } + ) + Session.commit() + return self.get_function_by_id(id) except: return None def deactivate_all_functions(self) -> Optional[bool]: try: - with get_session() as db: - db.query(Function).update( - { - "is_active": False, - "updated_at": int(time.time()), - } - ) - db.commit() + Session.query(Function).update( + { + "is_active": False, + "updated_at": int(time.time()), + } + ) + Session.commit() return True except: return None def delete_function_by_id(self, id: str) -> bool: try: - with get_session() as db: - db.query(Function).filter_by(id=id).delete() + Session.query(Function).filter_by(id=id).delete() return True except: return False diff --git a/backend/apps/webui/models/memories.py b/backend/apps/webui/models/memories.py index 941da5b26..263d1b5ab 100644 --- a/backend/apps/webui/models/memories.py +++ b/backend/apps/webui/models/memories.py @@ -2,10 +2,8 @@ from pydantic import BaseModel, ConfigDict from typing import List, Union, Optional from sqlalchemy import Column, String, BigInteger -from sqlalchemy.orm import Session -from apps.webui.internal.db import Base, get_session -from apps.webui.models.chats import Chats +from apps.webui.internal.db import Base, Session import time import uuid @@ -58,15 +56,14 @@ class MemoriesTable: "updated_at": int(time.time()), } ) - with get_session() as db: - result = Memory(**memory.model_dump()) - db.add(result) - db.commit() - db.refresh(result) - if result: - return MemoryModel.model_validate(result) - else: - return None + result = Memory(**memory.model_dump()) + Session.add(result) + Session.commit() + Session.refresh(result) + if result: + return MemoryModel.model_validate(result) + else: + return None def update_memory_by_id( self, @@ -74,62 +71,55 @@ class MemoriesTable: content: str, ) -> Optional[MemoryModel]: try: - with get_session() as db: - db.query(Memory).filter_by(id=id).update( - {"content": content, "updated_at": int(time.time())} - ) - db.commit() - return self.get_memory_by_id(id) + Session.query(Memory).filter_by(id=id).update( + {"content": content, "updated_at": int(time.time())} + ) + Session.commit() + return self.get_memory_by_id(id) except: return None def get_memories(self) -> List[MemoryModel]: try: - with get_session() as db: - memories = db.query(Memory).all() - return [MemoryModel.model_validate(memory) for memory in memories] + memories = Session.query(Memory).all() + return [MemoryModel.model_validate(memory) for memory in memories] except: return None def get_memories_by_user_id(self, user_id: str) -> List[MemoryModel]: try: - with get_session() as db: - memories = db.query(Memory).filter_by(user_id=user_id).all() - return [MemoryModel.model_validate(memory) for memory in memories] + memories = Session.query(Memory).filter_by(user_id=user_id).all() + return [MemoryModel.model_validate(memory) for memory in memories] except: return None def get_memory_by_id(self, id: str) -> Optional[MemoryModel]: try: - with get_session() as db: - memory = db.get(Memory, id) - return MemoryModel.model_validate(memory) + memory = Session.get(Memory, id) + return MemoryModel.model_validate(memory) except: return None def delete_memory_by_id(self, id: str) -> bool: try: - with get_session() as db: - db.query(Memory).filter_by(id=id).delete() + Session.query(Memory).filter_by(id=id).delete() return True except: return False - def delete_memories_by_user_id(self, db: Session, user_id: str) -> bool: + def delete_memories_by_user_id(self, user_id: str) -> bool: try: - with get_session() as db: - db.query(Memory).filter_by(user_id=user_id).delete() + Session.query(Memory).filter_by(user_id=user_id).delete() return True except: return False def delete_memory_by_id_and_user_id( - self, db: Session, id: str, user_id: str + self, id: str, user_id: str ) -> bool: try: - with get_session() as db: - db.query(Memory).filter_by(id=id, user_id=user_id).delete() + Session.query(Memory).filter_by(id=id, user_id=user_id).delete() return True except: return False diff --git a/backend/apps/webui/models/models.py b/backend/apps/webui/models/models.py index 86b4fa49b..dd736a73e 100644 --- a/backend/apps/webui/models/models.py +++ b/backend/apps/webui/models/models.py @@ -4,9 +4,8 @@ from typing import Optional from pydantic import BaseModel, ConfigDict from sqlalchemy import String, Column, BigInteger -from sqlalchemy.orm import Session -from apps.webui.internal.db import Base, JSONField, get_session +from apps.webui.internal.db import Base, JSONField, Session from typing import List, Union, Optional from config import SRC_LOG_LEVELS @@ -127,41 +126,37 @@ class ModelsTable: } ) try: - with get_session() as db: - result = Model(**model.model_dump()) - db.add(result) - db.commit() - db.refresh(result) + result = Model(**model.model_dump()) + Session.add(result) + Session.commit() + Session.refresh(result) - if result: - return ModelModel.model_validate(result) - else: - return None + if result: + return ModelModel.model_validate(result) + else: + return None except Exception as e: print(e) return None def get_all_models(self) -> List[ModelModel]: - with get_session() as db: - return [ModelModel.model_validate(model) for model in db.query(Model).all()] + return [ModelModel.model_validate(model) for model in Session.query(Model).all()] def get_model_by_id(self, id: str) -> Optional[ModelModel]: try: - with get_session() as db: - model = db.get(Model, id) - return ModelModel.model_validate(model) + model = Session.get(Model, id) + return ModelModel.model_validate(model) except: return None def update_model_by_id(self, id: str, model: ModelForm) -> Optional[ModelModel]: try: # update only the fields that are present in the model - with get_session() as db: - model = db.query(Model).get(id) - model.update(**model.model_dump()) - db.commit() - db.refresh(model) - return ModelModel.model_validate(model) + model = Session.query(Model).get(id) + model.update(**model.model_dump()) + Session.commit() + Session.refresh(model) + return ModelModel.model_validate(model) except Exception as e: print(e) @@ -169,8 +164,7 @@ class ModelsTable: def delete_model_by_id(self, id: str) -> bool: try: - with get_session() as db: - db.query(Model).filter_by(id=id).delete() + Session.query(Model).filter_by(id=id).delete() return True except: return False diff --git a/backend/apps/webui/models/prompts.py b/backend/apps/webui/models/prompts.py index 029fd5e1b..a2fd0366b 100644 --- a/backend/apps/webui/models/prompts.py +++ b/backend/apps/webui/models/prompts.py @@ -3,9 +3,8 @@ from typing import List, Optional import time from sqlalchemy import String, Column, BigInteger -from sqlalchemy.orm import Session -from apps.webui.internal.db import Base, get_session +from apps.webui.internal.db import Base, Session import json @@ -50,65 +49,59 @@ class PromptsTable: def insert_new_prompt( self, user_id: str, form_data: PromptForm ) -> Optional[PromptModel]: - with get_session() as db: - prompt = PromptModel( - **{ - "user_id": user_id, - "command": form_data.command, - "title": form_data.title, - "content": form_data.content, - "timestamp": int(time.time()), - } - ) + prompt = PromptModel( + **{ + "user_id": user_id, + "command": form_data.command, + "title": form_data.title, + "content": form_data.content, + "timestamp": int(time.time()), + } + ) - try: - result = Prompt(**prompt.dict()) - db.add(result) - db.commit() - db.refresh(result) - if result: - return PromptModel.model_validate(result) - else: - return None - except Exception as e: + try: + result = Prompt(**prompt.dict()) + Session.add(result) + Session.commit() + Session.refresh(result) + if result: + return PromptModel.model_validate(result) + else: return None + except Exception as e: + return None def get_prompt_by_command(self, command: str) -> Optional[PromptModel]: - with get_session() as db: - try: - prompt = db.query(Prompt).filter_by(command=command).first() - return PromptModel.model_validate(prompt) - except: - return None + try: + prompt = Session.query(Prompt).filter_by(command=command).first() + return PromptModel.model_validate(prompt) + except: + return None def get_prompts(self) -> List[PromptModel]: - with get_session() as db: - return [ - PromptModel.model_validate(prompt) for prompt in db.query(Prompt).all() - ] + return [ + PromptModel.model_validate(prompt) for prompt in Session.query(Prompt).all() + ] def update_prompt_by_command( self, command: str, form_data: PromptForm ) -> Optional[PromptModel]: - with get_session() as db: - try: - prompt = db.query(Prompt).filter_by(command=command).first() - prompt.title = form_data.title - prompt.content = form_data.content - prompt.timestamp = int(time.time()) - db.commit() - return prompt - # return self.get_prompt_by_command(command) - except: - return None + try: + prompt = Session.query(Prompt).filter_by(command=command).first() + prompt.title = form_data.title + prompt.content = form_data.content + prompt.timestamp = int(time.time()) + Session.commit() + return PromptModel.model_validate(prompt) + except: + return None def delete_prompt_by_command(self, command: str) -> bool: - with get_session() as db: - try: - db.query(Prompt).filter_by(command=command).delete() - return True - except: - return False + try: + Session.query(Prompt).filter_by(command=command).delete() + return True + except: + return False Prompts = PromptsTable() diff --git a/backend/apps/webui/models/tags.py b/backend/apps/webui/models/tags.py index dfe63688e..6cfe39d0c 100644 --- a/backend/apps/webui/models/tags.py +++ b/backend/apps/webui/models/tags.py @@ -7,9 +7,8 @@ import time import logging from sqlalchemy import String, Column, BigInteger -from sqlalchemy.orm import Session -from apps.webui.internal.db import Base, get_session +from apps.webui.internal.db import Base, Session from config import SRC_LOG_LEVELS @@ -83,15 +82,14 @@ class TagTable: id = str(uuid.uuid4()) tag = TagModel(**{"id": id, "user_id": user_id, "name": name}) try: - with get_session() as db: - result = Tag(**tag.model_dump()) - db.add(result) - db.commit() - db.refresh(result) - if result: - return TagModel.model_validate(result) - else: - return None + result = Tag(**tag.model_dump()) + Session.add(result) + Session.commit() + Session.refresh(result) + if result: + return TagModel.model_validate(result) + else: + return None except Exception as e: return None @@ -99,9 +97,8 @@ class TagTable: self, name: str, user_id: str ) -> Optional[TagModel]: try: - with get_session() as db: - tag = db.query(Tag).filter(name=name, user_id=user_id).first() - return TagModel.model_validate(tag) + tag = Session.query(Tag).filter(name=name, user_id=user_id).first() + return TagModel.model_validate(tag) except Exception as e: return None @@ -123,105 +120,99 @@ class TagTable: } ) try: - with get_session() as db: - result = ChatIdTag(**chatIdTag.model_dump()) - db.add(result) - db.commit() - db.refresh(result) - if result: - return ChatIdTagModel.model_validate(result) - else: - return None + result = ChatIdTag(**chatIdTag.model_dump()) + Session.add(result) + Session.commit() + Session.refresh(result) + if result: + return ChatIdTagModel.model_validate(result) + else: + return None except: return None def get_tags_by_user_id(self, user_id: str) -> List[TagModel]: - with get_session() as db: - tag_names = [ - chat_id_tag.tag_name - for chat_id_tag in ( - db.query(ChatIdTag) - .filter_by(user_id=user_id) - .order_by(ChatIdTag.timestamp.desc()) - .all() - ) - ] + tag_names = [ + chat_id_tag.tag_name + for chat_id_tag in ( + Session.query(ChatIdTag) + .filter_by(user_id=user_id) + .order_by(ChatIdTag.timestamp.desc()) + .all() + ) + ] - return [ - TagModel.model_validate(tag) - for tag in ( - db.query(Tag) - .filter_by(user_id=user_id) - .filter(Tag.name.in_(tag_names)) - .all() - ) - ] + return [ + TagModel.model_validate(tag) + for tag in ( + Session.query(Tag) + .filter_by(user_id=user_id) + .filter(Tag.name.in_(tag_names)) + .all() + ) + ] def get_tags_by_chat_id_and_user_id( self, chat_id: str, user_id: str ) -> List[TagModel]: - with get_session() as db: - tag_names = [ - chat_id_tag.tag_name - for chat_id_tag in ( - db.query(ChatIdTag) - .filter_by(user_id=user_id, chat_id=chat_id) - .order_by(ChatIdTag.timestamp.desc()) - .all() - ) - ] + tag_names = [ + chat_id_tag.tag_name + for chat_id_tag in ( + Session.query(ChatIdTag) + .filter_by(user_id=user_id, chat_id=chat_id) + .order_by(ChatIdTag.timestamp.desc()) + .all() + ) + ] - return [ - TagModel.model_validate(tag) - for tag in ( - db.query(Tag) - .filter_by(user_id=user_id) - .filter(Tag.name.in_(tag_names)) - .all() - ) - ] + return [ + TagModel.model_validate(tag) + for tag in ( + Session.query(Tag) + .filter_by(user_id=user_id) + .filter(Tag.name.in_(tag_names)) + .all() + ) + ] def get_chat_ids_by_tag_name_and_user_id( self, tag_name: str, user_id: str ) -> List[ChatIdTagModel]: - with get_session() as db: - return [ - ChatIdTagModel.model_validate(chat_id_tag) - for chat_id_tag in ( - db.query(ChatIdTag) - .filter_by(user_id=user_id, tag_name=tag_name) - .order_by(ChatIdTag.timestamp.desc()) - .all() - ) - ] + return [ + ChatIdTagModel.model_validate(chat_id_tag) + for chat_id_tag in ( + Session.query(ChatIdTag) + .filter_by(user_id=user_id, tag_name=tag_name) + .order_by(ChatIdTag.timestamp.desc()) + .all() + ) + ] def count_chat_ids_by_tag_name_and_user_id( self, tag_name: str, user_id: str ) -> int: - with get_session() as db: - return ( - db.query(ChatIdTag) - .filter_by(tag_name=tag_name, user_id=user_id) - .count() - ) + return ( + Session.query(ChatIdTag) + .filter_by(tag_name=tag_name, user_id=user_id) + .count() + ) def delete_tag_by_tag_name_and_user_id(self, tag_name: str, user_id: str) -> bool: try: - with get_session() as db: - res = ( - db.query(ChatIdTag) - .filter_by(tag_name=tag_name, user_id=user_id) - .delete() - ) - log.debug(f"res: {res}") - db.commit() + res = ( + Session.query(ChatIdTag) + .filter_by(tag_name=tag_name, user_id=user_id) + .delete() + ) + log.debug(f"res: {res}") + Session.commit() - tag_count = self.count_chat_ids_by_tag_name_and_user_id( - tag_name, user_id - ) - if tag_count == 0: - # Remove tag item from Tag col as well - db.query(Tag).filter_by(name=tag_name, user_id=user_id).delete() + tag_count = self.count_chat_ids_by_tag_name_and_user_id( + tag_name, user_id + ) + if tag_count == 0: + # Remove tag item from Tag col as well + Session.query(Tag).filter_by(name=tag_name, user_id=user_id).delete() return True except Exception as e: log.error(f"delete_tag: {e}") @@ -231,21 +222,20 @@ class TagTable: self, tag_name: str, chat_id: str, user_id: str ) -> bool: try: - with get_session() as db: - res = ( - db.query(ChatIdTag) - .filter_by(tag_name=tag_name, chat_id=chat_id, user_id=user_id) - .delete() - ) - log.debug(f"res: {res}") - db.commit() + res = ( + Session.query(ChatIdTag) + .filter_by(tag_name=tag_name, chat_id=chat_id, user_id=user_id) + .delete() + ) + log.debug(f"res: {res}") + Session.commit() - tag_count = self.count_chat_ids_by_tag_name_and_user_id( - tag_name, user_id - ) - if tag_count == 0: - # Remove tag item from Tag col as well - db.query(Tag).filter_by(name=tag_name, user_id=user_id).delete() + tag_count = self.count_chat_ids_by_tag_name_and_user_id( + tag_name, user_id + ) + if tag_count == 0: + # Remove tag item from Tag col as well + Session.query(Tag).filter_by(name=tag_name, user_id=user_id).delete() return True except Exception as e: diff --git a/backend/apps/webui/models/tools.py b/backend/apps/webui/models/tools.py index 534a4e3e8..20c608921 100644 --- a/backend/apps/webui/models/tools.py +++ b/backend/apps/webui/models/tools.py @@ -3,9 +3,8 @@ from typing import List, Optional import time import logging from sqlalchemy import String, Column, BigInteger -from sqlalchemy.orm import Session -from apps.webui.internal.db import Base, JSONField, get_session +from apps.webui.internal.db import Base, JSONField, Session from apps.webui.models.users import Users import json @@ -95,48 +94,43 @@ class ToolsTable: ) try: - with get_session() as db: - result = Tool(**tool.model_dump()) - db.add(result) - db.commit() - db.refresh(result) - if result: - return ToolModel.model_validate(result) - else: - return None + result = Tool(**tool.model_dump()) + Session.add(result) + Session.commit() + Session.refresh(result) + if result: + return ToolModel.model_validate(result) + else: + return None except Exception as e: print(f"Error creating tool: {e}") return None def get_tool_by_id(self, id: str) -> Optional[ToolModel]: try: - with get_session() as db: - tool = db.get(Tool, id) - return ToolModel.model_validate(tool) + tool = Session.get(Tool, id) + return ToolModel.model_validate(tool) except: return None def get_tools(self) -> List[ToolModel]: - with get_session() as db: - return [ToolModel.model_validate(tool) for tool in db.query(Tool).all()] + return [ToolModel.model_validate(tool) for tool in Session.query(Tool).all()] def get_tool_valves_by_id(self, id: str) -> Optional[dict]: try: - with get_session() as db: - tool = db.get(Tool, id) - return tool.valves if tool.valves else {} + tool = Session.get(Tool, id) + return tool.valves if tool.valves else {} except Exception as e: print(f"An error occurred: {e}") return None def update_tool_valves_by_id(self, id: str, valves: dict) -> Optional[ToolValves]: try: - with get_session() as db: - db.query(Tool).filter_by(id=id).update( - {"valves": valves, "updated_at": int(time.time())} - ) - db.commit() - return self.get_tool_by_id(id) + Session.query(Tool).filter_by(id=id).update( + {"valves": valves, "updated_at": int(time.time())} + ) + Session.commit() + return self.get_tool_by_id(id) except: return None @@ -183,19 +177,18 @@ class ToolsTable: def update_tool_by_id(self, id: str, updated: dict) -> Optional[ToolModel]: try: - with get_session() as db: - db.query(Tool).filter_by(id=id).update( - {**updated, "updated_at": int(time.time())} - ) - db.commit() - return self.get_tool_by_id(id) + tool = Session.get(Tool, id) + tool.update(**updated) + tool.updated_at = int(time.time()) + Session.commit() + Session.refresh(tool) + return ToolModel.model_validate(tool) except: return None def delete_tool_by_id(self, id: str) -> bool: try: - with get_session() as db: - db.query(Tool).filter_by(id=id).delete() + Session.query(Tool).filter_by(id=id).delete() return True except: return False diff --git a/backend/apps/webui/models/users.py b/backend/apps/webui/models/users.py index 796892927..252e3f122 100644 --- a/backend/apps/webui/models/users.py +++ b/backend/apps/webui/models/users.py @@ -3,11 +3,10 @@ from typing import List, Union, Optional import time from sqlalchemy import String, Column, BigInteger, Text -from sqlalchemy.orm import Session from utils.misc import get_gravatar_url -from apps.webui.internal.db import Base, JSONField, get_session +from apps.webui.internal.db import Base, JSONField, Session from apps.webui.models.chats import Chats #################### @@ -89,177 +88,161 @@ class UsersTable: role: str = "pending", oauth_sub: Optional[str] = None, ) -> Optional[UserModel]: - with get_session() as db: - user = UserModel( - **{ - "id": id, - "name": name, - "email": email, - "role": role, - "profile_image_url": profile_image_url, - "last_active_at": int(time.time()), - "created_at": int(time.time()), - "updated_at": int(time.time()), - "oauth_sub": oauth_sub, - } - ) - result = User(**user.model_dump()) - db.add(result) - db.commit() - db.refresh(result) - if result: - return user - else: - return None + user = UserModel( + **{ + "id": id, + "name": name, + "email": email, + "role": role, + "profile_image_url": profile_image_url, + "last_active_at": int(time.time()), + "created_at": int(time.time()), + "updated_at": int(time.time()), + "oauth_sub": oauth_sub, + } + ) + result = User(**user.model_dump()) + Session.add(result) + Session.commit() + Session.refresh(result) + if result: + return user + else: + return None def get_user_by_id(self, id: str) -> Optional[UserModel]: - with get_session() as db: - try: - user = db.query(User).filter_by(id=id).first() - return UserModel.model_validate(user) - except Exception as e: - return None + try: + user = Session.query(User).filter_by(id=id).first() + return UserModel.model_validate(user) + except Exception as e: + return None def get_user_by_api_key(self, api_key: str) -> Optional[UserModel]: - with get_session() as db: - try: - user = db.query(User).filter_by(api_key=api_key).first() - return UserModel.model_validate(user) - except: - return None + try: + user = Session.query(User).filter_by(api_key=api_key).first() + return UserModel.model_validate(user) + except: + return None def get_user_by_email(self, email: str) -> Optional[UserModel]: - with get_session() as db: - try: - user = db.query(User).filter_by(email=email).first() - return UserModel.model_validate(user) - except: - return None + try: + user = Session.query(User).filter_by(email=email).first() + return UserModel.model_validate(user) + except: + return None def get_user_by_oauth_sub(self, sub: str) -> Optional[UserModel]: - with get_session() as db: - try: - user = db.query(User).filter_by(oauth_sub=sub).first() - return UserModel.model_validate(user) - except: - return None + try: + user = Session.query(User).filter_by(oauth_sub=sub).first() + return UserModel.model_validate(user) + except: + return None def get_users(self, skip: int = 0, limit: int = 50) -> List[UserModel]: - with get_session() as db: - users = ( - db.query(User) - # .offset(skip).limit(limit) - .all() - ) - return [UserModel.model_validate(user) for user in users] + users = ( + Session.query(User) + # .offset(skip).limit(limit) + .all() + ) + return [UserModel.model_validate(user) for user in users] def get_num_users(self) -> Optional[int]: - with get_session() as db: - return db.query(User).count() + return Session.query(User).count() def get_first_user(self) -> UserModel: - with get_session() as db: - try: - user = db.query(User).order_by(User.created_at).first() - return UserModel.model_validate(user) - except: - return None + try: + user = Session.query(User).order_by(User.created_at).first() + return UserModel.model_validate(user) + except: + return None def update_user_role_by_id(self, id: str, role: str) -> Optional[UserModel]: - with get_session() as db: - try: - db.query(User).filter_by(id=id).update({"role": role}) - db.commit() + try: + Session.query(User).filter_by(id=id).update({"role": role}) + Session.commit() - user = db.query(User).filter_by(id=id).first() - return UserModel.model_validate(user) - except: - return None + user = Session.query(User).filter_by(id=id).first() + return UserModel.model_validate(user) + except: + return None def update_user_profile_image_url_by_id( self, id: str, profile_image_url: str ) -> Optional[UserModel]: - with get_session() as db: - try: - db.query(User).filter_by(id=id).update( - {"profile_image_url": profile_image_url} - ) - db.commit() + try: + Session.query(User).filter_by(id=id).update( + {"profile_image_url": profile_image_url} + ) + Session.commit() - user = db.query(User).filter_by(id=id).first() - return UserModel.model_validate(user) - except: - return None + user = Session.query(User).filter_by(id=id).first() + return UserModel.model_validate(user) + except: + return None def update_user_last_active_by_id(self, id: str) -> Optional[UserModel]: - with get_session() as db: - try: - db.query(User).filter_by(id=id).update( - {"last_active_at": int(time.time())} - ) + try: + Session.query(User).filter_by(id=id).update( + {"last_active_at": int(time.time())} + ) - user = db.query(User).filter_by(id=id).first() - return UserModel.model_validate(user) - except: - return None + user = Session.query(User).filter_by(id=id).first() + return UserModel.model_validate(user) + except: + return None def update_user_oauth_sub_by_id( self, id: str, oauth_sub: str ) -> Optional[UserModel]: - with get_session() as db: - try: - db.query(User).filter_by(id=id).update({"oauth_sub": oauth_sub}) + try: + Session.query(User).filter_by(id=id).update({"oauth_sub": oauth_sub}) - user = db.query(User).filter_by(id=id).first() - return UserModel.model_validate(user) - except: - return None + user = Session.query(User).filter_by(id=id).first() + return UserModel.model_validate(user) + except: + return None def update_user_by_id(self, id: str, updated: dict) -> Optional[UserModel]: - with get_session() as db: - try: - db.query(User).filter_by(id=id).update(updated) - db.commit() + try: + Session.query(User).filter_by(id=id).update(updated) + Session.commit() - user = db.query(User).filter_by(id=id).first() - return UserModel.model_validate(user) - # return UserModel(**user.dict()) - except Exception as e: - return None + user = Session.query(User).filter_by(id=id).first() + return UserModel.model_validate(user) + # return UserModel(**user.dict()) + except Exception as e: + return None def delete_user_by_id(self, id: str) -> bool: - with get_session() as db: - try: - # Delete User Chats - result = Chats.delete_chats_by_user_id(id) + try: + # Delete User Chats + result = Chats.delete_chats_by_user_id(id) - if result: - # Delete User - db.query(User).filter_by(id=id).delete() - db.commit() + if result: + # Delete User + Session.query(User).filter_by(id=id).delete() + Session.commit() - return True - else: - return False - except: + return True + else: return False + except: + return False def update_user_api_key_by_id(self, id: str, api_key: str) -> str: - with get_session() as db: - try: - result = db.query(User).filter_by(id=id).update({"api_key": api_key}) - db.commit() - return True if result == 1 else False - except: - return False + try: + result = Session.query(User).filter_by(id=id).update({"api_key": api_key}) + Session.commit() + return True if result == 1 else False + except: + return False def get_user_api_key_by_id(self, id: str) -> Optional[str]: - with get_session() as db: - try: - user = db.query(User).filter_by(id=id).first() - return user.api_key - except Exception as e: - return None + try: + user = Session.query(User).filter_by(id=id).first() + return user.api_key + except Exception as e: + return None Users = UsersTable() diff --git a/backend/main.py b/backend/main.py index f35095bf1..2120b499a 100644 --- a/backend/main.py +++ b/backend/main.py @@ -29,7 +29,6 @@ from fastapi import HTTPException from fastapi.middleware.wsgi import WSGIMiddleware from fastapi.middleware.cors import CORSMiddleware from sqlalchemy import text -from sqlalchemy.orm import Session from starlette.exceptions import HTTPException as StarletteHTTPException from starlette.middleware.base import BaseHTTPMiddleware from starlette.middleware.sessions import SessionMiddleware @@ -57,7 +56,7 @@ from apps.webui.main import ( get_pipe_models, generate_function_chat_completion, ) -from apps.webui.internal.db import get_session, SessionLocal +from apps.webui.internal.db import Session, SessionLocal from pydantic import BaseModel @@ -794,6 +793,14 @@ app.add_middleware( allow_headers=["*"], ) +@app.middleware("http") +async def remove_session_after_request(request: Request, call_next): + response = await call_next(request) + log.debug("Removing session after request") + Session.commit() + Session.remove() + return response + @app.middleware("http") async def check_url(request: Request, call_next): @@ -2034,8 +2041,7 @@ async def healthcheck(): @app.get("/health/db") async def healthcheck_with_db(): - with get_session() as db: - result = db.execute(text("SELECT 1;")).all() + Session.execute(text("SELECT 1;")).all() return {"status": True} diff --git a/backend/test/apps/webui/routers/test_chats.py b/backend/test/apps/webui/routers/test_chats.py index ea4518eaf..6d2dd35b1 100644 --- a/backend/test/apps/webui/routers/test_chats.py +++ b/backend/test/apps/webui/routers/test_chats.py @@ -90,6 +90,8 @@ class TestChats(AbstractPostgresTest): def test_get_user_archived_chats(self): self.chats.archive_all_chats_by_user_id("2") + from apps.webui.internal.db import Session + Session.commit() with mock_webui_user(id="2"): response = self.fast_api_client.get(self.create_url("/all/archived")) assert response.status_code == 200 diff --git a/backend/test/util/abstract_integration_test.py b/backend/test/util/abstract_integration_test.py index 781fbfff8..f8d6d4ff7 100644 --- a/backend/test/util/abstract_integration_test.py +++ b/backend/test/util/abstract_integration_test.py @@ -9,6 +9,7 @@ from pytest_docker.plugin import get_docker_ip from fastapi.testclient import TestClient from sqlalchemy import text, create_engine + log = logging.getLogger(__name__) @@ -50,11 +51,6 @@ class AbstractPostgresTest(AbstractIntegrationTest): DOCKER_CONTAINER_NAME = "postgres-test-container-will-get-deleted" docker_client: DockerClient - def get_db(self): - from apps.webui.internal.db import SessionLocal - - return SessionLocal() - @classmethod def _create_db_url(cls, env_vars_postgres: dict) -> str: host = get_docker_ip() @@ -113,21 +109,21 @@ class AbstractPostgresTest(AbstractIntegrationTest): pytest.fail(f"Could not setup test environment: {ex}") def _check_db_connection(self): + from apps.webui.internal.db import Session retries = 10 while retries > 0: try: - self.db_session.execute(text("SELECT 1")) - self.db_session.commit() + Session.execute(text("SELECT 1")) + Session.commit() break except Exception as e: - self.db_session.rollback() + Session.rollback() log.warning(e) time.sleep(3) retries -= 1 def setup_method(self): super().setup_method() - self.db_session = self.get_db() self._check_db_connection() @classmethod @@ -136,8 +132,9 @@ class AbstractPostgresTest(AbstractIntegrationTest): cls.docker_client.containers.get(cls.DOCKER_CONTAINER_NAME).remove(force=True) def teardown_method(self): + from apps.webui.internal.db import Session # rollback everything not yet committed - self.db_session.commit() + Session.commit() # truncate all tables tables = [ @@ -152,5 +149,5 @@ class AbstractPostgresTest(AbstractIntegrationTest): '"user"', ] for table in tables: - self.db_session.execute(text(f"TRUNCATE TABLE {table}")) - self.db_session.commit() + Session.execute(text(f"TRUNCATE TABLE {table}")) + Session.commit() From a9b148791d982b9635935a41ca6bdc3aa47165c3 Mon Sep 17 00:00:00 2001 From: Jonathan Rohde Date: Mon, 24 Jun 2024 13:21:51 +0200 Subject: [PATCH 008/181] feat(sqlalchemy): fix wrong column types --- backend/apps/webui/models/auths.py | 4 +- backend/apps/webui/models/chats.py | 8 +- backend/apps/webui/models/documents.py | 8 +- backend/apps/webui/models/files.py | 4 +- backend/apps/webui/models/memories.py | 4 +- backend/apps/webui/models/models.py | 10 +- backend/apps/webui/models/prompts.py | 6 +- backend/apps/webui/models/tags.py | 4 +- backend/apps/webui/models/tools.py | 6 +- backend/apps/webui/models/users.py | 2 +- .../migrations/versions/7e5b5dc7342b_init.py | 186 ++++++++++++++++++ .../migrations/versions/ba76b0bae648_init.py | 174 ---------------- 12 files changed, 214 insertions(+), 202 deletions(-) create mode 100644 backend/migrations/versions/7e5b5dc7342b_init.py delete mode 100644 backend/migrations/versions/ba76b0bae648_init.py diff --git a/backend/apps/webui/models/auths.py b/backend/apps/webui/models/auths.py index 1858b2c0d..aef895619 100644 --- a/backend/apps/webui/models/auths.py +++ b/backend/apps/webui/models/auths.py @@ -2,7 +2,7 @@ from pydantic import BaseModel from typing import Optional import uuid import logging -from sqlalchemy import String, Column, Boolean +from sqlalchemy import String, Column, Boolean, Text from apps.webui.models.users import UserModel, Users from utils.utils import verify_password @@ -24,7 +24,7 @@ class Auth(Base): id = Column(String, primary_key=True) email = Column(String) - password = Column(String) + password = Column(Text) active = Column(Boolean) diff --git a/backend/apps/webui/models/chats.py b/backend/apps/webui/models/chats.py index abf5b544c..1cf56c351 100644 --- a/backend/apps/webui/models/chats.py +++ b/backend/apps/webui/models/chats.py @@ -5,7 +5,7 @@ import json import uuid import time -from sqlalchemy import Column, String, BigInteger, Boolean +from sqlalchemy import Column, String, BigInteger, Boolean, Text from apps.webui.internal.db import Base, Session @@ -20,13 +20,13 @@ class Chat(Base): id = Column(String, primary_key=True) user_id = Column(String) - title = Column(String) - chat = Column(String) # Save Chat JSON as Text + title = Column(Text) + chat = Column(Text) # Save Chat JSON as Text created_at = Column(BigInteger) updated_at = Column(BigInteger) - share_id = Column(String, unique=True, nullable=True) + share_id = Column(Text, unique=True, nullable=True) archived = Column(Boolean, default=False) diff --git a/backend/apps/webui/models/documents.py b/backend/apps/webui/models/documents.py index f8e7153c5..1b69d44a5 100644 --- a/backend/apps/webui/models/documents.py +++ b/backend/apps/webui/models/documents.py @@ -3,7 +3,7 @@ from typing import List, Optional import time import logging -from sqlalchemy import String, Column, BigInteger +from sqlalchemy import String, Column, BigInteger, Text from apps.webui.internal.db import Base, Session @@ -24,9 +24,9 @@ class Document(Base): collection_name = Column(String, primary_key=True) name = Column(String, unique=True) - title = Column(String) - filename = Column(String) - content = Column(String, nullable=True) + title = Column(Text) + filename = Column(Text) + content = Column(Text, nullable=True) user_id = Column(String) timestamp = Column(BigInteger) diff --git a/backend/apps/webui/models/files.py b/backend/apps/webui/models/files.py index 7664bf4f1..ce904215d 100644 --- a/backend/apps/webui/models/files.py +++ b/backend/apps/webui/models/files.py @@ -3,7 +3,7 @@ from typing import List, Union, Optional import time import logging -from sqlalchemy import Column, String, BigInteger +from sqlalchemy import Column, String, BigInteger, Text from apps.webui.internal.db import JSONField, Base, Session @@ -24,7 +24,7 @@ class File(Base): id = Column(String, primary_key=True) user_id = Column(String) - filename = Column(String) + filename = Column(Text) meta = Column(JSONField) created_at = Column(BigInteger) diff --git a/backend/apps/webui/models/memories.py b/backend/apps/webui/models/memories.py index 263d1b5ab..f0bd6e291 100644 --- a/backend/apps/webui/models/memories.py +++ b/backend/apps/webui/models/memories.py @@ -1,7 +1,7 @@ from pydantic import BaseModel, ConfigDict from typing import List, Union, Optional -from sqlalchemy import Column, String, BigInteger +from sqlalchemy import Column, String, BigInteger, Text from apps.webui.internal.db import Base, Session @@ -18,7 +18,7 @@ class Memory(Base): id = Column(String, primary_key=True) user_id = Column(String) - content = Column(String) + content = Column(Text) updated_at = Column(BigInteger) created_at = Column(BigInteger) diff --git a/backend/apps/webui/models/models.py b/backend/apps/webui/models/models.py index dd736a73e..7d1da54ff 100644 --- a/backend/apps/webui/models/models.py +++ b/backend/apps/webui/models/models.py @@ -3,7 +3,7 @@ import logging from typing import Optional from pydantic import BaseModel, ConfigDict -from sqlalchemy import String, Column, BigInteger +from sqlalchemy import String, Column, BigInteger, Text from apps.webui.internal.db import Base, JSONField, Session @@ -46,18 +46,18 @@ class ModelMeta(BaseModel): class Model(Base): __tablename__ = "model" - id = Column(String, primary_key=True) + id = Column(Text, primary_key=True) """ The model's id as used in the API. If set to an existing model, it will override the model. """ - user_id = Column(String) + user_id = Column(Text) - base_model_id = Column(String, nullable=True) + base_model_id = Column(Text, nullable=True) """ An optional pointer to the actual model that should be used when proxying requests. """ - name = Column(String) + name = Column(Text) """ The human-readable display name of the model. """ diff --git a/backend/apps/webui/models/prompts.py b/backend/apps/webui/models/prompts.py index a2fd0366b..ab8cc04ce 100644 --- a/backend/apps/webui/models/prompts.py +++ b/backend/apps/webui/models/prompts.py @@ -2,7 +2,7 @@ from pydantic import BaseModel, ConfigDict from typing import List, Optional import time -from sqlalchemy import String, Column, BigInteger +from sqlalchemy import String, Column, BigInteger, Text from apps.webui.internal.db import Base, Session @@ -18,8 +18,8 @@ class Prompt(Base): command = Column(String, primary_key=True) user_id = Column(String) - title = Column(String) - content = Column(String) + title = Column(Text) + content = Column(Text) timestamp = Column(BigInteger) diff --git a/backend/apps/webui/models/tags.py b/backend/apps/webui/models/tags.py index 6cfe39d0c..87238c2a3 100644 --- a/backend/apps/webui/models/tags.py +++ b/backend/apps/webui/models/tags.py @@ -6,7 +6,7 @@ import uuid import time import logging -from sqlalchemy import String, Column, BigInteger +from sqlalchemy import String, Column, BigInteger, Text from apps.webui.internal.db import Base, Session @@ -26,7 +26,7 @@ class Tag(Base): id = Column(String, primary_key=True) name = Column(String) user_id = Column(String) - data = Column(String, nullable=True) + data = Column(Text, nullable=True) class ChatIdTag(Base): diff --git a/backend/apps/webui/models/tools.py b/backend/apps/webui/models/tools.py index 20c608921..f5df10637 100644 --- a/backend/apps/webui/models/tools.py +++ b/backend/apps/webui/models/tools.py @@ -2,7 +2,7 @@ from pydantic import BaseModel, ConfigDict from typing import List, Optional import time import logging -from sqlalchemy import String, Column, BigInteger +from sqlalchemy import String, Column, BigInteger, Text from apps.webui.internal.db import Base, JSONField, Session from apps.webui.models.users import Users @@ -26,8 +26,8 @@ class Tool(Base): id = Column(String, primary_key=True) user_id = Column(String) - name = Column(String) - content = Column(String) + name = Column(Text) + content = Column(Text) specs = Column(JSONField) meta = Column(JSONField) valves = Column(JSONField) diff --git a/backend/apps/webui/models/users.py b/backend/apps/webui/models/users.py index 252e3f122..e1c5ca9f3 100644 --- a/backend/apps/webui/models/users.py +++ b/backend/apps/webui/models/users.py @@ -21,7 +21,7 @@ class User(Base): name = Column(String) email = Column(String) role = Column(String) - profile_image_url = Column(String) + profile_image_url = Column(Text) last_active_at = Column(BigInteger) updated_at = Column(BigInteger) diff --git a/backend/migrations/versions/7e5b5dc7342b_init.py b/backend/migrations/versions/7e5b5dc7342b_init.py new file mode 100644 index 000000000..bd49d1b43 --- /dev/null +++ b/backend/migrations/versions/7e5b5dc7342b_init.py @@ -0,0 +1,186 @@ +"""init + +Revision ID: 7e5b5dc7342b +Revises: +Create Date: 2024-06-24 13:15:33.808998 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +import apps.webui.internal.db +from migrations.util import get_existing_tables + +# revision identifiers, used by Alembic. +revision: str = '7e5b5dc7342b' +down_revision: Union[str, None] = None +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + existing_tables = set(get_existing_tables()) + + # ### commands auto generated by Alembic - please adjust! ### + if "auth" not in existing_tables: + op.create_table('auth', + sa.Column('id', sa.String(), nullable=False), + sa.Column('email', sa.String(), nullable=True), + sa.Column('password', sa.Text(), nullable=True), + sa.Column('active', sa.Boolean(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + + if "chat" not in existing_tables: + op.create_table('chat', + sa.Column('id', sa.String(), nullable=False), + sa.Column('user_id', sa.String(), nullable=True), + sa.Column('title', sa.Text(), nullable=True), + sa.Column('chat', sa.Text(), nullable=True), + sa.Column('created_at', sa.BigInteger(), nullable=True), + sa.Column('updated_at', sa.BigInteger(), nullable=True), + sa.Column('share_id', sa.Text(), nullable=True), + sa.Column('archived', sa.Boolean(), nullable=True), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('share_id') + ) + + if "chatidtag" not in existing_tables: + op.create_table('chatidtag', + sa.Column('id', sa.String(), nullable=False), + sa.Column('tag_name', sa.String(), nullable=True), + sa.Column('chat_id', sa.String(), nullable=True), + sa.Column('user_id', sa.String(), nullable=True), + sa.Column('timestamp', sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + + if "document" not in existing_tables: + op.create_table('document', + sa.Column('collection_name', sa.String(), nullable=False), + sa.Column('name', sa.String(), nullable=True), + sa.Column('title', sa.Text(), nullable=True), + sa.Column('filename', sa.Text(), nullable=True), + sa.Column('content', sa.Text(), nullable=True), + sa.Column('user_id', sa.String(), nullable=True), + sa.Column('timestamp', sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint('collection_name'), + sa.UniqueConstraint('name') + ) + + if "file" not in existing_tables: + op.create_table('file', + sa.Column('id', sa.String(), nullable=False), + sa.Column('user_id', sa.String(), nullable=True), + sa.Column('filename', sa.Text(), nullable=True), + sa.Column('meta', apps.webui.internal.db.JSONField(), nullable=True), + sa.Column('created_at', sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + + if "function" not in existing_tables: + op.create_table('function', + sa.Column('id', sa.String(), nullable=False), + sa.Column('user_id', sa.String(), nullable=True), + sa.Column('name', sa.Text(), nullable=True), + sa.Column('type', sa.Text(), nullable=True), + sa.Column('content', sa.Text(), nullable=True), + sa.Column('meta', apps.webui.internal.db.JSONField(), nullable=True), + sa.Column('valves', apps.webui.internal.db.JSONField(), nullable=True), + sa.Column('is_active', sa.Boolean(), nullable=True), + sa.Column('updated_at', sa.BigInteger(), nullable=True), + sa.Column('created_at', sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + + if "memory" not in existing_tables: + op.create_table('memory', + sa.Column('id', sa.String(), nullable=False), + sa.Column('user_id', sa.String(), nullable=True), + sa.Column('content', sa.Text(), nullable=True), + sa.Column('updated_at', sa.BigInteger(), nullable=True), + sa.Column('created_at', sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + + if "model" not in existing_tables: + op.create_table('model', + sa.Column('id', sa.Text(), nullable=False), + sa.Column('user_id', sa.Text(), nullable=True), + sa.Column('base_model_id', sa.Text(), nullable=True), + sa.Column('name', sa.Text(), nullable=True), + sa.Column('params', apps.webui.internal.db.JSONField(), nullable=True), + sa.Column('meta', apps.webui.internal.db.JSONField(), nullable=True), + sa.Column('updated_at', sa.BigInteger(), nullable=True), + sa.Column('created_at', sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + + if "prompt" not in existing_tables: + op.create_table('prompt', + sa.Column('command', sa.String(), nullable=False), + sa.Column('user_id', sa.String(), nullable=True), + sa.Column('title', sa.Text(), nullable=True), + sa.Column('content', sa.Text(), nullable=True), + sa.Column('timestamp', sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint('command') + ) + + if "tag" not in existing_tables: + op.create_table('tag', + sa.Column('id', sa.String(), nullable=False), + sa.Column('name', sa.String(), nullable=True), + sa.Column('user_id', sa.String(), nullable=True), + sa.Column('data', sa.Text(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + + if "tool" not in existing_tables: + op.create_table('tool', + sa.Column('id', sa.String(), nullable=False), + sa.Column('user_id', sa.String(), nullable=True), + sa.Column('name', sa.Text(), nullable=True), + sa.Column('content', sa.Text(), nullable=True), + sa.Column('specs', apps.webui.internal.db.JSONField(), nullable=True), + sa.Column('meta', apps.webui.internal.db.JSONField(), nullable=True), + sa.Column('valves', apps.webui.internal.db.JSONField(), nullable=True), + sa.Column('updated_at', sa.BigInteger(), nullable=True), + sa.Column('created_at', sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + + if "user" not in existing_tables: + op.create_table('user', + sa.Column('id', sa.String(), nullable=False), + sa.Column('name', sa.String(), nullable=True), + sa.Column('email', sa.String(), nullable=True), + sa.Column('role', sa.String(), nullable=True), + sa.Column('profile_image_url', sa.Text(), nullable=True), + sa.Column('last_active_at', sa.BigInteger(), nullable=True), + sa.Column('updated_at', sa.BigInteger(), nullable=True), + sa.Column('created_at', sa.BigInteger(), nullable=True), + sa.Column('api_key', sa.String(), nullable=True), + sa.Column('settings', apps.webui.internal.db.JSONField(), nullable=True), + sa.Column('info', apps.webui.internal.db.JSONField(), nullable=True), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('api_key') + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('user') + op.drop_table('tool') + op.drop_table('tag') + op.drop_table('prompt') + op.drop_table('model') + op.drop_table('memory') + op.drop_table('function') + op.drop_table('file') + op.drop_table('document') + op.drop_table('chatidtag') + op.drop_table('chat') + op.drop_table('auth') + # ### end Alembic commands ### diff --git a/backend/migrations/versions/ba76b0bae648_init.py b/backend/migrations/versions/ba76b0bae648_init.py deleted file mode 100644 index c491ed46c..000000000 --- a/backend/migrations/versions/ba76b0bae648_init.py +++ /dev/null @@ -1,174 +0,0 @@ -"""init - -Revision ID: ba76b0bae648 -Revises: -Create Date: 2024-06-24 09:09:11.636336 - -""" - -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa -import apps.webui.internal.db - - -# revision identifiers, used by Alembic. -revision: str = "ba76b0bae648" -down_revision: Union[str, None] = None -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.create_table( - "auth", - sa.Column("id", sa.String(), nullable=False), - sa.Column("email", sa.String(), nullable=True), - sa.Column("password", sa.String(), nullable=True), - sa.Column("active", sa.Boolean(), nullable=True), - sa.PrimaryKeyConstraint("id"), - ) - op.create_table( - "chat", - sa.Column("id", sa.String(), nullable=False), - sa.Column("user_id", sa.String(), nullable=True), - sa.Column("title", sa.String(), nullable=True), - sa.Column("chat", sa.String(), nullable=True), - sa.Column("created_at", sa.BigInteger(), nullable=True), - sa.Column("updated_at", sa.BigInteger(), nullable=True), - sa.Column("share_id", sa.String(), nullable=True), - sa.Column("archived", sa.Boolean(), nullable=True), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint("share_id"), - ) - op.create_table( - "chatidtag", - sa.Column("id", sa.String(), nullable=False), - sa.Column("tag_name", sa.String(), nullable=True), - sa.Column("chat_id", sa.String(), nullable=True), - sa.Column("user_id", sa.String(), nullable=True), - sa.Column("timestamp", sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint("id"), - ) - op.create_table( - "document", - sa.Column("collection_name", sa.String(), nullable=False), - sa.Column("name", sa.String(), nullable=True), - sa.Column("title", sa.String(), nullable=True), - sa.Column("filename", sa.String(), nullable=True), - sa.Column("content", sa.String(), nullable=True), - sa.Column("user_id", sa.String(), nullable=True), - sa.Column("timestamp", sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint("collection_name"), - sa.UniqueConstraint("name"), - ) - op.create_table( - "file", - sa.Column("id", sa.String(), nullable=False), - sa.Column("user_id", sa.String(), nullable=True), - sa.Column("filename", sa.String(), nullable=True), - sa.Column("meta", apps.webui.internal.db.JSONField(), nullable=True), - sa.Column("created_at", sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint("id"), - ) - op.create_table( - "function", - sa.Column("id", sa.String(), nullable=False), - sa.Column("user_id", sa.String(), nullable=True), - sa.Column("name", sa.Text(), nullable=True), - sa.Column("type", sa.Text(), nullable=True), - sa.Column("content", sa.Text(), nullable=True), - sa.Column("meta", apps.webui.internal.db.JSONField(), nullable=True), - sa.Column("valves", apps.webui.internal.db.JSONField(), nullable=True), - sa.Column("is_active", sa.Boolean(), nullable=True), - sa.Column("updated_at", sa.BigInteger(), nullable=True), - sa.Column("created_at", sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint("id"), - ) - op.create_table( - "memory", - sa.Column("id", sa.String(), nullable=False), - sa.Column("user_id", sa.String(), nullable=True), - sa.Column("content", sa.String(), nullable=True), - sa.Column("updated_at", sa.BigInteger(), nullable=True), - sa.Column("created_at", sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint("id"), - ) - op.create_table( - "model", - sa.Column("id", sa.String(), nullable=False), - sa.Column("user_id", sa.String(), nullable=True), - sa.Column("base_model_id", sa.String(), nullable=True), - sa.Column("name", sa.String(), nullable=True), - sa.Column("params", apps.webui.internal.db.JSONField(), nullable=True), - sa.Column("meta", apps.webui.internal.db.JSONField(), nullable=True), - sa.Column("updated_at", sa.BigInteger(), nullable=True), - sa.Column("created_at", sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint("id"), - ) - op.create_table( - "prompt", - sa.Column("command", sa.String(), nullable=False), - sa.Column("user_id", sa.String(), nullable=True), - sa.Column("title", sa.String(), nullable=True), - sa.Column("content", sa.String(), nullable=True), - sa.Column("timestamp", sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint("command"), - ) - op.create_table( - "tag", - sa.Column("id", sa.String(), nullable=False), - sa.Column("name", sa.String(), nullable=True), - sa.Column("user_id", sa.String(), nullable=True), - sa.Column("data", sa.String(), nullable=True), - sa.PrimaryKeyConstraint("id"), - ) - op.create_table( - "tool", - sa.Column("id", sa.String(), nullable=False), - sa.Column("user_id", sa.String(), nullable=True), - sa.Column("name", sa.String(), nullable=True), - sa.Column("content", sa.String(), nullable=True), - sa.Column("specs", apps.webui.internal.db.JSONField(), nullable=True), - sa.Column("meta", apps.webui.internal.db.JSONField(), nullable=True), - sa.Column("valves", apps.webui.internal.db.JSONField(), nullable=True), - sa.Column("updated_at", sa.BigInteger(), nullable=True), - sa.Column("created_at", sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint("id"), - ) - op.create_table( - "user", - sa.Column("id", sa.String(), nullable=False), - sa.Column("name", sa.String(), nullable=True), - sa.Column("email", sa.String(), nullable=True), - sa.Column("role", sa.String(), nullable=True), - sa.Column("profile_image_url", sa.String(), nullable=True), - sa.Column("last_active_at", sa.BigInteger(), nullable=True), - sa.Column("updated_at", sa.BigInteger(), nullable=True), - sa.Column("created_at", sa.BigInteger(), nullable=True), - sa.Column("api_key", sa.String(), nullable=True), - sa.Column("settings", apps.webui.internal.db.JSONField(), nullable=True), - sa.Column("info", apps.webui.internal.db.JSONField(), nullable=True), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint("api_key"), - ) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table("user") - op.drop_table("tool") - op.drop_table("tag") - op.drop_table("prompt") - op.drop_table("model") - op.drop_table("memory") - op.drop_table("function") - op.drop_table("file") - op.drop_table("document") - op.drop_table("chatidtag") - op.drop_table("chat") - op.drop_table("auth") - # ### end Alembic commands ### From 8f939cf55bc4a4de63c859f033cf5da5378e2d30 Mon Sep 17 00:00:00 2001 From: Jonathan Rohde Date: Mon, 24 Jun 2024 13:45:33 +0200 Subject: [PATCH 009/181] feat(sqlalchemy): some fixes --- backend/apps/webui/models/users.py | 1 + backend/main.py | 5 ++--- backend/utils/utils.py | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/apps/webui/models/users.py b/backend/apps/webui/models/users.py index e1c5ca9f3..9e1e25ac6 100644 --- a/backend/apps/webui/models/users.py +++ b/backend/apps/webui/models/users.py @@ -185,6 +185,7 @@ class UsersTable: Session.query(User).filter_by(id=id).update( {"last_active_at": int(time.time())} ) + Session.commit() user = Session.query(User).filter_by(id=id).first() return UserModel.model_validate(user) diff --git a/backend/main.py b/backend/main.py index 2120b499a..ad519bdcb 100644 --- a/backend/main.py +++ b/backend/main.py @@ -794,11 +794,10 @@ app.add_middleware( ) @app.middleware("http") -async def remove_session_after_request(request: Request, call_next): +async def commit_session_after_request(request: Request, call_next): response = await call_next(request) - log.debug("Removing session after request") + log.debug("Commit session after request") Session.commit() - Session.remove() return response diff --git a/backend/utils/utils.py b/backend/utils/utils.py index 6409fc7aa..fbc539af5 100644 --- a/backend/utils/utils.py +++ b/backend/utils/utils.py @@ -113,8 +113,8 @@ def get_current_user( ) -def get_current_user_by_api_key(db: Session, api_key: str): - user = Users.get_user_by_api_key(db, api_key) +def get_current_user_by_api_key(api_key: str): + user = Users.get_user_by_api_key(api_key) if user is None: raise HTTPException( @@ -122,7 +122,7 @@ def get_current_user_by_api_key(db: Session, api_key: str): detail=ERROR_MESSAGES.INVALID_TOKEN, ) else: - Users.update_user_last_active_by_id(db, user.id) + Users.update_user_last_active_by_id(user.id) return user From 2fb27adbf67c13d89ac652f3652f7a578a3bcb25 Mon Sep 17 00:00:00 2001 From: Jonathan Rohde Date: Mon, 24 Jun 2024 13:54:24 +0200 Subject: [PATCH 010/181] feat(sqlalchemy): add missing file --- backend/migrations/util.py | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 backend/migrations/util.py diff --git a/backend/migrations/util.py b/backend/migrations/util.py new file mode 100644 index 000000000..401bb94d0 --- /dev/null +++ b/backend/migrations/util.py @@ -0,0 +1,9 @@ +from alembic import op +from sqlalchemy import Inspector + + +def get_existing_tables(): + con = op.get_bind() + inspector = Inspector.from_engine(con) + tables = set(inspector.get_table_names()) + return tables From d88bd51e3c446383b37a65ad2119ea640f7df913 Mon Sep 17 00:00:00 2001 From: Jonathan Rohde Date: Mon, 24 Jun 2024 13:55:18 +0200 Subject: [PATCH 011/181] feat(sqlalchemy): format backend --- backend/apps/webui/models/chats.py | 20 +- backend/apps/webui/models/memories.py | 4 +- backend/apps/webui/models/models.py | 4 +- backend/apps/webui/models/tags.py | 8 +- backend/main.py | 1 + .../migrations/versions/7e5b5dc7342b_init.py | 255 +++++++++--------- backend/test/apps/webui/routers/test_chats.py | 1 + .../test/util/abstract_integration_test.py | 2 + 8 files changed, 153 insertions(+), 142 deletions(-) diff --git a/backend/apps/webui/models/chats.py b/backend/apps/webui/models/chats.py index 1cf56c351..d6829ee7b 100644 --- a/backend/apps/webui/models/chats.py +++ b/backend/apps/webui/models/chats.py @@ -85,9 +85,7 @@ class ChatTable: "id": id, "user_id": user_id, "title": ( - form_data.chat["title"] - if "title" in form_data.chat - else "New Chat" + form_data.chat["title"] if "title" in form_data.chat else "New Chat" ), "chat": json.dumps(form_data.chat), "created_at": int(time.time()), @@ -197,14 +195,14 @@ class ChatTable: def get_archived_chat_list_by_user_id( self, user_id: str, skip: int = 0, limit: int = 50 ) -> List[ChatModel]: - all_chats = ( - Session.query(Chat) - .filter_by(user_id=user_id, archived=True) - .order_by(Chat.updated_at.desc()) - # .limit(limit).offset(skip) - .all() - ) - return [ChatModel.model_validate(chat) for chat in all_chats] + all_chats = ( + Session.query(Chat) + .filter_by(user_id=user_id, archived=True) + .order_by(Chat.updated_at.desc()) + # .limit(limit).offset(skip) + .all() + ) + return [ChatModel.model_validate(chat) for chat in all_chats] def get_chat_list_by_user_id( self, diff --git a/backend/apps/webui/models/memories.py b/backend/apps/webui/models/memories.py index f0bd6e291..1f03318fd 100644 --- a/backend/apps/webui/models/memories.py +++ b/backend/apps/webui/models/memories.py @@ -115,9 +115,7 @@ class MemoriesTable: except: return False - def delete_memory_by_id_and_user_id( - self, id: str, user_id: str - ) -> bool: + def delete_memory_by_id_and_user_id(self, id: str, user_id: str) -> bool: try: Session.query(Memory).filter_by(id=id, user_id=user_id).delete() return True diff --git a/backend/apps/webui/models/models.py b/backend/apps/webui/models/models.py index 7d1da54ff..6543edefc 100644 --- a/backend/apps/webui/models/models.py +++ b/backend/apps/webui/models/models.py @@ -140,7 +140,9 @@ class ModelsTable: return None def get_all_models(self) -> List[ModelModel]: - return [ModelModel.model_validate(model) for model in Session.query(Model).all()] + return [ + ModelModel.model_validate(model) for model in Session.query(Model).all() + ] def get_model_by_id(self, id: str) -> Optional[ModelModel]: try: diff --git a/backend/apps/webui/models/tags.py b/backend/apps/webui/models/tags.py index 87238c2a3..7b0df6b6b 100644 --- a/backend/apps/webui/models/tags.py +++ b/backend/apps/webui/models/tags.py @@ -207,9 +207,7 @@ class TagTable: log.debug(f"res: {res}") Session.commit() - tag_count = self.count_chat_ids_by_tag_name_and_user_id( - tag_name, user_id - ) + tag_count = self.count_chat_ids_by_tag_name_and_user_id(tag_name, user_id) if tag_count == 0: # Remove tag item from Tag col as well Session.query(Tag).filter_by(name=tag_name, user_id=user_id).delete() @@ -230,9 +228,7 @@ class TagTable: log.debug(f"res: {res}") Session.commit() - tag_count = self.count_chat_ids_by_tag_name_and_user_id( - tag_name, user_id - ) + tag_count = self.count_chat_ids_by_tag_name_and_user_id(tag_name, user_id) if tag_count == 0: # Remove tag item from Tag col as well Session.query(Tag).filter_by(name=tag_name, user_id=user_id).delete() diff --git a/backend/main.py b/backend/main.py index ad519bdcb..a29fde198 100644 --- a/backend/main.py +++ b/backend/main.py @@ -793,6 +793,7 @@ app.add_middleware( allow_headers=["*"], ) + @app.middleware("http") async def commit_session_after_request(request: Request, call_next): response = await call_next(request) diff --git a/backend/migrations/versions/7e5b5dc7342b_init.py b/backend/migrations/versions/7e5b5dc7342b_init.py index bd49d1b43..50deac526 100644 --- a/backend/migrations/versions/7e5b5dc7342b_init.py +++ b/backend/migrations/versions/7e5b5dc7342b_init.py @@ -5,6 +5,7 @@ Revises: Create Date: 2024-06-24 13:15:33.808998 """ + from typing import Sequence, Union from alembic import op @@ -13,7 +14,7 @@ import apps.webui.internal.db from migrations.util import get_existing_tables # revision identifiers, used by Alembic. -revision: str = '7e5b5dc7342b' +revision: str = "7e5b5dc7342b" down_revision: Union[str, None] = None branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None @@ -24,163 +25,175 @@ def upgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### if "auth" not in existing_tables: - op.create_table('auth', - sa.Column('id', sa.String(), nullable=False), - sa.Column('email', sa.String(), nullable=True), - sa.Column('password', sa.Text(), nullable=True), - sa.Column('active', sa.Boolean(), nullable=True), - sa.PrimaryKeyConstraint('id') + op.create_table( + "auth", + sa.Column("id", sa.String(), nullable=False), + sa.Column("email", sa.String(), nullable=True), + sa.Column("password", sa.Text(), nullable=True), + sa.Column("active", sa.Boolean(), nullable=True), + sa.PrimaryKeyConstraint("id"), ) if "chat" not in existing_tables: - op.create_table('chat', - sa.Column('id', sa.String(), nullable=False), - sa.Column('user_id', sa.String(), nullable=True), - sa.Column('title', sa.Text(), nullable=True), - sa.Column('chat', sa.Text(), nullable=True), - sa.Column('created_at', sa.BigInteger(), nullable=True), - sa.Column('updated_at', sa.BigInteger(), nullable=True), - sa.Column('share_id', sa.Text(), nullable=True), - sa.Column('archived', sa.Boolean(), nullable=True), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('share_id') + op.create_table( + "chat", + sa.Column("id", sa.String(), nullable=False), + sa.Column("user_id", sa.String(), nullable=True), + sa.Column("title", sa.Text(), nullable=True), + sa.Column("chat", sa.Text(), nullable=True), + sa.Column("created_at", sa.BigInteger(), nullable=True), + sa.Column("updated_at", sa.BigInteger(), nullable=True), + sa.Column("share_id", sa.Text(), nullable=True), + sa.Column("archived", sa.Boolean(), nullable=True), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("share_id"), ) if "chatidtag" not in existing_tables: - op.create_table('chatidtag', - sa.Column('id', sa.String(), nullable=False), - sa.Column('tag_name', sa.String(), nullable=True), - sa.Column('chat_id', sa.String(), nullable=True), - sa.Column('user_id', sa.String(), nullable=True), - sa.Column('timestamp', sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint('id') + op.create_table( + "chatidtag", + sa.Column("id", sa.String(), nullable=False), + sa.Column("tag_name", sa.String(), nullable=True), + sa.Column("chat_id", sa.String(), nullable=True), + sa.Column("user_id", sa.String(), nullable=True), + sa.Column("timestamp", sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint("id"), ) if "document" not in existing_tables: - op.create_table('document', - sa.Column('collection_name', sa.String(), nullable=False), - sa.Column('name', sa.String(), nullable=True), - sa.Column('title', sa.Text(), nullable=True), - sa.Column('filename', sa.Text(), nullable=True), - sa.Column('content', sa.Text(), nullable=True), - sa.Column('user_id', sa.String(), nullable=True), - sa.Column('timestamp', sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint('collection_name'), - sa.UniqueConstraint('name') + op.create_table( + "document", + sa.Column("collection_name", sa.String(), nullable=False), + sa.Column("name", sa.String(), nullable=True), + sa.Column("title", sa.Text(), nullable=True), + sa.Column("filename", sa.Text(), nullable=True), + sa.Column("content", sa.Text(), nullable=True), + sa.Column("user_id", sa.String(), nullable=True), + sa.Column("timestamp", sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint("collection_name"), + sa.UniqueConstraint("name"), ) if "file" not in existing_tables: - op.create_table('file', - sa.Column('id', sa.String(), nullable=False), - sa.Column('user_id', sa.String(), nullable=True), - sa.Column('filename', sa.Text(), nullable=True), - sa.Column('meta', apps.webui.internal.db.JSONField(), nullable=True), - sa.Column('created_at', sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint('id') + op.create_table( + "file", + sa.Column("id", sa.String(), nullable=False), + sa.Column("user_id", sa.String(), nullable=True), + sa.Column("filename", sa.Text(), nullable=True), + sa.Column("meta", apps.webui.internal.db.JSONField(), nullable=True), + sa.Column("created_at", sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint("id"), ) if "function" not in existing_tables: - op.create_table('function', - sa.Column('id', sa.String(), nullable=False), - sa.Column('user_id', sa.String(), nullable=True), - sa.Column('name', sa.Text(), nullable=True), - sa.Column('type', sa.Text(), nullable=True), - sa.Column('content', sa.Text(), nullable=True), - sa.Column('meta', apps.webui.internal.db.JSONField(), nullable=True), - sa.Column('valves', apps.webui.internal.db.JSONField(), nullable=True), - sa.Column('is_active', sa.Boolean(), nullable=True), - sa.Column('updated_at', sa.BigInteger(), nullable=True), - sa.Column('created_at', sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint('id') + op.create_table( + "function", + sa.Column("id", sa.String(), nullable=False), + sa.Column("user_id", sa.String(), nullable=True), + sa.Column("name", sa.Text(), nullable=True), + sa.Column("type", sa.Text(), nullable=True), + sa.Column("content", sa.Text(), nullable=True), + sa.Column("meta", apps.webui.internal.db.JSONField(), nullable=True), + sa.Column("valves", apps.webui.internal.db.JSONField(), nullable=True), + sa.Column("is_active", sa.Boolean(), nullable=True), + sa.Column("updated_at", sa.BigInteger(), nullable=True), + sa.Column("created_at", sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint("id"), ) if "memory" not in existing_tables: - op.create_table('memory', - sa.Column('id', sa.String(), nullable=False), - sa.Column('user_id', sa.String(), nullable=True), - sa.Column('content', sa.Text(), nullable=True), - sa.Column('updated_at', sa.BigInteger(), nullable=True), - sa.Column('created_at', sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint('id') + op.create_table( + "memory", + sa.Column("id", sa.String(), nullable=False), + sa.Column("user_id", sa.String(), nullable=True), + sa.Column("content", sa.Text(), nullable=True), + sa.Column("updated_at", sa.BigInteger(), nullable=True), + sa.Column("created_at", sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint("id"), ) if "model" not in existing_tables: - op.create_table('model', - sa.Column('id', sa.Text(), nullable=False), - sa.Column('user_id', sa.Text(), nullable=True), - sa.Column('base_model_id', sa.Text(), nullable=True), - sa.Column('name', sa.Text(), nullable=True), - sa.Column('params', apps.webui.internal.db.JSONField(), nullable=True), - sa.Column('meta', apps.webui.internal.db.JSONField(), nullable=True), - sa.Column('updated_at', sa.BigInteger(), nullable=True), - sa.Column('created_at', sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint('id') + op.create_table( + "model", + sa.Column("id", sa.Text(), nullable=False), + sa.Column("user_id", sa.Text(), nullable=True), + sa.Column("base_model_id", sa.Text(), nullable=True), + sa.Column("name", sa.Text(), nullable=True), + sa.Column("params", apps.webui.internal.db.JSONField(), nullable=True), + sa.Column("meta", apps.webui.internal.db.JSONField(), nullable=True), + sa.Column("updated_at", sa.BigInteger(), nullable=True), + sa.Column("created_at", sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint("id"), ) if "prompt" not in existing_tables: - op.create_table('prompt', - sa.Column('command', sa.String(), nullable=False), - sa.Column('user_id', sa.String(), nullable=True), - sa.Column('title', sa.Text(), nullable=True), - sa.Column('content', sa.Text(), nullable=True), - sa.Column('timestamp', sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint('command') + op.create_table( + "prompt", + sa.Column("command", sa.String(), nullable=False), + sa.Column("user_id", sa.String(), nullable=True), + sa.Column("title", sa.Text(), nullable=True), + sa.Column("content", sa.Text(), nullable=True), + sa.Column("timestamp", sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint("command"), ) if "tag" not in existing_tables: - op.create_table('tag', - sa.Column('id', sa.String(), nullable=False), - sa.Column('name', sa.String(), nullable=True), - sa.Column('user_id', sa.String(), nullable=True), - sa.Column('data', sa.Text(), nullable=True), - sa.PrimaryKeyConstraint('id') + op.create_table( + "tag", + sa.Column("id", sa.String(), nullable=False), + sa.Column("name", sa.String(), nullable=True), + sa.Column("user_id", sa.String(), nullable=True), + sa.Column("data", sa.Text(), nullable=True), + sa.PrimaryKeyConstraint("id"), ) if "tool" not in existing_tables: - op.create_table('tool', - sa.Column('id', sa.String(), nullable=False), - sa.Column('user_id', sa.String(), nullable=True), - sa.Column('name', sa.Text(), nullable=True), - sa.Column('content', sa.Text(), nullable=True), - sa.Column('specs', apps.webui.internal.db.JSONField(), nullable=True), - sa.Column('meta', apps.webui.internal.db.JSONField(), nullable=True), - sa.Column('valves', apps.webui.internal.db.JSONField(), nullable=True), - sa.Column('updated_at', sa.BigInteger(), nullable=True), - sa.Column('created_at', sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint('id') + op.create_table( + "tool", + sa.Column("id", sa.String(), nullable=False), + sa.Column("user_id", sa.String(), nullable=True), + sa.Column("name", sa.Text(), nullable=True), + sa.Column("content", sa.Text(), nullable=True), + sa.Column("specs", apps.webui.internal.db.JSONField(), nullable=True), + sa.Column("meta", apps.webui.internal.db.JSONField(), nullable=True), + sa.Column("valves", apps.webui.internal.db.JSONField(), nullable=True), + sa.Column("updated_at", sa.BigInteger(), nullable=True), + sa.Column("created_at", sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint("id"), ) if "user" not in existing_tables: - op.create_table('user', - sa.Column('id', sa.String(), nullable=False), - sa.Column('name', sa.String(), nullable=True), - sa.Column('email', sa.String(), nullable=True), - sa.Column('role', sa.String(), nullable=True), - sa.Column('profile_image_url', sa.Text(), nullable=True), - sa.Column('last_active_at', sa.BigInteger(), nullable=True), - sa.Column('updated_at', sa.BigInteger(), nullable=True), - sa.Column('created_at', sa.BigInteger(), nullable=True), - sa.Column('api_key', sa.String(), nullable=True), - sa.Column('settings', apps.webui.internal.db.JSONField(), nullable=True), - sa.Column('info', apps.webui.internal.db.JSONField(), nullable=True), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('api_key') + op.create_table( + "user", + sa.Column("id", sa.String(), nullable=False), + sa.Column("name", sa.String(), nullable=True), + sa.Column("email", sa.String(), nullable=True), + sa.Column("role", sa.String(), nullable=True), + sa.Column("profile_image_url", sa.Text(), nullable=True), + sa.Column("last_active_at", sa.BigInteger(), nullable=True), + sa.Column("updated_at", sa.BigInteger(), nullable=True), + sa.Column("created_at", sa.BigInteger(), nullable=True), + sa.Column("api_key", sa.String(), nullable=True), + sa.Column("settings", apps.webui.internal.db.JSONField(), nullable=True), + sa.Column("info", apps.webui.internal.db.JSONField(), nullable=True), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("api_key"), ) # ### end Alembic commands ### def downgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('user') - op.drop_table('tool') - op.drop_table('tag') - op.drop_table('prompt') - op.drop_table('model') - op.drop_table('memory') - op.drop_table('function') - op.drop_table('file') - op.drop_table('document') - op.drop_table('chatidtag') - op.drop_table('chat') - op.drop_table('auth') + op.drop_table("user") + op.drop_table("tool") + op.drop_table("tag") + op.drop_table("prompt") + op.drop_table("model") + op.drop_table("memory") + op.drop_table("function") + op.drop_table("file") + op.drop_table("document") + op.drop_table("chatidtag") + op.drop_table("chat") + op.drop_table("auth") # ### end Alembic commands ### diff --git a/backend/test/apps/webui/routers/test_chats.py b/backend/test/apps/webui/routers/test_chats.py index 6d2dd35b1..f4661b625 100644 --- a/backend/test/apps/webui/routers/test_chats.py +++ b/backend/test/apps/webui/routers/test_chats.py @@ -91,6 +91,7 @@ class TestChats(AbstractPostgresTest): def test_get_user_archived_chats(self): self.chats.archive_all_chats_by_user_id("2") from apps.webui.internal.db import Session + Session.commit() with mock_webui_user(id="2"): response = self.fast_api_client.get(self.create_url("/all/archived")) diff --git a/backend/test/util/abstract_integration_test.py b/backend/test/util/abstract_integration_test.py index f8d6d4ff7..4e99dcc2f 100644 --- a/backend/test/util/abstract_integration_test.py +++ b/backend/test/util/abstract_integration_test.py @@ -110,6 +110,7 @@ class AbstractPostgresTest(AbstractIntegrationTest): def _check_db_connection(self): from apps.webui.internal.db import Session + retries = 10 while retries > 0: try: @@ -133,6 +134,7 @@ class AbstractPostgresTest(AbstractIntegrationTest): def teardown_method(self): from apps.webui.internal.db import Session + # rollback everything not yet committed Session.commit() From 642c352c69ceadb118ed5347c091c761a5a65a8b Mon Sep 17 00:00:00 2001 From: Jonathan Rohde Date: Tue, 25 Jun 2024 08:15:59 +0200 Subject: [PATCH 012/181] feat(sqlalchemy): rebase --- .../internal/migrations/001_initial_schema.py | 254 ------------------ .../migrations/002_add_local_sharing.py | 48 ---- .../migrations/003_add_auth_api_key.py | 48 ---- .../internal/migrations/004_add_archived.py | 46 ---- .../internal/migrations/005_add_updated_at.py | 130 --------- .../006_migrate_timestamps_and_charfields.py | 130 --------- .../migrations/007_add_user_last_active_at.py | 79 ------ .../internal/migrations/008_add_memory.py | 53 ---- .../internal/migrations/009_add_models.py | 61 ----- .../010_migrate_modelfiles_to_models.py | 130 --------- .../migrations/011_add_user_settings.py | 48 ---- .../internal/migrations/012_add_tools.py | 61 ----- .../internal/migrations/013_add_user_info.py | 48 ---- .../internal/migrations/014_add_files.py | 55 ---- .../internal/migrations/015_add_functions.py | 61 ----- .../016_add_valves_and_is_active.py | 50 ---- .../migrations/017_add_user_oauth_sub.py | 49 ---- .../apps/webui/internal/migrations/README.md | 21 -- .../migrations/versions/7e5b5dc7342b_init.py | 2 + 19 files changed, 2 insertions(+), 1372 deletions(-) delete mode 100644 backend/apps/webui/internal/migrations/001_initial_schema.py delete mode 100644 backend/apps/webui/internal/migrations/002_add_local_sharing.py delete mode 100644 backend/apps/webui/internal/migrations/003_add_auth_api_key.py delete mode 100644 backend/apps/webui/internal/migrations/004_add_archived.py delete mode 100644 backend/apps/webui/internal/migrations/005_add_updated_at.py delete mode 100644 backend/apps/webui/internal/migrations/006_migrate_timestamps_and_charfields.py delete mode 100644 backend/apps/webui/internal/migrations/007_add_user_last_active_at.py delete mode 100644 backend/apps/webui/internal/migrations/008_add_memory.py delete mode 100644 backend/apps/webui/internal/migrations/009_add_models.py delete mode 100644 backend/apps/webui/internal/migrations/010_migrate_modelfiles_to_models.py delete mode 100644 backend/apps/webui/internal/migrations/011_add_user_settings.py delete mode 100644 backend/apps/webui/internal/migrations/012_add_tools.py delete mode 100644 backend/apps/webui/internal/migrations/013_add_user_info.py delete mode 100644 backend/apps/webui/internal/migrations/014_add_files.py delete mode 100644 backend/apps/webui/internal/migrations/015_add_functions.py delete mode 100644 backend/apps/webui/internal/migrations/016_add_valves_and_is_active.py delete mode 100644 backend/apps/webui/internal/migrations/017_add_user_oauth_sub.py delete mode 100644 backend/apps/webui/internal/migrations/README.md diff --git a/backend/apps/webui/internal/migrations/001_initial_schema.py b/backend/apps/webui/internal/migrations/001_initial_schema.py deleted file mode 100644 index 93f278f15..000000000 --- a/backend/apps/webui/internal/migrations/001_initial_schema.py +++ /dev/null @@ -1,254 +0,0 @@ -"""Peewee migrations -- 001_initial_schema.py. - -Some examples (model - class or model name):: - - > Model = migrator.orm['table_name'] # Return model in current state by name - > Model = migrator.ModelClass # Return model in current state by name - - > migrator.sql(sql) # Run custom SQL - > migrator.run(func, *args, **kwargs) # Run python function with the given args - > migrator.create_model(Model) # Create a model (could be used as decorator) - > migrator.remove_model(model, cascade=True) # Remove a model - > migrator.add_fields(model, **fields) # Add fields to a model - > migrator.change_fields(model, **fields) # Change fields - > migrator.remove_fields(model, *field_names, cascade=True) - > migrator.rename_field(model, old_field_name, new_field_name) - > migrator.rename_table(model, new_table_name) - > migrator.add_index(model, *col_names, unique=False) - > migrator.add_not_null(model, *field_names) - > migrator.add_default(model, field_name, default) - > migrator.add_constraint(model, name, sql) - > migrator.drop_index(model, *col_names) - > migrator.drop_not_null(model, *field_names) - > migrator.drop_constraints(model, *constraints) - -""" - -from contextlib import suppress - -import peewee as pw -from peewee_migrate import Migrator - - -with suppress(ImportError): - import playhouse.postgres_ext as pw_pext - - -def migrate(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your migrations here.""" - - # We perform different migrations for SQLite and other databases - # This is because SQLite is very loose with enforcing its schema, and trying to migrate other databases like SQLite - # will require per-database SQL queries. - # Instead, we assume that because external DB support was added at a later date, it is safe to assume a newer base - # schema instead of trying to migrate from an older schema. - if isinstance(database, pw.SqliteDatabase): - migrate_sqlite(migrator, database, fake=fake) - else: - migrate_external(migrator, database, fake=fake) - - -def migrate_sqlite(migrator: Migrator, database: pw.Database, *, fake=False): - @migrator.create_model - class Auth(pw.Model): - id = pw.CharField(max_length=255, unique=True) - email = pw.CharField(max_length=255) - password = pw.CharField(max_length=255) - active = pw.BooleanField() - - class Meta: - table_name = "auth" - - @migrator.create_model - class Chat(pw.Model): - id = pw.CharField(max_length=255, unique=True) - user_id = pw.CharField(max_length=255) - title = pw.CharField() - chat = pw.TextField() - timestamp = pw.BigIntegerField() - - class Meta: - table_name = "chat" - - @migrator.create_model - class ChatIdTag(pw.Model): - id = pw.CharField(max_length=255, unique=True) - tag_name = pw.CharField(max_length=255) - chat_id = pw.CharField(max_length=255) - user_id = pw.CharField(max_length=255) - timestamp = pw.BigIntegerField() - - class Meta: - table_name = "chatidtag" - - @migrator.create_model - class Document(pw.Model): - id = pw.AutoField() - collection_name = pw.CharField(max_length=255, unique=True) - name = pw.CharField(max_length=255, unique=True) - title = pw.CharField() - filename = pw.CharField() - content = pw.TextField(null=True) - user_id = pw.CharField(max_length=255) - timestamp = pw.BigIntegerField() - - class Meta: - table_name = "document" - - @migrator.create_model - class Modelfile(pw.Model): - id = pw.AutoField() - tag_name = pw.CharField(max_length=255, unique=True) - user_id = pw.CharField(max_length=255) - modelfile = pw.TextField() - timestamp = pw.BigIntegerField() - - class Meta: - table_name = "modelfile" - - @migrator.create_model - class Prompt(pw.Model): - id = pw.AutoField() - command = pw.CharField(max_length=255, unique=True) - user_id = pw.CharField(max_length=255) - title = pw.CharField() - content = pw.TextField() - timestamp = pw.BigIntegerField() - - class Meta: - table_name = "prompt" - - @migrator.create_model - class Tag(pw.Model): - id = pw.CharField(max_length=255, unique=True) - name = pw.CharField(max_length=255) - user_id = pw.CharField(max_length=255) - data = pw.TextField(null=True) - - class Meta: - table_name = "tag" - - @migrator.create_model - class User(pw.Model): - id = pw.CharField(max_length=255, unique=True) - name = pw.CharField(max_length=255) - email = pw.CharField(max_length=255) - role = pw.CharField(max_length=255) - profile_image_url = pw.CharField(max_length=255) - timestamp = pw.BigIntegerField() - - class Meta: - table_name = "user" - - -def migrate_external(migrator: Migrator, database: pw.Database, *, fake=False): - @migrator.create_model - class Auth(pw.Model): - id = pw.CharField(max_length=255, unique=True) - email = pw.CharField(max_length=255) - password = pw.TextField() - active = pw.BooleanField() - - class Meta: - table_name = "auth" - - @migrator.create_model - class Chat(pw.Model): - id = pw.CharField(max_length=255, unique=True) - user_id = pw.CharField(max_length=255) - title = pw.TextField() - chat = pw.TextField() - timestamp = pw.BigIntegerField() - - class Meta: - table_name = "chat" - - @migrator.create_model - class ChatIdTag(pw.Model): - id = pw.CharField(max_length=255, unique=True) - tag_name = pw.CharField(max_length=255) - chat_id = pw.CharField(max_length=255) - user_id = pw.CharField(max_length=255) - timestamp = pw.BigIntegerField() - - class Meta: - table_name = "chatidtag" - - @migrator.create_model - class Document(pw.Model): - id = pw.AutoField() - collection_name = pw.CharField(max_length=255, unique=True) - name = pw.CharField(max_length=255, unique=True) - title = pw.TextField() - filename = pw.TextField() - content = pw.TextField(null=True) - user_id = pw.CharField(max_length=255) - timestamp = pw.BigIntegerField() - - class Meta: - table_name = "document" - - @migrator.create_model - class Modelfile(pw.Model): - id = pw.AutoField() - tag_name = pw.CharField(max_length=255, unique=True) - user_id = pw.CharField(max_length=255) - modelfile = pw.TextField() - timestamp = pw.BigIntegerField() - - class Meta: - table_name = "modelfile" - - @migrator.create_model - class Prompt(pw.Model): - id = pw.AutoField() - command = pw.CharField(max_length=255, unique=True) - user_id = pw.CharField(max_length=255) - title = pw.TextField() - content = pw.TextField() - timestamp = pw.BigIntegerField() - - class Meta: - table_name = "prompt" - - @migrator.create_model - class Tag(pw.Model): - id = pw.CharField(max_length=255, unique=True) - name = pw.CharField(max_length=255) - user_id = pw.CharField(max_length=255) - data = pw.TextField(null=True) - - class Meta: - table_name = "tag" - - @migrator.create_model - class User(pw.Model): - id = pw.CharField(max_length=255, unique=True) - name = pw.CharField(max_length=255) - email = pw.CharField(max_length=255) - role = pw.CharField(max_length=255) - profile_image_url = pw.TextField() - timestamp = pw.BigIntegerField() - - class Meta: - table_name = "user" - - -def rollback(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your rollback migrations here.""" - - migrator.remove_model("user") - - migrator.remove_model("tag") - - migrator.remove_model("prompt") - - migrator.remove_model("modelfile") - - migrator.remove_model("document") - - migrator.remove_model("chatidtag") - - migrator.remove_model("chat") - - migrator.remove_model("auth") diff --git a/backend/apps/webui/internal/migrations/002_add_local_sharing.py b/backend/apps/webui/internal/migrations/002_add_local_sharing.py deleted file mode 100644 index e93501aee..000000000 --- a/backend/apps/webui/internal/migrations/002_add_local_sharing.py +++ /dev/null @@ -1,48 +0,0 @@ -"""Peewee migrations -- 002_add_local_sharing.py. - -Some examples (model - class or model name):: - - > Model = migrator.orm['table_name'] # Return model in current state by name - > Model = migrator.ModelClass # Return model in current state by name - - > migrator.sql(sql) # Run custom SQL - > migrator.run(func, *args, **kwargs) # Run python function with the given args - > migrator.create_model(Model) # Create a model (could be used as decorator) - > migrator.remove_model(model, cascade=True) # Remove a model - > migrator.add_fields(model, **fields) # Add fields to a model - > migrator.change_fields(model, **fields) # Change fields - > migrator.remove_fields(model, *field_names, cascade=True) - > migrator.rename_field(model, old_field_name, new_field_name) - > migrator.rename_table(model, new_table_name) - > migrator.add_index(model, *col_names, unique=False) - > migrator.add_not_null(model, *field_names) - > migrator.add_default(model, field_name, default) - > migrator.add_constraint(model, name, sql) - > migrator.drop_index(model, *col_names) - > migrator.drop_not_null(model, *field_names) - > migrator.drop_constraints(model, *constraints) - -""" - -from contextlib import suppress - -import peewee as pw -from peewee_migrate import Migrator - - -with suppress(ImportError): - import playhouse.postgres_ext as pw_pext - - -def migrate(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your migrations here.""" - - migrator.add_fields( - "chat", share_id=pw.CharField(max_length=255, null=True, unique=True) - ) - - -def rollback(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your rollback migrations here.""" - - migrator.remove_fields("chat", "share_id") diff --git a/backend/apps/webui/internal/migrations/003_add_auth_api_key.py b/backend/apps/webui/internal/migrations/003_add_auth_api_key.py deleted file mode 100644 index 07144f3ac..000000000 --- a/backend/apps/webui/internal/migrations/003_add_auth_api_key.py +++ /dev/null @@ -1,48 +0,0 @@ -"""Peewee migrations -- 002_add_local_sharing.py. - -Some examples (model - class or model name):: - - > Model = migrator.orm['table_name'] # Return model in current state by name - > Model = migrator.ModelClass # Return model in current state by name - - > migrator.sql(sql) # Run custom SQL - > migrator.run(func, *args, **kwargs) # Run python function with the given args - > migrator.create_model(Model) # Create a model (could be used as decorator) - > migrator.remove_model(model, cascade=True) # Remove a model - > migrator.add_fields(model, **fields) # Add fields to a model - > migrator.change_fields(model, **fields) # Change fields - > migrator.remove_fields(model, *field_names, cascade=True) - > migrator.rename_field(model, old_field_name, new_field_name) - > migrator.rename_table(model, new_table_name) - > migrator.add_index(model, *col_names, unique=False) - > migrator.add_not_null(model, *field_names) - > migrator.add_default(model, field_name, default) - > migrator.add_constraint(model, name, sql) - > migrator.drop_index(model, *col_names) - > migrator.drop_not_null(model, *field_names) - > migrator.drop_constraints(model, *constraints) - -""" - -from contextlib import suppress - -import peewee as pw -from peewee_migrate import Migrator - - -with suppress(ImportError): - import playhouse.postgres_ext as pw_pext - - -def migrate(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your migrations here.""" - - migrator.add_fields( - "user", api_key=pw.CharField(max_length=255, null=True, unique=True) - ) - - -def rollback(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your rollback migrations here.""" - - migrator.remove_fields("user", "api_key") diff --git a/backend/apps/webui/internal/migrations/004_add_archived.py b/backend/apps/webui/internal/migrations/004_add_archived.py deleted file mode 100644 index d01c06b4e..000000000 --- a/backend/apps/webui/internal/migrations/004_add_archived.py +++ /dev/null @@ -1,46 +0,0 @@ -"""Peewee migrations -- 002_add_local_sharing.py. - -Some examples (model - class or model name):: - - > Model = migrator.orm['table_name'] # Return model in current state by name - > Model = migrator.ModelClass # Return model in current state by name - - > migrator.sql(sql) # Run custom SQL - > migrator.run(func, *args, **kwargs) # Run python function with the given args - > migrator.create_model(Model) # Create a model (could be used as decorator) - > migrator.remove_model(model, cascade=True) # Remove a model - > migrator.add_fields(model, **fields) # Add fields to a model - > migrator.change_fields(model, **fields) # Change fields - > migrator.remove_fields(model, *field_names, cascade=True) - > migrator.rename_field(model, old_field_name, new_field_name) - > migrator.rename_table(model, new_table_name) - > migrator.add_index(model, *col_names, unique=False) - > migrator.add_not_null(model, *field_names) - > migrator.add_default(model, field_name, default) - > migrator.add_constraint(model, name, sql) - > migrator.drop_index(model, *col_names) - > migrator.drop_not_null(model, *field_names) - > migrator.drop_constraints(model, *constraints) - -""" - -from contextlib import suppress - -import peewee as pw -from peewee_migrate import Migrator - - -with suppress(ImportError): - import playhouse.postgres_ext as pw_pext - - -def migrate(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your migrations here.""" - - migrator.add_fields("chat", archived=pw.BooleanField(default=False)) - - -def rollback(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your rollback migrations here.""" - - migrator.remove_fields("chat", "archived") diff --git a/backend/apps/webui/internal/migrations/005_add_updated_at.py b/backend/apps/webui/internal/migrations/005_add_updated_at.py deleted file mode 100644 index 950866ef0..000000000 --- a/backend/apps/webui/internal/migrations/005_add_updated_at.py +++ /dev/null @@ -1,130 +0,0 @@ -"""Peewee migrations -- 002_add_local_sharing.py. - -Some examples (model - class or model name):: - - > Model = migrator.orm['table_name'] # Return model in current state by name - > Model = migrator.ModelClass # Return model in current state by name - - > migrator.sql(sql) # Run custom SQL - > migrator.run(func, *args, **kwargs) # Run python function with the given args - > migrator.create_model(Model) # Create a model (could be used as decorator) - > migrator.remove_model(model, cascade=True) # Remove a model - > migrator.add_fields(model, **fields) # Add fields to a model - > migrator.change_fields(model, **fields) # Change fields - > migrator.remove_fields(model, *field_names, cascade=True) - > migrator.rename_field(model, old_field_name, new_field_name) - > migrator.rename_table(model, new_table_name) - > migrator.add_index(model, *col_names, unique=False) - > migrator.add_not_null(model, *field_names) - > migrator.add_default(model, field_name, default) - > migrator.add_constraint(model, name, sql) - > migrator.drop_index(model, *col_names) - > migrator.drop_not_null(model, *field_names) - > migrator.drop_constraints(model, *constraints) - -""" - -from contextlib import suppress - -import peewee as pw -from peewee_migrate import Migrator - - -with suppress(ImportError): - import playhouse.postgres_ext as pw_pext - - -def migrate(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your migrations here.""" - - if isinstance(database, pw.SqliteDatabase): - migrate_sqlite(migrator, database, fake=fake) - else: - migrate_external(migrator, database, fake=fake) - - -def migrate_sqlite(migrator: Migrator, database: pw.Database, *, fake=False): - # Adding fields created_at and updated_at to the 'chat' table - migrator.add_fields( - "chat", - created_at=pw.DateTimeField(null=True), # Allow null for transition - updated_at=pw.DateTimeField(null=True), # Allow null for transition - ) - - # Populate the new fields from an existing 'timestamp' field - migrator.sql( - "UPDATE chat SET created_at = timestamp, updated_at = timestamp WHERE timestamp IS NOT NULL" - ) - - # Now that the data has been copied, remove the original 'timestamp' field - migrator.remove_fields("chat", "timestamp") - - # Update the fields to be not null now that they are populated - migrator.change_fields( - "chat", - created_at=pw.DateTimeField(null=False), - updated_at=pw.DateTimeField(null=False), - ) - - -def migrate_external(migrator: Migrator, database: pw.Database, *, fake=False): - # Adding fields created_at and updated_at to the 'chat' table - migrator.add_fields( - "chat", - created_at=pw.BigIntegerField(null=True), # Allow null for transition - updated_at=pw.BigIntegerField(null=True), # Allow null for transition - ) - - # Populate the new fields from an existing 'timestamp' field - migrator.sql( - "UPDATE chat SET created_at = timestamp, updated_at = timestamp WHERE timestamp IS NOT NULL" - ) - - # Now that the data has been copied, remove the original 'timestamp' field - migrator.remove_fields("chat", "timestamp") - - # Update the fields to be not null now that they are populated - migrator.change_fields( - "chat", - created_at=pw.BigIntegerField(null=False), - updated_at=pw.BigIntegerField(null=False), - ) - - -def rollback(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your rollback migrations here.""" - - if isinstance(database, pw.SqliteDatabase): - rollback_sqlite(migrator, database, fake=fake) - else: - rollback_external(migrator, database, fake=fake) - - -def rollback_sqlite(migrator: Migrator, database: pw.Database, *, fake=False): - # Recreate the timestamp field initially allowing null values for safe transition - migrator.add_fields("chat", timestamp=pw.DateTimeField(null=True)) - - # Copy the earliest created_at date back into the new timestamp field - # This assumes created_at was originally a copy of timestamp - migrator.sql("UPDATE chat SET timestamp = created_at") - - # Remove the created_at and updated_at fields - migrator.remove_fields("chat", "created_at", "updated_at") - - # Finally, alter the timestamp field to not allow nulls if that was the original setting - migrator.change_fields("chat", timestamp=pw.DateTimeField(null=False)) - - -def rollback_external(migrator: Migrator, database: pw.Database, *, fake=False): - # Recreate the timestamp field initially allowing null values for safe transition - migrator.add_fields("chat", timestamp=pw.BigIntegerField(null=True)) - - # Copy the earliest created_at date back into the new timestamp field - # This assumes created_at was originally a copy of timestamp - migrator.sql("UPDATE chat SET timestamp = created_at") - - # Remove the created_at and updated_at fields - migrator.remove_fields("chat", "created_at", "updated_at") - - # Finally, alter the timestamp field to not allow nulls if that was the original setting - migrator.change_fields("chat", timestamp=pw.BigIntegerField(null=False)) diff --git a/backend/apps/webui/internal/migrations/006_migrate_timestamps_and_charfields.py b/backend/apps/webui/internal/migrations/006_migrate_timestamps_and_charfields.py deleted file mode 100644 index caca14d32..000000000 --- a/backend/apps/webui/internal/migrations/006_migrate_timestamps_and_charfields.py +++ /dev/null @@ -1,130 +0,0 @@ -"""Peewee migrations -- 006_migrate_timestamps_and_charfields.py. - -Some examples (model - class or model name):: - - > Model = migrator.orm['table_name'] # Return model in current state by name - > Model = migrator.ModelClass # Return model in current state by name - - > migrator.sql(sql) # Run custom SQL - > migrator.run(func, *args, **kwargs) # Run python function with the given args - > migrator.create_model(Model) # Create a model (could be used as decorator) - > migrator.remove_model(model, cascade=True) # Remove a model - > migrator.add_fields(model, **fields) # Add fields to a model - > migrator.change_fields(model, **fields) # Change fields - > migrator.remove_fields(model, *field_names, cascade=True) - > migrator.rename_field(model, old_field_name, new_field_name) - > migrator.rename_table(model, new_table_name) - > migrator.add_index(model, *col_names, unique=False) - > migrator.add_not_null(model, *field_names) - > migrator.add_default(model, field_name, default) - > migrator.add_constraint(model, name, sql) - > migrator.drop_index(model, *col_names) - > migrator.drop_not_null(model, *field_names) - > migrator.drop_constraints(model, *constraints) - -""" - -from contextlib import suppress - -import peewee as pw -from peewee_migrate import Migrator - - -with suppress(ImportError): - import playhouse.postgres_ext as pw_pext - - -def migrate(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your migrations here.""" - - # Alter the tables with timestamps - migrator.change_fields( - "chatidtag", - timestamp=pw.BigIntegerField(), - ) - migrator.change_fields( - "document", - timestamp=pw.BigIntegerField(), - ) - migrator.change_fields( - "modelfile", - timestamp=pw.BigIntegerField(), - ) - migrator.change_fields( - "prompt", - timestamp=pw.BigIntegerField(), - ) - migrator.change_fields( - "user", - timestamp=pw.BigIntegerField(), - ) - # Alter the tables with varchar to text where necessary - migrator.change_fields( - "auth", - password=pw.TextField(), - ) - migrator.change_fields( - "chat", - title=pw.TextField(), - ) - migrator.change_fields( - "document", - title=pw.TextField(), - filename=pw.TextField(), - ) - migrator.change_fields( - "prompt", - title=pw.TextField(), - ) - migrator.change_fields( - "user", - profile_image_url=pw.TextField(), - ) - - -def rollback(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your rollback migrations here.""" - - if isinstance(database, pw.SqliteDatabase): - # Alter the tables with timestamps - migrator.change_fields( - "chatidtag", - timestamp=pw.DateField(), - ) - migrator.change_fields( - "document", - timestamp=pw.DateField(), - ) - migrator.change_fields( - "modelfile", - timestamp=pw.DateField(), - ) - migrator.change_fields( - "prompt", - timestamp=pw.DateField(), - ) - migrator.change_fields( - "user", - timestamp=pw.DateField(), - ) - migrator.change_fields( - "auth", - password=pw.CharField(max_length=255), - ) - migrator.change_fields( - "chat", - title=pw.CharField(), - ) - migrator.change_fields( - "document", - title=pw.CharField(), - filename=pw.CharField(), - ) - migrator.change_fields( - "prompt", - title=pw.CharField(), - ) - migrator.change_fields( - "user", - profile_image_url=pw.CharField(), - ) diff --git a/backend/apps/webui/internal/migrations/007_add_user_last_active_at.py b/backend/apps/webui/internal/migrations/007_add_user_last_active_at.py deleted file mode 100644 index dd176ba73..000000000 --- a/backend/apps/webui/internal/migrations/007_add_user_last_active_at.py +++ /dev/null @@ -1,79 +0,0 @@ -"""Peewee migrations -- 002_add_local_sharing.py. - -Some examples (model - class or model name):: - - > Model = migrator.orm['table_name'] # Return model in current state by name - > Model = migrator.ModelClass # Return model in current state by name - - > migrator.sql(sql) # Run custom SQL - > migrator.run(func, *args, **kwargs) # Run python function with the given args - > migrator.create_model(Model) # Create a model (could be used as decorator) - > migrator.remove_model(model, cascade=True) # Remove a model - > migrator.add_fields(model, **fields) # Add fields to a model - > migrator.change_fields(model, **fields) # Change fields - > migrator.remove_fields(model, *field_names, cascade=True) - > migrator.rename_field(model, old_field_name, new_field_name) - > migrator.rename_table(model, new_table_name) - > migrator.add_index(model, *col_names, unique=False) - > migrator.add_not_null(model, *field_names) - > migrator.add_default(model, field_name, default) - > migrator.add_constraint(model, name, sql) - > migrator.drop_index(model, *col_names) - > migrator.drop_not_null(model, *field_names) - > migrator.drop_constraints(model, *constraints) - -""" - -from contextlib import suppress - -import peewee as pw -from peewee_migrate import Migrator - - -with suppress(ImportError): - import playhouse.postgres_ext as pw_pext - - -def migrate(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your migrations here.""" - - # Adding fields created_at and updated_at to the 'user' table - migrator.add_fields( - "user", - created_at=pw.BigIntegerField(null=True), # Allow null for transition - updated_at=pw.BigIntegerField(null=True), # Allow null for transition - last_active_at=pw.BigIntegerField(null=True), # Allow null for transition - ) - - # Populate the new fields from an existing 'timestamp' field - migrator.sql( - 'UPDATE "user" SET created_at = timestamp, updated_at = timestamp, last_active_at = timestamp WHERE timestamp IS NOT NULL' - ) - - # Now that the data has been copied, remove the original 'timestamp' field - migrator.remove_fields("user", "timestamp") - - # Update the fields to be not null now that they are populated - migrator.change_fields( - "user", - created_at=pw.BigIntegerField(null=False), - updated_at=pw.BigIntegerField(null=False), - last_active_at=pw.BigIntegerField(null=False), - ) - - -def rollback(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your rollback migrations here.""" - - # Recreate the timestamp field initially allowing null values for safe transition - migrator.add_fields("user", timestamp=pw.BigIntegerField(null=True)) - - # Copy the earliest created_at date back into the new timestamp field - # This assumes created_at was originally a copy of timestamp - migrator.sql('UPDATE "user" SET timestamp = created_at') - - # Remove the created_at and updated_at fields - migrator.remove_fields("user", "created_at", "updated_at", "last_active_at") - - # Finally, alter the timestamp field to not allow nulls if that was the original setting - migrator.change_fields("user", timestamp=pw.BigIntegerField(null=False)) diff --git a/backend/apps/webui/internal/migrations/008_add_memory.py b/backend/apps/webui/internal/migrations/008_add_memory.py deleted file mode 100644 index 9307aa4d5..000000000 --- a/backend/apps/webui/internal/migrations/008_add_memory.py +++ /dev/null @@ -1,53 +0,0 @@ -"""Peewee migrations -- 002_add_local_sharing.py. - -Some examples (model - class or model name):: - - > Model = migrator.orm['table_name'] # Return model in current state by name - > Model = migrator.ModelClass # Return model in current state by name - - > migrator.sql(sql) # Run custom SQL - > migrator.run(func, *args, **kwargs) # Run python function with the given args - > migrator.create_model(Model) # Create a model (could be used as decorator) - > migrator.remove_model(model, cascade=True) # Remove a model - > migrator.add_fields(model, **fields) # Add fields to a model - > migrator.change_fields(model, **fields) # Change fields - > migrator.remove_fields(model, *field_names, cascade=True) - > migrator.rename_field(model, old_field_name, new_field_name) - > migrator.rename_table(model, new_table_name) - > migrator.add_index(model, *col_names, unique=False) - > migrator.add_not_null(model, *field_names) - > migrator.add_default(model, field_name, default) - > migrator.add_constraint(model, name, sql) - > migrator.drop_index(model, *col_names) - > migrator.drop_not_null(model, *field_names) - > migrator.drop_constraints(model, *constraints) - -""" - -from contextlib import suppress - -import peewee as pw -from peewee_migrate import Migrator - - -with suppress(ImportError): - import playhouse.postgres_ext as pw_pext - - -def migrate(migrator: Migrator, database: pw.Database, *, fake=False): - @migrator.create_model - class Memory(pw.Model): - id = pw.CharField(max_length=255, unique=True) - user_id = pw.CharField(max_length=255) - content = pw.TextField(null=False) - updated_at = pw.BigIntegerField(null=False) - created_at = pw.BigIntegerField(null=False) - - class Meta: - table_name = "memory" - - -def rollback(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your rollback migrations here.""" - - migrator.remove_model("memory") diff --git a/backend/apps/webui/internal/migrations/009_add_models.py b/backend/apps/webui/internal/migrations/009_add_models.py deleted file mode 100644 index 548ec7cdc..000000000 --- a/backend/apps/webui/internal/migrations/009_add_models.py +++ /dev/null @@ -1,61 +0,0 @@ -"""Peewee migrations -- 009_add_models.py. - -Some examples (model - class or model name):: - - > Model = migrator.orm['table_name'] # Return model in current state by name - > Model = migrator.ModelClass # Return model in current state by name - - > migrator.sql(sql) # Run custom SQL - > migrator.run(func, *args, **kwargs) # Run python function with the given args - > migrator.create_model(Model) # Create a model (could be used as decorator) - > migrator.remove_model(model, cascade=True) # Remove a model - > migrator.add_fields(model, **fields) # Add fields to a model - > migrator.change_fields(model, **fields) # Change fields - > migrator.remove_fields(model, *field_names, cascade=True) - > migrator.rename_field(model, old_field_name, new_field_name) - > migrator.rename_table(model, new_table_name) - > migrator.add_index(model, *col_names, unique=False) - > migrator.add_not_null(model, *field_names) - > migrator.add_default(model, field_name, default) - > migrator.add_constraint(model, name, sql) - > migrator.drop_index(model, *col_names) - > migrator.drop_not_null(model, *field_names) - > migrator.drop_constraints(model, *constraints) - -""" - -from contextlib import suppress - -import peewee as pw -from peewee_migrate import Migrator - - -with suppress(ImportError): - import playhouse.postgres_ext as pw_pext - - -def migrate(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your migrations here.""" - - @migrator.create_model - class Model(pw.Model): - id = pw.TextField(unique=True) - user_id = pw.TextField() - base_model_id = pw.TextField(null=True) - - name = pw.TextField() - - meta = pw.TextField() - params = pw.TextField() - - created_at = pw.BigIntegerField(null=False) - updated_at = pw.BigIntegerField(null=False) - - class Meta: - table_name = "model" - - -def rollback(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your rollback migrations here.""" - - migrator.remove_model("model") diff --git a/backend/apps/webui/internal/migrations/010_migrate_modelfiles_to_models.py b/backend/apps/webui/internal/migrations/010_migrate_modelfiles_to_models.py deleted file mode 100644 index 2ef814c06..000000000 --- a/backend/apps/webui/internal/migrations/010_migrate_modelfiles_to_models.py +++ /dev/null @@ -1,130 +0,0 @@ -"""Peewee migrations -- 009_add_models.py. - -Some examples (model - class or model name):: - - > Model = migrator.orm['table_name'] # Return model in current state by name - > Model = migrator.ModelClass # Return model in current state by name - - > migrator.sql(sql) # Run custom SQL - > migrator.run(func, *args, **kwargs) # Run python function with the given args - > migrator.create_model(Model) # Create a model (could be used as decorator) - > migrator.remove_model(model, cascade=True) # Remove a model - > migrator.add_fields(model, **fields) # Add fields to a model - > migrator.change_fields(model, **fields) # Change fields - > migrator.remove_fields(model, *field_names, cascade=True) - > migrator.rename_field(model, old_field_name, new_field_name) - > migrator.rename_table(model, new_table_name) - > migrator.add_index(model, *col_names, unique=False) - > migrator.add_not_null(model, *field_names) - > migrator.add_default(model, field_name, default) - > migrator.add_constraint(model, name, sql) - > migrator.drop_index(model, *col_names) - > migrator.drop_not_null(model, *field_names) - > migrator.drop_constraints(model, *constraints) - -""" - -from contextlib import suppress - -import peewee as pw -from peewee_migrate import Migrator -import json - -from utils.misc import parse_ollama_modelfile - -with suppress(ImportError): - import playhouse.postgres_ext as pw_pext - - -def migrate(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your migrations here.""" - - # Fetch data from 'modelfile' table and insert into 'model' table - migrate_modelfile_to_model(migrator, database) - # Drop the 'modelfile' table - migrator.remove_model("modelfile") - - -def migrate_modelfile_to_model(migrator: Migrator, database: pw.Database): - ModelFile = migrator.orm["modelfile"] - Model = migrator.orm["model"] - - modelfiles = ModelFile.select() - - for modelfile in modelfiles: - # Extract and transform data in Python - - modelfile.modelfile = json.loads(modelfile.modelfile) - meta = json.dumps( - { - "description": modelfile.modelfile.get("desc"), - "profile_image_url": modelfile.modelfile.get("imageUrl"), - "ollama": {"modelfile": modelfile.modelfile.get("content")}, - "suggestion_prompts": modelfile.modelfile.get("suggestionPrompts"), - "categories": modelfile.modelfile.get("categories"), - "user": {**modelfile.modelfile.get("user", {}), "community": True}, - } - ) - - info = parse_ollama_modelfile(modelfile.modelfile.get("content")) - - # Insert the processed data into the 'model' table - Model.create( - id=f"ollama-{modelfile.tag_name}", - user_id=modelfile.user_id, - base_model_id=info.get("base_model_id"), - name=modelfile.modelfile.get("title"), - meta=meta, - params=json.dumps(info.get("params", {})), - created_at=modelfile.timestamp, - updated_at=modelfile.timestamp, - ) - - -def rollback(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your rollback migrations here.""" - - recreate_modelfile_table(migrator, database) - move_data_back_to_modelfile(migrator, database) - migrator.remove_model("model") - - -def recreate_modelfile_table(migrator: Migrator, database: pw.Database): - query = """ - CREATE TABLE IF NOT EXISTS modelfile ( - user_id TEXT, - tag_name TEXT, - modelfile JSON, - timestamp BIGINT - ) - """ - migrator.sql(query) - - -def move_data_back_to_modelfile(migrator: Migrator, database: pw.Database): - Model = migrator.orm["model"] - Modelfile = migrator.orm["modelfile"] - - models = Model.select() - - for model in models: - # Extract and transform data in Python - meta = json.loads(model.meta) - - modelfile_data = { - "title": model.name, - "desc": meta.get("description"), - "imageUrl": meta.get("profile_image_url"), - "content": meta.get("ollama", {}).get("modelfile"), - "suggestionPrompts": meta.get("suggestion_prompts"), - "categories": meta.get("categories"), - "user": {k: v for k, v in meta.get("user", {}).items() if k != "community"}, - } - - # Insert the processed data back into the 'modelfile' table - Modelfile.create( - user_id=model.user_id, - tag_name=model.id, - modelfile=modelfile_data, - timestamp=model.created_at, - ) diff --git a/backend/apps/webui/internal/migrations/011_add_user_settings.py b/backend/apps/webui/internal/migrations/011_add_user_settings.py deleted file mode 100644 index a1620dcad..000000000 --- a/backend/apps/webui/internal/migrations/011_add_user_settings.py +++ /dev/null @@ -1,48 +0,0 @@ -"""Peewee migrations -- 002_add_local_sharing.py. - -Some examples (model - class or model name):: - - > Model = migrator.orm['table_name'] # Return model in current state by name - > Model = migrator.ModelClass # Return model in current state by name - - > migrator.sql(sql) # Run custom SQL - > migrator.run(func, *args, **kwargs) # Run python function with the given args - > migrator.create_model(Model) # Create a model (could be used as decorator) - > migrator.remove_model(model, cascade=True) # Remove a model - > migrator.add_fields(model, **fields) # Add fields to a model - > migrator.change_fields(model, **fields) # Change fields - > migrator.remove_fields(model, *field_names, cascade=True) - > migrator.rename_field(model, old_field_name, new_field_name) - > migrator.rename_table(model, new_table_name) - > migrator.add_index(model, *col_names, unique=False) - > migrator.add_not_null(model, *field_names) - > migrator.add_default(model, field_name, default) - > migrator.add_constraint(model, name, sql) - > migrator.drop_index(model, *col_names) - > migrator.drop_not_null(model, *field_names) - > migrator.drop_constraints(model, *constraints) - -""" - -from contextlib import suppress - -import peewee as pw -from peewee_migrate import Migrator - - -with suppress(ImportError): - import playhouse.postgres_ext as pw_pext - - -def migrate(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your migrations here.""" - - # Adding fields settings to the 'user' table - migrator.add_fields("user", settings=pw.TextField(null=True)) - - -def rollback(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your rollback migrations here.""" - - # Remove the settings field - migrator.remove_fields("user", "settings") diff --git a/backend/apps/webui/internal/migrations/012_add_tools.py b/backend/apps/webui/internal/migrations/012_add_tools.py deleted file mode 100644 index 4a68eea55..000000000 --- a/backend/apps/webui/internal/migrations/012_add_tools.py +++ /dev/null @@ -1,61 +0,0 @@ -"""Peewee migrations -- 009_add_models.py. - -Some examples (model - class or model name):: - - > Model = migrator.orm['table_name'] # Return model in current state by name - > Model = migrator.ModelClass # Return model in current state by name - - > migrator.sql(sql) # Run custom SQL - > migrator.run(func, *args, **kwargs) # Run python function with the given args - > migrator.create_model(Model) # Create a model (could be used as decorator) - > migrator.remove_model(model, cascade=True) # Remove a model - > migrator.add_fields(model, **fields) # Add fields to a model - > migrator.change_fields(model, **fields) # Change fields - > migrator.remove_fields(model, *field_names, cascade=True) - > migrator.rename_field(model, old_field_name, new_field_name) - > migrator.rename_table(model, new_table_name) - > migrator.add_index(model, *col_names, unique=False) - > migrator.add_not_null(model, *field_names) - > migrator.add_default(model, field_name, default) - > migrator.add_constraint(model, name, sql) - > migrator.drop_index(model, *col_names) - > migrator.drop_not_null(model, *field_names) - > migrator.drop_constraints(model, *constraints) - -""" - -from contextlib import suppress - -import peewee as pw -from peewee_migrate import Migrator - - -with suppress(ImportError): - import playhouse.postgres_ext as pw_pext - - -def migrate(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your migrations here.""" - - @migrator.create_model - class Tool(pw.Model): - id = pw.TextField(unique=True) - user_id = pw.TextField() - - name = pw.TextField() - content = pw.TextField() - specs = pw.TextField() - - meta = pw.TextField() - - created_at = pw.BigIntegerField(null=False) - updated_at = pw.BigIntegerField(null=False) - - class Meta: - table_name = "tool" - - -def rollback(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your rollback migrations here.""" - - migrator.remove_model("tool") diff --git a/backend/apps/webui/internal/migrations/013_add_user_info.py b/backend/apps/webui/internal/migrations/013_add_user_info.py deleted file mode 100644 index 0f68669cc..000000000 --- a/backend/apps/webui/internal/migrations/013_add_user_info.py +++ /dev/null @@ -1,48 +0,0 @@ -"""Peewee migrations -- 002_add_local_sharing.py. - -Some examples (model - class or model name):: - - > Model = migrator.orm['table_name'] # Return model in current state by name - > Model = migrator.ModelClass # Return model in current state by name - - > migrator.sql(sql) # Run custom SQL - > migrator.run(func, *args, **kwargs) # Run python function with the given args - > migrator.create_model(Model) # Create a model (could be used as decorator) - > migrator.remove_model(model, cascade=True) # Remove a model - > migrator.add_fields(model, **fields) # Add fields to a model - > migrator.change_fields(model, **fields) # Change fields - > migrator.remove_fields(model, *field_names, cascade=True) - > migrator.rename_field(model, old_field_name, new_field_name) - > migrator.rename_table(model, new_table_name) - > migrator.add_index(model, *col_names, unique=False) - > migrator.add_not_null(model, *field_names) - > migrator.add_default(model, field_name, default) - > migrator.add_constraint(model, name, sql) - > migrator.drop_index(model, *col_names) - > migrator.drop_not_null(model, *field_names) - > migrator.drop_constraints(model, *constraints) - -""" - -from contextlib import suppress - -import peewee as pw -from peewee_migrate import Migrator - - -with suppress(ImportError): - import playhouse.postgres_ext as pw_pext - - -def migrate(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your migrations here.""" - - # Adding fields info to the 'user' table - migrator.add_fields("user", info=pw.TextField(null=True)) - - -def rollback(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your rollback migrations here.""" - - # Remove the settings field - migrator.remove_fields("user", "info") diff --git a/backend/apps/webui/internal/migrations/014_add_files.py b/backend/apps/webui/internal/migrations/014_add_files.py deleted file mode 100644 index 5e1acf0ad..000000000 --- a/backend/apps/webui/internal/migrations/014_add_files.py +++ /dev/null @@ -1,55 +0,0 @@ -"""Peewee migrations -- 009_add_models.py. - -Some examples (model - class or model name):: - - > Model = migrator.orm['table_name'] # Return model in current state by name - > Model = migrator.ModelClass # Return model in current state by name - - > migrator.sql(sql) # Run custom SQL - > migrator.run(func, *args, **kwargs) # Run python function with the given args - > migrator.create_model(Model) # Create a model (could be used as decorator) - > migrator.remove_model(model, cascade=True) # Remove a model - > migrator.add_fields(model, **fields) # Add fields to a model - > migrator.change_fields(model, **fields) # Change fields - > migrator.remove_fields(model, *field_names, cascade=True) - > migrator.rename_field(model, old_field_name, new_field_name) - > migrator.rename_table(model, new_table_name) - > migrator.add_index(model, *col_names, unique=False) - > migrator.add_not_null(model, *field_names) - > migrator.add_default(model, field_name, default) - > migrator.add_constraint(model, name, sql) - > migrator.drop_index(model, *col_names) - > migrator.drop_not_null(model, *field_names) - > migrator.drop_constraints(model, *constraints) - -""" - -from contextlib import suppress - -import peewee as pw -from peewee_migrate import Migrator - - -with suppress(ImportError): - import playhouse.postgres_ext as pw_pext - - -def migrate(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your migrations here.""" - - @migrator.create_model - class File(pw.Model): - id = pw.TextField(unique=True) - user_id = pw.TextField() - filename = pw.TextField() - meta = pw.TextField() - created_at = pw.BigIntegerField(null=False) - - class Meta: - table_name = "file" - - -def rollback(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your rollback migrations here.""" - - migrator.remove_model("file") diff --git a/backend/apps/webui/internal/migrations/015_add_functions.py b/backend/apps/webui/internal/migrations/015_add_functions.py deleted file mode 100644 index 8316a9333..000000000 --- a/backend/apps/webui/internal/migrations/015_add_functions.py +++ /dev/null @@ -1,61 +0,0 @@ -"""Peewee migrations -- 009_add_models.py. - -Some examples (model - class or model name):: - - > Model = migrator.orm['table_name'] # Return model in current state by name - > Model = migrator.ModelClass # Return model in current state by name - - > migrator.sql(sql) # Run custom SQL - > migrator.run(func, *args, **kwargs) # Run python function with the given args - > migrator.create_model(Model) # Create a model (could be used as decorator) - > migrator.remove_model(model, cascade=True) # Remove a model - > migrator.add_fields(model, **fields) # Add fields to a model - > migrator.change_fields(model, **fields) # Change fields - > migrator.remove_fields(model, *field_names, cascade=True) - > migrator.rename_field(model, old_field_name, new_field_name) - > migrator.rename_table(model, new_table_name) - > migrator.add_index(model, *col_names, unique=False) - > migrator.add_not_null(model, *field_names) - > migrator.add_default(model, field_name, default) - > migrator.add_constraint(model, name, sql) - > migrator.drop_index(model, *col_names) - > migrator.drop_not_null(model, *field_names) - > migrator.drop_constraints(model, *constraints) - -""" - -from contextlib import suppress - -import peewee as pw -from peewee_migrate import Migrator - - -with suppress(ImportError): - import playhouse.postgres_ext as pw_pext - - -def migrate(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your migrations here.""" - - @migrator.create_model - class Function(pw.Model): - id = pw.TextField(unique=True) - user_id = pw.TextField() - - name = pw.TextField() - type = pw.TextField() - - content = pw.TextField() - meta = pw.TextField() - - created_at = pw.BigIntegerField(null=False) - updated_at = pw.BigIntegerField(null=False) - - class Meta: - table_name = "function" - - -def rollback(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your rollback migrations here.""" - - migrator.remove_model("function") diff --git a/backend/apps/webui/internal/migrations/016_add_valves_and_is_active.py b/backend/apps/webui/internal/migrations/016_add_valves_and_is_active.py deleted file mode 100644 index e3af521b7..000000000 --- a/backend/apps/webui/internal/migrations/016_add_valves_and_is_active.py +++ /dev/null @@ -1,50 +0,0 @@ -"""Peewee migrations -- 009_add_models.py. - -Some examples (model - class or model name):: - - > Model = migrator.orm['table_name'] # Return model in current state by name - > Model = migrator.ModelClass # Return model in current state by name - - > migrator.sql(sql) # Run custom SQL - > migrator.run(func, *args, **kwargs) # Run python function with the given args - > migrator.create_model(Model) # Create a model (could be used as decorator) - > migrator.remove_model(model, cascade=True) # Remove a model - > migrator.add_fields(model, **fields) # Add fields to a model - > migrator.change_fields(model, **fields) # Change fields - > migrator.remove_fields(model, *field_names, cascade=True) - > migrator.rename_field(model, old_field_name, new_field_name) - > migrator.rename_table(model, new_table_name) - > migrator.add_index(model, *col_names, unique=False) - > migrator.add_not_null(model, *field_names) - > migrator.add_default(model, field_name, default) - > migrator.add_constraint(model, name, sql) - > migrator.drop_index(model, *col_names) - > migrator.drop_not_null(model, *field_names) - > migrator.drop_constraints(model, *constraints) - -""" - -from contextlib import suppress - -import peewee as pw -from peewee_migrate import Migrator - - -with suppress(ImportError): - import playhouse.postgres_ext as pw_pext - - -def migrate(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your migrations here.""" - - migrator.add_fields("tool", valves=pw.TextField(null=True)) - migrator.add_fields("function", valves=pw.TextField(null=True)) - migrator.add_fields("function", is_active=pw.BooleanField(default=False)) - - -def rollback(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your rollback migrations here.""" - - migrator.remove_fields("tool", "valves") - migrator.remove_fields("function", "valves") - migrator.remove_fields("function", "is_active") diff --git a/backend/apps/webui/internal/migrations/017_add_user_oauth_sub.py b/backend/apps/webui/internal/migrations/017_add_user_oauth_sub.py deleted file mode 100644 index fd1d9b560..000000000 --- a/backend/apps/webui/internal/migrations/017_add_user_oauth_sub.py +++ /dev/null @@ -1,49 +0,0 @@ -"""Peewee migrations -- 017_add_user_oauth_sub.py. - -Some examples (model - class or model name):: - - > Model = migrator.orm['table_name'] # Return model in current state by name - > Model = migrator.ModelClass # Return model in current state by name - - > migrator.sql(sql) # Run custom SQL - > migrator.run(func, *args, **kwargs) # Run python function with the given args - > migrator.create_model(Model) # Create a model (could be used as decorator) - > migrator.remove_model(model, cascade=True) # Remove a model - > migrator.add_fields(model, **fields) # Add fields to a model - > migrator.change_fields(model, **fields) # Change fields - > migrator.remove_fields(model, *field_names, cascade=True) - > migrator.rename_field(model, old_field_name, new_field_name) - > migrator.rename_table(model, new_table_name) - > migrator.add_index(model, *col_names, unique=False) - > migrator.add_not_null(model, *field_names) - > migrator.add_default(model, field_name, default) - > migrator.add_constraint(model, name, sql) - > migrator.drop_index(model, *col_names) - > migrator.drop_not_null(model, *field_names) - > migrator.drop_constraints(model, *constraints) - -""" - -from contextlib import suppress - -import peewee as pw -from peewee_migrate import Migrator - - -with suppress(ImportError): - import playhouse.postgres_ext as pw_pext - - -def migrate(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your migrations here.""" - - migrator.add_fields( - "user", - oauth_sub=pw.TextField(null=True, unique=True), - ) - - -def rollback(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your rollback migrations here.""" - - migrator.remove_fields("user", "oauth_sub") diff --git a/backend/apps/webui/internal/migrations/README.md b/backend/apps/webui/internal/migrations/README.md deleted file mode 100644 index 260214113..000000000 --- a/backend/apps/webui/internal/migrations/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# Database Migrations - -This directory contains all the database migrations for the web app. -Migrations are done using the [`peewee-migrate`](https://github.com/klen/peewee_migrate) library. - -Migrations are automatically ran at app startup. - -## Creating a migration - -Have you made a change to the schema of an existing model? -You will need to create a migration file to ensure that existing databases are updated for backwards compatibility. - -1. Have a database file (`webui.db`) that has the old schema prior to any of your changes. -2. Make your changes to the models. -3. From the `backend` directory, run the following command: - ```bash - pw_migrate create --auto --auto-source apps.webui.models --database sqlite:///${SQLITE_DB} --directory apps/web/internal/migrations ${MIGRATION_NAME} - ``` - - `$SQLITE_DB` should be the path to the database file. - - `$MIGRATION_NAME` should be a descriptive name for the migration. -4. The migration file will be created in the `apps/web/internal/migrations` directory. diff --git a/backend/migrations/versions/7e5b5dc7342b_init.py b/backend/migrations/versions/7e5b5dc7342b_init.py index 50deac526..8f197ce5b 100644 --- a/backend/migrations/versions/7e5b5dc7342b_init.py +++ b/backend/migrations/versions/7e5b5dc7342b_init.py @@ -176,8 +176,10 @@ def upgrade() -> None: sa.Column("api_key", sa.String(), nullable=True), sa.Column("settings", apps.webui.internal.db.JSONField(), nullable=True), sa.Column("info", apps.webui.internal.db.JSONField(), nullable=True), + sa.Column('oauth_sub', sa.Text(), nullable=True), sa.PrimaryKeyConstraint("id"), sa.UniqueConstraint("api_key"), + sa.UniqueConstraint("oauth_sub"), ) # ### end Alembic commands ### From d4b6b7c4e8c9930003290c15d624d1f2c5bcd8a6 Mon Sep 17 00:00:00 2001 From: Jonathan Rohde Date: Tue, 25 Jun 2024 08:29:18 +0200 Subject: [PATCH 013/181] feat(sqlalchemy): reverted not needed api change --- backend/apps/webui/routers/models.py | 2 +- backend/test/apps/webui/routers/test_models.py | 4 ++-- backend/test/util/abstract_integration_test.py | 10 ++++++++-- src/lib/apis/models/index.ts | 5 ++++- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/backend/apps/webui/routers/models.py b/backend/apps/webui/routers/models.py index eaf459d73..eeae9e1c4 100644 --- a/backend/apps/webui/routers/models.py +++ b/backend/apps/webui/routers/models.py @@ -56,7 +56,7 @@ async def add_new_model( ############################ -@router.get("/{id}", response_model=Optional[ModelModel]) +@router.get("/", response_model=Optional[ModelModel]) async def get_model_by_id(id: str, user=Depends(get_verified_user)): model = Models.get_model_by_id(id) diff --git a/backend/test/apps/webui/routers/test_models.py b/backend/test/apps/webui/routers/test_models.py index 991c83bee..a8495403b 100644 --- a/backend/test/apps/webui/routers/test_models.py +++ b/backend/test/apps/webui/routers/test_models.py @@ -42,9 +42,9 @@ class TestModels(AbstractPostgresTest): assert len(response.json()) == 1 with mock_webui_user(id="2"): - response = self.fast_api_client.get(self.create_url("/my-model")) + response = self.fast_api_client.get(self.create_url(query_params={"id": "my-model"})) assert response.status_code == 200 - data = response.json() + data = response.json()[0] assert data["id"] == "my-model" assert data["name"] == "Hello World" diff --git a/backend/test/util/abstract_integration_test.py b/backend/test/util/abstract_integration_test.py index 4e99dcc2f..8535221a8 100644 --- a/backend/test/util/abstract_integration_test.py +++ b/backend/test/util/abstract_integration_test.py @@ -23,14 +23,20 @@ def get_fast_api_client(): class AbstractIntegrationTest: BASE_PATH = None - def create_url(self, path): + def create_url(self, path="", query_params=None): if self.BASE_PATH is None: raise Exception("BASE_PATH is not set") parts = self.BASE_PATH.split("/") parts = [part.strip() for part in parts if part.strip() != ""] path_parts = path.split("/") path_parts = [part.strip() for part in path_parts if part.strip() != ""] - return "/".join(parts + path_parts) + query_parts = "" + if query_params: + query_parts = "&".join( + [f"{key}={value}" for key, value in query_params.items()] + ) + query_parts = f"?{query_parts}" + return "/".join(parts + path_parts) + query_parts @classmethod def setup_class(cls): diff --git a/src/lib/apis/models/index.ts b/src/lib/apis/models/index.ts index 17d11d816..9faa358d3 100644 --- a/src/lib/apis/models/index.ts +++ b/src/lib/apis/models/index.ts @@ -63,7 +63,10 @@ export const getModelInfos = async (token: string = '') => { export const getModelById = async (token: string, id: string) => { let error = null; - const res = await fetch(`${WEBUI_API_BASE_URL}/models/${id}`, { + const searchParams = new URLSearchParams(); + searchParams.append('id', id); + + const res = await fetch(`${WEBUI_API_BASE_URL}/models?${searchParams.toString()}`, { method: 'GET', headers: { Accept: 'application/json', From 23e4d9daff157d18442374fdd52bc8acbc1cb81c Mon Sep 17 00:00:00 2001 From: Jonathan Rohde Date: Tue, 25 Jun 2024 08:35:55 +0200 Subject: [PATCH 014/181] feat(sqlalchemy): formatting --- backend/apps/webui/models/auths.py | 3 ++- backend/migrations/versions/7e5b5dc7342b_init.py | 2 +- backend/test/apps/webui/routers/test_models.py | 4 +++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/backend/apps/webui/models/auths.py b/backend/apps/webui/models/auths.py index aef895619..560d9a686 100644 --- a/backend/apps/webui/models/auths.py +++ b/backend/apps/webui/models/auths.py @@ -113,7 +113,8 @@ class AuthsTable: Session.add(result) user = Users.insert_new_user( - id, name, email, profile_image_url, role, oauth_sub) + id, name, email, profile_image_url, role, oauth_sub + ) Session.commit() Session.refresh(result) diff --git a/backend/migrations/versions/7e5b5dc7342b_init.py b/backend/migrations/versions/7e5b5dc7342b_init.py index 8f197ce5b..90597ec9b 100644 --- a/backend/migrations/versions/7e5b5dc7342b_init.py +++ b/backend/migrations/versions/7e5b5dc7342b_init.py @@ -176,7 +176,7 @@ def upgrade() -> None: sa.Column("api_key", sa.String(), nullable=True), sa.Column("settings", apps.webui.internal.db.JSONField(), nullable=True), sa.Column("info", apps.webui.internal.db.JSONField(), nullable=True), - sa.Column('oauth_sub', sa.Text(), nullable=True), + sa.Column("oauth_sub", sa.Text(), nullable=True), sa.PrimaryKeyConstraint("id"), sa.UniqueConstraint("api_key"), sa.UniqueConstraint("oauth_sub"), diff --git a/backend/test/apps/webui/routers/test_models.py b/backend/test/apps/webui/routers/test_models.py index a8495403b..34d3e30bd 100644 --- a/backend/test/apps/webui/routers/test_models.py +++ b/backend/test/apps/webui/routers/test_models.py @@ -42,7 +42,9 @@ class TestModels(AbstractPostgresTest): assert len(response.json()) == 1 with mock_webui_user(id="2"): - response = self.fast_api_client.get(self.create_url(query_params={"id": "my-model"})) + response = self.fast_api_client.get( + self.create_url(query_params={"id": "my-model"}) + ) assert response.status_code == 200 data = response.json()[0] assert data["id"] == "my-model" From 827b1e58e96e76ef1d7d150e8a984178f2caf923 Mon Sep 17 00:00:00 2001 From: Jonathan Rohde Date: Tue, 25 Jun 2024 09:06:04 +0200 Subject: [PATCH 015/181] feat(sqlalchemy): execute tests in github actions --- .github/workflows/integration-test.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index c8e7c1672..3b455820d 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -67,6 +67,28 @@ jobs: path: compose-logs.txt if-no-files-found: ignore + pytest: + name: Run backend tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r backend/requirements.txt + + - name: pytest run + run: | + ls -al + cd backend + PYTHONPATH=. pytest . -o log_cli=true -o log_cli_level=INFO + migration_test: name: Run Migration Tests runs-on: ubuntu-latest From 5391f4c1f7e09516b0878f504efe673821be4e7c Mon Sep 17 00:00:00 2001 From: Jonathan Rohde Date: Fri, 28 Jun 2024 09:21:07 +0200 Subject: [PATCH 016/181] feat(sqlalchemy): add new column --- backend/migrations/versions/7e5b5dc7342b_init.py | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/migrations/versions/7e5b5dc7342b_init.py b/backend/migrations/versions/7e5b5dc7342b_init.py index 90597ec9b..b82627f5b 100644 --- a/backend/migrations/versions/7e5b5dc7342b_init.py +++ b/backend/migrations/versions/7e5b5dc7342b_init.py @@ -96,6 +96,7 @@ def upgrade() -> None: sa.Column("meta", apps.webui.internal.db.JSONField(), nullable=True), sa.Column("valves", apps.webui.internal.db.JSONField(), nullable=True), sa.Column("is_active", sa.Boolean(), nullable=True), + sa.Column("is_global", sa.Boolean(), nullable=True), sa.Column("updated_at", sa.BigInteger(), nullable=True), sa.Column("created_at", sa.BigInteger(), nullable=True), sa.PrimaryKeyConstraint("id"), From 0c3f9a16e3c3ecd882b69bea2363902889a3c4c8 Mon Sep 17 00:00:00 2001 From: Sergey Mihaylin Date: Fri, 28 Jun 2024 16:31:40 +0300 Subject: [PATCH 017/181] custom env for set custom claims for openid --- backend/apps/webui/main.py | 5 +++++ backend/config.py | 12 ++++++++++++ backend/main.py | 6 ++++-- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/backend/apps/webui/main.py b/backend/apps/webui/main.py index 28b1b4aac..e7f0683c6 100644 --- a/backend/apps/webui/main.py +++ b/backend/apps/webui/main.py @@ -39,6 +39,8 @@ from config import ( WEBUI_BANNERS, ENABLE_COMMUNITY_SHARING, AppConfig, + OAUTH_USERNAME_CLAIM, + OAUTH_PICTURE_CLAIM ) import inspect @@ -74,6 +76,9 @@ app.state.config.BANNERS = WEBUI_BANNERS app.state.config.ENABLE_COMMUNITY_SHARING = ENABLE_COMMUNITY_SHARING +app.state.config.OAUTH_USERNAME_CLAIM = OAUTH_USERNAME_CLAIM +app.state.config.OAUTH_PICTURE_CLAIM = OAUTH_PICTURE_CLAIM + app.state.MODELS = {} app.state.TOOLS = {} app.state.FUNCTIONS = {} diff --git a/backend/config.py b/backend/config.py index 3a825f53a..cd184aab8 100644 --- a/backend/config.py +++ b/backend/config.py @@ -395,6 +395,18 @@ OAUTH_PROVIDER_NAME = PersistentConfig( os.environ.get("OAUTH_PROVIDER_NAME", "SSO"), ) +OAUTH_USERNAME_CLAIM = PersistentConfig( + "OAUTH_USERNAME_CLAIM", + "oauth.oidc.username_claim", + os.environ.get("OAUTH_USERNAME_CLAIM", "name"), +) + +OAUTH_PICTURE_CLAIM = PersistentConfig( + "OAUTH_USERNAME_CLAIM", + "oauth.oidc.avatar_claim", + os.environ.get("OAUTH_PICTURE_CLAIM", "picture"), +) + def load_oauth_providers(): OAUTH_PROVIDERS.clear() diff --git a/backend/main.py b/backend/main.py index aae305c5e..b4fd10c21 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1920,11 +1920,13 @@ async def oauth_callback(provider: str, request: Request, response: Response): # If the user does not exist, check if signups are enabled if ENABLE_OAUTH_SIGNUP.value: # Check if an existing user with the same email already exists - existing_user = Users.get_user_by_email(user_data.get("email", "").lower()) + email_claim = webui_app.state.config.OAUTH_USERNAME_CLAIM + existing_user = Users.get_user_by_email(user_data.get(email_claim, "").lower()) if existing_user: raise HTTPException(400, detail=ERROR_MESSAGES.EMAIL_TAKEN) - picture_url = user_data.get("picture", "") + picture_claim = webui_app.state.config.OAUTH_PICTURE_CLAIM + picture_url = user_data.get(picture_claim, "") if picture_url: # Download the profile image into a base64 string try: From 9f32e9ef602fdb25e69a95d41ae4a96358ed88f2 Mon Sep 17 00:00:00 2001 From: Sergey Mihaylin Date: Fri, 28 Jun 2024 17:08:32 +0300 Subject: [PATCH 018/181] fix username claim --- backend/main.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/main.py b/backend/main.py index b4fd10c21..72527c310 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1920,8 +1920,7 @@ async def oauth_callback(provider: str, request: Request, response: Response): # If the user does not exist, check if signups are enabled if ENABLE_OAUTH_SIGNUP.value: # Check if an existing user with the same email already exists - email_claim = webui_app.state.config.OAUTH_USERNAME_CLAIM - existing_user = Users.get_user_by_email(user_data.get(email_claim, "").lower()) + existing_user = Users.get_user_by_email(user_data.get("email", "").lower()) if existing_user: raise HTTPException(400, detail=ERROR_MESSAGES.EMAIL_TAKEN) @@ -1946,12 +1945,13 @@ async def oauth_callback(provider: str, request: Request, response: Response): picture_url = "" if not picture_url: picture_url = "/user.png" + username_claim = webui_app.state.config.OAUTH_USERNAME_CLAIM user = Auths.insert_new_auth( email=email, password=get_password_hash( str(uuid.uuid4()) ), # Random password, not used - name=user_data.get("name", "User"), + name=user_data.get(username_claim, "User"), profile_image_url=picture_url, role=webui_app.state.config.DEFAULT_USER_ROLE, oauth_sub=provider_sub, From c13844594462b7bbd25104acf744f0118e905750 Mon Sep 17 00:00:00 2001 From: bannert <58707896+bannert1337@users.noreply.github.com> Date: Fri, 28 Jun 2024 19:02:50 +0200 Subject: [PATCH 019/181] feat(i18n): update and optimize de_DE --- src/lib/i18n/locales/de-DE/translation.json | 650 ++++++++++---------- 1 file changed, 325 insertions(+), 325 deletions(-) diff --git a/src/lib/i18n/locales/de-DE/translation.json b/src/lib/i18n/locales/de-DE/translation.json index 84d3c370d..146763a93 100644 --- a/src/lib/i18n/locales/de-DE/translation.json +++ b/src/lib/i18n/locales/de-DE/translation.json @@ -1,135 +1,135 @@ { - "'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' oder '-1' für kein Ablaufdatum.", + "'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' oder '-1' für kein Ablaufzeit.", "(Beta)": "(Beta)", - "(e.g. `sh webui.sh --api --api-auth username_password`)": "", - "(e.g. `sh webui.sh --api`)": "(z.B. `sh webui.sh --api`)", + "(e.g. `sh webui.sh --api --api-auth username_password`)": "(z. B. `sh webui.sh --api --api-auth username_password`)", + "(e.g. `sh webui.sh --api`)": "(z. B. `sh webui.sh --api`)", "(latest)": "(neueste)", "{{ models }}": "{{ Modelle }}", "{{ owner }}: You cannot delete a base model": "{{ owner }}: Sie können ein Basismodell nicht löschen", "{{modelName}} is thinking...": "{{modelName}} denkt nach...", - "{{user}}'s Chats": "{{user}}s Chats", + "{{user}}'s Chats": "{{user}}s Unterhaltungen", "{{webUIName}} Backend Required": "{{webUIName}}-Backend erforderlich", - "A task model is used when performing tasks such as generating titles for chats and web search queries": "Ein Aufgabenmodell wird verwendet, wenn Aufgaben wie das Generieren von Titeln für Chats und Websuchanfragen ausgeführt werden", + "A task model is used when performing tasks such as generating titles for chats and web search queries": "Aufgabenmodelle können Unterhaltungstitel oder Websuchanfragen generieren.", "a user": "ein Benutzer", "About": "Über", - "Account": "Account", - "Account Activation Pending": "", - "Accurate information": "Genaue Information", + "Account": "Konto", + "Account Activation Pending": "Kontoaktivierung ausstehend", + "Accurate information": "Präzise Information(en)", "Active Users": "Aktive Benutzer", "Add": "Hinzufügen", - "Add a model id": "Hinzufügen einer Modell-ID", - "Add a short description about what this model does": "Fügen Sie eine kurze Beschreibung hinzu, was dieses Modell tut", - "Add a short title for this prompt": "Füge einen kurzen Titel für diesen Prompt hinzu", - "Add a tag": "benenne", - "Add custom prompt": "Eigenen Prompt hinzufügen", + "Add a model id": "Modell-ID hinzufügen", + "Add a short description about what this model does": "Fügen Sie eine kurze Beschreibung über dieses Modell hinzu", + "Add a short title for this prompt": "Fügen Sie einen kurzen Titel für diesen Prompt hinzu", + "Add a tag": "Tag hinzufügen", + "Add custom prompt": "Benutzerdefinierten Prompt hinzufügen", "Add Docs": "Dokumente hinzufügen", "Add Files": "Dateien hinzufügen", - "Add Memory": "Speicher hinzufügen", - "Add message": "Nachricht eingeben", + "Add Memory": "Erinnerung hinzufügen", + "Add message": "Nachricht hinzufügen", "Add Model": "Modell hinzufügen", "Add Tags": "Tags hinzufügen", - "Add User": "User hinzufügen", - "Adjusting these settings will apply changes universally to all users.": "Das Anpassen dieser Einstellungen wirkt sich universell auf alle Benutzer aus.", + "Add User": "Benutzer hinzufügen", + "Adjusting these settings will apply changes universally to all users.": "Das Anpassen dieser Einstellungen wird Änderungen universell auf alle Benutzer anwenden.", "admin": "Administrator", - "Admin": "", - "Admin Panel": "Admin Panel", - "Admin Settings": "Admin Einstellungen", - "Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "", + "Admin": "Administrator", + "Admin Panel": "Administrationsbereich", + "Admin Settings": "Administrator-Einstellungen", + "Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "Administratoren haben jederzeit Zugriff auf alle Werkzeuge. Benutzer können im Arbeitsbereich zugewiesen.", "Advanced Parameters": "Erweiterte Parameter", "Advanced Params": "Erweiterte Parameter", "all": "Alle", "All Documents": "Alle Dokumente", "All Users": "Alle Benutzer", "Allow": "Erlauben", - "Allow Chat Deletion": "Chat Löschung erlauben", + "Allow Chat Deletion": "Chat löschen erlauben", "Allow non-local voices": "Nicht-lokale Stimmen erlauben", - "Allow User Location": "", - "Allow Voice Interruption in Call": "", + "Allow User Location": "Standort freigeben", + "Allow Voice Interruption in Call": "Unterbrechung durch Stimme im Anruf zulassen", "alphanumeric characters and hyphens": "alphanumerische Zeichen und Bindestriche", "Already have an account?": "Hast du vielleicht schon ein Account?", "an assistant": "ein Assistent", "and": "und", - "and create a new shared link.": "und einen neuen geteilten Link zu erstellen.", - "API Base URL": "API Basis URL", - "API Key": "API Key", - "API Key created.": "API Key erstellt", - "API keys": "API Schlüssel", + "and create a new shared link.": "und erstellen Sie einen neuen freigegebenen Link.", + "API Base URL": "API-Basis-URL", + "API Key": "API-Schlüssel", + "API Key created.": "API-Schlüssel erstellt.", + "API keys": "API-Schlüssel", "April": "April", "Archive": "Archivieren", - "Archive All Chats": "Alle Chats archivieren", - "Archived Chats": "Archivierte Chats", - "are allowed - Activate this command by typing": "sind erlaubt - Aktiviere diesen Befehl, indem du", - "Are you sure?": "Bist du sicher?", + "Archive All Chats": "Alle Unterhaltungen archivieren", + "Archived Chats": "Archivierte Unterhaltungen", + "are allowed - Activate this command by typing": "sind erlaubt - Aktivieren Sie diesen Befehl durch Eingabe von", + "Are you sure?": "Sind Sie sicher?", "Attach file": "Datei anhängen", - "Attention to detail": "Auge fürs Detail", + "Attention to detail": "Aufmerksamkeit für Details", "Audio": "Audio", - "Audio settings updated successfully": "", + "Audio settings updated successfully": "Audioeinstellungen erfolgreich aktualisiert", "August": "August", - "Auto-playback response": "Automatische Wiedergabe der Antwort", - "AUTOMATIC1111 Api Auth String": "", - "AUTOMATIC1111 Base URL": "AUTOMATIC1111 Basis URL", - "AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 Basis URL wird benötigt", - "available!": "verfügbar!", + "Auto-playback response": "Antwort automatisch abspielen", + "AUTOMATIC1111 Api Auth String": "AUTOMATIC1111-API-Authentifizierungszeichenfolge", + "AUTOMATIC1111 Base URL": "AUTOMATIC1111-Basis-URL", + "AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111-Basis-URL ist erforderlich.", + "available!": "Verfügbar!", "Back": "Zurück", "Bad Response": "Schlechte Antwort", "Banners": "Banner", - "Base Model (From)": "Basismodell (von)", - "Batch Size (num_batch)": "", + "Base Model (From)": "Basismodell (From)", + "Batch Size (num_batch)": "Stapelgröße (num_batch)", "before": "bereits geteilt", - "Being lazy": "Faul sein", - "Brave Search API Key": "API-Schlüssel für die Brave-Suche", - "Bypass SSL verification for Websites": "Bypass SSL-Verifizierung für Websites", - "Call": "", - "Call feature is not supported when using Web STT engine": "", - "Camera": "", + "Being lazy": "Faulheit", + "Brave Search API Key": "Brave Search API-Schlüssel", + "Bypass SSL verification for Websites": "SSL-Überprüfung für Webseiten umgehen", + "Call": "Anrufen", + "Call feature is not supported when using Web STT engine": "Die Anruffunktion wird nicht unterstützt, wenn die Web-STT-Engine verwendet wird.", + "Camera": "Kamera", "Cancel": "Abbrechen", "Capabilities": "Fähigkeiten", "Change Password": "Passwort ändern", - "Chat": "Chat", - "Chat Background Image": "", + "Chat": "Gespräch", + "Chat Background Image": "Unterhaltungs-Hintergrundbild", "Chat Bubble UI": "Chat Bubble UI", - "Chat direction": "Chat Richtung", - "Chat History": "Chat Verlauf", - "Chat History is off for this browser.": "Chat Verlauf ist für diesen Browser ausgeschaltet.", - "Chats": "Chats", + "Chat direction": "Textrichtung", + "Chat History": "Unterhaltungsverlauf", + "Chat History is off for this browser.": "Unterhaltungsverlauf ist in diesem Browser deaktiviert.", + "Chats": "Unterhaltungen", "Check Again": "Erneut überprüfen", "Check for updates": "Nach Updates suchen", "Checking for updates...": "Sucht nach Updates...", "Choose a model before saving...": "Wähle bitte zuerst ein Modell, bevor du speicherst...", - "Chunk Overlap": "Chunk Overlap", - "Chunk Params": "Chunk Parameter", - "Chunk Size": "Chunk Size", + "Chunk Overlap": "Blocküberlappung", + "Chunk Params": "Blockparameter", + "Chunk Size": "Blockgröße", "Citation": "Zitate", - "Clear memory": "Memory löschen", - "Click here for help.": "Klicke hier für Hilfe.", - "Click here to": "Klicke hier, um", - "Click here to download user import template file.": "", - "Click here to select": "Klicke hier um auszuwählen", - "Click here to select a csv file.": "Klicke hier um eine CSV-Datei auszuwählen.", - "Click here to select a py file.": "", - "Click here to select documents.": "Klicke hier um Dokumente auszuwählen", + "Clear memory": "Erinnerungen löschen", + "Click here for help.": "Klicken Sie hier für Hilfe.", + "Click here to": "Klicke Sie hier, um", + "Click here to download user import template file.": "Klicken Sie hier, um die Vorlage für den Benutzerimport herunterzuladen.", + "Click here to select": "Klicke Sie zum Auswählen hier", + "Click here to select a csv file.": "Klicken Sie zum Auswählen einer CSV-Datei hier.", + "Click here to select a py file.": "Klicken Sie zum Auswählen einer py-Datei hier.", + "Click here to select documents.": "Klicken Sie zum Auswählen von Dokumenten hier", "click here.": "hier klicken.", - "Click on the user role button to change a user's role.": "Klicke auf die Benutzerrollenschaltfläche, um die Rolle eines Benutzers zu ändern.", - "Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "", + "Click on the user role button to change a user's role.": "Klicken Sie auf die Benutzerrolle, um sie zu ändern.", + "Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "Schreibberechtigung für die Zwischenablage verweigert. Bitte überprüfen Sie Ihre Browsereinstellungen, um den erforderlichen Zugriff zu erlauben.", "Clone": "Klonen", - "Close": "Schließe", - "Code formatted successfully": "", + "Close": "Schließen", + "Code formatted successfully": "Code erfolgreich formatiert", "Collection": "Kollektion", "ComfyUI": "ComfyUI", - "ComfyUI Base URL": "ComfyUI Base URL", - "ComfyUI Base URL is required.": "ComfyUI Base URL wird benötigt.", + "ComfyUI Base URL": "ComfyUI-Basis-URL", + "ComfyUI Base URL is required.": "ComfyUI-Basis-URL wird benötigt.", "Command": "Befehl", "Concurrent Requests": "Gleichzeitige Anforderungen", - "Confirm": "", + "Confirm": "Bestätigen", "Confirm Password": "Passwort bestätigen", - "Confirm your action": "", + "Confirm your action": "Bestätigen Sie Ihre Aktion.", "Connections": "Verbindungen", - "Contact Admin for WebUI Access": "", + "Contact Admin for WebUI Access": "Kontaktieren Sie den Administrator für den Zugriff auf die Weboberfläche", "Content": "Info", - "Context Length": "Context Length", + "Context Length": "Kontextlänge", "Continue Response": "Antwort fortsetzen", - "Continue with {{provider}}": "", - "Copied shared chat URL to clipboard!": "Geteilte Chat-URL in die Zwischenablage kopiert!", + "Continue with {{provider}}": "Mit {{Anbieter}} fortfahren", + "Copied shared chat URL to clipboard!": "Freigabelink in die Zwischenablage kopiert!", "Copy": "Kopieren", "Copy last code block": "Letzten Codeblock kopieren", "Copy last response": "Letzte Antwort kopieren", @@ -138,17 +138,17 @@ "Create a model": "Ein Modell erstellen", "Create Account": "Konto erstellen", "Create new key": "Neuen Schlüssel erstellen", - "Create new secret key": "Neuen API Schlüssel erstellen", + "Create new secret key": "Neuen API-Schlüssel erstellen", "Created at": "Erstellt am", "Created At": "Erstellt am", - "Created by": "", - "CSV Import": "", + "Created by": "Erstellt von", + "CSV Import": "CSV-Import", "Current Model": "Aktuelles Modell", "Current Password": "Aktuelles Passwort", "Custom": "Benutzerdefiniert", "Customize models for a specific purpose": "Modelle für einen bestimmten Zweck anpassen", "Dark": "Dunkel", - "Dashboard": "", + "Dashboard": "Übersicht", "Database": "Datenbank", "December": "Dezember", "Default": "Standard", @@ -156,52 +156,52 @@ "Default (SentenceTransformers)": "Standard (SentenceTransformers)", "Default Model": "Standardmodell", "Default model updated": "Standardmodell aktualisiert", - "Default Prompt Suggestions": "Standard-Prompt-Vorschläge", + "Default Prompt Suggestions": "Prompt-Vorschläge", "Default User Role": "Standardbenutzerrolle", "delete": "löschen", "Delete": "Löschen", "Delete a model": "Ein Modell löschen", - "Delete All Chats": "Alle Chats löschen", - "Delete chat": "Chat löschen", - "Delete Chat": "Chat löschen", - "Delete chat?": "", - "Delete function?": "", - "Delete prompt?": "", - "delete this link": "diesen Link zu löschen", - "Delete tool?": "", + "Delete All Chats": "Alle Unterhaltungen löschen", + "Delete chat": "Unterhaltung löschen", + "Delete Chat": "Unterhaltung löschen", + "Delete chat?": "Unterhaltung löschen?", + "Delete function?": "Funktion löschen?", + "Delete prompt?": "Prompt löschen?", + "delete this link": "diesen Link löschen", + "Delete tool?": "Werkzeug löschen?", "Delete User": "Benutzer löschen", "Deleted {{deleteModelTag}}": "{{deleteModelTag}} gelöscht", - "Deleted {{name}}": "Gelöscht {{name}}", + "Deleted {{name}}": "{{name}} gelöscht", "Description": "Beschreibung", "Didn't fully follow instructions": "Nicht genau den Answeisungen gefolgt", - "Discover a function": "", - "Discover a model": "Entdecken Sie ein Modell", - "Discover a prompt": "Einen Prompt entdecken", - "Discover a tool": "", - "Discover, download, and explore custom functions": "", - "Discover, download, and explore custom prompts": "Benutzerdefinierte Prompts entdecken, herunterladen und erkunden", - "Discover, download, and explore custom tools": "", - "Discover, download, and explore model presets": "Modellvorgaben entdecken, herunterladen und erkunden", + "Discover a function": "Entdecken Sie weitere Funktionen", + "Discover a model": "Entdecken Sie weitere Modelle", + "Discover a prompt": "Entdecken Sie weitere Prompts", + "Discover a tool": "Entdecken Sie weitere Werkzeuge", + "Discover, download, and explore custom functions": "Entdecken und beziehen Sie benutzerdefinierte Funktionen", + "Discover, download, and explore custom prompts": "Entdecken und beziehen Sie benutzerdefinierte Prompts", + "Discover, download, and explore custom tools": "Entdecken und beziehen Sie benutzerdefinierte Werkzeuge", + "Discover, download, and explore model presets": "Entdecken und beziehen Sie benutzerdefinierte Modellvorlagen", "Dismissible": "ausblendbar", - "Display Emoji in Call": "", - "Display the username instead of You in the Chat": "Den Benutzernamen anstelle von 'du' im Chat anzeigen", + "Display Emoji in Call": "Emojis im Anruf anzeigen", + "Display the username instead of You in the Chat": "Soll \"Sie\" durch Ihren Benutzernamen ersetzt werden?", "Document": "Dokument", "Document Settings": "Dokumenteinstellungen", "Documentation": "Dokumentation", "Documents": "Dokumente", - "does not make any external connections, and your data stays securely on your locally hosted server.": "stellt keine externen Verbindungen her, und Deine Daten bleiben sicher auf Deinen lokal gehosteten Server.", + "does not make any external connections, and your data stays securely on your locally hosted server.": "stellt keine externen Verbindungen her, und Ihre Daten bleiben sicher auf Ihrem lokal gehosteten Server.", "Don't Allow": "Nicht erlauben", - "Don't have an account?": "Hast du vielleicht noch kein Konto?", - "Don't like the style": "Dir gefällt der Style nicht", - "Done": "", - "Download": "Herunterladen", - "Download canceled": "Download abgebrochen", - "Download Database": "Datenbank herunterladen", - "Drop any files here to add to the conversation": "Ziehe Dateien in diesen Bereich, um sie an den Chat anzuhängen", - "e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "z.B. '30s','10m'. Gültige Zeiteinheiten sind 's', 'm', 'h'.", + "Don't have an account?": "Haben Sie noch kein Benutzerkonto?", + "Don't like the style": "schlechter Schreibstil", + "Done": "Erledigt", + "Download": "Exportieren", + "Download canceled": "Exportierung abgebrochen", + "Download Database": "Datenbank exportieren", + "Drop any files here to add to the conversation": "Ziehen Sie beliebige Dateien hierher, um sie dem Gespräch hinzuzufügen", + "e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "z. B. '30s','10m'. Gültige Zeiteinheiten sind 's', 'm', 'h'.", "Edit": "Bearbeiten", "Edit Doc": "Dokument bearbeiten", - "Edit Memory": "", + "Edit Memory": "Erinnerungen bearbeiten", "Edit User": "Benutzer bearbeiten", "Email": "E-Mail", "Embedding Batch Size": "Embedding Batch Größe", @@ -215,8 +215,8 @@ "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Stellen Sie sicher, dass Ihre CSV-Datei 4 Spalten in dieser Reihenfolge enthält: Name, E-Mail, Passwort, Rolle.", "Enter {{role}} message here": "Gib die {{role}} Nachricht hier ein", "Enter a detail about yourself for your LLMs to recall": "Geben Sie einen Detail über sich selbst ein, um für Ihre LLMs zu erinnern", - "Enter api auth string (e.g. username:password)": "", - "Enter Brave Search API Key": "Geben Sie den API-Schlüssel für die Brave-Suche ein", + "Enter api auth string (e.g. username:password)": "Geben Sie die API-Authentifizierungszeichenfolge ein (z. B. Benutzername:Passwort)", + "Enter Brave Search API Key": "Geben Sie den Brave Search API-Schlüssel ein", "Enter Chunk Overlap": "Gib den Chunk Overlap ein", "Enter Chunk Size": "Gib die Chunk Size ein", "Enter Github Raw URL": "Geben Sie die Github Raw-URL ein", @@ -226,62 +226,62 @@ "Enter language codes": "Geben Sie die Sprachcodes ein", "Enter model tag (e.g. {{modelTag}})": "Gib den Model-Tag ein", "Enter Number of Steps (e.g. 50)": "Gib die Anzahl an Schritten ein (z.B. 50)", - "Enter Score": "Score eingeben", + "Enter Score": "Punktzahl eingeben", "Enter Searxng Query URL": "Geben Sie die Searxng-Abfrage-URL ein", - "Enter Serper API Key": "Serper-API-Schlüssel eingeben", - "Enter Serply API Key": "", + "Enter Serper API Key": "Geben Sie den Serper-API-Schlüssel ein", + "Enter Serply API Key": "Geben Sie den", "Enter Serpstack API Key": "Geben Sie den Serpstack-API-Schlüssel ein", "Enter stop sequence": "Stop-Sequenz eingeben", - "Enter Tavily API Key": "", - "Enter Top K": "Gib Top K ein", - "Enter URL (e.g. http://127.0.0.1:7860/)": "Gib die URL ein (z.B. http://127.0.0.1:7860/)", - "Enter URL (e.g. http://localhost:11434)": "Gib die URL ein (z.B. http://localhost:11434)", - "Enter Your Email": "E-Mail-Adresse", - "Enter Your Full Name": "Name", - "Enter Your Password": "Passwort", - "Enter Your Role": "Gebe deine Rolle ein", + "Enter Tavily API Key": "Geben Sie den Tavily-API-Schlüssel ein", + "Enter Top K": "Geben Sie Top K ein", + "Enter URL (e.g. http://127.0.0.1:7860/)": "Geben Sie die URL ein (z. B. http://127.0.0.1:7860/)", + "Enter URL (e.g. http://localhost:11434)": "Geben Sie die URL ein (z. B. http://localhost:11434)", + "Enter Your Email": "Geben Sie Ihre E-Mail-Adresse ein", + "Enter Your Full Name": "Geben Sie Ihren vollständigen Namen ein", + "Enter Your Password": "Geben Sie Ihr Passwort ein", + "Enter Your Role": "Geben Sie Ihre Rolle ein", "Error": "Fehler", "Experimental": "Experimentell", "Export": "Exportieren", - "Export All Chats (All Users)": "Alle Chats exportieren (alle Benutzer)", - "Export chat (.json)": "Chat exportieren (.json)", - "Export Chats": "Chats exportieren", - "Export Documents Mapping": "Dokumentenmapping exportieren", - "Export Functions": "", - "Export LiteLLM config.yaml": "", + "Export All Chats (All Users)": "Alle Unterhaltungen exportieren (alle Benutzer)", + "Export chat (.json)": "Unterhaltung exportieren (.json)", + "Export Chats": "Unterhaltungen exportieren", + "Export Documents Mapping": "Dokumentenzuordnung exportieren", + "Export Functions": "Funktionen exportieren", + "Export LiteLLM config.yaml": "LiteLLM-Konfiguration exportieren (config.yaml)", "Export Models": "Modelle exportieren", "Export Prompts": "Prompts exportieren", - "Export Tools": "", - "External Models": "", - "Failed to create API Key.": "API Key erstellen fehlgeschlagen", - "Failed to read clipboard contents": "Fehler beim Lesen des Zwischenablageninhalts", + "Export Tools": "Werkzeuge exportieren", + "External Models": "Externe Modelle", + "Failed to create API Key.": "Fehler beim Erstellen des API-Schlüssels.", + "Failed to read clipboard contents": "Fehler beim Abruf der Zwischenablage", "Failed to update settings": "Fehler beim Aktualisieren der Einstellungen", "February": "Februar", - "Feel free to add specific details": "Ergänze Details.", - "File": "", - "File Mode": "File Modus", + "Feel free to add specific details": "Fühlen Sie sich frei, spezifische Details hinzuzufügen", + "File": "Datei", + "File Mode": "Datei-Modus", "File not found.": "Datei nicht gefunden.", - "Filter is now globally disabled": "", - "Filter is now globally enabled": "", - "Filters": "", - "Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Fingerprint spoofing erkannt: Initialen können nicht als Avatar verwendet werden. Es wird auf das Standardprofilbild zurückgegriffen.", - "Fluidly stream large external response chunks": "Große externe Antwortblöcke flüssig streamen", + "Filter is now globally disabled": "Filter ist jetzt global deaktiviert", + "Filter is now globally enabled": "Filter ist jetzt global aktiviert", + "Filters": "Filter", + "Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Fingerabdruck-Spoofing erkannt: Initialen können nicht als Avatar verwendet werden. Standard-Avatar wird verwendet.", + "Fluidly stream large external response chunks": "Nahtlose Übertragung großer externer Antwortabschnitte", "Focus chat input": "Chat-Eingabe fokussieren", "Followed instructions perfectly": "Anweisungen perfekt befolgt", - "Form": "", - "Format your variables using square brackets like this:": "Formatiere deine Variablen mit eckigen Klammern wie folgt:", - "Frequency Penalty": "Frequenz-Strafe", - "Function created successfully": "", - "Function deleted successfully": "", - "Function updated successfully": "", - "Functions": "", - "Functions imported successfully": "", + "Form": "Formular", + "Format your variables using square brackets like this:": "Formatieren Sie Ihre Variablen mit eckigen Klammern wie folgt:", + "Frequency Penalty": "Frequenzstrafe", + "Function created successfully": "Funktion erfolgreich erstellt", + "Function deleted successfully": "Funktion erfolgreich gelöscht", + "Function updated successfully": "Funktion erfolgreich aktualisiert", + "Functions": "Funktionen", + "Functions imported successfully": "Funktionen erfolgreich importiert", "General": "Allgemein", "General Settings": "Allgemeine Einstellungen", - "Generate Image": "", - "Generating search query": "Suchanfrage generieren", + "Generate Image": "Bild erzeugen", + "Generating search query": "Suchanfrage wird erstellt", "Generation Info": "Generierungsinformationen", - "Global": "", + "Global": "Global", "Good Response": "Gute Antwort", "Google PSE API Key": "Google PSE-API-Schlüssel", "Google PSE Engine Id": "Google PSE-Engine-ID", @@ -290,25 +290,25 @@ "Hello, {{name}}": "Hallo, {{name}}", "Help": "Hilfe", "Hide": "Verbergen", - "Hide Model": "", - "How can I help you today?": "Wie kann ich dir heute helfen?", + "Hide Model": "Modell ausblenden", + "How can I help you today?": "Wie kann ich Ihnen heute helfen?", "Hybrid Search": "Hybride Suche", "Image Generation (Experimental)": "Bildgenerierung (experimentell)", "Image Generation Engine": "Bildgenerierungs-Engine", "Image Settings": "Bildeinstellungen", "Images": "Bilder", "Import Chats": "Chats importieren", - "Import Documents Mapping": "Dokumentenmapping importieren", - "Import Functions": "", + "Import Documents Mapping": "Dokumentenzuordnung importieren", + "Import Functions": "Funktionen importieren", "Import Models": "Modelle importieren", "Import Prompts": "Prompts importieren", - "Import Tools": "", - "Include `--api-auth` flag when running stable-diffusion-webui": "", - "Include `--api` flag when running stable-diffusion-webui": "Füge das `--api`-Flag hinzu, wenn du stable-diffusion-webui nutzt", + "Import Tools": "Werkzeuge importieren", + "Include `--api-auth` flag when running stable-diffusion-webui": "Fügen Sie beim Ausführen von stable-diffusion-webui die Option `--api-auth` hinzu", + "Include `--api` flag when running stable-diffusion-webui": "Fügen Sie beim Ausführen von stable-diffusion-webui die Option `--api` hinzu", "Info": "Info", "Input commands": "Eingabebefehle", "Install from Github URL": "Installieren Sie von der Github-URL", - "Instant Auto-Send After Voice Transcription": "", + "Instant Auto-Send After Voice Transcription": "Spracherkennung direkt absenden", "Interface": "Benutzeroberfläche", "Invalid Tag": "Ungültiger Tag", "January": "Januar", @@ -319,56 +319,56 @@ "June": "Juni", "JWT Expiration": "JWT-Ablauf", "JWT Token": "JWT-Token", - "Keep Alive": "Keep Alive", - "Keyboard shortcuts": "Tastenkürzel", - "Knowledge": "", + "Keep Alive": "Verbindung aufrechterhalten", + "Keyboard shortcuts": "Tastenkombinationen", + "Knowledge": "Wissen", "Language": "Sprache", "Last Active": "Zuletzt aktiv", - "Last Modified": "", + "Last Modified": "Zuletzt bearbeitet", "Light": "Hell", - "Listening...": "", + "Listening...": "Höre zu...", "LLMs can make mistakes. Verify important information.": "LLMs können Fehler machen. Überprüfe wichtige Informationen.", - "Local Models": "", + "Local Models": "Lokale Modelle", "LTR": "LTR", "Made by OpenWebUI Community": "Von der OpenWebUI-Community", - "Make sure to enclose them with": "Formatiere deine Variablen mit:", + "Make sure to enclose them with": "Umschließen Sie Variablen mit", "Manage": "Verwalten", "Manage Models": "Modelle verwalten", "Manage Ollama Models": "Ollama-Modelle verwalten", - "Manage Pipelines": "Verwalten von Pipelines", - "Manage Valves": "", + "Manage Pipelines": "Pipelines verwalten", + "Manage Valves": "Valves verwalten", "March": "März", - "Max Tokens (num_predict)": "Max. Token (num_predict)", - "Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Es können maximal 3 Modelle gleichzeitig heruntergeladen werden. Bitte versuche es später erneut.", + "Max Tokens (num_predict)": "Maximale Tokenanzahl (num_predict)", + "Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Es können maximal 3 Modelle gleichzeitig heruntergeladen werden. Bitte versuchen Sie es später erneut.", "May": "Mai", - "Memories accessible by LLMs will be shown here.": "Memories, die von LLMs zugänglich sind, werden hier angezeigt.", - "Memory": "Memory", - "Memory added successfully": "", - "Memory cleared successfully": "", - "Memory deleted successfully": "", - "Memory updated successfully": "", - "Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Nachdem Sie Ihren Link erstellt haben, werden Ihre Nachrichten nicht geteilt. Benutzer mit dem Link können den geteilten Chat sehen.", - "Minimum Score": "Mindestscore", + "Memories accessible by LLMs will be shown here.": "Erinnerungen, die für Modelle zugänglich sind, werden hier angezeigt.", + "Memory": "Erinnerung", + "Memory added successfully": "Erinnerung erfolgreich hinzugefügt", + "Memory cleared successfully": "Erinnerung erfolgreich gelöscht", + "Memory deleted successfully": "Erinnerung erfolgreich gelöscht", + "Memory updated successfully": "Erinnerung erfolgreich aktualisiert", + "Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Nachrichten, die Sie nach der Erstellung Ihres Links senden, werden nicht geteilt. Nutzer mit der URL können die freigegebene Unterhaltung einsehen.", + "Minimum Score": "Mindestpunktzahl", "Mirostat": "Mirostat", "Mirostat Eta": "Mirostat Eta", "Mirostat Tau": "Mirostat Tau", "MMMM DD, YYYY": "DD MMMM YYYY", "MMMM DD, YYYY HH:mm": "DD MMMM YYYY HH:mm", - "MMMM DD, YYYY hh:mm:ss A": "", + "MMMM DD, YYYY hh:mm:ss A": "DD MMMM YYYY HH:mm A", "Model '{{modelName}}' has been successfully downloaded.": "Modell '{{modelName}}' wurde erfolgreich heruntergeladen.", "Model '{{modelTag}}' is already in queue for downloading.": "Modell '{{modelTag}}' befindet sich bereits in der Warteschlange zum Herunterladen.", "Model {{modelId}} not found": "Modell {{modelId}} nicht gefunden", "Model {{modelName}} is not vision capable": "Das Modell {{modelName}} ist nicht sehfähig", "Model {{name}} is now {{status}}": "Modell {{name}} ist jetzt {{status}}", - "Model created successfully!": "", + "Model created successfully!": "Modell erfolgreich erstellt!", "Model filesystem path detected. Model shortname is required for update, cannot continue.": "Modell-Dateisystempfad erkannt. Modellkurzname ist für das Update erforderlich, Fortsetzung nicht möglich.", "Model ID": "Modell-ID", "Model not selected": "Modell nicht ausgewählt", "Model Params": "Modell-Params", - "Model updated successfully": "", + "Model updated successfully": "Modell erfolgreich aktualisiert", "Model Whitelisting": "Modell-Whitelisting", "Model(s) Whitelisted": "Modell(e) auf der Whitelist", - "Modelfile Content": "Modelfile Content", + "Modelfile Content": "Modelfile-Inhalt", "Models": "Modelle", "More": "Mehr", "Name": "Name", @@ -376,59 +376,59 @@ "Name your model": "Benennen Sie Ihr Modell", "New Chat": "Neuer Chat", "New Password": "Neues Passwort", - "No content to speak": "", - "No documents found": "", - "No file selected": "", + "No content to speak": "Kein Inhalt zum Vorlesen", + "No documents found": "Keine Dokumente gefunden", + "No file selected": "Keine Datei ausgewählt", "No results found": "Keine Ergebnisse gefunden", "No search query generated": "Keine Suchanfrage generiert", - "No source available": "Keine Quelle verfügbar.", - "No valves to update": "", + "No source available": "Keine Quelle verfügbar", + "No valves to update": "Keine Valves zum Aktualisieren", "None": "Nichts", - "Not factually correct": "Nicht sachlich korrekt.", + "Not factually correct": "Nicht sachlich korrekt", "Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Hinweis: Wenn du einen Mindestscore festlegst, wird die Suche nur Dokumente zurückgeben, deren Score größer oder gleich dem Mindestscore ist.", - "Notifications": "Desktop-Benachrichtigungen", + "Notifications": "Benachrichtigungen", "November": "November", "num_thread (Ollama)": "num_thread (Ollama)", - "OAuth ID": "", + "OAuth ID": "OAuth-ID", "October": "Oktober", "Off": "Aus", "Okay, Let's Go!": "Okay, los geht's!", - "OLED Dark": "OLED Dunkel", + "OLED Dark": "OLED-Dunkel", "Ollama": "Ollama", "Ollama API": "Ollama-API", "Ollama API disabled": "Ollama-API deaktiviert", - "Ollama API is disabled": "", + "Ollama API is disabled": "Ollama-API ist deaktiviert.", "Ollama Version": "Ollama-Version", "On": "Ein", "Only": "Nur", - "Only alphanumeric characters and hyphens are allowed in the command string.": "Nur alphanumerische Zeichen und Bindestriche sind im Befehlsstring erlaubt.", - "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.": "Hoppla! Warte noch einen Moment! Die Dateien sind noch im der Verarbeitung. Bitte habe etwas Geduld und wir informieren Dich, sobald sie bereit sind.", - "Oops! Looks like the URL is invalid. Please double-check and try again.": "Hoppla! Es sieht so aus, als wäre die URL ungültig. Bitte überprüfe sie und versuche es nochmal.", - "Oops! There was an error in the previous response. Please try again or contact admin.": "", + "Only alphanumeric characters and hyphens are allowed in the command string.": "In der Befehlszeichenfolge sind nur alphanumerische Zeichen und Bindestriche erlaubt.", + "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.": "Ups! Bitte gedulden Sie sich einen Moment! Ihre Dateien sind noch in der Verarbeitung. Wir bereiten sie sorgfältig vor. Bitte haben Sie etwas Geduld, und wir werden Sie benachrichtigen, sobald sie fertig sind.", + "Oops! Looks like the URL is invalid. Please double-check and try again.": "Hoppla! Es scheint, dass die URL ungültig ist. Bitte überprüfen Sie diese und versuchen Sie es erneut.", + "Oops! There was an error in the previous response. Please try again or contact admin.": "Hoppla! Es gab einen Fehler in der vorherigen Antwort. Bitte versuchen Sie es erneut oder kontaktieren Sie den Administrator.", "Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Hoppla! du verwendest eine nicht unterstützte Methode (nur Frontend). Bitte stelle die WebUI vom Backend aus bereit.", "Open": "Öffne", "Open AI (Dall-E)": "Open AI (Dall-E)", "Open new chat": "Neuen Chat öffnen", "OpenAI": "OpenAI", "OpenAI API": "OpenAI-API", - "OpenAI API Config": "OpenAI API Konfiguration", - "OpenAI API Key is required.": "OpenAI API Key erforderlich.", - "OpenAI URL/Key required.": "OpenAI URL/Key erforderlich.", + "OpenAI API Config": "OpenAI-API-Konfiguration", + "OpenAI API Key is required.": "OpenAI-API-Schlüssel erforderlich.", + "OpenAI URL/Key required.": "OpenAI-URL/Schlüssel erforderlich.", "or": "oder", "Other": "Andere", "Password": "Passwort", "PDF document (.pdf)": "PDF-Dokument (.pdf)", "PDF Extract Images (OCR)": "Text von Bildern aus PDFs extrahieren (OCR)", "pending": "ausstehend", - "Permission denied when accessing media devices": "", - "Permission denied when accessing microphone": "", + "Permission denied when accessing media devices": "Zugriff auf Mediengeräte verweigert", + "Permission denied when accessing microphone": "Zugriff auf das Mikrofon verweigert", "Permission denied when accessing microphone: {{error}}": "Zugriff auf das Mikrofon verweigert: {{error}}", "Personalization": "Personalisierung", - "Pipeline deleted successfully": "", - "Pipeline downloaded successfully": "", + "Pipeline deleted successfully": "Pipeline erfolgreich gelöscht", + "Pipeline downloaded successfully": "Pipeline erfolgreich heruntergeladen", "Pipelines": "Pipelines", - "Pipelines Not Detected": "", - "Pipelines Valves": "Rohrleitungen Ventile", + "Pipelines Not Detected": "Pipelines nicht erkannt", + "Pipelines Valves": "Pipeline Valves", "Plain text (.txt)": "Nur Text (.txt)", "Playground": "Testumgebung", "Positive attitude": "Positive Einstellung", @@ -436,232 +436,232 @@ "Previous 7 days": "Vorherige 7 Tage", "Profile Image": "Profilbild", "Prompt": "Prompt", - "Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Prompt (z.B. Erzähle mir eine interessante Tatsache über das Römische Reich.", + "Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Prompt (z. B. \"Erzähle mir eine interessante Tatsache über das Römische Reich\")", "Prompt Content": "Prompt-Inhalt", "Prompt suggestions": "Prompt-Vorschläge", "Prompts": "Prompts", - "Pull \"{{searchValue}}\" from Ollama.com": "\"{{searchValue}}\" von Ollama.com herunterladen", - "Pull a model from Ollama.com": "Ein Modell von Ollama.com abrufen", - "Query Params": "Abfrage Parameter", - "RAG Template": "RAG-Template", + "Pull \"{{searchValue}}\" from Ollama.com": "\"{{searchValue}}\" von Ollama.com beziehen", + "Pull a model from Ollama.com": "Modell von Ollama.com beziehn", + "Query Params": "Abfrageparameter", + "RAG Template": "RAG-Vorlage", "Read Aloud": "Vorlesen", "Record voice": "Stimme aufnehmen", - "Redirecting you to OpenWebUI Community": "Du wirst zur OpenWebUI-Community weitergeleitet", + "Redirecting you to OpenWebUI Community": "Sie werden zur OpenWebUI-Community weitergeleitet", "Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "", - "Refused when it shouldn't have": "Abgelehnt, obwohl es nicht hätte sein sollen.", + "Refused when it shouldn't have": "Abgelehnt, obwohl es nicht hätte abgelehnt werden sollen", "Regenerate": "Neu generieren", - "Release Notes": "Versionshinweise", + "Release Notes": "Veröffentlichungshinweise", "Remove": "Entfernen", "Remove Model": "Modell entfernen", "Rename": "Umbenennen", - "Repeat Last N": "Repeat Last N", - "Request Mode": "Request-Modus", - "Reranking Model": "Reranking Modell", - "Reranking model disabled": "Rranking Modell deaktiviert", - "Reranking model set to \"{{reranking_model}}\"": "Reranking Modell auf \"{{reranking_model}}\" gesetzt", - "Reset": "", - "Reset Upload Directory": "Uploadverzeichnis löschen", + "Repeat Last N": "Wiederhole die letzten N", + "Request Mode": "Anforderungsmodus", + "Reranking Model": "Reranking-Modell", + "Reranking model disabled": "Reranking-Modell deaktiviert", + "Reranking model set to \"{{reranking_model}}\"": "Reranking-Modell \"{{reranking_model}}\" fesgelegt", + "Reset": "Zurücksetzen", + "Reset Upload Directory": "Upload-Verzeichnis zurücksetzen", "Reset Vector Storage": "Vektorspeicher zurücksetzen", "Response AutoCopy to Clipboard": "Antwort automatisch in die Zwischenablage kopieren", - "Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "", + "Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "Benachrichtigungen können nicht aktiviert werden, da die Website-Berechtigungen abgelehnt wurden. Bitte besuchen Sie Ihre Browser-Einstellungen, um den erforderlichen Zugriff zu gewähren.", "Role": "Rolle", "Rosé Pine": "Rosé Pine", "Rosé Pine Dawn": "Rosé Pine Dawn", "RTL": "RTL", "Running": "Läuft", "Save": "Speichern", - "Save & Create": "Speichern und erstellen", - "Save & Update": "Speichern und aktualisieren", - "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": "Das direkte Speichern von Chat-Protokollen im Browser-Speicher wird nicht mehr unterstützt. Bitte nimm dir einen Moment Zeit, um deine Chat-Protokolle herunterzuladen und zu löschen, indem du auf die Schaltfläche unten klickst. Keine Sorge, du kannst deine Chat-Protokolle problemlos über das Backend wieder importieren.", + "Save & Create": "Erstellen", + "Save & Update": "Aktualisieren", + "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": "Das direkte Speichern von Unterhaltungen im Browser-Speicher wird nicht mehr unterstützt. Bitte nehmen Sie einen Moment Zeit, um Ihre Unterhaltungen zu exportieren und zu löschen, indem Sie auf die Schaltfläche unten klicken. Keine Sorge, Sie können Ihre Unterhaltungen problemlos über das Backend wieder importieren.", "Scan": "Scannen", "Scan complete!": "Scan abgeschlossen!", - "Scan for documents from {{path}}": "Dokumente von {{path}} scannen", + "Scan for documents from {{path}}": "Dokumente im {{path}} scannen", "Search": "Suchen", - "Search a model": "Nach einem Modell suchen", - "Search Chats": "Chats durchsuchen", - "Search Documents": "Dokumente suchen", - "Search Functions": "", - "Search Models": "Modelle suchen", - "Search Prompts": "Prompts suchen", - "Search Query Generation Prompt": "", - "Search Query Generation Prompt Length Threshold": "", + "Search a model": "Modell suchen", + "Search Chats": "Unterhaltungen durchsuchen...", + "Search Documents": "Dokumente durchsuchen...", + "Search Functions": "Funktionen durchsuchen...", + "Search Models": "Modelle durchsuchen...", + "Search Prompts": "Prompts durchsuchen...", + "Search Query Generation Prompt": "Suchanfragen-Generierungs-Prompt", + "Search Query Generation Prompt Length Threshold": "Suchanfragen-Generierungs-Prompt-Längenschwellenwert", "Search Result Count": "Anzahl der Suchergebnisse", - "Search Tools": "", - "Searched {{count}} sites_one": "Gesucht {{count}} sites_one", - "Searched {{count}} sites_other": "Gesucht {{count}} sites_other", - "Searching \"{{searchQuery}}\"": "", + "Search Tools": "Suchwerkzeuge", + "Searched {{count}} sites_one": "{{count}} Seite durchsucht", + "Searched {{count}} sites_other": "{{count}} Seiten durchsucht", + "Searching \"{{searchQuery}}\"": "Suche nach \"{{searchQuery}}\"", "Searxng Query URL": "Searxng-Abfrage-URL", "See readme.md for instructions": "Anleitung in readme.md anzeigen", - "See what's new": "Was gibt's Neues", + "See what's new": "Entdecken Sie die Neuigkeiten", "Seed": "Seed", "Select a base model": "Wählen Sie ein Basismodell", - "Select a engine": "Wähle eine Engine", - "Select a function": "", - "Select a mode": "Einen Modus auswählen", - "Select a model": "Ein Modell auswählen", - "Select a pipeline": "Wählen Sie eine Pipeline aus", - "Select a pipeline url": "Auswählen einer Pipeline-URL", - "Select a tool": "", - "Select an Ollama instance": "Eine Ollama Instanz auswählen", - "Select Documents": "", + "Select a engine": "Wählen Sie eine Engine", + "Select a function": "Wählen Sie eine Funktion", + "Select a mode": "Wählen Sie einen Modus", + "Select a model": "Wählen Sie ein Modell", + "Select a pipeline": "Wählen Sie eine Pipeline", + "Select a pipeline url": "Wählen Sie eine Pipeline-URL", + "Select a tool": "Wählen Sie ein Werkzeug", + "Select an Ollama instance": "Wählen Sie eine Ollama-Instanz", + "Select Documents": "Dokumente auswählen", "Select model": "Modell auswählen", - "Select only one model to call": "", - "Selected model(s) do not support image inputs": "Ausgewählte Modelle unterstützen keine Bildeingaben", + "Select only one model to call": "Wählen Sie nur ein Modell zum Anrufen aus", + "Selected model(s) do not support image inputs": "Ihre ausgewählten Modelle unterstützen keine Bildeingaben", "Send": "Senden", "Send a Message": "Eine Nachricht senden", "Send message": "Nachricht senden", "September": "September", "Serper API Key": "Serper-API-Schlüssel", - "Serply API Key": "", + "Serply API Key": "Serply-API-Schlüssel", "Serpstack API Key": "Serpstack-API-Schlüssel", "Server connection verified": "Serververbindung überprüft", "Set as default": "Als Standard festlegen", "Set Default Model": "Standardmodell festlegen", - "Set embedding model (e.g. {{model}})": "Eingabemodell festlegen (z.B. {{model}})", + "Set embedding model (e.g. {{model}})": "Einbettungsmodell festlegen (z. B. {{model}})", "Set Image Size": "Bildgröße festlegen", - "Set reranking model (e.g. {{model}})": "Rerankingmodell festlegen (z.B. {{model}})", - "Set Steps": "Schritte festlegen", + "Set reranking model (e.g. {{model}})": "Rerankingmodell festlegen (z. B. {{model}})", + "Set Steps": "Schrittgröße festlegen", "Set Task Model": "Aufgabenmodell festlegen", "Set Voice": "Stimme festlegen", "Settings": "Einstellungen", "Settings saved successfully!": "Einstellungen erfolgreich gespeichert!", - "Settings updated successfully": "Settings updated successfully", + "Settings updated successfully": "Einstellungen erfolgreich aktualisiert", "Share": "Teilen", - "Share Chat": "Chat teilen", + "Share Chat": "Unterhaltung teilen", "Share to OpenWebUI Community": "Mit OpenWebUI Community teilen", "short-summary": "kurze-zusammenfassung", "Show": "Anzeigen", "Show Admin Details in Account Pending Overlay": "Admin-Details im Account-Pending-Overlay anzeigen", - "Show Model": "", + "Show Model": "Modell anzeigen", "Show shortcuts": "Verknüpfungen anzeigen", - "Show your support!": "", - "Showcased creativity": "Kreativität zur Schau gestellt", + "Show your support!": "Zeigen Sie Ihre Unterstützung!", + "Showcased creativity": "Kreativität gezeigt", "sidebar": "Seitenleiste", "Sign in": "Anmelden", "Sign Out": "Abmelden", "Sign up": "Registrieren", "Signing in": "Anmeldung", - "Source": "Quellen", + "Source": "Quelle", "Speech recognition error: {{error}}": "Spracherkennungsfehler: {{error}}", "Speech-to-Text Engine": "Sprache-zu-Text-Engine", - "Stop Sequence": "Stop Sequence", - "STT Model": "", + "Stop Sequence": "Stop-Sequenz", + "STT Model": "STT-Modell", "STT Settings": "STT-Einstellungen", "Submit": "Senden", - "Subtitle (e.g. about the Roman Empire)": "Untertitel (z.B. über das Römische Reich)", + "Subtitle (e.g. about the Roman Empire)": "Untertitel (z. B. über das Römische Reich)", "Success": "Erfolg", "Successfully updated.": "Erfolgreich aktualisiert.", "Suggested": "Vorgeschlagen", "System": "System", "System Prompt": "System-Prompt", "Tags": "Tags", - "Tap to interrupt": "", - "Tavily API Key": "", + "Tap to interrupt": "Zum Unterbrechen tippen", + "Tavily API Key": "Tavily-API-Schlüssel", "Tell us more:": "Erzähl uns mehr", "Temperature": "Temperatur", "Template": "Vorlage", "Text Completion": "Textvervollständigung", "Text-to-Speech Engine": "Text-zu-Sprache-Engine", "Tfs Z": "Tfs Z", - "Thanks for your feedback!": "Danke für dein Feedback", - "The score should be a value between 0.0 (0%) and 1.0 (100%).": "Der Score sollte ein Wert zwischen 0,0 (0 %) und 1,0 (100 %) sein.", + "Thanks for your feedback!": "Danke für Ihr Feedback!", + "The score should be a value between 0.0 (0%) and 1.0 (100%).": "Die Punktzahl sollte ein Wert zwischen 0,0 (0 %) und 1,0 (100 %) sein.", "Theme": "Design", - "Thinking...": "", - "This action cannot be undone. Do you wish to continue?": "", - "This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Dadurch werden deine wertvollen Unterhaltungen sicher in der Backend-Datenbank gespeichert. Vielen Dank!", - "This is an experimental feature, it may not function as expected and is subject to change at any time.": "", + "Thinking...": "Denke nach...", + "This action cannot be undone. Do you wish to continue?": "Diese Aktion kann nicht rückgängig gemacht werden. Möchten Sie fortfahren?", + "This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Dies stellt sicher, dass Ihre wertvollen Unterhaltungen sicher in Ihrer Backend-Datenbank gespeichert werden. Vielen Dank!", + "This is an experimental feature, it may not function as expected and is subject to change at any time.": "Dies ist eine experimentelle Funktion, sie funktioniert möglicherweise nicht wie erwartet und kann jederzeit geändert werden.", "This setting does not sync across browsers or devices.": "Diese Einstellung wird nicht zwischen Browsern oder Geräten synchronisiert.", - "This will delete": "", - "Thorough explanation": "Genaue Erklärung", - "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Tipp: Aktualisiere mehrere Variablen nacheinander, indem du nach jeder Aktualisierung die Tabulatortaste im Chat-Eingabefeld drückst.", + "This will delete": "Dies löscht", + "Thorough explanation": "Ausführliche Erklärung", + "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Tipp: Aktualisieren Sie mehrere Variablenfelder nacheinander, indem Sie nach jedem Ersetzen die Tabulatortaste im Eingabefeld der Unterhaltung drücken.", "Title": "Titel", - "Title (e.g. Tell me a fun fact)": "Titel (z.B. Erzähle mir eine lustige Tatsache", - "Title Auto-Generation": "Automatische Titelgenerierung", + "Title (e.g. Tell me a fun fact)": "Titel (z. B. Erzähl mir einen lustigen Fakt)", + "Title Auto-Generation": "Automatische Titelerstellung", "Title cannot be an empty string.": "Titel darf nicht leer sein.", - "Title Generation Prompt": "Prompt für Titelgenerierung", + "Title Generation Prompt": "Titelerstellung-Prompt", "to": "für", - "To access the available model names for downloading,": "Um auf die verfügbaren Modellnamen zum Herunterladen zuzugreifen,", - "To access the GGUF models available for downloading,": "Um auf die verfügbaren GGUF Modelle zum Herunterladen zuzugreifen", - "To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "", - "To add documents here, upload them to the \"Documents\" workspace first.": "", - "to chat input.": "to chat input.", - "To select filters here, add them to the \"Functions\" workspace first.": "", - "To select toolkits here, add them to the \"Tools\" workspace first.": "", + "To access the available model names for downloading,": "Um auf die verfügbaren Modellnamen zuzugreifen,", + "To access the GGUF models available for downloading,": "Um auf die verfügbaren GGUF-Modelle zuzugreifen,", + "To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "Um auf das WebUI zugreifen zu könnrn, wenden Sie sich bitte an einen Administrator. Administratoren können den Benutzerstatus über das Admin-Panel verwalten.", + "To add documents here, upload them to the \"Documents\" workspace first.": "Um Dokumente hinzuzufügen, laden Sie sie zuerst im Arbeitsbereich „Dokumente“ hoch.", + "to chat input.": "zum Eingabefeld der Unterhaltung.", + "To select filters here, add them to the \"Functions\" workspace first.": "Um Filter auszuwählen, fügen Sie diese zuerst dem Arbeitsbereich „Funktionen“ hinzu.", + "To select toolkits here, add them to the \"Tools\" workspace first.": "Um Toolkits auszuwählen, fügen Sie sie zuerst zum Arbeitsbereich „Werkzeuge“ hinzu.", "Today": "Heute", "Toggle settings": "Einstellungen umschalten", "Toggle sidebar": "Seitenleiste umschalten", - "Tokens To Keep On Context Refresh (num_keep)": "", - "Tool created successfully": "", - "Tool deleted successfully": "", - "Tool imported successfully": "", - "Tool updated successfully": "", - "Tools": "", + "Tokens To Keep On Context Refresh (num_keep)": "Beizubehaltende Tokens bei Kontextaktualisierung (num_keep)", + "Tool created successfully": "Werkzeug erfolgreich erstellt", + "Tool deleted successfully": "Werkzeug erfolgreich gelöscht", + "Tool imported successfully": "Werkzeug erfolgreich importiert", + "Tool updated successfully": "Werkzeug erfolgreich aktualisiert", + "Tools": "Werkzeuge", "Top K": "Top K", "Top P": "Top P", "Trouble accessing Ollama?": "Probleme beim Zugriff auf Ollama?", - "TTS Model": "", + "TTS Model": "TTS-Modell", "TTS Settings": "TTS-Einstellungen", - "TTS Voice": "", + "TTS Voice": "TTS-Stimme", "Type": "Art", "Type Hugging Face Resolve (Download) URL": "Gib die Hugging Face Resolve (Download) URL ein", "Uh-oh! There was an issue connecting to {{provider}}.": "Ups! Es gab ein Problem bei der Verbindung mit {{provider}}.", - "UI": "", - "Unknown file type '{{file_type}}'. Proceeding with the file upload anyway.": "", - "Update": "", - "Update and Copy Link": "Erneuern und kopieren", + "UI": "Oberfläche", + "Unknown file type '{{file_type}}'. Proceeding with the file upload anyway.": "Unbekannter Dateityp '{{file_type}}'. Der Datei-Upload wird trotzdem fortgesetzt.", + "Update": "Aktualisieren", + "Update and Copy Link": "Aktualisieren und Link kopieren", "Update password": "Passwort aktualisieren", - "Updated at": "", - "Upload": "", - "Upload a GGUF model": "GGUF Model hochladen", - "Upload Files": "Dateien hochladen", - "Upload Pipeline": "", - "Upload Progress": "Upload Progress", - "URL Mode": "URL Modus", - "Use '#' in the prompt input to load and select your documents.": "Verwende '#' in der Prompt-Eingabe, um deine Dokumente zu laden und auszuwählen.", + "Updated at": "Aktualisiert am", + "Upload": "Hochladen", + "Upload a GGUF model": "GGUF-Model hochladen", + "Upload Files": "Datei(en) hochladen", + "Upload Pipeline": "Pipeline hochladen", + "Upload Progress": "Hochladefortschritt", + "URL Mode": "URL-Modus", + "Use '#' in the prompt input to load and select your documents.": "Verwenden Sie '#' in der Eingabeaufforderung, um Ihre Dokumente zu laden und auszuwählen.", "Use Gravatar": "Gravatar verwenden", "Use Initials": "Initialen verwenden", "use_mlock (Ollama)": "use_mlock (Ollama)", "use_mmap (Ollama)": "use_mmap (Ollama)", "user": "Benutzer", - "User location successfully retrieved.": "", + "User location successfully retrieved.": "Benutzerstandort erfolgreich ermittelt.", "User Permissions": "Benutzerberechtigungen", "Users": "Benutzer", - "Utilize": "Nutze die", + "Utilize": "Verwende", "Valid time units:": "Gültige Zeiteinheiten:", - "Valves": "", - "Valves updated": "", - "Valves updated successfully": "", + "Valves": "Valves", + "Valves updated": "Valves aktualisiert", + "Valves updated successfully": "Valves erfolgreich aktualisiert", "variable": "Variable", "variable to have them replaced with clipboard content.": "Variable, um den Inhalt der Zwischenablage beim Nutzen des Prompts zu ersetzen.", "Version": "Version", - "Voice": "", + "Voice": "Stimme", "Warning": "Warnung", - "Warning: If you update or change your embedding model, you will need to re-import all documents.": "Warnung: Wenn du dein Einbettungsmodell aktualisierst oder änderst, musst du alle Dokumente erneut importieren.", + "Warning: If you update or change your embedding model, you will need to re-import all documents.": "Warnung: Wenn Sie das Einbettungsmodell aktualisieren oder ändern, müssen Sie alle Dokumente erneut importieren.", "Web": "Web", - "Web API": "", + "Web API": "Web-API", "Web Loader Settings": "Web Loader Einstellungen", "Web Params": "Web Parameter", "Web Search": "Websuche", - "Web Search Engine": "Web-Suchmaschine", + "Web Search Engine": "Suchmaschine", "Webhook URL": "Webhook URL", "WebUI Settings": "WebUI-Einstellungen", "WebUI will make requests to": "Wenn aktiviert sendet WebUI externe Anfragen an", - "What’s New in": "Was gibt's Neues in", - "When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Wenn die Historie ausgeschaltet ist, werden neue Chats nicht in deiner Historie auf deine Geräte angezeigt.", - "Whisper (Local)": "", - "Widescreen Mode": "Widescreen Modus", + "What’s New in": "Neuigkeiten von", + "When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Wenn der Verlauf deaktiviert ist, werden neue Unterhaltungen in diesem Browser nicht im Verlauf Ihrer anderen Geräte erscheinen.", + "Whisper (Local)": "Whisper (lokal)", + "Widescreen Mode": "Breitbildmodus", "Workspace": "Arbeitsbereich", - "Write a prompt suggestion (e.g. Who are you?)": "Gebe einen Prompt-Vorschlag ein (z.B. Wer bist du?)", + "Write a prompt suggestion (e.g. Who are you?)": "Gebe einen Prompt-Vorschlag ein (z. B. \"Wer bist du?\")", "Write a summary in 50 words that summarizes [topic or keyword].": "Schreibe eine kurze Zusammenfassung in 50 Wörtern, die [Thema oder Schlüsselwort] zusammenfasst.", "Yesterday": "Gestern", - "You": "Du", - "You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Du kannst deine Interaktionen mit LLMs personalisieren, indem du Erinnerungen durch den 'Verwalten'-Button unten hinzufügst, um sie hilfreicher und auf dich zugeschnitten zu machen.", - "You cannot clone a base model": "Sie können ein Basismodell nicht klonen", + "You": "Sie", + "You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Personalisieren Sie Interaktionen mit LLMs, indem Sie über die Schaltfläche \"Verwalten\" Erinnerungen hinzufügen.", + "You cannot clone a base model": "Sie können Basismodelle nicht klonen", "You have no archived conversations.": "Du hast keine archivierten Unterhaltungen.", - "You have shared this chat": "Du hast diesen Chat", + "You have shared this chat": "Sie haben diese Unterhaltung geteilt", "You're a helpful assistant.": "Du bist ein hilfreicher Assistent.", - "You're now logged in.": "Du bist nun eingeloggt.", - "Your account status is currently pending activation.": "", + "You're now logged in.": "Sie sind jetzt eingeloggt.", + "Your account status is currently pending activation.": "Ihr Kontostatus ist derzeit ausstehend und wartet auf Aktivierung.", "Youtube": "YouTube", "Youtube Loader Settings": "YouTube-Ladeeinstellungen" } From 24f84a6069cfdd1e2cfd8248ad79656a66e142bd Mon Sep 17 00:00:00 2001 From: bannert <58707896+bannert1337@users.noreply.github.com> Date: Fri, 28 Jun 2024 19:19:45 +0200 Subject: [PATCH 020/181] feat(i18n): fixed a word in German translation --- src/lib/i18n/locales/de-DE/translation.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/i18n/locales/de-DE/translation.json b/src/lib/i18n/locales/de-DE/translation.json index 146763a93..cbafa747e 100644 --- a/src/lib/i18n/locales/de-DE/translation.json +++ b/src/lib/i18n/locales/de-DE/translation.json @@ -1,5 +1,5 @@ { - "'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' oder '-1' für kein Ablaufzeit.", + "'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' oder '-1' für keine Ablaufzeit.", "(Beta)": "(Beta)", "(e.g. `sh webui.sh --api --api-auth username_password`)": "(z. B. `sh webui.sh --api --api-auth username_password`)", "(e.g. `sh webui.sh --api`)": "(z. B. `sh webui.sh --api`)", From 6ffa18981417e734d8870d83a7b10d6a6d7ba9f0 Mon Sep 17 00:00:00 2001 From: bannert <58707896+bannert1337@users.noreply.github.com> Date: Fri, 28 Jun 2024 20:03:36 +0200 Subject: [PATCH 021/181] i18n: split locale for German formal and informal translation-common.json is the base translation-formal.json and translation-informal.json extend the base --- .../locales/de-DE/translation-common.json | 0 ...anslation.json => translation-formal.json} | 12 +- .../locales/de-DE/translation-informal.json | 667 ++++++++++++++++++ src/lib/i18n/locales/languages.json | 10 +- 4 files changed, 680 insertions(+), 9 deletions(-) create mode 100644 src/lib/i18n/locales/de-DE/translation-common.json rename src/lib/i18n/locales/de-DE/{translation.json => translation-formal.json} (98%) create mode 100644 src/lib/i18n/locales/de-DE/translation-informal.json diff --git a/src/lib/i18n/locales/de-DE/translation-common.json b/src/lib/i18n/locales/de-DE/translation-common.json new file mode 100644 index 000000000..e69de29bb diff --git a/src/lib/i18n/locales/de-DE/translation.json b/src/lib/i18n/locales/de-DE/translation-formal.json similarity index 98% rename from src/lib/i18n/locales/de-DE/translation.json rename to src/lib/i18n/locales/de-DE/translation-formal.json index cbafa747e..63766cf67 100644 --- a/src/lib/i18n/locales/de-DE/translation.json +++ b/src/lib/i18n/locales/de-DE/translation-formal.json @@ -197,7 +197,7 @@ "Download": "Exportieren", "Download canceled": "Exportierung abgebrochen", "Download Database": "Datenbank exportieren", - "Drop any files here to add to the conversation": "Ziehen Sie beliebige Dateien hierher, um sie dem Gespräch hinzuzufügen", + "Drop any files here to add to the conversation": "Ziehen Sie beliebige Dateien hierher, um sie der Unterhaltung hinzuzufügen", "e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "z. B. '30s','10m'. Gültige Zeiteinheiten sind 's', 'm', 'h'.", "Edit": "Bearbeiten", "Edit Doc": "Dokument bearbeiten", @@ -213,8 +213,8 @@ "Enable New Sign Ups": "Neue Anmeldungen aktivieren", "Enable Web Search": "Websuche aktivieren", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Stellen Sie sicher, dass Ihre CSV-Datei 4 Spalten in dieser Reihenfolge enthält: Name, E-Mail, Passwort, Rolle.", - "Enter {{role}} message here": "Gib die {{role}} Nachricht hier ein", - "Enter a detail about yourself for your LLMs to recall": "Geben Sie einen Detail über sich selbst ein, um für Ihre LLMs zu erinnern", + "Enter {{role}} message here": "Geben Sie die {{role}}-Nachricht hier ein", + "Enter a detail about yourself for your LLMs to recall": "Geben Sie ein Detail über sich selbst ein, das Ihre Sprachmodelle (LLMs) sich merken sollen", "Enter api auth string (e.g. username:password)": "Geben Sie die API-Authentifizierungszeichenfolge ein (z. B. Benutzername:Passwort)", "Enter Brave Search API Key": "Geben Sie den Brave Search API-Schlüssel ein", "Enter Chunk Overlap": "Gib den Chunk Overlap ein", @@ -307,7 +307,7 @@ "Include `--api` flag when running stable-diffusion-webui": "Fügen Sie beim Ausführen von stable-diffusion-webui die Option `--api` hinzu", "Info": "Info", "Input commands": "Eingabebefehle", - "Install from Github URL": "Installieren Sie von der Github-URL", + "Install from Github URL": "Installiere von der Github-URL", "Instant Auto-Send After Voice Transcription": "Spracherkennung direkt absenden", "Interface": "Benutzeroberfläche", "Invalid Tag": "Ungültiger Tag", @@ -331,7 +331,7 @@ "Local Models": "Lokale Modelle", "LTR": "LTR", "Made by OpenWebUI Community": "Von der OpenWebUI-Community", - "Make sure to enclose them with": "Umschließen Sie Variablen mit", + "Make sure to enclose them with": "Umschließe Variablen mit", "Manage": "Verwalten", "Manage Models": "Modelle verwalten", "Manage Ollama Models": "Ollama-Modelle verwalten", @@ -358,7 +358,7 @@ "Model '{{modelName}}' has been successfully downloaded.": "Modell '{{modelName}}' wurde erfolgreich heruntergeladen.", "Model '{{modelTag}}' is already in queue for downloading.": "Modell '{{modelTag}}' befindet sich bereits in der Warteschlange zum Herunterladen.", "Model {{modelId}} not found": "Modell {{modelId}} nicht gefunden", - "Model {{modelName}} is not vision capable": "Das Modell {{modelName}} ist nicht sehfähig", + "Model {{modelName}} is not vision capable": "Das Modell {{modelName}} ist nicht für die Bildverarbeitung geeignet", "Model {{name}} is now {{status}}": "Modell {{name}} ist jetzt {{status}}", "Model created successfully!": "Modell erfolgreich erstellt!", "Model filesystem path detected. Model shortname is required for update, cannot continue.": "Modell-Dateisystempfad erkannt. Modellkurzname ist für das Update erforderlich, Fortsetzung nicht möglich.", diff --git a/src/lib/i18n/locales/de-DE/translation-informal.json b/src/lib/i18n/locales/de-DE/translation-informal.json new file mode 100644 index 000000000..c69f875a0 --- /dev/null +++ b/src/lib/i18n/locales/de-DE/translation-informal.json @@ -0,0 +1,667 @@ +{ + "'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' oder '-1' für keine Ablaufzeit.", + "(Beta)": "(Beta)", + "(e.g. `sh webui.sh --api --api-auth username_password`)": "(z. B. `sh webui.sh --api --api-auth username_password`)", + "(e.g. `sh webui.sh --api`)": "(z. B. `sh webui.sh --api`)", + "(latest)": "(neueste)", + "{{ models }}": "{{ Modelle }}", + "{{ owner }}: You cannot delete a base model": "{{ owner }}: Du kannst ein Basismodell nicht löschen", + "{{modelName}} is thinking...": "{{modelName}} denkt nach...", + "{{user}}'s Chats": "{{user}}s Unterhaltungen", + "{{webUIName}} Backend Required": "{{webUIName}}-Backend erforderlich", + "A task model is used when performing tasks such as generating titles for chats and web search queries": "Aufgabenmodelle können Unterhaltungstitel oder Websuchanfragen generieren.", + "a user": "ein Benutzer", + "About": "Über", + "Account": "Konto", + "Account Activation Pending": "Kontoaktivierung ausstehend", + "Accurate information": "Präzise Information(en)", + "Active Users": "Aktive Benutzer", + "Add": "Hinzufügen", + "Add a model id": "Modell-ID hinzufügen", + "Add a short description about what this model does": "Füge eine kurze Beschreibung über dieses Modell hinzu", + "Add a short title for this prompt": "Füge einen kurzen Titel für diesen Prompt hinzu", + "Add a tag": "Tag hinzufügen", + "Add custom prompt": "Benutzerdefinierten Prompt hinzufügen", + "Add Docs": "Dokumente hinzufügen", + "Add Files": "Dateien hinzufügen", + "Add Memory": "Erinnerung hinzufügen", + "Add message": "Nachricht hinzufügen", + "Add Model": "Modell hinzufügen", + "Add Tags": "Tags hinzufügen", + "Add User": "Benutzer hinzufügen", + "Adjusting these settings will apply changes universally to all users.": "Das Anpassen dieser Einstellungen wird Änderungen universell auf alle Benutzer anwenden.", + "admin": "Administrator", + "Admin": "Administrator", + "Admin Panel": "Administrationsbereich", + "Admin Settings": "Administrator-Einstellungen", + "Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "Administratoren haben jederzeit Zugriff auf alle Werkzeuge. Benutzer können im Arbeitsbereich zugewiesen.", + "Advanced Parameters": "Erweiterte Parameter", + "Advanced Params": "Erweiterte Parameter", + "all": "Alle", + "All Documents": "Alle Dokumente", + "All Users": "Alle Benutzer", + "Allow": "Erlauben", + "Allow Chat Deletion": "Chat löschen erlauben", + "Allow non-local voices": "Nicht-lokale Stimmen erlauben", + "Allow User Location": "Standort freigeben", + "Allow Voice Interruption in Call": "Unterbrechung durch Stimme im Anruf zulassen", + "alphanumeric characters and hyphens": "alphanumerische Zeichen und Bindestriche", + "Already have an account?": "Hast du vielleicht schon ein Account?", + "an assistant": "ein Assistent", + "and": "und", + "and create a new shared link.": "und erstelle einen neuen freigegebenen Link.", + "API Base URL": "API-Basis-URL", + "API Key": "API-Schlüssel", + "API Key created.": "API-Schlüssel erstellt.", + "API keys": "API-Schlüssel", + "April": "April", + "Archive": "Archivieren", + "Archive All Chats": "Alle Unterhaltungen archivieren", + "Archived Chats": "Archivierte Unterhaltungen", + "are allowed - Activate this command by typing": "sind erlaubt - Aktiviere diesen Befehl durch Eingabe von", + "Are you sure?": "Bist du sicher?", + "Attach file": "Datei anhängen", + "Attention to detail": "Aufmerksamkeit für Details", + "Audio": "Audio", + "Audio settings updated successfully": "Audioeinstellungen erfolgreich aktualisiert", + "August": "August", + "Auto-playback response": "Antwort automatisch abspielen", + "AUTOMATIC1111 Api Auth String": "AUTOMATIC1111-API-Authentifizierungszeichenfolge", + "AUTOMATIC1111 Base URL": "AUTOMATIC1111-Basis-URL", + "AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111-Basis-URL ist erforderlich.", + "available!": "Verfügbar!", + "Back": "Zurück", + "Bad Response": "Schlechte Antwort", + "Banners": "Banner", + "Base Model (From)": "Basismodell (From)", + "Batch Size (num_batch)": "Stapelgröße (num_batch)", + "before": "bereits geteilt", + "Being lazy": "Faulheit", + "Brave Search API Key": "Brave Search API-Schlüssel", + "Bypass SSL verification for Websites": "SSL-Überprüfung für Webseiten umgehen", + "Call": "Anrufen", + "Call feature is not supported when using Web STT engine": "Die Anruffunktion wird nicht unterstützt, wenn die Web-STT-Engine verwendet wird.", + "Camera": "Kamera", + "Cancel": "Abbrechen", + "Capabilities": "Fähigkeiten", + "Change Password": "Passwort ändern", + "Chat": "Gespräch", + "Chat Background Image": "Unterhaltungs-Hintergrundbild", + "Chat Bubble UI": "Chat Bubble UI", + "Chat direction": "Textrichtung", + "Chat History": "Unterhaltungsverlauf", + "Chat History is off for this browser.": "Unterhaltungsverlauf ist in diesem Browser deaktiviert.", + "Chats": "Unterhaltungen", + "Check Again": "Erneut überprüfen", + "Check for updates": "Nach Updates suchen", + "Checking for updates...": "Sucht nach Updates...", + "Choose a model before saving...": "Wähle bitte zuerst ein Modell, bevor du speicherst...", + "Chunk Overlap": "Blocküberlappung", + "Chunk Params": "Blockparameter", + "Chunk Size": "Blockgröße", + "Citation": "Zitate", + "Clear memory": "Erinnerungen löschen", + "Click here for help.": "Klicke hier für Hilfe.", + "Click here to": "Klicke hier, um", + "Click here to download user import template file.": "Klicke hier, um die Vorlage für den Benutzerimport herunterzuladen.", + "Click here to select": "Klicke zum Auswählen hier", + "Click here to select a csv file.": "Klicke zum Auswählen einer CSV-Datei hier.", + "Click here to select a py file.": "Klicke zum Auswählen einer py-Datei hier.", + "Click here to select documents.": "Klicke zum Auswählen von Dokumenten hier", + "click here.": "hier klicken.", + "Click on the user role button to change a user's role.": "Klicke auf die Benutzerrolle, um sie zu ändern.", + "Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "Schreibberechtigung für die Zwischenablage verweigert. Bitte überprüfe deine Browsereinstellungen, um den erforderlichen Zugriff zu erlauben.", + "Clone": "Klonen", + "Close": "Schließen", + "Code formatted successfully": "Code erfolgreich formatiert", + "Collection": "Kollektion", + "ComfyUI": "ComfyUI", + "ComfyUI Base URL": "ComfyUI-Basis-URL", + "ComfyUI Base URL is required.": "ComfyUI-Basis-URL wird benötigt.", + "Command": "Befehl", + "Concurrent Requests": "Gleichzeitige Anforderungen", + "Confirm": "Bestätigen", + "Confirm Password": "Passwort bestätigen", + "Confirm your action": "Bestätige deine Aktion.", + "Connections": "Verbindungen", + "Contact Admin for WebUI Access": "Kontaktiere den Administrator für den Zugriff auf die Weboberfläche", + "Content": "Info", + "Context Length": "Kontextlänge", + "Continue Response": "Antwort fortsetzen", + "Continue with {{provider}}": "Mit {{Anbieter}} fortfahren", + "Copied shared chat URL to clipboard!": "Freigabelink in die Zwischenablage kopiert!", + "Copy": "Kopieren", + "Copy last code block": "Letzten Codeblock kopieren", + "Copy last response": "Letzte Antwort kopieren", + "Copy Link": "Link kopieren", + "Copying to clipboard was successful!": "Das Kopieren in die Zwischenablage war erfolgreich!", + "Create a model": "Ein Modell erstellen", + "Create Account": "Konto erstellen", + "Create new key": "Neuen Schlüssel erstellen", + "Create new secret key": "Neuen API-Schlüssel erstellen", + "Created at": "Erstellt am", + "Created At": "Erstellt am", + "Created by": "Erstellt von", + "CSV Import": "CSV-Import", + "Current Model": "Aktuelles Modell", + "Current Password": "Aktuelles Passwort", + "Custom": "Benutzerdefiniert", + "Customize models for a specific purpose": "Modelle für einen bestimmten Zweck anpassen", + "Dark": "Dunkel", + "Dashboard": "Übersicht", + "Database": "Datenbank", + "December": "Dezember", + "Default": "Standard", + "Default (Automatic1111)": "Standard (Automatic1111)", + "Default (SentenceTransformers)": "Standard (SentenceTransformers)", + "Default Model": "Standardmodell", + "Default model updated": "Standardmodell aktualisiert", + "Default Prompt Suggestions": "Prompt-Vorschläge", + "Default User Role": "Standardbenutzerrolle", + "delete": "löschen", + "Delete": "Löschen", + "Delete a model": "Ein Modell löschen", + "Delete All Chats": "Alle Unterhaltungen löschen", + "Delete chat": "Unterhaltung löschen", + "Delete Chat": "Unterhaltung löschen", + "Delete chat?": "Unterhaltung löschen?", + "Delete function?": "Funktion löschen?", + "Delete prompt?": "Prompt löschen?", + "delete this link": "diesen Link löschen", + "Delete tool?": "Werkzeug löschen?", + "Delete User": "Benutzer löschen", + "Deleted {{deleteModelTag}}": "{{deleteModelTag}} gelöscht", + "Deleted {{name}}": "{{name}} gelöscht", + "Description": "Beschreibung", + "Didn't fully follow instructions": "Nicht genau den Answeisungen gefolgt", + "Discover a function": "Entdecke weitere Funktionen", + "Discover a model": "Entdecke weitere Modelle", + "Discover a prompt": "Entdecke weitere Prompts", + "Discover a tool": "Entdecke weitere Werkzeuge", + "Discover, download, and explore custom functions": "Entdecke und beziehe benutzerdefinierte Funktionen", + "Discover, download, and explore custom prompts": "Entdecke und beziehe benutzerdefinierte Prompts", + "Discover, download, and explore custom tools": "Entdecke und beziehe benutzerdefinierte Werkzeuge", + "Discover, download, and explore model presets": "Entdecke und beziehe benutzerdefinierte Modellvorlagen", + "Dismissible": "ausblendbar", + "Display Emoji in Call": "Emojis im Anruf anzeigen", + "Display the username instead of You in the Chat": "Soll \"Du\" durch deinen Benutzernamen ersetzt werden?", + "Document": "Dokument", + "Document Settings": "Dokumenteinstellungen", + "Documentation": "Dokumentation", + "Documents": "Dokumente", + "does not make any external connections, and your data stays securely on your locally hosted server.": "stellt keine externen Verbindungen her, und Ihre Daten bleiben sicher auf Ihrem lokal gehosteten Server.", + "Don't Allow": "Nicht erlauben", + "Don't have an account?": "Hast du noch kein Benutzerkonto?", + "Don't like the style": "schlechter Schreibstil", + "Done": "Erledigt", + "Download": "Exportieren", + "Download canceled": "Exportierung abgebrochen", + "Download Database": "Datenbank exportieren", + "Drop any files here to add to the conversation": "Ziehe beliebige Dateien hierher, um sie der Unterhaltung hinzuzufügen", + "e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "z. B. '30s','10m'. Gültige Zeiteinheiten sind 's', 'm', 'h'.", + "Edit": "Bearbeiten", + "Edit Doc": "Dokument bearbeiten", + "Edit Memory": "Erinnerungen bearbeiten", + "Edit User": "Benutzer bearbeiten", + "Email": "E-Mail", + "Embedding Batch Size": "Embedding Batch Größe", + "Embedding Model": "Embedding-Modell", + "Embedding Model Engine": "Embedding-Modell-Engine", + "Embedding model set to \"{{embedding_model}}\"": "Embedding-Modell auf \"{{embedding_model}}\" gesetzt", + "Enable Chat History": "Chat-Verlauf aktivieren", + "Enable Community Sharing": "Community-Freigabe aktivieren", + "Enable New Sign Ups": "Neue Anmeldungen aktivieren", + "Enable Web Search": "Websuche aktivieren", + "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Stelle sicher, dass deine CSV-Datei 4 Spalten in dieser Reihenfolge enthält: Name, E-Mail, Passwort, Rolle.", + "Enter {{role}} message here": "Gib die {{role}}-Nachricht hier ein", + "Enter a detail about yourself for your LLMs to recall": "Gib ein Detail über dich selbst ein, das deine Sprachmodelle (LLMs) sich merken sollen", + "Enter api auth string (e.g. username:password)": "Gib die API-Authentifizierungszeichenfolge ein (z. B. Benutzername:Passwort)", + "Enter Brave Search API Key": "Gib den Brave Search API-Schlüssel ein", + "Enter Chunk Overlap": "Gib den Chunk Overlap ein", + "Enter Chunk Size": "Gib die Chunk Size ein", + "Enter Github Raw URL": "Geben Sie die Github Raw-URL ein", + "Enter Google PSE API Key": "Geben Sie den Google PSE-API-Schlüssel ein", + "Enter Google PSE Engine Id": "Geben Sie die Google PSE-Engine-ID ein", + "Enter Image Size (e.g. 512x512)": "Gib die Bildgröße ein (z.B. 512x512)", + "Enter language codes": "Geben Sie die Sprachcodes ein", + "Enter model tag (e.g. {{modelTag}})": "Gib den Model-Tag ein", + "Enter Number of Steps (e.g. 50)": "Gib die Anzahl an Schritten ein (z.B. 50)", + "Enter Score": "Punktzahl eingeben", + "Enter Searxng Query URL": "Geben Sie die Searxng-Abfrage-URL ein", + "Enter Serper API Key": "Geben Sie den Serper-API-Schlüssel ein", + "Enter Serply API Key": "Geben Sie den", + "Enter Serpstack API Key": "Geben Sie den Serpstack-API-Schlüssel ein", + "Enter stop sequence": "Stop-Sequenz eingeben", + "Enter Tavily API Key": "Geben Sie den Tavily-API-Schlüssel ein", + "Enter Top K": "Geben Sie Top K ein", + "Enter URL (e.g. http://127.0.0.1:7860/)": "Geben Sie die URL ein (z. B. http://127.0.0.1:7860/)", + "Enter URL (e.g. http://localhost:11434)": "Geben Sie die URL ein (z. B. http://localhost:11434)", + "Enter Your Email": "Geben Sie Ihre E-Mail-Adresse ein", + "Enter Your Full Name": "Geben Sie Ihren vollständigen Namen ein", + "Enter Your Password": "Geben Sie Ihr Passwort ein", + "Enter Your Role": "Geben Sie Ihre Rolle ein", + "Error": "Fehler", + "Experimental": "Experimentell", + "Export": "Exportieren", + "Export All Chats (All Users)": "Alle Unterhaltungen exportieren (alle Benutzer)", + "Export chat (.json)": "Unterhaltung exportieren (.json)", + "Export Chats": "Unterhaltungen exportieren", + "Export Documents Mapping": "Dokumentenzuordnung exportieren", + "Export Functions": "Funktionen exportieren", + "Export LiteLLM config.yaml": "LiteLLM-Konfiguration exportieren (config.yaml)", + "Export Models": "Modelle exportieren", + "Export Prompts": "Prompts exportieren", + "Export Tools": "Werkzeuge exportieren", + "External Models": "Externe Modelle", + "Failed to create API Key.": "Fehler beim Erstellen des API-Schlüssels.", + "Failed to read clipboard contents": "Fehler beim Abruf der Zwischenablage", + "Failed to update settings": "Fehler beim Aktualisieren der Einstellungen", + "February": "Februar", + "Feel free to add specific details": "Fühlen Sie sich frei, spezifische Details hinzuzufügen", + "File": "Datei", + "File Mode": "Datei-Modus", + "File not found.": "Datei nicht gefunden.", + "Filter is now globally disabled": "Filter ist jetzt global deaktiviert", + "Filter is now globally enabled": "Filter ist jetzt global aktiviert", + "Filters": "Filter", + "Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Fingerabdruck-Spoofing erkannt: Initialen können nicht als Avatar verwendet werden. Standard-Avatar wird verwendet.", + "Fluidly stream large external response chunks": "Nahtlose Übertragung großer externer Antwortabschnitte", + "Focus chat input": "Chat-Eingabe fokussieren", + "Followed instructions perfectly": "Anweisungen perfekt befolgt", + "Form": "Formular", + "Format your variables using square brackets like this:": "Formatieren Sie Ihre Variablen mit eckigen Klammern wie folgt:", + "Frequency Penalty": "Frequenzstrafe", + "Function created successfully": "Funktion erfolgreich erstellt", + "Function deleted successfully": "Funktion erfolgreich gelöscht", + "Function updated successfully": "Funktion erfolgreich aktualisiert", + "Functions": "Funktionen", + "Functions imported successfully": "Funktionen erfolgreich importiert", + "General": "Allgemein", + "General Settings": "Allgemeine Einstellungen", + "Generate Image": "Bild erzeugen", + "Generating search query": "Suchanfrage wird erstellt", + "Generation Info": "Generierungsinformationen", + "Global": "Global", + "Good Response": "Gute Antwort", + "Google PSE API Key": "Google PSE-API-Schlüssel", + "Google PSE Engine Id": "Google PSE-Engine-ID", + "h:mm a": "h:mm a", + "has no conversations.": "hat keine Unterhaltungen.", + "Hello, {{name}}": "Hallo, {{name}}", + "Help": "Hilfe", + "Hide": "Verbergen", + "Hide Model": "Modell ausblenden", + "How can I help you today?": "Wie kann ich Ihnen heute helfen?", + "Hybrid Search": "Hybride Suche", + "Image Generation (Experimental)": "Bildgenerierung (experimentell)", + "Image Generation Engine": "Bildgenerierungs-Engine", + "Image Settings": "Bildeinstellungen", + "Images": "Bilder", + "Import Chats": "Chats importieren", + "Import Documents Mapping": "Dokumentenzuordnung importieren", + "Import Functions": "Funktionen importieren", + "Import Models": "Modelle importieren", + "Import Prompts": "Prompts importieren", + "Import Tools": "Werkzeuge importieren", + "Include `--api-auth` flag when running stable-diffusion-webui": "Fügen Sie beim Ausführen von stable-diffusion-webui die Option `--api-auth` hinzu", + "Include `--api` flag when running stable-diffusion-webui": "Fügen Sie beim Ausführen von stable-diffusion-webui die Option `--api` hinzu", + "Info": "Info", + "Input commands": "Eingabebefehle", + "Install from Github URL": "Installieren Sie von der Github-URL", + "Instant Auto-Send After Voice Transcription": "Spracherkennung direkt absenden", + "Interface": "Benutzeroberfläche", + "Invalid Tag": "Ungültiger Tag", + "January": "Januar", + "join our Discord for help.": "Trete unserem Discord bei, um Hilfe zu erhalten.", + "JSON": "JSON", + "JSON Preview": "JSON-Vorschau", + "July": "Juli", + "June": "Juni", + "JWT Expiration": "JWT-Ablauf", + "JWT Token": "JWT-Token", + "Keep Alive": "Verbindung aufrechterhalten", + "Keyboard shortcuts": "Tastenkombinationen", + "Knowledge": "Wissen", + "Language": "Sprache", + "Last Active": "Zuletzt aktiv", + "Last Modified": "Zuletzt bearbeitet", + "Light": "Hell", + "Listening...": "Höre zu...", + "LLMs can make mistakes. Verify important information.": "LLMs können Fehler machen. Überprüfe wichtige Informationen.", + "Local Models": "Lokale Modelle", + "LTR": "LTR", + "Made by OpenWebUI Community": "Von der OpenWebUI-Community", + "Make sure to enclose them with": "Umschließen Sie Variablen mit", + "Manage": "Verwalten", + "Manage Models": "Modelle verwalten", + "Manage Ollama Models": "Ollama-Modelle verwalten", + "Manage Pipelines": "Pipelines verwalten", + "Manage Valves": "Valves verwalten", + "March": "März", + "Max Tokens (num_predict)": "Maximale Tokenanzahl (num_predict)", + "Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Es können maximal 3 Modelle gleichzeitig heruntergeladen werden. Bitte versuchen Sie es später erneut.", + "May": "Mai", + "Memories accessible by LLMs will be shown here.": "Erinnerungen, die für Modelle zugänglich sind, werden hier angezeigt.", + "Memory": "Erinnerung", + "Memory added successfully": "Erinnerung erfolgreich hinzugefügt", + "Memory cleared successfully": "Erinnerung erfolgreich gelöscht", + "Memory deleted successfully": "Erinnerung erfolgreich gelöscht", + "Memory updated successfully": "Erinnerung erfolgreich aktualisiert", + "Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Nachrichten, die Sie nach der Erstellung Ihres Links senden, werden nicht geteilt. Nutzer mit der URL können die freigegebene Unterhaltung einsehen.", + "Minimum Score": "Mindestpunktzahl", + "Mirostat": "Mirostat", + "Mirostat Eta": "Mirostat Eta", + "Mirostat Tau": "Mirostat Tau", + "MMMM DD, YYYY": "DD MMMM YYYY", + "MMMM DD, YYYY HH:mm": "DD MMMM YYYY HH:mm", + "MMMM DD, YYYY hh:mm:ss A": "DD MMMM YYYY HH:mm A", + "Model '{{modelName}}' has been successfully downloaded.": "Modell '{{modelName}}' wurde erfolgreich heruntergeladen.", + "Model '{{modelTag}}' is already in queue for downloading.": "Modell '{{modelTag}}' befindet sich bereits in der Warteschlange zum Herunterladen.", + "Model {{modelId}} not found": "Modell {{modelId}} nicht gefunden", + "Model {{modelName}} is not vision capable": "Das Modell {{modelName}} ist nicht für die Bildverarbeitung geeignet", + "Model {{name}} is now {{status}}": "Modell {{name}} ist jetzt {{status}}", + "Model created successfully!": "Modell erfolgreich erstellt!", + "Model filesystem path detected. Model shortname is required for update, cannot continue.": "Modell-Dateisystempfad erkannt. Modellkurzname ist für das Update erforderlich, Fortsetzung nicht möglich.", + "Model ID": "Modell-ID", + "Model not selected": "Modell nicht ausgewählt", + "Model Params": "Modell-Params", + "Model updated successfully": "Modell erfolgreich aktualisiert", + "Model Whitelisting": "Modell-Whitelisting", + "Model(s) Whitelisted": "Modell(e) auf der Whitelist", + "Modelfile Content": "Modelfile-Inhalt", + "Models": "Modelle", + "More": "Mehr", + "Name": "Name", + "Name Tag": "Namens-Tag", + "Name your model": "Benennen Sie Ihr Modell", + "New Chat": "Neuer Chat", + "New Password": "Neues Passwort", + "No content to speak": "Kein Inhalt zum Vorlesen", + "No documents found": "Keine Dokumente gefunden", + "No file selected": "Keine Datei ausgewählt", + "No results found": "Keine Ergebnisse gefunden", + "No search query generated": "Keine Suchanfrage generiert", + "No source available": "Keine Quelle verfügbar", + "No valves to update": "Keine Valves zum Aktualisieren", + "None": "Nichts", + "Not factually correct": "Nicht sachlich korrekt", + "Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Hinweis: Wenn du einen Mindestscore festlegst, wird die Suche nur Dokumente zurückgeben, deren Score größer oder gleich dem Mindestscore ist.", + "Notifications": "Benachrichtigungen", + "November": "November", + "num_thread (Ollama)": "num_thread (Ollama)", + "OAuth ID": "OAuth-ID", + "October": "Oktober", + "Off": "Aus", + "Okay, Let's Go!": "Okay, los geht's!", + "OLED Dark": "OLED-Dunkel", + "Ollama": "Ollama", + "Ollama API": "Ollama-API", + "Ollama API disabled": "Ollama-API deaktiviert", + "Ollama API is disabled": "Ollama-API ist deaktiviert.", + "Ollama Version": "Ollama-Version", + "On": "Ein", + "Only": "Nur", + "Only alphanumeric characters and hyphens are allowed in the command string.": "In der Befehlszeichenfolge sind nur alphanumerische Zeichen und Bindestriche erlaubt.", + "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.": "Ups! Bitte gedulden Sie sich einen Moment! Ihre Dateien sind noch in der Verarbeitung. Wir bereiten sie sorgfältig vor. Bitte haben Sie etwas Geduld, und wir werden Sie benachrichtigen, sobald sie fertig sind.", + "Oops! Looks like the URL is invalid. Please double-check and try again.": "Hoppla! Es scheint, dass die URL ungültig ist. Bitte überprüfen Sie diese und versuchen Sie es erneut.", + "Oops! There was an error in the previous response. Please try again or contact admin.": "Hoppla! Es gab einen Fehler in der vorherigen Antwort. Bitte versuchen Sie es erneut oder kontaktieren Sie den Administrator.", + "Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Hoppla! du verwendest eine nicht unterstützte Methode (nur Frontend). Bitte stelle die WebUI vom Backend aus bereit.", + "Open": "Öffne", + "Open AI (Dall-E)": "Open AI (Dall-E)", + "Open new chat": "Neuen Chat öffnen", + "OpenAI": "OpenAI", + "OpenAI API": "OpenAI-API", + "OpenAI API Config": "OpenAI-API-Konfiguration", + "OpenAI API Key is required.": "OpenAI-API-Schlüssel erforderlich.", + "OpenAI URL/Key required.": "OpenAI-URL/Schlüssel erforderlich.", + "or": "oder", + "Other": "Andere", + "Password": "Passwort", + "PDF document (.pdf)": "PDF-Dokument (.pdf)", + "PDF Extract Images (OCR)": "Text von Bildern aus PDFs extrahieren (OCR)", + "pending": "ausstehend", + "Permission denied when accessing media devices": "Zugriff auf Mediengeräte verweigert", + "Permission denied when accessing microphone": "Zugriff auf das Mikrofon verweigert", + "Permission denied when accessing microphone: {{error}}": "Zugriff auf das Mikrofon verweigert: {{error}}", + "Personalization": "Personalisierung", + "Pipeline deleted successfully": "Pipeline erfolgreich gelöscht", + "Pipeline downloaded successfully": "Pipeline erfolgreich heruntergeladen", + "Pipelines": "Pipelines", + "Pipelines Not Detected": "Pipelines nicht erkannt", + "Pipelines Valves": "Pipeline Valves", + "Plain text (.txt)": "Nur Text (.txt)", + "Playground": "Testumgebung", + "Positive attitude": "Positive Einstellung", + "Previous 30 days": "Vorherige 30 Tage", + "Previous 7 days": "Vorherige 7 Tage", + "Profile Image": "Profilbild", + "Prompt": "Prompt", + "Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Prompt (z. B. \"Erzähle mir eine interessante Tatsache über das Römische Reich\")", + "Prompt Content": "Prompt-Inhalt", + "Prompt suggestions": "Prompt-Vorschläge", + "Prompts": "Prompts", + "Pull \"{{searchValue}}\" from Ollama.com": "\"{{searchValue}}\" von Ollama.com beziehen", + "Pull a model from Ollama.com": "Modell von Ollama.com beziehn", + "Query Params": "Abfrageparameter", + "RAG Template": "RAG-Vorlage", + "Read Aloud": "Vorlesen", + "Record voice": "Stimme aufnehmen", + "Redirecting you to OpenWebUI Community": "Sie werden zur OpenWebUI-Community weitergeleitet", + "Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "", + "Refused when it shouldn't have": "Abgelehnt, obwohl es nicht hätte abgelehnt werden sollen", + "Regenerate": "Neu generieren", + "Release Notes": "Veröffentlichungshinweise", + "Remove": "Entfernen", + "Remove Model": "Modell entfernen", + "Rename": "Umbenennen", + "Repeat Last N": "Wiederhole die letzten N", + "Request Mode": "Anforderungsmodus", + "Reranking Model": "Reranking-Modell", + "Reranking model disabled": "Reranking-Modell deaktiviert", + "Reranking model set to \"{{reranking_model}}\"": "Reranking-Modell \"{{reranking_model}}\" fesgelegt", + "Reset": "Zurücksetzen", + "Reset Upload Directory": "Upload-Verzeichnis zurücksetzen", + "Reset Vector Storage": "Vektorspeicher zurücksetzen", + "Response AutoCopy to Clipboard": "Antwort automatisch in die Zwischenablage kopieren", + "Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "Benachrichtigungen können nicht aktiviert werden, da die Website-Berechtigungen abgelehnt wurden. Bitte besuchen Sie Ihre Browser-Einstellungen, um den erforderlichen Zugriff zu gewähren.", + "Role": "Rolle", + "Rosé Pine": "Rosé Pine", + "Rosé Pine Dawn": "Rosé Pine Dawn", + "RTL": "RTL", + "Running": "Läuft", + "Save": "Speichern", + "Save & Create": "Erstellen", + "Save & Update": "Aktualisieren", + "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": "Das direkte Speichern von Unterhaltungen im Browser-Speicher wird nicht mehr unterstützt. Bitte nehmen Sie einen Moment Zeit, um Ihre Unterhaltungen zu exportieren und zu löschen, indem Sie auf die Schaltfläche unten klicken. Keine Sorge, Sie können Ihre Unterhaltungen problemlos über das Backend wieder importieren.", + "Scan": "Scannen", + "Scan complete!": "Scan abgeschlossen!", + "Scan for documents from {{path}}": "Dokumente im {{path}} scannen", + "Search": "Suchen", + "Search a model": "Modell suchen", + "Search Chats": "Unterhaltungen durchsuchen...", + "Search Documents": "Dokumente durchsuchen...", + "Search Functions": "Funktionen durchsuchen...", + "Search Models": "Modelle durchsuchen...", + "Search Prompts": "Prompts durchsuchen...", + "Search Query Generation Prompt": "Suchanfragen-Generierungs-Prompt", + "Search Query Generation Prompt Length Threshold": "Suchanfragen-Generierungs-Prompt-Längenschwellenwert", + "Search Result Count": "Anzahl der Suchergebnisse", + "Search Tools": "Suchwerkzeuge", + "Searched {{count}} sites_one": "{{count}} Seite durchsucht", + "Searched {{count}} sites_other": "{{count}} Seiten durchsucht", + "Searching \"{{searchQuery}}\"": "Suche nach \"{{searchQuery}}\"", + "Searxng Query URL": "Searxng-Abfrage-URL", + "See readme.md for instructions": "Anleitung in readme.md anzeigen", + "See what's new": "Entdecken Sie die Neuigkeiten", + "Seed": "Seed", + "Select a base model": "Wählen Sie ein Basismodell", + "Select a engine": "Wählen Sie eine Engine", + "Select a function": "Wählen Sie eine Funktion", + "Select a mode": "Wählen Sie einen Modus", + "Select a model": "Wählen Sie ein Modell", + "Select a pipeline": "Wählen Sie eine Pipeline", + "Select a pipeline url": "Wählen Sie eine Pipeline-URL", + "Select a tool": "Wählen Sie ein Werkzeug", + "Select an Ollama instance": "Wählen Sie eine Ollama-Instanz", + "Select Documents": "Dokumente auswählen", + "Select model": "Modell auswählen", + "Select only one model to call": "Wählen Sie nur ein Modell zum Anrufen aus", + "Selected model(s) do not support image inputs": "Ihre ausgewählten Modelle unterstützen keine Bildeingaben", + "Send": "Senden", + "Send a Message": "Eine Nachricht senden", + "Send message": "Nachricht senden", + "September": "September", + "Serper API Key": "Serper-API-Schlüssel", + "Serply API Key": "Serply-API-Schlüssel", + "Serpstack API Key": "Serpstack-API-Schlüssel", + "Server connection verified": "Serververbindung überprüft", + "Set as default": "Als Standard festlegen", + "Set Default Model": "Standardmodell festlegen", + "Set embedding model (e.g. {{model}})": "Einbettungsmodell festlegen (z. B. {{model}})", + "Set Image Size": "Bildgröße festlegen", + "Set reranking model (e.g. {{model}})": "Rerankingmodell festlegen (z. B. {{model}})", + "Set Steps": "Schrittgröße festlegen", + "Set Task Model": "Aufgabenmodell festlegen", + "Set Voice": "Stimme festlegen", + "Settings": "Einstellungen", + "Settings saved successfully!": "Einstellungen erfolgreich gespeichert!", + "Settings updated successfully": "Einstellungen erfolgreich aktualisiert", + "Share": "Teilen", + "Share Chat": "Unterhaltung teilen", + "Share to OpenWebUI Community": "Mit OpenWebUI Community teilen", + "short-summary": "kurze-zusammenfassung", + "Show": "Anzeigen", + "Show Admin Details in Account Pending Overlay": "Admin-Details im Account-Pending-Overlay anzeigen", + "Show Model": "Modell anzeigen", + "Show shortcuts": "Verknüpfungen anzeigen", + "Show your support!": "Zeigen Sie Ihre Unterstützung!", + "Showcased creativity": "Kreativität gezeigt", + "sidebar": "Seitenleiste", + "Sign in": "Anmelden", + "Sign Out": "Abmelden", + "Sign up": "Registrieren", + "Signing in": "Anmeldung", + "Source": "Quelle", + "Speech recognition error: {{error}}": "Spracherkennungsfehler: {{error}}", + "Speech-to-Text Engine": "Sprache-zu-Text-Engine", + "Stop Sequence": "Stop-Sequenz", + "STT Model": "STT-Modell", + "STT Settings": "STT-Einstellungen", + "Submit": "Senden", + "Subtitle (e.g. about the Roman Empire)": "Untertitel (z. B. über das Römische Reich)", + "Success": "Erfolg", + "Successfully updated.": "Erfolgreich aktualisiert.", + "Suggested": "Vorgeschlagen", + "System": "System", + "System Prompt": "System-Prompt", + "Tags": "Tags", + "Tap to interrupt": "Zum Unterbrechen tippen", + "Tavily API Key": "Tavily-API-Schlüssel", + "Tell us more:": "Erzähl uns mehr", + "Temperature": "Temperatur", + "Template": "Vorlage", + "Text Completion": "Textvervollständigung", + "Text-to-Speech Engine": "Text-zu-Sprache-Engine", + "Tfs Z": "Tfs Z", + "Thanks for your feedback!": "Danke für Ihr Feedback!", + "The score should be a value between 0.0 (0%) and 1.0 (100%).": "Die Punktzahl sollte ein Wert zwischen 0,0 (0 %) und 1,0 (100 %) sein.", + "Theme": "Design", + "Thinking...": "Denke nach...", + "This action cannot be undone. Do you wish to continue?": "Diese Aktion kann nicht rückgängig gemacht werden. Möchten Sie fortfahren?", + "This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Dies stellt sicher, dass Ihre wertvollen Unterhaltungen sicher in Ihrer Backend-Datenbank gespeichert werden. Vielen Dank!", + "This is an experimental feature, it may not function as expected and is subject to change at any time.": "Dies ist eine experimentelle Funktion, sie funktioniert möglicherweise nicht wie erwartet und kann jederzeit geändert werden.", + "This setting does not sync across browsers or devices.": "Diese Einstellung wird nicht zwischen Browsern oder Geräten synchronisiert.", + "This will delete": "Dies löscht", + "Thorough explanation": "Ausführliche Erklärung", + "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Tipp: Aktualisieren Sie mehrere Variablenfelder nacheinander, indem Sie nach jedem Ersetzen die Tabulatortaste im Eingabefeld der Unterhaltung drücken.", + "Title": "Titel", + "Title (e.g. Tell me a fun fact)": "Titel (z. B. Erzähl mir einen lustigen Fakt)", + "Title Auto-Generation": "Automatische Titelerstellung", + "Title cannot be an empty string.": "Titel darf nicht leer sein.", + "Title Generation Prompt": "Titelerstellung-Prompt", + "to": "für", + "To access the available model names for downloading,": "Um auf die verfügbaren Modellnamen zuzugreifen,", + "To access the GGUF models available for downloading,": "Um auf die verfügbaren GGUF-Modelle zuzugreifen,", + "To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "Um auf das WebUI zugreifen zu könnrn, wenden Sie sich bitte an einen Administrator. Administratoren können den Benutzerstatus über das Admin-Panel verwalten.", + "To add documents here, upload them to the \"Documents\" workspace first.": "Um Dokumente hinzuzufügen, laden Sie sie zuerst im Arbeitsbereich „Dokumente“ hoch.", + "to chat input.": "zum Eingabefeld der Unterhaltung.", + "To select filters here, add them to the \"Functions\" workspace first.": "Um Filter auszuwählen, fügen Sie diese zuerst dem Arbeitsbereich „Funktionen“ hinzu.", + "To select toolkits here, add them to the \"Tools\" workspace first.": "Um Toolkits auszuwählen, fügen Sie sie zuerst zum Arbeitsbereich „Werkzeuge“ hinzu.", + "Today": "Heute", + "Toggle settings": "Einstellungen umschalten", + "Toggle sidebar": "Seitenleiste umschalten", + "Tokens To Keep On Context Refresh (num_keep)": "Beizubehaltende Tokens bei Kontextaktualisierung (num_keep)", + "Tool created successfully": "Werkzeug erfolgreich erstellt", + "Tool deleted successfully": "Werkzeug erfolgreich gelöscht", + "Tool imported successfully": "Werkzeug erfolgreich importiert", + "Tool updated successfully": "Werkzeug erfolgreich aktualisiert", + "Tools": "Werkzeuge", + "Top K": "Top K", + "Top P": "Top P", + "Trouble accessing Ollama?": "Probleme beim Zugriff auf Ollama?", + "TTS Model": "TTS-Modell", + "TTS Settings": "TTS-Einstellungen", + "TTS Voice": "TTS-Stimme", + "Type": "Art", + "Type Hugging Face Resolve (Download) URL": "Gib die Hugging Face Resolve (Download) URL ein", + "Uh-oh! There was an issue connecting to {{provider}}.": "Ups! Es gab ein Problem bei der Verbindung mit {{provider}}.", + "UI": "Oberfläche", + "Unknown file type '{{file_type}}'. Proceeding with the file upload anyway.": "Unbekannter Dateityp '{{file_type}}'. Der Datei-Upload wird trotzdem fortgesetzt.", + "Update": "Aktualisieren", + "Update and Copy Link": "Aktualisieren und Link kopieren", + "Update password": "Passwort aktualisieren", + "Updated at": "Aktualisiert am", + "Upload": "Hochladen", + "Upload a GGUF model": "GGUF-Model hochladen", + "Upload Files": "Datei(en) hochladen", + "Upload Pipeline": "Pipeline hochladen", + "Upload Progress": "Hochladefortschritt", + "URL Mode": "URL-Modus", + "Use '#' in the prompt input to load and select your documents.": "Verwenden Sie '#' in der Eingabeaufforderung, um Ihre Dokumente zu laden und auszuwählen.", + "Use Gravatar": "Gravatar verwenden", + "Use Initials": "Initialen verwenden", + "use_mlock (Ollama)": "use_mlock (Ollama)", + "use_mmap (Ollama)": "use_mmap (Ollama)", + "user": "Benutzer", + "User location successfully retrieved.": "Benutzerstandort erfolgreich ermittelt.", + "User Permissions": "Benutzerberechtigungen", + "Users": "Benutzer", + "Utilize": "Verwende", + "Valid time units:": "Gültige Zeiteinheiten:", + "Valves": "Valves", + "Valves updated": "Valves aktualisiert", + "Valves updated successfully": "Valves erfolgreich aktualisiert", + "variable": "Variable", + "variable to have them replaced with clipboard content.": "Variable, um den Inhalt der Zwischenablage beim Nutzen des Prompts zu ersetzen.", + "Version": "Version", + "Voice": "Stimme", + "Warning": "Warnung", + "Warning: If you update or change your embedding model, you will need to re-import all documents.": "Warnung: Wenn Sie das Einbettungsmodell aktualisieren oder ändern, müssen Sie alle Dokumente erneut importieren.", + "Web": "Web", + "Web API": "Web-API", + "Web Loader Settings": "Web Loader Einstellungen", + "Web Params": "Web Parameter", + "Web Search": "Websuche", + "Web Search Engine": "Suchmaschine", + "Webhook URL": "Webhook URL", + "WebUI Settings": "WebUI-Einstellungen", + "WebUI will make requests to": "Wenn aktiviert sendet WebUI externe Anfragen an", + "What’s New in": "Neuigkeiten von", + "When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Wenn der Verlauf deaktiviert ist, werden neue Unterhaltungen in diesem Browser nicht im Verlauf Ihrer anderen Geräte erscheinen.", + "Whisper (Local)": "Whisper (lokal)", + "Widescreen Mode": "Breitbildmodus", + "Workspace": "Arbeitsbereich", + "Write a prompt suggestion (e.g. Who are you?)": "Gebe einen Prompt-Vorschlag ein (z. B. \"Wer bist du?\")", + "Write a summary in 50 words that summarizes [topic or keyword].": "Schreibe eine kurze Zusammenfassung in 50 Wörtern, die [Thema oder Schlüsselwort] zusammenfasst.", + "Yesterday": "Gestern", + "You": "Sie", + "You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Personalisieren Sie Interaktionen mit LLMs, indem Sie über die Schaltfläche \"Verwalten\" Erinnerungen hinzufügen.", + "You cannot clone a base model": "Sie können Basismodelle nicht klonen", + "You have no archived conversations.": "Du hast keine archivierten Unterhaltungen.", + "You have shared this chat": "Sie haben diese Unterhaltung geteilt", + "You're a helpful assistant.": "Du bist ein hilfreicher Assistent.", + "You're now logged in.": "Sie sind jetzt eingeloggt.", + "Your account status is currently pending activation.": "Ihr Kontostatus ist derzeit ausstehend und wartet auf Aktivierung.", + "Youtube": "YouTube", + "Youtube Loader Settings": "YouTube-Ladeeinstellungen" +} diff --git a/src/lib/i18n/locales/languages.json b/src/lib/i18n/locales/languages.json index f205b8049..0143db3d1 100644 --- a/src/lib/i18n/locales/languages.json +++ b/src/lib/i18n/locales/languages.json @@ -28,8 +28,12 @@ "title": "Cebuano (Filipino)" }, { - "code": "de-DE", - "title": "German (Deutsch)" + "code": "de-DE-formal", + "title": "German (Deutsch - Sie)" + }, + { + "code": "de-DE-informal", + "title": "German (Deutsch - Du)" }, { "code": "es-ES", @@ -147,4 +151,4 @@ "code": "dg-DG", "title": "Doge (🐶)" } -] +] \ No newline at end of file From 9c2313a1bd511be648cabcf2900bdfa69ace3393 Mon Sep 17 00:00:00 2001 From: bannert <58707896+bannert1337@users.noreply.github.com> Date: Sun, 30 Jun 2024 02:44:03 +0200 Subject: [PATCH 022/181] feat(i18n): improve init function with cleaner code and nullish coalescing operator - Use object destructuring for clearer variable assignment. - Utilize the nullish coalescing operator to simplify ternary expressions. - Refactored the import statement to remove duplication. --- src/lib/i18n/index.ts | 56 ++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/src/lib/i18n/index.ts b/src/lib/i18n/index.ts index 172c42f91..265e0bd6f 100644 --- a/src/lib/i18n/index.ts +++ b/src/lib/i18n/index.ts @@ -1,6 +1,6 @@ import i18next from 'i18next'; -import resourcesToBackend from 'i18next-resources-to-backend'; import LanguageDetector from 'i18next-browser-languagedetector'; +import resourcesToBackend from 'i18next-resources-to-backend'; import type { i18n as i18nType } from 'i18next'; import { writable } from 'svelte/store'; @@ -37,36 +37,32 @@ const createIsLoadingStore = (i18n: i18nType) => { return isLoading; }; -export const initI18n = (defaultLocale: string | undefined) => { - let detectionOrder = defaultLocale - ? ['querystring', 'localStorage'] - : ['querystring', 'localStorage', 'navigator']; - let fallbackDefaultLocale = defaultLocale ? [defaultLocale] : ['en-US']; - - const loadResource = (language: string, namespace: string) => - import(`./locales/${language}/${namespace}.json`); - +export const initI18n = (defaultLocale?: string) => { + // Use object destructuring for cleaner code + const [defaultDetection, fallbackDetection] = defaultLocale ? ['querystring', 'localStorage'] : ['querystring', 'localStorage', 'navigator']; + + // Use nullish coalescing operator to simplify the ternary expression + const fallbackDefaultLocale = defaultLocale ?? 'en-US'; + + const loadResource = (language: string, namespace: string) => import(`./locales/${language}/${namespace}.json`); + i18next - .use(resourcesToBackend(loadResource)) - .use(LanguageDetector) - .init({ - debug: false, - detection: { - order: detectionOrder, - caches: ['localStorage'], - lookupQuerystring: 'lang', - lookupLocalStorage: 'locale' - }, - fallbackLng: { - default: fallbackDefaultLocale - }, - ns: 'translation', - returnEmptyString: false, - interpolation: { - escapeValue: false // not needed for svelte as it escapes by default - } - }); -}; + .use(resourcesToBackend(loadResource)) + .use(LanguageDetector) + .init({ + debug: false, + detection: { + order: [defaultDetection, fallbackDetection], + caches: ['localStorage'], + lookupQuerystring: 'lang', + lookupLocalStorage: 'locale' + }, + fallbackLng: fallbackDefaultLocale, + ns: 'translation', + returnEmptyString: false, + interpolation: { escapeValue: false } + }); + }; const i18n = createI18nStore(i18next); const isLoadingStore = createIsLoadingStore(i18next); From 5dacd41278e3013c599c4b7ba7ad3daa8095cb0f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 30 Jun 2024 03:27:46 +0000 Subject: [PATCH 023/181] chore(deps): bump openpyxl from 3.1.2 to 3.1.5 in /backend Bumps [openpyxl](https://openpyxl.readthedocs.io) from 3.1.2 to 3.1.5. --- updated-dependencies: - dependency-name: openpyxl dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- backend/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/requirements.txt b/backend/requirements.txt index dd9de2ff1..21d4bcffd 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -44,7 +44,7 @@ unstructured==0.14.0 Markdown==3.6 pypandoc==1.13 pandas==2.2.2 -openpyxl==3.1.2 +openpyxl==3.1.5 pyxlsb==1.0.10 xlrd==2.0.1 validators==0.28.1 From 9cf622d98118e40a92194a4ef76c1cacd92d2640 Mon Sep 17 00:00:00 2001 From: Nicko van Someren Date: Sun, 30 Jun 2024 15:49:15 -0600 Subject: [PATCH 024/181] Added support for using Apache Tika as a document loader. Added persistent configuration options to configure use and location of Tika service. Updated backend.apps.rag.main:get_loader() to make use of Tika document loader. --- .dockerignore | 2 +- backend/apps/rag/main.py | 127 +++++++++++++++++++++++++++------------ backend/config.py | 16 +++++ 3 files changed, 104 insertions(+), 41 deletions(-) diff --git a/.dockerignore b/.dockerignore index e28863bf6..c7f330f4a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -10,7 +10,7 @@ node_modules vite.config.js.timestamp-* vite.config.ts.timestamp-* __pycache__ -.env +.idea _old uploads .ipynb_checkpoints diff --git a/backend/apps/rag/main.py b/backend/apps/rag/main.py index 7c6974535..b6e2ee5e2 100644 --- a/backend/apps/rag/main.py +++ b/backend/apps/rag/main.py @@ -93,6 +93,8 @@ from config import ( SRC_LOG_LEVELS, UPLOAD_DIR, DOCS_DIR, + DOCUMENT_USE_TIKA, + TIKA_SERVER_URL, RAG_TOP_K, RAG_RELEVANCE_THRESHOLD, RAG_EMBEDDING_ENGINE, @@ -985,6 +987,41 @@ def store_docs_in_vector_db(docs, collection_name, overwrite: bool = False) -> b return False +class TikaLoader: + def __init__(self, file_path, mime_type=None): + self.file_path = file_path + self.mime_type = mime_type + + def load(self) -> List[Document]: + with (open(self.file_path, "rb") as f): + data = f.read() + + if self.mime_type is not None: + headers = {"Content-Type": self.mime_type} + else: + headers = {} + + endpoint = str(TIKA_SERVER_URL) + if not endpoint.endswith("/"): + endpoint += "/" + endpoint += "tika/text" + + r = requests.put(endpoint, data=data, headers=headers) + + if r.ok: + raw_metadata = r.json() + text = raw_metadata.get("X-TIKA:content", "") + + if "Content-Type" in raw_metadata: + headers["Content-Type"] = raw_metadata["Content-Type"] + + log.info("Tika extracted text: %s", text) + + return [Document(page_content=text, metadata=headers)] + else: + raise Exception(f"Error calling Tika: {r.reason}") + + def get_loader(filename: str, file_content_type: str, file_path: str): file_ext = filename.split(".")[-1].lower() known_type = True @@ -1035,47 +1072,57 @@ def get_loader(filename: str, file_content_type: str, file_path: str): "msg", ] - if file_ext == "pdf": - loader = PyPDFLoader( - file_path, extract_images=app.state.config.PDF_EXTRACT_IMAGES - ) - elif file_ext == "csv": - loader = CSVLoader(file_path) - elif file_ext == "rst": - loader = UnstructuredRSTLoader(file_path, mode="elements") - elif file_ext == "xml": - loader = UnstructuredXMLLoader(file_path) - elif file_ext in ["htm", "html"]: - loader = BSHTMLLoader(file_path, open_encoding="unicode_escape") - elif file_ext == "md": - loader = UnstructuredMarkdownLoader(file_path) - elif file_content_type == "application/epub+zip": - loader = UnstructuredEPubLoader(file_path) - elif ( - file_content_type - == "application/vnd.openxmlformats-officedocument.wordprocessingml.document" - or file_ext in ["doc", "docx"] - ): - loader = Docx2txtLoader(file_path) - elif file_content_type in [ - "application/vnd.ms-excel", - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - ] or file_ext in ["xls", "xlsx"]: - loader = UnstructuredExcelLoader(file_path) - elif file_content_type in [ - "application/vnd.ms-powerpoint", - "application/vnd.openxmlformats-officedocument.presentationml.presentation", - ] or file_ext in ["ppt", "pptx"]: - loader = UnstructuredPowerPointLoader(file_path) - elif file_ext == "msg": - loader = OutlookMessageLoader(file_path) - elif file_ext in known_source_ext or ( - file_content_type and file_content_type.find("text/") >= 0 - ): - loader = TextLoader(file_path, autodetect_encoding=True) + log.warning("Use tika: %s, server URL: %s", DOCUMENT_USE_TIKA, TIKA_SERVER_URL) + + if DOCUMENT_USE_TIKA and TIKA_SERVER_URL: + if file_ext in known_source_ext or ( + file_content_type and file_content_type.find("text/") >= 0 + ): + loader = TextLoader(file_path, autodetect_encoding=True) + else: + loader = TikaLoader(file_path, file_content_type) else: - loader = TextLoader(file_path, autodetect_encoding=True) - known_type = False + if file_ext == "pdf": + loader = PyPDFLoader( + file_path, extract_images=app.state.config.PDF_EXTRACT_IMAGES + ) + elif file_ext == "csv": + loader = CSVLoader(file_path) + elif file_ext == "rst": + loader = UnstructuredRSTLoader(file_path, mode="elements") + elif file_ext == "xml": + loader = UnstructuredXMLLoader(file_path) + elif file_ext in ["htm", "html"]: + loader = BSHTMLLoader(file_path, open_encoding="unicode_escape") + elif file_ext == "md": + loader = UnstructuredMarkdownLoader(file_path) + elif file_content_type == "application/epub+zip": + loader = UnstructuredEPubLoader(file_path) + elif ( + file_content_type + == "application/vnd.openxmlformats-officedocument.wordprocessingml.document" + or file_ext in ["doc", "docx"] + ): + loader = Docx2txtLoader(file_path) + elif file_content_type in [ + "application/vnd.ms-excel", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + ] or file_ext in ["xls", "xlsx"]: + loader = UnstructuredExcelLoader(file_path) + elif file_content_type in [ + "application/vnd.ms-powerpoint", + "application/vnd.openxmlformats-officedocument.presentationml.presentation", + ] or file_ext in ["ppt", "pptx"]: + loader = UnstructuredPowerPointLoader(file_path) + elif file_ext == "msg": + loader = OutlookMessageLoader(file_path) + elif file_ext in known_source_ext or ( + file_content_type and file_content_type.find("text/") >= 0 + ): + loader = TextLoader(file_path, autodetect_encoding=True) + else: + loader = TextLoader(file_path, autodetect_encoding=True) + known_type = False return loader, known_type diff --git a/backend/config.py b/backend/config.py index 3a825f53a..fe9d995ef 100644 --- a/backend/config.py +++ b/backend/config.py @@ -878,6 +878,22 @@ WEBUI_SESSION_COOKIE_SECURE = os.environ.get( if WEBUI_AUTH and WEBUI_SECRET_KEY == "": raise ValueError(ERROR_MESSAGES.ENV_VAR_NOT_FOUND) +#################################### +# RAG document text extraction +#################################### + +DOCUMENT_USE_TIKA = PersistentConfig( + "DOCUMENT_USE_TIKA", + "rag.document_use_tika", + os.environ.get("DOCUMENT_USE_TIKA", "false").lower() == "true" +) + +TIKA_SERVER_URL = PersistentConfig( + "TIKA_SERVER_URL", + "rag.tika_server_url", + os.getenv("TIKA_SERVER_URL", "http://tika:9998"), # Default for sidecar deployment +) + #################################### # RAG #################################### From 11210fb6e4a8c68bdaae30cfd95eea53091901aa Mon Sep 17 00:00:00 2001 From: Naufal Rabbani <110365631+Naufal05R@users.noreply.github.com> Date: Mon, 1 Jul 2024 06:48:57 +0700 Subject: [PATCH 025/181] i18n: Update Indonesian translation --- src/lib/i18n/locales/id-ID/translation.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lib/i18n/locales/id-ID/translation.json b/src/lib/i18n/locales/id-ID/translation.json index ac5df3586..9d0340a85 100644 --- a/src/lib/i18n/locales/id-ID/translation.json +++ b/src/lib/i18n/locales/id-ID/translation.json @@ -5,9 +5,9 @@ "(e.g. `sh webui.sh --api`)": "(contoh: `sh webui.sh --api`)", "(latest)": "(terbaru)", "{{ models }}": "{{ models }}", - "{{ owner }}: You cannot delete a base model": "{{ pemilik }}: Anda tidak dapat menghapus model dasar", + "{{ owner }}: You cannot delete a base model": "{{ owner }}: Anda tidak dapat menghapus model dasar", "{{modelName}} is thinking...": "{{modelName}} sedang berpikir...", - "{{user}}'s Chats": "Obrolan {{pengguna}}", + "{{user}}'s Chats": "Obrolan {{user}}", "{{webUIName}} Backend Required": "{{webUIName}} Diperlukan Backend", "A task model is used when performing tasks such as generating titles for chats and web search queries": "Model tugas digunakan saat melakukan tugas seperti membuat judul untuk obrolan dan kueri penelusuran web", "a user": "seorang pengguna", @@ -171,7 +171,7 @@ "Delete tool?": "Hapus alat?", "Delete User": "Menghapus Pengguna", "Deleted {{deleteModelTag}}": "Menghapus {{deleteModelTag}}", - "Deleted {{name}}": "Menghapus {{nama}}", + "Deleted {{name}}": "Menghapus {{name}}", "Description": "Deskripsi", "Didn't fully follow instructions": "Tidak sepenuhnya mengikuti instruksi", "Discover a function": "Menemukan sebuah fungsi", @@ -287,7 +287,7 @@ "Google PSE Engine Id": "Id Mesin Google PSE", "h:mm a": "h:mm a", "has no conversations.": "tidak memiliki percakapan.", - "Hello, {{name}}": "Halo, {{nama}}", + "Hello, {{name}}": "Halo, {{name}}", "Help": "Bantuan", "Hide": "Sembunyikan", "Hide Model": "Sembunyikan Model", @@ -359,7 +359,7 @@ "Model '{{modelTag}}' is already in queue for downloading.": "Model '{{modelTag}}' sudah berada dalam antrean untuk diunduh.", "Model {{modelId}} not found": "Model {{modelId}} tidak ditemukan", "Model {{modelName}} is not vision capable": "Model {{modelName}} tidak dapat dilihat", - "Model {{name}} is now {{status}}": "Model {{nama}} sekarang menjadi {{status}}", + "Model {{name}} is now {{status}}": "Model {{name}} sekarang menjadi {{status}}", "Model created successfully!": "Model berhasil dibuat!", "Model filesystem path detected. Model shortname is required for update, cannot continue.": "Jalur sistem berkas model terdeteksi. Nama pendek model diperlukan untuk pembaruan, tidak dapat dilanjutkan.", "Model ID": "ID Model", @@ -487,8 +487,8 @@ "Search Query Generation Prompt Length Threshold": "Ambang Batas Panjang Permintaan Pembuatan Kueri Pencarian", "Search Result Count": "Jumlah Hasil Pencarian", "Search Tools": "Alat Pencarian", - "Searched {{count}} sites_one": "Mencari {{hitung}} situs_satu", - "Searched {{count}} sites_other": "Mencari {{hitung}} situs_lain", + "Searched {{count}} sites_one": "Mencari {{count}} situs_satu", + "Searched {{count}} sites_other": "Mencari {{count}} situs_lain", "Searching \"{{searchQuery}}\"": "Mencari \"{{searchQuery}}\"", "Searxng Query URL": "URL Kueri Pencarian Searxng", "See readme.md for instructions": "Lihat readme.md untuk instruksi", @@ -604,7 +604,7 @@ "TTS Voice": "Suara TTS", "Type": "Ketik", "Type Hugging Face Resolve (Download) URL": "Ketik Hugging Face Resolve (Unduh) URL", - "Uh-oh! There was an issue connecting to {{provider}}.": "Uh-oh! Ada masalah saat menyambung ke {{penyedia}}.", + "Uh-oh! There was an issue connecting to {{provider}}.": "Uh-oh! Ada masalah saat menyambung ke {{provider}}.", "UI": "UI", "Unknown file type '{{file_type}}'. Proceeding with the file upload anyway.": "Jenis file tidak dikenal '{{file_type}}'. Tetap lanjutkan dengan mengunggah file.", "Update": "Memperbarui", From 17c684369e9a1432ce21037d8f0975cfd753457e Mon Sep 17 00:00:00 2001 From: Jun Siang Cheah Date: Mon, 1 Jul 2024 08:13:02 +0800 Subject: [PATCH 026/181] refac: lazily load faster_whisper to reduce start up memory usage --- backend/apps/audio/main.py | 3 ++- backend/apps/images/main.py | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/apps/audio/main.py b/backend/apps/audio/main.py index 8843f376f..f866d867f 100644 --- a/backend/apps/audio/main.py +++ b/backend/apps/audio/main.py @@ -14,7 +14,6 @@ from fastapi import ( from fastapi.responses import StreamingResponse, JSONResponse, FileResponse from fastapi.middleware.cors import CORSMiddleware -from faster_whisper import WhisperModel from pydantic import BaseModel import uuid @@ -277,6 +276,8 @@ def transcribe( f.close() if app.state.config.STT_ENGINE == "": + from faster_whisper import WhisperModel + whisper_kwargs = { "model_size_or_path": WHISPER_MODEL, "device": whisper_device_type, diff --git a/backend/apps/images/main.py b/backend/apps/images/main.py index 8f1a08e04..24542ee93 100644 --- a/backend/apps/images/main.py +++ b/backend/apps/images/main.py @@ -12,7 +12,6 @@ from fastapi import ( Form, ) from fastapi.middleware.cors import CORSMiddleware -from faster_whisper import WhisperModel from constants import ERROR_MESSAGES from utils.utils import ( From a48ac6a20973d0d433e49ff5176e7d0704d84d14 Mon Sep 17 00:00:00 2001 From: Jun Siang Cheah Date: Mon, 1 Jul 2024 08:13:56 +0800 Subject: [PATCH 027/181] refac: lazily load sentence_transformers to reduce start up memory usage --- backend/apps/rag/main.py | 6 ++++-- backend/apps/rag/utils.py | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/backend/apps/rag/main.py b/backend/apps/rag/main.py index 7c6974535..5e4ec03c3 100644 --- a/backend/apps/rag/main.py +++ b/backend/apps/rag/main.py @@ -48,8 +48,6 @@ import mimetypes import uuid import json -import sentence_transformers - from apps.webui.models.documents import ( Documents, DocumentForm, @@ -190,6 +188,8 @@ def update_embedding_model( update_model: bool = False, ): if embedding_model and app.state.config.RAG_EMBEDDING_ENGINE == "": + import sentence_transformers + app.state.sentence_transformer_ef = sentence_transformers.SentenceTransformer( get_model_path(embedding_model, update_model), device=DEVICE_TYPE, @@ -204,6 +204,8 @@ def update_reranking_model( update_model: bool = False, ): if reranking_model: + import sentence_transformers + app.state.sentence_transformer_rf = sentence_transformers.CrossEncoder( get_model_path(reranking_model, update_model), device=DEVICE_TYPE, diff --git a/backend/apps/rag/utils.py b/backend/apps/rag/utils.py index 7b4324d9a..3a3dad4a2 100644 --- a/backend/apps/rag/utils.py +++ b/backend/apps/rag/utils.py @@ -442,8 +442,6 @@ from langchain_core.documents import BaseDocumentCompressor, Document from langchain_core.callbacks import Callbacks from langchain_core.pydantic_v1 import Extra -from sentence_transformers import util - class RerankCompressor(BaseDocumentCompressor): embedding_function: Any @@ -468,6 +466,8 @@ class RerankCompressor(BaseDocumentCompressor): [(query, doc.page_content) for doc in documents] ) else: + from sentence_transformers import util + query_embedding = self.embedding_function(query) document_embedding = self.embedding_function( [doc.page_content for doc in documents] From a55d6e607720594cbeb0ae38b6642d0a62bc0079 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Sun, 30 Jun 2024 17:45:28 -0700 Subject: [PATCH 028/181] fix: compare message --- .../chat/Messages/CompareMessages.svelte | 112 +++++++++--------- 1 file changed, 58 insertions(+), 54 deletions(-) diff --git a/src/lib/components/chat/Messages/CompareMessages.svelte b/src/lib/components/chat/Messages/CompareMessages.svelte index 12c1e9d1e..27fefb6cb 100644 --- a/src/lib/components/chat/Messages/CompareMessages.svelte +++ b/src/lib/components/chat/Messages/CompareMessages.svelte @@ -100,64 +100,68 @@ class="flex snap-x snap-mandatory overflow-x-auto scrollbar-hidden" id="responses-container-{parentMessage.id}" > - {#each Object.keys(groupedMessages) as model} - {#if groupedMessagesIdx[model] !== undefined && groupedMessages[model].messages.length > 0} - - + {#key currentMessageId} + {#each Object.keys(groupedMessages) as model} + {#if groupedMessagesIdx[model] !== undefined && groupedMessages[model].messages.length > 0} + + + {@const message = groupedMessages[model].messages[groupedMessagesIdx[model]]} -
{ - currentMessageId = groupedMessages[model].messages[groupedMessagesIdx[model]].id; +
{ + if (currentMessageId != message.id) { + currentMessageId = message.id; + let messageId = message.id; + console.log(messageId); - let messageId = groupedMessages[model].messages[groupedMessagesIdx[model]].id; + // + let messageChildrenIds = history.messages[messageId].childrenIds; + while (messageChildrenIds.length !== 0) { + messageId = messageChildrenIds.at(-1); + messageChildrenIds = history.messages[messageId].childrenIds; + } - console.log(messageId); - let messageChildrenIds = history.messages[messageId].childrenIds; - - while (messageChildrenIds.length !== 0) { - messageId = messageChildrenIds.at(-1); - messageChildrenIds = history.messages[messageId].childrenIds; - } - - history.currentId = messageId; - dispatch('change'); - }} - > - m.id)} - isLastMessage={true} - {updateChatMessages} - {confirmEditResponseMessage} - showPreviousMessage={() => showPreviousMessage(model)} - showNextMessage={() => showNextMessage(model)} - {readOnly} - {rateMessage} - {copyToClipboard} - {continueGeneration} - regenerateResponse={async (message) => { - regenerateResponse(message); - await tick(); - groupedMessagesIdx[model] = groupedMessages[model].messages.length - 1; + history.currentId = messageId; + dispatch('change'); + } }} - on:save={async (e) => { - console.log('save', e); + > + m.id)} + isLastMessage={true} + {updateChatMessages} + {confirmEditResponseMessage} + showPreviousMessage={() => showPreviousMessage(model)} + showNextMessage={() => showNextMessage(model)} + {readOnly} + {rateMessage} + {copyToClipboard} + {continueGeneration} + regenerateResponse={async (message) => { + regenerateResponse(message); + await tick(); + groupedMessagesIdx[model] = groupedMessages[model].messages.length - 1; + }} + on:save={async (e) => { + console.log('save', e); - const message = e.detail; - history.messages[message.id] = message; - await updateChatById(localStorage.token, chatId, { - messages: messages, - history: history - }); - }} - /> -
- {/if} - {/each} + const message = e.detail; + history.messages[message.id] = message; + await updateChatById(localStorage.token, chatId, { + messages: messages, + history: history + }); + }} + /> +
+ {/if} + {/each} + {/key} From 3f31bb0975ec5033786a906a6f3fcf75ddd25cf3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 02:30:20 +0000 Subject: [PATCH 029/181] chore(deps): bump langfuse from 2.33.0 to 2.36.2 in /backend Bumps [langfuse](https://github.com/langfuse/langfuse) from 2.33.0 to 2.36.2. - [Release notes](https://github.com/langfuse/langfuse/releases) - [Commits](https://github.com/langfuse/langfuse/commits) --- updated-dependencies: - dependency-name: langfuse dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- backend/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/requirements.txt b/backend/requirements.txt index 329637ef4..2f435266a 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -61,7 +61,7 @@ PyJWT[crypto]==2.8.0 authlib==1.3.1 black==24.4.2 -langfuse==2.33.0 +langfuse==2.36.2 youtube-transcript-api==0.6.2 pytube==15.0.0 From 2fadc0c68ffb3bb8fa362553b2a5102d6f434d1d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 02:30:22 +0000 Subject: [PATCH 030/181] chore(deps): bump requests from 2.32.2 to 2.32.3 in /backend Bumps [requests](https://github.com/psf/requests) from 2.32.2 to 2.32.3. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.3) --- updated-dependencies: - dependency-name: requests dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- backend/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/requirements.txt b/backend/requirements.txt index 329637ef4..b784e6cf3 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -10,7 +10,7 @@ python-socketio==5.11.3 python-jose==3.3.0 passlib[bcrypt]==1.7.4 -requests==2.32.2 +requests==2.32.3 aiohttp==3.9.5 peewee==3.17.5 peewee-migrate==1.12.2 From 0ee984b44457721b0585381b0ac73cad5911df3a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 02:30:25 +0000 Subject: [PATCH 031/181] chore(deps): bump sentence-transformers from 2.7.0 to 3.0.1 in /backend Bumps [sentence-transformers](https://github.com/UKPLab/sentence-transformers) from 2.7.0 to 3.0.1. - [Release notes](https://github.com/UKPLab/sentence-transformers/releases) - [Commits](https://github.com/UKPLab/sentence-transformers/compare/v2.7.0...v3.0.1) --- updated-dependencies: - dependency-name: sentence-transformers dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- backend/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/requirements.txt b/backend/requirements.txt index 329637ef4..a4af1e041 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -36,7 +36,7 @@ langchain-chroma==0.1.2 fake-useragent==1.5.1 chromadb==0.5.3 -sentence-transformers==2.7.0 +sentence-transformers==3.0.1 pypdf==4.2.0 docx2txt==0.8 python-pptx==0.6.23 From 8c7fb0312c31ac81ac1bd8672c11714e557c6c37 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 02:30:30 +0000 Subject: [PATCH 032/181] chore(deps): bump langchain-community from 0.2.0 to 0.2.6 in /backend Bumps [langchain-community](https://github.com/langchain-ai/langchain) from 0.2.0 to 0.2.6. - [Release notes](https://github.com/langchain-ai/langchain/releases) - [Commits](https://github.com/langchain-ai/langchain/compare/langchain-community==0.2.0...langchain-community==0.2.6) --- updated-dependencies: - dependency-name: langchain-community dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- backend/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/requirements.txt b/backend/requirements.txt index 329637ef4..095c5f407 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -31,7 +31,7 @@ anthropic google-generativeai==0.5.4 langchain==0.2.0 -langchain-community==0.2.0 +langchain-community==0.2.6 langchain-chroma==0.1.2 fake-useragent==1.5.1 From de3d49000e89f5cb88995627fd257ddaf0355e66 Mon Sep 17 00:00:00 2001 From: WanderingMeow <128711250+wanderingmeow@users.noreply.github.com> Date: Mon, 1 Jul 2024 10:42:55 +0800 Subject: [PATCH 033/181] fix: RegExp "invalid group specifier name" exception on Safari < 16.4 (#3306, #3371) --- src/lib/utils/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts index d80fcc799..af3ab9a01 100644 --- a/src/lib/utils/index.ts +++ b/src/lib/utils/index.ts @@ -525,7 +525,7 @@ export const extractSentences = (text) => { }); // Split the modified text into sentences based on common punctuation marks, avoiding these blocks - let sentences = text.split(/(?<=[.!?])\s+/); + let sentences = text.match(/[^.?!]+[.!?]+[\])'"`’”]*|.+/g); // Restore code blocks and process sentences sentences = sentences.map((sentence) => { From 7955c9ba3bb9655645ecf649ea3435a34e9eab34 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Sun, 30 Jun 2024 22:28:43 -0700 Subject: [PATCH 034/181] refac --- backend/apps/webui/main.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/apps/webui/main.py b/backend/apps/webui/main.py index 28b1b4aac..b23ac782b 100644 --- a/backend/apps/webui/main.py +++ b/backend/apps/webui/main.py @@ -259,6 +259,9 @@ async def generate_function_chat_completion(form_data, user): if isinstance(line, BaseModel): line = line.model_dump_json() line = f"data: {line}" + if isinstance(line, dict): + line = f"data: {json.dumps(line)}" + try: line = line.decode("utf-8") except: From 4547afe0b992bb0513fffc54fb832186a6f89974 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Sun, 30 Jun 2024 22:29:03 -0700 Subject: [PATCH 035/181] chore: bump --- backend/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/requirements.txt b/backend/requirements.txt index 83cfd8dcd..53c5fbc81 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -30,7 +30,7 @@ openai anthropic google-generativeai==0.5.4 -langchain==0.2.0 +langchain==0.2.6 langchain-community==0.2.6 langchain-chroma==0.1.2 From f89fa061e87d83058c96fbc548a0eff982af5e64 Mon Sep 17 00:00:00 2001 From: Fish Lung Date: Mon, 1 Jul 2024 14:29:26 +0800 Subject: [PATCH 036/181] fix: use AIOHTTP_CLIENT_TIMEOUT timeout setting for openai streaming response --- backend/apps/openai/main.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/backend/apps/openai/main.py b/backend/apps/openai/main.py index 31dd48741..7c67c40ae 100644 --- a/backend/apps/openai/main.py +++ b/backend/apps/openai/main.py @@ -25,6 +25,7 @@ from utils.task import prompt_template from config import ( SRC_LOG_LEVELS, ENABLE_OPENAI_API, + AIOHTTP_CLIENT_TIMEOUT, OPENAI_API_BASE_URLS, OPENAI_API_KEYS, CACHE_DIR, @@ -463,7 +464,9 @@ async def generate_chat_completion( streaming = False try: - session = aiohttp.ClientSession(trust_env=True) + session = aiohttp.ClientSession( + trust_env=True, timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT) + ) r = await session.request( method="POST", url=f"{url}/chat/completions", From e475f025b74250213b71b4c4853bfeae66573890 Mon Sep 17 00:00:00 2001 From: Sergey Mihaylin Date: Mon, 1 Jul 2024 10:25:25 +0300 Subject: [PATCH 037/181] fix: merge request fail (remove picture_claim) --- backend/main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/main.py b/backend/main.py index 3c4ffbbd2..edb7c74ae 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1920,7 +1920,8 @@ async def oauth_callback(provider: str, request: Request, response: Response): if existing_user: raise HTTPException(400, detail=ERROR_MESSAGES.EMAIL_TAKEN) - picture_url = user_data.get("picture", "") + picture_claim = webui_app.state.config.OAUTH_PICTURE_CLAIM + picture_url = user_data.get(picture_claim, "") if picture_url: # Download the profile image into a base64 string try: From a94c7e5c0973811b82ff8443220286525d0b1929 Mon Sep 17 00:00:00 2001 From: Sergey Mihaylin Date: Mon, 1 Jul 2024 10:36:21 +0300 Subject: [PATCH 038/181] fix lint --- backend/apps/webui/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/apps/webui/main.py b/backend/apps/webui/main.py index e7f0683c6..8f1d8e334 100644 --- a/backend/apps/webui/main.py +++ b/backend/apps/webui/main.py @@ -40,7 +40,7 @@ from config import ( ENABLE_COMMUNITY_SHARING, AppConfig, OAUTH_USERNAME_CLAIM, - OAUTH_PICTURE_CLAIM + OAUTH_PICTURE_CLAIM, ) import inspect From 586de1f5ba28a5866847802bf13a326a7743c2c7 Mon Sep 17 00:00:00 2001 From: bannert <58707896+bannert1337@users.noreply.github.com> Date: Mon, 1 Jul 2024 18:31:13 +0200 Subject: [PATCH 039/181] feat(i18n): unify German translations and remove formal/informal versions --- .../de-DE/{translation-formal.json => translation.json} | 4 ++-- src/lib/i18n/locales/languages.json | 8 ++------ 2 files changed, 4 insertions(+), 8 deletions(-) rename src/lib/i18n/locales/de-DE/{translation-formal.json => translation.json} (99%) diff --git a/src/lib/i18n/locales/de-DE/translation-formal.json b/src/lib/i18n/locales/de-DE/translation.json similarity index 99% rename from src/lib/i18n/locales/de-DE/translation-formal.json rename to src/lib/i18n/locales/de-DE/translation.json index 63766cf67..557d51908 100644 --- a/src/lib/i18n/locales/de-DE/translation-formal.json +++ b/src/lib/i18n/locales/de-DE/translation.json @@ -208,9 +208,9 @@ "Embedding Model": "Embedding-Modell", "Embedding Model Engine": "Embedding-Modell-Engine", "Embedding model set to \"{{embedding_model}}\"": "Embedding-Modell auf \"{{embedding_model}}\" gesetzt", - "Enable Chat History": "Chat-Verlauf aktivieren", + "Enable Chat History": "Unterhaltungshistorie aktivieren", "Enable Community Sharing": "Community-Freigabe aktivieren", - "Enable New Sign Ups": "Neue Anmeldungen aktivieren", + "Enable New Sign Ups": "Registrierung erlauben", "Enable Web Search": "Websuche aktivieren", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Stellen Sie sicher, dass Ihre CSV-Datei 4 Spalten in dieser Reihenfolge enthält: Name, E-Mail, Passwort, Rolle.", "Enter {{role}} message here": "Geben Sie die {{role}}-Nachricht hier ein", diff --git a/src/lib/i18n/locales/languages.json b/src/lib/i18n/locales/languages.json index 0143db3d1..8d502e90c 100644 --- a/src/lib/i18n/locales/languages.json +++ b/src/lib/i18n/locales/languages.json @@ -28,12 +28,8 @@ "title": "Cebuano (Filipino)" }, { - "code": "de-DE-formal", - "title": "German (Deutsch - Sie)" - }, - { - "code": "de-DE-informal", - "title": "German (Deutsch - Du)" + "code": "de-DE", + "title": "German (Deutsch)" }, { "code": "es-ES", From 8634140306513d60c252142d5b8973da5196f5a0 Mon Sep 17 00:00:00 2001 From: bannert <58707896+bannert1337@users.noreply.github.com> Date: Mon, 1 Jul 2024 18:38:52 +0200 Subject: [PATCH 040/181] refactor: Remove changes to i18n/index.ts --- src/lib/i18n/index.ts | 56 +++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/src/lib/i18n/index.ts b/src/lib/i18n/index.ts index 265e0bd6f..172c42f91 100644 --- a/src/lib/i18n/index.ts +++ b/src/lib/i18n/index.ts @@ -1,6 +1,6 @@ import i18next from 'i18next'; -import LanguageDetector from 'i18next-browser-languagedetector'; import resourcesToBackend from 'i18next-resources-to-backend'; +import LanguageDetector from 'i18next-browser-languagedetector'; import type { i18n as i18nType } from 'i18next'; import { writable } from 'svelte/store'; @@ -37,32 +37,36 @@ const createIsLoadingStore = (i18n: i18nType) => { return isLoading; }; -export const initI18n = (defaultLocale?: string) => { - // Use object destructuring for cleaner code - const [defaultDetection, fallbackDetection] = defaultLocale ? ['querystring', 'localStorage'] : ['querystring', 'localStorage', 'navigator']; - - // Use nullish coalescing operator to simplify the ternary expression - const fallbackDefaultLocale = defaultLocale ?? 'en-US'; - - const loadResource = (language: string, namespace: string) => import(`./locales/${language}/${namespace}.json`); - +export const initI18n = (defaultLocale: string | undefined) => { + let detectionOrder = defaultLocale + ? ['querystring', 'localStorage'] + : ['querystring', 'localStorage', 'navigator']; + let fallbackDefaultLocale = defaultLocale ? [defaultLocale] : ['en-US']; + + const loadResource = (language: string, namespace: string) => + import(`./locales/${language}/${namespace}.json`); + i18next - .use(resourcesToBackend(loadResource)) - .use(LanguageDetector) - .init({ - debug: false, - detection: { - order: [defaultDetection, fallbackDetection], - caches: ['localStorage'], - lookupQuerystring: 'lang', - lookupLocalStorage: 'locale' - }, - fallbackLng: fallbackDefaultLocale, - ns: 'translation', - returnEmptyString: false, - interpolation: { escapeValue: false } - }); - }; + .use(resourcesToBackend(loadResource)) + .use(LanguageDetector) + .init({ + debug: false, + detection: { + order: detectionOrder, + caches: ['localStorage'], + lookupQuerystring: 'lang', + lookupLocalStorage: 'locale' + }, + fallbackLng: { + default: fallbackDefaultLocale + }, + ns: 'translation', + returnEmptyString: false, + interpolation: { + escapeValue: false // not needed for svelte as it escapes by default + } + }); +}; const i18n = createI18nStore(i18next); const isLoadingStore = createIsLoadingStore(i18next); From db495a1df0b4b38b1efd280a3e5c0b9b727477d5 Mon Sep 17 00:00:00 2001 From: bannert <58707896+bannert1337@users.noreply.github.com> Date: Mon, 1 Jul 2024 18:40:57 +0200 Subject: [PATCH 041/181] refactor: Revert changes to i18n/locales/languages.json --- 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 8d502e90c..a22a4351a 100644 --- a/src/lib/i18n/locales/languages.json +++ b/src/lib/i18n/locales/languages.json @@ -63,6 +63,10 @@ "code": "hr-HR", "title": "Croatian (Hrvatski)" }, + { + "code": "id-ID", + "title": "Indonesian (Bahasa Indonesia)" + }, { "code": "it-IT", "title": "Italian (Italiano)" @@ -147,4 +151,4 @@ "code": "dg-DG", "title": "Doge (🐶)" } -] \ No newline at end of file +] From 77f5d90be3ff097548a37b17208aa7b5ec999a48 Mon Sep 17 00:00:00 2001 From: bannert <58707896+bannert1337@users.noreply.github.com> Date: Mon, 1 Jul 2024 18:41:49 +0200 Subject: [PATCH 042/181] Delete src/lib/i18n/locales/de-DE/translation-informal.json --- .../locales/de-DE/translation-informal.json | 667 ------------------ 1 file changed, 667 deletions(-) delete mode 100644 src/lib/i18n/locales/de-DE/translation-informal.json diff --git a/src/lib/i18n/locales/de-DE/translation-informal.json b/src/lib/i18n/locales/de-DE/translation-informal.json deleted file mode 100644 index c69f875a0..000000000 --- a/src/lib/i18n/locales/de-DE/translation-informal.json +++ /dev/null @@ -1,667 +0,0 @@ -{ - "'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' oder '-1' für keine Ablaufzeit.", - "(Beta)": "(Beta)", - "(e.g. `sh webui.sh --api --api-auth username_password`)": "(z. B. `sh webui.sh --api --api-auth username_password`)", - "(e.g. `sh webui.sh --api`)": "(z. B. `sh webui.sh --api`)", - "(latest)": "(neueste)", - "{{ models }}": "{{ Modelle }}", - "{{ owner }}: You cannot delete a base model": "{{ owner }}: Du kannst ein Basismodell nicht löschen", - "{{modelName}} is thinking...": "{{modelName}} denkt nach...", - "{{user}}'s Chats": "{{user}}s Unterhaltungen", - "{{webUIName}} Backend Required": "{{webUIName}}-Backend erforderlich", - "A task model is used when performing tasks such as generating titles for chats and web search queries": "Aufgabenmodelle können Unterhaltungstitel oder Websuchanfragen generieren.", - "a user": "ein Benutzer", - "About": "Über", - "Account": "Konto", - "Account Activation Pending": "Kontoaktivierung ausstehend", - "Accurate information": "Präzise Information(en)", - "Active Users": "Aktive Benutzer", - "Add": "Hinzufügen", - "Add a model id": "Modell-ID hinzufügen", - "Add a short description about what this model does": "Füge eine kurze Beschreibung über dieses Modell hinzu", - "Add a short title for this prompt": "Füge einen kurzen Titel für diesen Prompt hinzu", - "Add a tag": "Tag hinzufügen", - "Add custom prompt": "Benutzerdefinierten Prompt hinzufügen", - "Add Docs": "Dokumente hinzufügen", - "Add Files": "Dateien hinzufügen", - "Add Memory": "Erinnerung hinzufügen", - "Add message": "Nachricht hinzufügen", - "Add Model": "Modell hinzufügen", - "Add Tags": "Tags hinzufügen", - "Add User": "Benutzer hinzufügen", - "Adjusting these settings will apply changes universally to all users.": "Das Anpassen dieser Einstellungen wird Änderungen universell auf alle Benutzer anwenden.", - "admin": "Administrator", - "Admin": "Administrator", - "Admin Panel": "Administrationsbereich", - "Admin Settings": "Administrator-Einstellungen", - "Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "Administratoren haben jederzeit Zugriff auf alle Werkzeuge. Benutzer können im Arbeitsbereich zugewiesen.", - "Advanced Parameters": "Erweiterte Parameter", - "Advanced Params": "Erweiterte Parameter", - "all": "Alle", - "All Documents": "Alle Dokumente", - "All Users": "Alle Benutzer", - "Allow": "Erlauben", - "Allow Chat Deletion": "Chat löschen erlauben", - "Allow non-local voices": "Nicht-lokale Stimmen erlauben", - "Allow User Location": "Standort freigeben", - "Allow Voice Interruption in Call": "Unterbrechung durch Stimme im Anruf zulassen", - "alphanumeric characters and hyphens": "alphanumerische Zeichen und Bindestriche", - "Already have an account?": "Hast du vielleicht schon ein Account?", - "an assistant": "ein Assistent", - "and": "und", - "and create a new shared link.": "und erstelle einen neuen freigegebenen Link.", - "API Base URL": "API-Basis-URL", - "API Key": "API-Schlüssel", - "API Key created.": "API-Schlüssel erstellt.", - "API keys": "API-Schlüssel", - "April": "April", - "Archive": "Archivieren", - "Archive All Chats": "Alle Unterhaltungen archivieren", - "Archived Chats": "Archivierte Unterhaltungen", - "are allowed - Activate this command by typing": "sind erlaubt - Aktiviere diesen Befehl durch Eingabe von", - "Are you sure?": "Bist du sicher?", - "Attach file": "Datei anhängen", - "Attention to detail": "Aufmerksamkeit für Details", - "Audio": "Audio", - "Audio settings updated successfully": "Audioeinstellungen erfolgreich aktualisiert", - "August": "August", - "Auto-playback response": "Antwort automatisch abspielen", - "AUTOMATIC1111 Api Auth String": "AUTOMATIC1111-API-Authentifizierungszeichenfolge", - "AUTOMATIC1111 Base URL": "AUTOMATIC1111-Basis-URL", - "AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111-Basis-URL ist erforderlich.", - "available!": "Verfügbar!", - "Back": "Zurück", - "Bad Response": "Schlechte Antwort", - "Banners": "Banner", - "Base Model (From)": "Basismodell (From)", - "Batch Size (num_batch)": "Stapelgröße (num_batch)", - "before": "bereits geteilt", - "Being lazy": "Faulheit", - "Brave Search API Key": "Brave Search API-Schlüssel", - "Bypass SSL verification for Websites": "SSL-Überprüfung für Webseiten umgehen", - "Call": "Anrufen", - "Call feature is not supported when using Web STT engine": "Die Anruffunktion wird nicht unterstützt, wenn die Web-STT-Engine verwendet wird.", - "Camera": "Kamera", - "Cancel": "Abbrechen", - "Capabilities": "Fähigkeiten", - "Change Password": "Passwort ändern", - "Chat": "Gespräch", - "Chat Background Image": "Unterhaltungs-Hintergrundbild", - "Chat Bubble UI": "Chat Bubble UI", - "Chat direction": "Textrichtung", - "Chat History": "Unterhaltungsverlauf", - "Chat History is off for this browser.": "Unterhaltungsverlauf ist in diesem Browser deaktiviert.", - "Chats": "Unterhaltungen", - "Check Again": "Erneut überprüfen", - "Check for updates": "Nach Updates suchen", - "Checking for updates...": "Sucht nach Updates...", - "Choose a model before saving...": "Wähle bitte zuerst ein Modell, bevor du speicherst...", - "Chunk Overlap": "Blocküberlappung", - "Chunk Params": "Blockparameter", - "Chunk Size": "Blockgröße", - "Citation": "Zitate", - "Clear memory": "Erinnerungen löschen", - "Click here for help.": "Klicke hier für Hilfe.", - "Click here to": "Klicke hier, um", - "Click here to download user import template file.": "Klicke hier, um die Vorlage für den Benutzerimport herunterzuladen.", - "Click here to select": "Klicke zum Auswählen hier", - "Click here to select a csv file.": "Klicke zum Auswählen einer CSV-Datei hier.", - "Click here to select a py file.": "Klicke zum Auswählen einer py-Datei hier.", - "Click here to select documents.": "Klicke zum Auswählen von Dokumenten hier", - "click here.": "hier klicken.", - "Click on the user role button to change a user's role.": "Klicke auf die Benutzerrolle, um sie zu ändern.", - "Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "Schreibberechtigung für die Zwischenablage verweigert. Bitte überprüfe deine Browsereinstellungen, um den erforderlichen Zugriff zu erlauben.", - "Clone": "Klonen", - "Close": "Schließen", - "Code formatted successfully": "Code erfolgreich formatiert", - "Collection": "Kollektion", - "ComfyUI": "ComfyUI", - "ComfyUI Base URL": "ComfyUI-Basis-URL", - "ComfyUI Base URL is required.": "ComfyUI-Basis-URL wird benötigt.", - "Command": "Befehl", - "Concurrent Requests": "Gleichzeitige Anforderungen", - "Confirm": "Bestätigen", - "Confirm Password": "Passwort bestätigen", - "Confirm your action": "Bestätige deine Aktion.", - "Connections": "Verbindungen", - "Contact Admin for WebUI Access": "Kontaktiere den Administrator für den Zugriff auf die Weboberfläche", - "Content": "Info", - "Context Length": "Kontextlänge", - "Continue Response": "Antwort fortsetzen", - "Continue with {{provider}}": "Mit {{Anbieter}} fortfahren", - "Copied shared chat URL to clipboard!": "Freigabelink in die Zwischenablage kopiert!", - "Copy": "Kopieren", - "Copy last code block": "Letzten Codeblock kopieren", - "Copy last response": "Letzte Antwort kopieren", - "Copy Link": "Link kopieren", - "Copying to clipboard was successful!": "Das Kopieren in die Zwischenablage war erfolgreich!", - "Create a model": "Ein Modell erstellen", - "Create Account": "Konto erstellen", - "Create new key": "Neuen Schlüssel erstellen", - "Create new secret key": "Neuen API-Schlüssel erstellen", - "Created at": "Erstellt am", - "Created At": "Erstellt am", - "Created by": "Erstellt von", - "CSV Import": "CSV-Import", - "Current Model": "Aktuelles Modell", - "Current Password": "Aktuelles Passwort", - "Custom": "Benutzerdefiniert", - "Customize models for a specific purpose": "Modelle für einen bestimmten Zweck anpassen", - "Dark": "Dunkel", - "Dashboard": "Übersicht", - "Database": "Datenbank", - "December": "Dezember", - "Default": "Standard", - "Default (Automatic1111)": "Standard (Automatic1111)", - "Default (SentenceTransformers)": "Standard (SentenceTransformers)", - "Default Model": "Standardmodell", - "Default model updated": "Standardmodell aktualisiert", - "Default Prompt Suggestions": "Prompt-Vorschläge", - "Default User Role": "Standardbenutzerrolle", - "delete": "löschen", - "Delete": "Löschen", - "Delete a model": "Ein Modell löschen", - "Delete All Chats": "Alle Unterhaltungen löschen", - "Delete chat": "Unterhaltung löschen", - "Delete Chat": "Unterhaltung löschen", - "Delete chat?": "Unterhaltung löschen?", - "Delete function?": "Funktion löschen?", - "Delete prompt?": "Prompt löschen?", - "delete this link": "diesen Link löschen", - "Delete tool?": "Werkzeug löschen?", - "Delete User": "Benutzer löschen", - "Deleted {{deleteModelTag}}": "{{deleteModelTag}} gelöscht", - "Deleted {{name}}": "{{name}} gelöscht", - "Description": "Beschreibung", - "Didn't fully follow instructions": "Nicht genau den Answeisungen gefolgt", - "Discover a function": "Entdecke weitere Funktionen", - "Discover a model": "Entdecke weitere Modelle", - "Discover a prompt": "Entdecke weitere Prompts", - "Discover a tool": "Entdecke weitere Werkzeuge", - "Discover, download, and explore custom functions": "Entdecke und beziehe benutzerdefinierte Funktionen", - "Discover, download, and explore custom prompts": "Entdecke und beziehe benutzerdefinierte Prompts", - "Discover, download, and explore custom tools": "Entdecke und beziehe benutzerdefinierte Werkzeuge", - "Discover, download, and explore model presets": "Entdecke und beziehe benutzerdefinierte Modellvorlagen", - "Dismissible": "ausblendbar", - "Display Emoji in Call": "Emojis im Anruf anzeigen", - "Display the username instead of You in the Chat": "Soll \"Du\" durch deinen Benutzernamen ersetzt werden?", - "Document": "Dokument", - "Document Settings": "Dokumenteinstellungen", - "Documentation": "Dokumentation", - "Documents": "Dokumente", - "does not make any external connections, and your data stays securely on your locally hosted server.": "stellt keine externen Verbindungen her, und Ihre Daten bleiben sicher auf Ihrem lokal gehosteten Server.", - "Don't Allow": "Nicht erlauben", - "Don't have an account?": "Hast du noch kein Benutzerkonto?", - "Don't like the style": "schlechter Schreibstil", - "Done": "Erledigt", - "Download": "Exportieren", - "Download canceled": "Exportierung abgebrochen", - "Download Database": "Datenbank exportieren", - "Drop any files here to add to the conversation": "Ziehe beliebige Dateien hierher, um sie der Unterhaltung hinzuzufügen", - "e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "z. B. '30s','10m'. Gültige Zeiteinheiten sind 's', 'm', 'h'.", - "Edit": "Bearbeiten", - "Edit Doc": "Dokument bearbeiten", - "Edit Memory": "Erinnerungen bearbeiten", - "Edit User": "Benutzer bearbeiten", - "Email": "E-Mail", - "Embedding Batch Size": "Embedding Batch Größe", - "Embedding Model": "Embedding-Modell", - "Embedding Model Engine": "Embedding-Modell-Engine", - "Embedding model set to \"{{embedding_model}}\"": "Embedding-Modell auf \"{{embedding_model}}\" gesetzt", - "Enable Chat History": "Chat-Verlauf aktivieren", - "Enable Community Sharing": "Community-Freigabe aktivieren", - "Enable New Sign Ups": "Neue Anmeldungen aktivieren", - "Enable Web Search": "Websuche aktivieren", - "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Stelle sicher, dass deine CSV-Datei 4 Spalten in dieser Reihenfolge enthält: Name, E-Mail, Passwort, Rolle.", - "Enter {{role}} message here": "Gib die {{role}}-Nachricht hier ein", - "Enter a detail about yourself for your LLMs to recall": "Gib ein Detail über dich selbst ein, das deine Sprachmodelle (LLMs) sich merken sollen", - "Enter api auth string (e.g. username:password)": "Gib die API-Authentifizierungszeichenfolge ein (z. B. Benutzername:Passwort)", - "Enter Brave Search API Key": "Gib den Brave Search API-Schlüssel ein", - "Enter Chunk Overlap": "Gib den Chunk Overlap ein", - "Enter Chunk Size": "Gib die Chunk Size ein", - "Enter Github Raw URL": "Geben Sie die Github Raw-URL ein", - "Enter Google PSE API Key": "Geben Sie den Google PSE-API-Schlüssel ein", - "Enter Google PSE Engine Id": "Geben Sie die Google PSE-Engine-ID ein", - "Enter Image Size (e.g. 512x512)": "Gib die Bildgröße ein (z.B. 512x512)", - "Enter language codes": "Geben Sie die Sprachcodes ein", - "Enter model tag (e.g. {{modelTag}})": "Gib den Model-Tag ein", - "Enter Number of Steps (e.g. 50)": "Gib die Anzahl an Schritten ein (z.B. 50)", - "Enter Score": "Punktzahl eingeben", - "Enter Searxng Query URL": "Geben Sie die Searxng-Abfrage-URL ein", - "Enter Serper API Key": "Geben Sie den Serper-API-Schlüssel ein", - "Enter Serply API Key": "Geben Sie den", - "Enter Serpstack API Key": "Geben Sie den Serpstack-API-Schlüssel ein", - "Enter stop sequence": "Stop-Sequenz eingeben", - "Enter Tavily API Key": "Geben Sie den Tavily-API-Schlüssel ein", - "Enter Top K": "Geben Sie Top K ein", - "Enter URL (e.g. http://127.0.0.1:7860/)": "Geben Sie die URL ein (z. B. http://127.0.0.1:7860/)", - "Enter URL (e.g. http://localhost:11434)": "Geben Sie die URL ein (z. B. http://localhost:11434)", - "Enter Your Email": "Geben Sie Ihre E-Mail-Adresse ein", - "Enter Your Full Name": "Geben Sie Ihren vollständigen Namen ein", - "Enter Your Password": "Geben Sie Ihr Passwort ein", - "Enter Your Role": "Geben Sie Ihre Rolle ein", - "Error": "Fehler", - "Experimental": "Experimentell", - "Export": "Exportieren", - "Export All Chats (All Users)": "Alle Unterhaltungen exportieren (alle Benutzer)", - "Export chat (.json)": "Unterhaltung exportieren (.json)", - "Export Chats": "Unterhaltungen exportieren", - "Export Documents Mapping": "Dokumentenzuordnung exportieren", - "Export Functions": "Funktionen exportieren", - "Export LiteLLM config.yaml": "LiteLLM-Konfiguration exportieren (config.yaml)", - "Export Models": "Modelle exportieren", - "Export Prompts": "Prompts exportieren", - "Export Tools": "Werkzeuge exportieren", - "External Models": "Externe Modelle", - "Failed to create API Key.": "Fehler beim Erstellen des API-Schlüssels.", - "Failed to read clipboard contents": "Fehler beim Abruf der Zwischenablage", - "Failed to update settings": "Fehler beim Aktualisieren der Einstellungen", - "February": "Februar", - "Feel free to add specific details": "Fühlen Sie sich frei, spezifische Details hinzuzufügen", - "File": "Datei", - "File Mode": "Datei-Modus", - "File not found.": "Datei nicht gefunden.", - "Filter is now globally disabled": "Filter ist jetzt global deaktiviert", - "Filter is now globally enabled": "Filter ist jetzt global aktiviert", - "Filters": "Filter", - "Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Fingerabdruck-Spoofing erkannt: Initialen können nicht als Avatar verwendet werden. Standard-Avatar wird verwendet.", - "Fluidly stream large external response chunks": "Nahtlose Übertragung großer externer Antwortabschnitte", - "Focus chat input": "Chat-Eingabe fokussieren", - "Followed instructions perfectly": "Anweisungen perfekt befolgt", - "Form": "Formular", - "Format your variables using square brackets like this:": "Formatieren Sie Ihre Variablen mit eckigen Klammern wie folgt:", - "Frequency Penalty": "Frequenzstrafe", - "Function created successfully": "Funktion erfolgreich erstellt", - "Function deleted successfully": "Funktion erfolgreich gelöscht", - "Function updated successfully": "Funktion erfolgreich aktualisiert", - "Functions": "Funktionen", - "Functions imported successfully": "Funktionen erfolgreich importiert", - "General": "Allgemein", - "General Settings": "Allgemeine Einstellungen", - "Generate Image": "Bild erzeugen", - "Generating search query": "Suchanfrage wird erstellt", - "Generation Info": "Generierungsinformationen", - "Global": "Global", - "Good Response": "Gute Antwort", - "Google PSE API Key": "Google PSE-API-Schlüssel", - "Google PSE Engine Id": "Google PSE-Engine-ID", - "h:mm a": "h:mm a", - "has no conversations.": "hat keine Unterhaltungen.", - "Hello, {{name}}": "Hallo, {{name}}", - "Help": "Hilfe", - "Hide": "Verbergen", - "Hide Model": "Modell ausblenden", - "How can I help you today?": "Wie kann ich Ihnen heute helfen?", - "Hybrid Search": "Hybride Suche", - "Image Generation (Experimental)": "Bildgenerierung (experimentell)", - "Image Generation Engine": "Bildgenerierungs-Engine", - "Image Settings": "Bildeinstellungen", - "Images": "Bilder", - "Import Chats": "Chats importieren", - "Import Documents Mapping": "Dokumentenzuordnung importieren", - "Import Functions": "Funktionen importieren", - "Import Models": "Modelle importieren", - "Import Prompts": "Prompts importieren", - "Import Tools": "Werkzeuge importieren", - "Include `--api-auth` flag when running stable-diffusion-webui": "Fügen Sie beim Ausführen von stable-diffusion-webui die Option `--api-auth` hinzu", - "Include `--api` flag when running stable-diffusion-webui": "Fügen Sie beim Ausführen von stable-diffusion-webui die Option `--api` hinzu", - "Info": "Info", - "Input commands": "Eingabebefehle", - "Install from Github URL": "Installieren Sie von der Github-URL", - "Instant Auto-Send After Voice Transcription": "Spracherkennung direkt absenden", - "Interface": "Benutzeroberfläche", - "Invalid Tag": "Ungültiger Tag", - "January": "Januar", - "join our Discord for help.": "Trete unserem Discord bei, um Hilfe zu erhalten.", - "JSON": "JSON", - "JSON Preview": "JSON-Vorschau", - "July": "Juli", - "June": "Juni", - "JWT Expiration": "JWT-Ablauf", - "JWT Token": "JWT-Token", - "Keep Alive": "Verbindung aufrechterhalten", - "Keyboard shortcuts": "Tastenkombinationen", - "Knowledge": "Wissen", - "Language": "Sprache", - "Last Active": "Zuletzt aktiv", - "Last Modified": "Zuletzt bearbeitet", - "Light": "Hell", - "Listening...": "Höre zu...", - "LLMs can make mistakes. Verify important information.": "LLMs können Fehler machen. Überprüfe wichtige Informationen.", - "Local Models": "Lokale Modelle", - "LTR": "LTR", - "Made by OpenWebUI Community": "Von der OpenWebUI-Community", - "Make sure to enclose them with": "Umschließen Sie Variablen mit", - "Manage": "Verwalten", - "Manage Models": "Modelle verwalten", - "Manage Ollama Models": "Ollama-Modelle verwalten", - "Manage Pipelines": "Pipelines verwalten", - "Manage Valves": "Valves verwalten", - "March": "März", - "Max Tokens (num_predict)": "Maximale Tokenanzahl (num_predict)", - "Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Es können maximal 3 Modelle gleichzeitig heruntergeladen werden. Bitte versuchen Sie es später erneut.", - "May": "Mai", - "Memories accessible by LLMs will be shown here.": "Erinnerungen, die für Modelle zugänglich sind, werden hier angezeigt.", - "Memory": "Erinnerung", - "Memory added successfully": "Erinnerung erfolgreich hinzugefügt", - "Memory cleared successfully": "Erinnerung erfolgreich gelöscht", - "Memory deleted successfully": "Erinnerung erfolgreich gelöscht", - "Memory updated successfully": "Erinnerung erfolgreich aktualisiert", - "Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Nachrichten, die Sie nach der Erstellung Ihres Links senden, werden nicht geteilt. Nutzer mit der URL können die freigegebene Unterhaltung einsehen.", - "Minimum Score": "Mindestpunktzahl", - "Mirostat": "Mirostat", - "Mirostat Eta": "Mirostat Eta", - "Mirostat Tau": "Mirostat Tau", - "MMMM DD, YYYY": "DD MMMM YYYY", - "MMMM DD, YYYY HH:mm": "DD MMMM YYYY HH:mm", - "MMMM DD, YYYY hh:mm:ss A": "DD MMMM YYYY HH:mm A", - "Model '{{modelName}}' has been successfully downloaded.": "Modell '{{modelName}}' wurde erfolgreich heruntergeladen.", - "Model '{{modelTag}}' is already in queue for downloading.": "Modell '{{modelTag}}' befindet sich bereits in der Warteschlange zum Herunterladen.", - "Model {{modelId}} not found": "Modell {{modelId}} nicht gefunden", - "Model {{modelName}} is not vision capable": "Das Modell {{modelName}} ist nicht für die Bildverarbeitung geeignet", - "Model {{name}} is now {{status}}": "Modell {{name}} ist jetzt {{status}}", - "Model created successfully!": "Modell erfolgreich erstellt!", - "Model filesystem path detected. Model shortname is required for update, cannot continue.": "Modell-Dateisystempfad erkannt. Modellkurzname ist für das Update erforderlich, Fortsetzung nicht möglich.", - "Model ID": "Modell-ID", - "Model not selected": "Modell nicht ausgewählt", - "Model Params": "Modell-Params", - "Model updated successfully": "Modell erfolgreich aktualisiert", - "Model Whitelisting": "Modell-Whitelisting", - "Model(s) Whitelisted": "Modell(e) auf der Whitelist", - "Modelfile Content": "Modelfile-Inhalt", - "Models": "Modelle", - "More": "Mehr", - "Name": "Name", - "Name Tag": "Namens-Tag", - "Name your model": "Benennen Sie Ihr Modell", - "New Chat": "Neuer Chat", - "New Password": "Neues Passwort", - "No content to speak": "Kein Inhalt zum Vorlesen", - "No documents found": "Keine Dokumente gefunden", - "No file selected": "Keine Datei ausgewählt", - "No results found": "Keine Ergebnisse gefunden", - "No search query generated": "Keine Suchanfrage generiert", - "No source available": "Keine Quelle verfügbar", - "No valves to update": "Keine Valves zum Aktualisieren", - "None": "Nichts", - "Not factually correct": "Nicht sachlich korrekt", - "Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Hinweis: Wenn du einen Mindestscore festlegst, wird die Suche nur Dokumente zurückgeben, deren Score größer oder gleich dem Mindestscore ist.", - "Notifications": "Benachrichtigungen", - "November": "November", - "num_thread (Ollama)": "num_thread (Ollama)", - "OAuth ID": "OAuth-ID", - "October": "Oktober", - "Off": "Aus", - "Okay, Let's Go!": "Okay, los geht's!", - "OLED Dark": "OLED-Dunkel", - "Ollama": "Ollama", - "Ollama API": "Ollama-API", - "Ollama API disabled": "Ollama-API deaktiviert", - "Ollama API is disabled": "Ollama-API ist deaktiviert.", - "Ollama Version": "Ollama-Version", - "On": "Ein", - "Only": "Nur", - "Only alphanumeric characters and hyphens are allowed in the command string.": "In der Befehlszeichenfolge sind nur alphanumerische Zeichen und Bindestriche erlaubt.", - "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.": "Ups! Bitte gedulden Sie sich einen Moment! Ihre Dateien sind noch in der Verarbeitung. Wir bereiten sie sorgfältig vor. Bitte haben Sie etwas Geduld, und wir werden Sie benachrichtigen, sobald sie fertig sind.", - "Oops! Looks like the URL is invalid. Please double-check and try again.": "Hoppla! Es scheint, dass die URL ungültig ist. Bitte überprüfen Sie diese und versuchen Sie es erneut.", - "Oops! There was an error in the previous response. Please try again or contact admin.": "Hoppla! Es gab einen Fehler in der vorherigen Antwort. Bitte versuchen Sie es erneut oder kontaktieren Sie den Administrator.", - "Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Hoppla! du verwendest eine nicht unterstützte Methode (nur Frontend). Bitte stelle die WebUI vom Backend aus bereit.", - "Open": "Öffne", - "Open AI (Dall-E)": "Open AI (Dall-E)", - "Open new chat": "Neuen Chat öffnen", - "OpenAI": "OpenAI", - "OpenAI API": "OpenAI-API", - "OpenAI API Config": "OpenAI-API-Konfiguration", - "OpenAI API Key is required.": "OpenAI-API-Schlüssel erforderlich.", - "OpenAI URL/Key required.": "OpenAI-URL/Schlüssel erforderlich.", - "or": "oder", - "Other": "Andere", - "Password": "Passwort", - "PDF document (.pdf)": "PDF-Dokument (.pdf)", - "PDF Extract Images (OCR)": "Text von Bildern aus PDFs extrahieren (OCR)", - "pending": "ausstehend", - "Permission denied when accessing media devices": "Zugriff auf Mediengeräte verweigert", - "Permission denied when accessing microphone": "Zugriff auf das Mikrofon verweigert", - "Permission denied when accessing microphone: {{error}}": "Zugriff auf das Mikrofon verweigert: {{error}}", - "Personalization": "Personalisierung", - "Pipeline deleted successfully": "Pipeline erfolgreich gelöscht", - "Pipeline downloaded successfully": "Pipeline erfolgreich heruntergeladen", - "Pipelines": "Pipelines", - "Pipelines Not Detected": "Pipelines nicht erkannt", - "Pipelines Valves": "Pipeline Valves", - "Plain text (.txt)": "Nur Text (.txt)", - "Playground": "Testumgebung", - "Positive attitude": "Positive Einstellung", - "Previous 30 days": "Vorherige 30 Tage", - "Previous 7 days": "Vorherige 7 Tage", - "Profile Image": "Profilbild", - "Prompt": "Prompt", - "Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Prompt (z. B. \"Erzähle mir eine interessante Tatsache über das Römische Reich\")", - "Prompt Content": "Prompt-Inhalt", - "Prompt suggestions": "Prompt-Vorschläge", - "Prompts": "Prompts", - "Pull \"{{searchValue}}\" from Ollama.com": "\"{{searchValue}}\" von Ollama.com beziehen", - "Pull a model from Ollama.com": "Modell von Ollama.com beziehn", - "Query Params": "Abfrageparameter", - "RAG Template": "RAG-Vorlage", - "Read Aloud": "Vorlesen", - "Record voice": "Stimme aufnehmen", - "Redirecting you to OpenWebUI Community": "Sie werden zur OpenWebUI-Community weitergeleitet", - "Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "", - "Refused when it shouldn't have": "Abgelehnt, obwohl es nicht hätte abgelehnt werden sollen", - "Regenerate": "Neu generieren", - "Release Notes": "Veröffentlichungshinweise", - "Remove": "Entfernen", - "Remove Model": "Modell entfernen", - "Rename": "Umbenennen", - "Repeat Last N": "Wiederhole die letzten N", - "Request Mode": "Anforderungsmodus", - "Reranking Model": "Reranking-Modell", - "Reranking model disabled": "Reranking-Modell deaktiviert", - "Reranking model set to \"{{reranking_model}}\"": "Reranking-Modell \"{{reranking_model}}\" fesgelegt", - "Reset": "Zurücksetzen", - "Reset Upload Directory": "Upload-Verzeichnis zurücksetzen", - "Reset Vector Storage": "Vektorspeicher zurücksetzen", - "Response AutoCopy to Clipboard": "Antwort automatisch in die Zwischenablage kopieren", - "Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "Benachrichtigungen können nicht aktiviert werden, da die Website-Berechtigungen abgelehnt wurden. Bitte besuchen Sie Ihre Browser-Einstellungen, um den erforderlichen Zugriff zu gewähren.", - "Role": "Rolle", - "Rosé Pine": "Rosé Pine", - "Rosé Pine Dawn": "Rosé Pine Dawn", - "RTL": "RTL", - "Running": "Läuft", - "Save": "Speichern", - "Save & Create": "Erstellen", - "Save & Update": "Aktualisieren", - "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": "Das direkte Speichern von Unterhaltungen im Browser-Speicher wird nicht mehr unterstützt. Bitte nehmen Sie einen Moment Zeit, um Ihre Unterhaltungen zu exportieren und zu löschen, indem Sie auf die Schaltfläche unten klicken. Keine Sorge, Sie können Ihre Unterhaltungen problemlos über das Backend wieder importieren.", - "Scan": "Scannen", - "Scan complete!": "Scan abgeschlossen!", - "Scan for documents from {{path}}": "Dokumente im {{path}} scannen", - "Search": "Suchen", - "Search a model": "Modell suchen", - "Search Chats": "Unterhaltungen durchsuchen...", - "Search Documents": "Dokumente durchsuchen...", - "Search Functions": "Funktionen durchsuchen...", - "Search Models": "Modelle durchsuchen...", - "Search Prompts": "Prompts durchsuchen...", - "Search Query Generation Prompt": "Suchanfragen-Generierungs-Prompt", - "Search Query Generation Prompt Length Threshold": "Suchanfragen-Generierungs-Prompt-Längenschwellenwert", - "Search Result Count": "Anzahl der Suchergebnisse", - "Search Tools": "Suchwerkzeuge", - "Searched {{count}} sites_one": "{{count}} Seite durchsucht", - "Searched {{count}} sites_other": "{{count}} Seiten durchsucht", - "Searching \"{{searchQuery}}\"": "Suche nach \"{{searchQuery}}\"", - "Searxng Query URL": "Searxng-Abfrage-URL", - "See readme.md for instructions": "Anleitung in readme.md anzeigen", - "See what's new": "Entdecken Sie die Neuigkeiten", - "Seed": "Seed", - "Select a base model": "Wählen Sie ein Basismodell", - "Select a engine": "Wählen Sie eine Engine", - "Select a function": "Wählen Sie eine Funktion", - "Select a mode": "Wählen Sie einen Modus", - "Select a model": "Wählen Sie ein Modell", - "Select a pipeline": "Wählen Sie eine Pipeline", - "Select a pipeline url": "Wählen Sie eine Pipeline-URL", - "Select a tool": "Wählen Sie ein Werkzeug", - "Select an Ollama instance": "Wählen Sie eine Ollama-Instanz", - "Select Documents": "Dokumente auswählen", - "Select model": "Modell auswählen", - "Select only one model to call": "Wählen Sie nur ein Modell zum Anrufen aus", - "Selected model(s) do not support image inputs": "Ihre ausgewählten Modelle unterstützen keine Bildeingaben", - "Send": "Senden", - "Send a Message": "Eine Nachricht senden", - "Send message": "Nachricht senden", - "September": "September", - "Serper API Key": "Serper-API-Schlüssel", - "Serply API Key": "Serply-API-Schlüssel", - "Serpstack API Key": "Serpstack-API-Schlüssel", - "Server connection verified": "Serververbindung überprüft", - "Set as default": "Als Standard festlegen", - "Set Default Model": "Standardmodell festlegen", - "Set embedding model (e.g. {{model}})": "Einbettungsmodell festlegen (z. B. {{model}})", - "Set Image Size": "Bildgröße festlegen", - "Set reranking model (e.g. {{model}})": "Rerankingmodell festlegen (z. B. {{model}})", - "Set Steps": "Schrittgröße festlegen", - "Set Task Model": "Aufgabenmodell festlegen", - "Set Voice": "Stimme festlegen", - "Settings": "Einstellungen", - "Settings saved successfully!": "Einstellungen erfolgreich gespeichert!", - "Settings updated successfully": "Einstellungen erfolgreich aktualisiert", - "Share": "Teilen", - "Share Chat": "Unterhaltung teilen", - "Share to OpenWebUI Community": "Mit OpenWebUI Community teilen", - "short-summary": "kurze-zusammenfassung", - "Show": "Anzeigen", - "Show Admin Details in Account Pending Overlay": "Admin-Details im Account-Pending-Overlay anzeigen", - "Show Model": "Modell anzeigen", - "Show shortcuts": "Verknüpfungen anzeigen", - "Show your support!": "Zeigen Sie Ihre Unterstützung!", - "Showcased creativity": "Kreativität gezeigt", - "sidebar": "Seitenleiste", - "Sign in": "Anmelden", - "Sign Out": "Abmelden", - "Sign up": "Registrieren", - "Signing in": "Anmeldung", - "Source": "Quelle", - "Speech recognition error: {{error}}": "Spracherkennungsfehler: {{error}}", - "Speech-to-Text Engine": "Sprache-zu-Text-Engine", - "Stop Sequence": "Stop-Sequenz", - "STT Model": "STT-Modell", - "STT Settings": "STT-Einstellungen", - "Submit": "Senden", - "Subtitle (e.g. about the Roman Empire)": "Untertitel (z. B. über das Römische Reich)", - "Success": "Erfolg", - "Successfully updated.": "Erfolgreich aktualisiert.", - "Suggested": "Vorgeschlagen", - "System": "System", - "System Prompt": "System-Prompt", - "Tags": "Tags", - "Tap to interrupt": "Zum Unterbrechen tippen", - "Tavily API Key": "Tavily-API-Schlüssel", - "Tell us more:": "Erzähl uns mehr", - "Temperature": "Temperatur", - "Template": "Vorlage", - "Text Completion": "Textvervollständigung", - "Text-to-Speech Engine": "Text-zu-Sprache-Engine", - "Tfs Z": "Tfs Z", - "Thanks for your feedback!": "Danke für Ihr Feedback!", - "The score should be a value between 0.0 (0%) and 1.0 (100%).": "Die Punktzahl sollte ein Wert zwischen 0,0 (0 %) und 1,0 (100 %) sein.", - "Theme": "Design", - "Thinking...": "Denke nach...", - "This action cannot be undone. Do you wish to continue?": "Diese Aktion kann nicht rückgängig gemacht werden. Möchten Sie fortfahren?", - "This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Dies stellt sicher, dass Ihre wertvollen Unterhaltungen sicher in Ihrer Backend-Datenbank gespeichert werden. Vielen Dank!", - "This is an experimental feature, it may not function as expected and is subject to change at any time.": "Dies ist eine experimentelle Funktion, sie funktioniert möglicherweise nicht wie erwartet und kann jederzeit geändert werden.", - "This setting does not sync across browsers or devices.": "Diese Einstellung wird nicht zwischen Browsern oder Geräten synchronisiert.", - "This will delete": "Dies löscht", - "Thorough explanation": "Ausführliche Erklärung", - "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Tipp: Aktualisieren Sie mehrere Variablenfelder nacheinander, indem Sie nach jedem Ersetzen die Tabulatortaste im Eingabefeld der Unterhaltung drücken.", - "Title": "Titel", - "Title (e.g. Tell me a fun fact)": "Titel (z. B. Erzähl mir einen lustigen Fakt)", - "Title Auto-Generation": "Automatische Titelerstellung", - "Title cannot be an empty string.": "Titel darf nicht leer sein.", - "Title Generation Prompt": "Titelerstellung-Prompt", - "to": "für", - "To access the available model names for downloading,": "Um auf die verfügbaren Modellnamen zuzugreifen,", - "To access the GGUF models available for downloading,": "Um auf die verfügbaren GGUF-Modelle zuzugreifen,", - "To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "Um auf das WebUI zugreifen zu könnrn, wenden Sie sich bitte an einen Administrator. Administratoren können den Benutzerstatus über das Admin-Panel verwalten.", - "To add documents here, upload them to the \"Documents\" workspace first.": "Um Dokumente hinzuzufügen, laden Sie sie zuerst im Arbeitsbereich „Dokumente“ hoch.", - "to chat input.": "zum Eingabefeld der Unterhaltung.", - "To select filters here, add them to the \"Functions\" workspace first.": "Um Filter auszuwählen, fügen Sie diese zuerst dem Arbeitsbereich „Funktionen“ hinzu.", - "To select toolkits here, add them to the \"Tools\" workspace first.": "Um Toolkits auszuwählen, fügen Sie sie zuerst zum Arbeitsbereich „Werkzeuge“ hinzu.", - "Today": "Heute", - "Toggle settings": "Einstellungen umschalten", - "Toggle sidebar": "Seitenleiste umschalten", - "Tokens To Keep On Context Refresh (num_keep)": "Beizubehaltende Tokens bei Kontextaktualisierung (num_keep)", - "Tool created successfully": "Werkzeug erfolgreich erstellt", - "Tool deleted successfully": "Werkzeug erfolgreich gelöscht", - "Tool imported successfully": "Werkzeug erfolgreich importiert", - "Tool updated successfully": "Werkzeug erfolgreich aktualisiert", - "Tools": "Werkzeuge", - "Top K": "Top K", - "Top P": "Top P", - "Trouble accessing Ollama?": "Probleme beim Zugriff auf Ollama?", - "TTS Model": "TTS-Modell", - "TTS Settings": "TTS-Einstellungen", - "TTS Voice": "TTS-Stimme", - "Type": "Art", - "Type Hugging Face Resolve (Download) URL": "Gib die Hugging Face Resolve (Download) URL ein", - "Uh-oh! There was an issue connecting to {{provider}}.": "Ups! Es gab ein Problem bei der Verbindung mit {{provider}}.", - "UI": "Oberfläche", - "Unknown file type '{{file_type}}'. Proceeding with the file upload anyway.": "Unbekannter Dateityp '{{file_type}}'. Der Datei-Upload wird trotzdem fortgesetzt.", - "Update": "Aktualisieren", - "Update and Copy Link": "Aktualisieren und Link kopieren", - "Update password": "Passwort aktualisieren", - "Updated at": "Aktualisiert am", - "Upload": "Hochladen", - "Upload a GGUF model": "GGUF-Model hochladen", - "Upload Files": "Datei(en) hochladen", - "Upload Pipeline": "Pipeline hochladen", - "Upload Progress": "Hochladefortschritt", - "URL Mode": "URL-Modus", - "Use '#' in the prompt input to load and select your documents.": "Verwenden Sie '#' in der Eingabeaufforderung, um Ihre Dokumente zu laden und auszuwählen.", - "Use Gravatar": "Gravatar verwenden", - "Use Initials": "Initialen verwenden", - "use_mlock (Ollama)": "use_mlock (Ollama)", - "use_mmap (Ollama)": "use_mmap (Ollama)", - "user": "Benutzer", - "User location successfully retrieved.": "Benutzerstandort erfolgreich ermittelt.", - "User Permissions": "Benutzerberechtigungen", - "Users": "Benutzer", - "Utilize": "Verwende", - "Valid time units:": "Gültige Zeiteinheiten:", - "Valves": "Valves", - "Valves updated": "Valves aktualisiert", - "Valves updated successfully": "Valves erfolgreich aktualisiert", - "variable": "Variable", - "variable to have them replaced with clipboard content.": "Variable, um den Inhalt der Zwischenablage beim Nutzen des Prompts zu ersetzen.", - "Version": "Version", - "Voice": "Stimme", - "Warning": "Warnung", - "Warning: If you update or change your embedding model, you will need to re-import all documents.": "Warnung: Wenn Sie das Einbettungsmodell aktualisieren oder ändern, müssen Sie alle Dokumente erneut importieren.", - "Web": "Web", - "Web API": "Web-API", - "Web Loader Settings": "Web Loader Einstellungen", - "Web Params": "Web Parameter", - "Web Search": "Websuche", - "Web Search Engine": "Suchmaschine", - "Webhook URL": "Webhook URL", - "WebUI Settings": "WebUI-Einstellungen", - "WebUI will make requests to": "Wenn aktiviert sendet WebUI externe Anfragen an", - "What’s New in": "Neuigkeiten von", - "When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Wenn der Verlauf deaktiviert ist, werden neue Unterhaltungen in diesem Browser nicht im Verlauf Ihrer anderen Geräte erscheinen.", - "Whisper (Local)": "Whisper (lokal)", - "Widescreen Mode": "Breitbildmodus", - "Workspace": "Arbeitsbereich", - "Write a prompt suggestion (e.g. Who are you?)": "Gebe einen Prompt-Vorschlag ein (z. B. \"Wer bist du?\")", - "Write a summary in 50 words that summarizes [topic or keyword].": "Schreibe eine kurze Zusammenfassung in 50 Wörtern, die [Thema oder Schlüsselwort] zusammenfasst.", - "Yesterday": "Gestern", - "You": "Sie", - "You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Personalisieren Sie Interaktionen mit LLMs, indem Sie über die Schaltfläche \"Verwalten\" Erinnerungen hinzufügen.", - "You cannot clone a base model": "Sie können Basismodelle nicht klonen", - "You have no archived conversations.": "Du hast keine archivierten Unterhaltungen.", - "You have shared this chat": "Sie haben diese Unterhaltung geteilt", - "You're a helpful assistant.": "Du bist ein hilfreicher Assistent.", - "You're now logged in.": "Sie sind jetzt eingeloggt.", - "Your account status is currently pending activation.": "Ihr Kontostatus ist derzeit ausstehend und wartet auf Aktivierung.", - "Youtube": "YouTube", - "Youtube Loader Settings": "YouTube-Ladeeinstellungen" -} From 4ead3dabd824a92d391ced462c8a0d0b54329ca7 Mon Sep 17 00:00:00 2001 From: bannert <58707896+bannert1337@users.noreply.github.com> Date: Mon, 1 Jul 2024 18:41:56 +0200 Subject: [PATCH 043/181] Delete src/lib/i18n/locales/de-DE/translation-common.json --- src/lib/i18n/locales/de-DE/translation-common.json | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/lib/i18n/locales/de-DE/translation-common.json diff --git a/src/lib/i18n/locales/de-DE/translation-common.json b/src/lib/i18n/locales/de-DE/translation-common.json deleted file mode 100644 index e69de29bb..000000000 From 19d4c7f6cda4ddbca1eaec1799cb998a96878105 Mon Sep 17 00:00:00 2001 From: bannert <58707896+bannert1337@users.noreply.github.com> Date: Mon, 1 Jul 2024 18:42:56 +0200 Subject: [PATCH 044/181] Update languages.json --- src/lib/i18n/locales/languages.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/lib/i18n/locales/languages.json b/src/lib/i18n/locales/languages.json index a22a4351a..f205b8049 100644 --- a/src/lib/i18n/locales/languages.json +++ b/src/lib/i18n/locales/languages.json @@ -63,10 +63,6 @@ "code": "hr-HR", "title": "Croatian (Hrvatski)" }, - { - "code": "id-ID", - "title": "Indonesian (Bahasa Indonesia)" - }, { "code": "it-IT", "title": "Italian (Italiano)" From 8a38b0a286b3f31ba81e916861b323ebb14d09f0 Mon Sep 17 00:00:00 2001 From: bannert <58707896+bannert1337@users.noreply.github.com> Date: Mon, 1 Jul 2024 18:52:19 +0200 Subject: [PATCH 045/181] feat(config): exclude .vscode/settings.json from version control --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index a54fef595..32271f808 100644 --- a/.gitignore +++ b/.gitignore @@ -306,3 +306,4 @@ dist # cypress artifacts cypress/videos cypress/screenshots +.vscode/settings.json From 4041484a19c4126de9904644a741473bb4a59afd Mon Sep 17 00:00:00 2001 From: bannert <58707896+bannert1337@users.noreply.github.com> Date: Mon, 1 Jul 2024 18:55:28 +0200 Subject: [PATCH 046/181] i18n(de-DE): Improve translation consistency and accuracy --- src/lib/i18n/locales/de-DE/translation.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/i18n/locales/de-DE/translation.json b/src/lib/i18n/locales/de-DE/translation.json index 557d51908..1d13bf416 100644 --- a/src/lib/i18n/locales/de-DE/translation.json +++ b/src/lib/i18n/locales/de-DE/translation.json @@ -41,12 +41,12 @@ "All Documents": "Alle Dokumente", "All Users": "Alle Benutzer", "Allow": "Erlauben", - "Allow Chat Deletion": "Chat löschen erlauben", + "Allow Chat Deletion": "Unterhaltungen löschen erlauben", "Allow non-local voices": "Nicht-lokale Stimmen erlauben", "Allow User Location": "Standort freigeben", "Allow Voice Interruption in Call": "Unterbrechung durch Stimme im Anruf zulassen", "alphanumeric characters and hyphens": "alphanumerische Zeichen und Bindestriche", - "Already have an account?": "Hast du vielleicht schon ein Account?", + "Already have an account?": "Haben Sie bereits einen Account?", "an assistant": "ein Assistent", "and": "und", "and create a new shared link.": "und erstellen Sie einen neuen freigegebenen Link.", @@ -58,7 +58,7 @@ "Archive": "Archivieren", "Archive All Chats": "Alle Unterhaltungen archivieren", "Archived Chats": "Archivierte Unterhaltungen", - "are allowed - Activate this command by typing": "sind erlaubt - Aktivieren Sie diesen Befehl durch Eingabe von", + "are allowed - Activate this command by typing": "sind erlaubt — Aktivieren Sie diesen Befehl durch Eingabe von", "Are you sure?": "Sind Sie sicher?", "Attach file": "Datei anhängen", "Attention to detail": "Aufmerksamkeit für Details", From 7f937af0290f5bab6dfc45105b167f5c6d487bcc Mon Sep 17 00:00:00 2001 From: bannert <58707896+bannert1337@users.noreply.github.com> Date: Mon, 1 Jul 2024 19:01:22 +0200 Subject: [PATCH 047/181] i18n(de_DE): Update German translation strings for better grammar and clarity --- src/lib/i18n/locales/de-DE/translation.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/i18n/locales/de-DE/translation.json b/src/lib/i18n/locales/de-DE/translation.json index 1d13bf416..3ce56c06d 100644 --- a/src/lib/i18n/locales/de-DE/translation.json +++ b/src/lib/i18n/locales/de-DE/translation.json @@ -95,7 +95,7 @@ "Check Again": "Erneut überprüfen", "Check for updates": "Nach Updates suchen", "Checking for updates...": "Sucht nach Updates...", - "Choose a model before saving...": "Wähle bitte zuerst ein Modell, bevor du speicherst...", + "Choose a model before saving...": "Wählen Sie ein Modell, bevor Sie speichern...", "Chunk Overlap": "Blocküberlappung", "Chunk Params": "Blockparameter", "Chunk Size": "Blockgröße", @@ -385,7 +385,7 @@ "No valves to update": "Keine Valves zum Aktualisieren", "None": "Nichts", "Not factually correct": "Nicht sachlich korrekt", - "Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Hinweis: Wenn du einen Mindestscore festlegst, wird die Suche nur Dokumente zurückgeben, deren Score größer oder gleich dem Mindestscore ist.", + "Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Hinweis: Wenn Sie eine Mindestpunktzahl festlegen, werden in der Suche nur Dokumente mit einer Punktzahl größer oder gleich der Mindestpunktzahl zurückgegeben.", "Notifications": "Benachrichtigungen", "November": "November", "num_thread (Ollama)": "num_thread (Ollama)", @@ -405,7 +405,7 @@ "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.": "Ups! Bitte gedulden Sie sich einen Moment! Ihre Dateien sind noch in der Verarbeitung. Wir bereiten sie sorgfältig vor. Bitte haben Sie etwas Geduld, und wir werden Sie benachrichtigen, sobald sie fertig sind.", "Oops! Looks like the URL is invalid. Please double-check and try again.": "Hoppla! Es scheint, dass die URL ungültig ist. Bitte überprüfen Sie diese und versuchen Sie es erneut.", "Oops! There was an error in the previous response. Please try again or contact admin.": "Hoppla! Es gab einen Fehler in der vorherigen Antwort. Bitte versuchen Sie es erneut oder kontaktieren Sie den Administrator.", - "Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Hoppla! du verwendest eine nicht unterstützte Methode (nur Frontend). Bitte stelle die WebUI vom Backend aus bereit.", + "Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Hoppla! Sie verwenden eine nicht unterstützte Methode (nur Frontend). Bitte stellen Sie die WebUI vom Backend bereit.", "Open": "Öffne", "Open AI (Dall-E)": "Open AI (Dall-E)", "Open new chat": "Neuen Chat öffnen", @@ -651,7 +651,7 @@ "Whisper (Local)": "Whisper (lokal)", "Widescreen Mode": "Breitbildmodus", "Workspace": "Arbeitsbereich", - "Write a prompt suggestion (e.g. Who are you?)": "Gebe einen Prompt-Vorschlag ein (z. B. \"Wer bist du?\")", + "Write a prompt suggestion (e.g. Who are you?)": "Schreiben Sie einen Promptvorschlag (z. B. Wer sind Sie?)", "Write a summary in 50 words that summarizes [topic or keyword].": "Schreibe eine kurze Zusammenfassung in 50 Wörtern, die [Thema oder Schlüsselwort] zusammenfasst.", "Yesterday": "Gestern", "You": "Sie", From 7aa35a37573c1d0af136176756a16ba73b74f74b Mon Sep 17 00:00:00 2001 From: Nicko van Someren Date: Mon, 1 Jul 2024 12:10:59 -0600 Subject: [PATCH 048/181] Added HTML and Typescript UI components to support configration of text extraction engine. Updated RAG /config and /config/update endpoints to support UI updates. Fixed .dockerignore to prevent Python venv from being copied into Docker image. --- .dockerignore | 1 + backend/apps/rag/main.py | 30 +++++++++-- backend/config.py | 8 +-- src/lib/apis/rag/index.ts | 6 +++ .../admin/Settings/Documents.svelte | 50 +++++++++++++++++++ 5 files changed, 86 insertions(+), 9 deletions(-) diff --git a/.dockerignore b/.dockerignore index c7f330f4a..d7e716758 100644 --- a/.dockerignore +++ b/.dockerignore @@ -11,6 +11,7 @@ vite.config.js.timestamp-* vite.config.ts.timestamp-* __pycache__ .idea +venv _old uploads .ipynb_checkpoints diff --git a/backend/apps/rag/main.py b/backend/apps/rag/main.py index b6e2ee5e2..ce56d4f4d 100644 --- a/backend/apps/rag/main.py +++ b/backend/apps/rag/main.py @@ -93,7 +93,7 @@ from config import ( SRC_LOG_LEVELS, UPLOAD_DIR, DOCS_DIR, - DOCUMENT_USE_TIKA, + TEXT_EXTRACTION_ENGINE, TIKA_SERVER_URL, RAG_TOP_K, RAG_RELEVANCE_THRESHOLD, @@ -150,6 +150,9 @@ app.state.config.ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION = ( ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION ) +app.state.config.TEXT_EXTRACTION_ENGINE = TEXT_EXTRACTION_ENGINE +app.state.config.TIKA_SERVER_URL = TIKA_SERVER_URL + app.state.config.CHUNK_SIZE = CHUNK_SIZE app.state.config.CHUNK_OVERLAP = CHUNK_OVERLAP @@ -390,6 +393,10 @@ async def get_rag_config(user=Depends(get_admin_user)): return { "status": True, "pdf_extract_images": app.state.config.PDF_EXTRACT_IMAGES, + "text_extraction": { + "engine": app.state.config.TEXT_EXTRACTION_ENGINE, + "tika_server_url": app.state.config.TIKA_SERVER_URL, + }, "chunk": { "chunk_size": app.state.config.CHUNK_SIZE, "chunk_overlap": app.state.config.CHUNK_OVERLAP, @@ -419,6 +426,11 @@ async def get_rag_config(user=Depends(get_admin_user)): } +class TextExtractionConfig(BaseModel): + engine: str = "" + tika_server_url: Optional[str] = None + + class ChunkParamUpdateForm(BaseModel): chunk_size: int chunk_overlap: int @@ -452,6 +464,7 @@ class WebConfig(BaseModel): class ConfigUpdateForm(BaseModel): pdf_extract_images: Optional[bool] = None + text_extraction: Optional[TextExtractionConfig] = None chunk: Optional[ChunkParamUpdateForm] = None youtube: Optional[YoutubeLoaderConfig] = None web: Optional[WebConfig] = None @@ -465,6 +478,11 @@ async def update_rag_config(form_data: ConfigUpdateForm, user=Depends(get_admin_ else app.state.config.PDF_EXTRACT_IMAGES ) + if form_data.text_extraction is not None: + log.info(f"Updating text settings: {form_data.text_extraction}") + app.state.config.TEXT_EXTRACTION_ENGINE = form_data.text_extraction.engine + app.state.config.TIKA_SERVER_URL = form_data.text_extraction.tika_server_url + if form_data.chunk is not None: app.state.config.CHUNK_SIZE = form_data.chunk.chunk_size app.state.config.CHUNK_OVERLAP = form_data.chunk.chunk_overlap @@ -501,6 +519,10 @@ async def update_rag_config(form_data: ConfigUpdateForm, user=Depends(get_admin_ return { "status": True, "pdf_extract_images": app.state.config.PDF_EXTRACT_IMAGES, + "text_extraction": { + "engine": app.state.config.TEXT_EXTRACTION_ENGINE, + "tika_server_url": app.state.config.TIKA_SERVER_URL, + }, "chunk": { "chunk_size": app.state.config.CHUNK_SIZE, "chunk_overlap": app.state.config.CHUNK_OVERLAP, @@ -1001,7 +1023,7 @@ class TikaLoader: else: headers = {} - endpoint = str(TIKA_SERVER_URL) + endpoint = app.state.config.TIKA_SERVER_URL if not endpoint.endswith("/"): endpoint += "/" endpoint += "tika/text" @@ -1072,9 +1094,7 @@ def get_loader(filename: str, file_content_type: str, file_path: str): "msg", ] - log.warning("Use tika: %s, server URL: %s", DOCUMENT_USE_TIKA, TIKA_SERVER_URL) - - if DOCUMENT_USE_TIKA and TIKA_SERVER_URL: + if app.state.config.TEXT_EXTRACTION_ENGINE == "tika" and app.state.config.TIKA_SERVER_URL: if file_ext in known_source_ext or ( file_content_type and file_content_type.find("text/") >= 0 ): diff --git a/backend/config.py b/backend/config.py index fe9d995ef..e4097b4cb 100644 --- a/backend/config.py +++ b/backend/config.py @@ -882,10 +882,10 @@ if WEBUI_AUTH and WEBUI_SECRET_KEY == "": # RAG document text extraction #################################### -DOCUMENT_USE_TIKA = PersistentConfig( - "DOCUMENT_USE_TIKA", - "rag.document_use_tika", - os.environ.get("DOCUMENT_USE_TIKA", "false").lower() == "true" +TEXT_EXTRACTION_ENGINE = PersistentConfig( + "TEXT_EXTRACTION_ENGINE", + "rag.text_extraction_engine", + os.environ.get("TEXT_EXTRACTION_ENGINE", "").lower() ) TIKA_SERVER_URL = PersistentConfig( diff --git a/src/lib/apis/rag/index.ts b/src/lib/apis/rag/index.ts index 50f236e06..4047c419a 100644 --- a/src/lib/apis/rag/index.ts +++ b/src/lib/apis/rag/index.ts @@ -32,6 +32,11 @@ type ChunkConfigForm = { chunk_overlap: number; }; +type TextExtractConfigForm = { + engine: string; + tika_server_url: string | null; +}; + type YoutubeConfigForm = { language: string[]; translation?: string | null; @@ -40,6 +45,7 @@ type YoutubeConfigForm = { type RAGConfigForm = { pdf_extract_images?: boolean; chunk?: ChunkConfigForm; + text_extraction?: TextExtractConfigForm; web_loader_ssl_verification?: boolean; youtube?: YoutubeConfigForm; }; diff --git a/src/lib/components/admin/Settings/Documents.svelte b/src/lib/components/admin/Settings/Documents.svelte index 575014b13..1377eb5bb 100644 --- a/src/lib/components/admin/Settings/Documents.svelte +++ b/src/lib/components/admin/Settings/Documents.svelte @@ -37,6 +37,10 @@ let embeddingModel = ''; let rerankingModel = ''; + let textExtractionEngine = 'default'; + let tikaServerUrl = ''; + let showTikaServerUrl = false; + let chunkSize = 0; let chunkOverlap = 0; let pdfExtractImages = true; @@ -163,11 +167,20 @@ rerankingModelUpdateHandler(); } + if (textExtractionEngine === 'tika' && tikaServerUrl === '') { + toast.error($i18n.t('Tika Server URL required.')); + return; + } + const res = await updateRAGConfig(localStorage.token, { pdf_extract_images: pdfExtractImages, chunk: { chunk_overlap: chunkOverlap, chunk_size: chunkSize + }, + text_extraction: { + engine: textExtractionEngine, + tika_server_url: tikaServerUrl } }); @@ -213,6 +226,10 @@ chunkSize = res.chunk.chunk_size; chunkOverlap = res.chunk.chunk_overlap; + + textExtractionEngine = res.text_extraction.engine; + tikaServerUrl = res.text_extraction.tika_server_url; + showTikaServerUrl = textExtractionEngine === 'tika'; } }); @@ -388,6 +405,39 @@ +
+ +
+
{$i18n.t('Text Extraction')}
+ +
+
{$i18n.t('Engine')}
+
+ +
+
+ + {#if showTikaServerUrl} +
+
+ +
+
+ {/if} +

From 9cc46629c2ed4d59cee3ef8746a07119282bb43b Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Mon, 1 Jul 2024 15:43:19 -0700 Subject: [PATCH 049/181] refac --- backend/main.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/backend/main.py b/backend/main.py index df3f884b3..e1172f026 100644 --- a/backend/main.py +++ b/backend/main.py @@ -492,6 +492,12 @@ class ChatCompletionMiddleware(BaseHTTPMiddleware): "__id__": filter_id, } + if "__model__" in sig.parameters: + params = { + **params, + "__model__": model, + } + if inspect.iscoroutinefunction(inlet): data = await inlet(**params) else: @@ -1083,6 +1089,12 @@ async def chat_completed(form_data: dict, user=Depends(get_verified_user)): "__id__": filter_id, } + if "__model__" in sig.parameters: + params = { + **params, + "__model__": model, + } + if inspect.iscoroutinefunction(outlet): data = await outlet(**params) else: From 7212b7c8aa71e6398cff065ff1468bf0b285e424 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Mon, 1 Jul 2024 15:49:48 -0700 Subject: [PATCH 050/181] chore: bump --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9c009e356..9eb09d421 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "open-webui", - "version": "0.3.7", + "version": "0.3.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "open-webui", - "version": "0.3.7", + "version": "0.3.8", "dependencies": { "@codemirror/lang-javascript": "^6.2.2", "@codemirror/lang-python": "^6.1.6", diff --git a/package.json b/package.json index 0ad2445df..080f8ed5b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "open-webui", - "version": "0.3.7", + "version": "0.3.8", "private": true, "scripts": { "dev": "npm run pyodide:fetch && vite dev --host", From 6d350fb8bcdbe00722a22e2a77ce14f7d93f5118 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Mon, 1 Jul 2024 16:04:24 -0700 Subject: [PATCH 051/181] revert: text split --- src/lib/utils/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts index af3ab9a01..d80fcc799 100644 --- a/src/lib/utils/index.ts +++ b/src/lib/utils/index.ts @@ -525,7 +525,7 @@ export const extractSentences = (text) => { }); // Split the modified text into sentences based on common punctuation marks, avoiding these blocks - let sentences = text.match(/[^.?!]+[.!?]+[\])'"`’”]*|.+/g); + let sentences = text.split(/(?<=[.!?])\s+/); // Restore code blocks and process sentences sentences = sentences.map((sentence) => { From 9c01297191cc935ffbae409f5e8c1e6d3dc0ece3 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Mon, 1 Jul 2024 16:11:24 -0700 Subject: [PATCH 052/181] fix --- backend/apps/webui/models/tools.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/apps/webui/models/tools.py b/backend/apps/webui/models/tools.py index 694081df9..950972c2d 100644 --- a/backend/apps/webui/models/tools.py +++ b/backend/apps/webui/models/tools.py @@ -170,8 +170,7 @@ class ToolsTable: user_settings["tools"]["valves"][id] = valves # Update the user settings in the database - query = Users.update_user_by_id(user_id, {"settings": user_settings}) - query.execute() + Users.update_user_by_id(user_id, {"settings": user_settings}) return user_settings["tools"]["valves"][id] except Exception as e: From bd45b7a04b28f83cf1765eac28ab8a3bda6bdff3 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Mon, 1 Jul 2024 16:11:44 -0700 Subject: [PATCH 053/181] fix --- backend/apps/webui/models/functions.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/apps/webui/models/functions.py b/backend/apps/webui/models/functions.py index 2cace54c4..677f022f6 100644 --- a/backend/apps/webui/models/functions.py +++ b/backend/apps/webui/models/functions.py @@ -214,8 +214,7 @@ class FunctionsTable: user_settings["functions"]["valves"][id] = valves # Update the user settings in the database - query = Users.update_user_by_id(user_id, {"settings": user_settings}) - query.execute() + Users.update_user_by_id(user_id, {"settings": user_settings}) return user_settings["functions"]["valves"][id] except Exception as e: From b875efab1f463bb719a14364f2fe204133e33bde Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Mon, 1 Jul 2024 16:50:35 -0700 Subject: [PATCH 054/181] enh: validate required open webui version --- .../(app)/workspace/functions/create/+page.svelte | 10 ++++++++++ src/routes/(app)/workspace/functions/edit/+page.svelte | 10 ++++++++++ src/routes/(app)/workspace/tools/create/+page.svelte | 10 ++++++++++ src/routes/(app)/workspace/tools/edit/+page.svelte | 10 ++++++++++ 4 files changed, 40 insertions(+) diff --git a/src/routes/(app)/workspace/functions/create/+page.svelte b/src/routes/(app)/workspace/functions/create/+page.svelte index 49711b8ae..02e108e55 100644 --- a/src/routes/(app)/workspace/functions/create/+page.svelte +++ b/src/routes/(app)/workspace/functions/create/+page.svelte @@ -7,6 +7,8 @@ import { createNewFunction, getFunctions } from '$lib/apis/functions'; import FunctionEditor from '$lib/components/workspace/Functions/FunctionEditor.svelte'; import { getModels } from '$lib/apis'; + import { compareVersion, extractFrontmatter } from '$lib/utils'; + import { WEBUI_VERSION } from '$lib/constants'; const i18n = getContext('i18n'); @@ -16,6 +18,14 @@ const saveHandler = async (data) => { console.log(data); + + const manifest = extractFrontmatter(data.content); + if (compareVersion(manifest?.required_open_webui_version ?? '0.0.0', WEBUI_VERSION)) { + console.log('Version is lower than required'); + toast.error($i18n.t('Open WebUI version is lower than required version')); + return; + } + const res = await createNewFunction(localStorage.token, { id: data.id, name: data.name, diff --git a/src/routes/(app)/workspace/functions/edit/+page.svelte b/src/routes/(app)/workspace/functions/edit/+page.svelte index c54b38fa5..7c60e70c6 100644 --- a/src/routes/(app)/workspace/functions/edit/+page.svelte +++ b/src/routes/(app)/workspace/functions/edit/+page.svelte @@ -10,6 +10,8 @@ import FunctionEditor from '$lib/components/workspace/Functions/FunctionEditor.svelte'; import Spinner from '$lib/components/common/Spinner.svelte'; import { getModels } from '$lib/apis'; + import { compareVersion, extractFrontmatter } from '$lib/utils'; + import { WEBUI_VERSION } from '$lib/constants'; const i18n = getContext('i18n'); @@ -17,6 +19,14 @@ const saveHandler = async (data) => { console.log(data); + + const manifest = extractFrontmatter(data.content); + if (compareVersion(manifest?.required_open_webui_version ?? '0.0.0', WEBUI_VERSION)) { + console.log('Version is lower than required'); + toast.error($i18n.t('Open WebUI version is lower than required version')); + return; + } + const res = await updateFunctionById(localStorage.token, func.id, { id: data.id, name: data.name, diff --git a/src/routes/(app)/workspace/tools/create/+page.svelte b/src/routes/(app)/workspace/tools/create/+page.svelte index a3e243c21..0f0b90cbd 100644 --- a/src/routes/(app)/workspace/tools/create/+page.svelte +++ b/src/routes/(app)/workspace/tools/create/+page.svelte @@ -2,7 +2,9 @@ import { goto } from '$app/navigation'; import { createNewTool, getTools } from '$lib/apis/tools'; import ToolkitEditor from '$lib/components/workspace/Tools/ToolkitEditor.svelte'; + import { WEBUI_VERSION } from '$lib/constants'; import { tools } from '$lib/stores'; + import { compareVersion, extractFrontmatter } from '$lib/utils'; import { onMount, getContext } from 'svelte'; import { toast } from 'svelte-sonner'; @@ -14,6 +16,14 @@ const saveHandler = async (data) => { console.log(data); + + const manifest = extractFrontmatter(data.content); + if (compareVersion(manifest?.required_open_webui_version ?? '0.0.0', WEBUI_VERSION)) { + console.log('Version is lower than required'); + toast.error($i18n.t('Open WebUI version is lower than required version')); + return; + } + const res = await createNewTool(localStorage.token, { id: data.id, name: data.name, diff --git a/src/routes/(app)/workspace/tools/edit/+page.svelte b/src/routes/(app)/workspace/tools/edit/+page.svelte index aaa5fb836..48803db54 100644 --- a/src/routes/(app)/workspace/tools/edit/+page.svelte +++ b/src/routes/(app)/workspace/tools/edit/+page.svelte @@ -4,7 +4,9 @@ import { getToolById, getTools, updateToolById } from '$lib/apis/tools'; import Spinner from '$lib/components/common/Spinner.svelte'; import ToolkitEditor from '$lib/components/workspace/Tools/ToolkitEditor.svelte'; + import { WEBUI_VERSION } from '$lib/constants'; import { tools } from '$lib/stores'; + import { compareVersion, extractFrontmatter } from '$lib/utils'; import { onMount, getContext } from 'svelte'; import { toast } from 'svelte-sonner'; @@ -14,6 +16,14 @@ const saveHandler = async (data) => { console.log(data); + + const manifest = extractFrontmatter(data.content); + if (compareVersion(manifest?.required_open_webui_version ?? '0.0.0', WEBUI_VERSION)) { + console.log('Version is lower than required'); + toast.error($i18n.t('Open WebUI version is lower than required version')); + return; + } + const res = await updateToolById(localStorage.token, tool.id, { id: data.id, name: data.name, From 62ba6a241356731c7098f076d3066a254acb2dff Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Mon, 1 Jul 2024 16:52:46 -0700 Subject: [PATCH 055/181] chore: format --- src/lib/i18n/locales/ar-BH/translation.json | 1 + src/lib/i18n/locales/bg-BG/translation.json | 1 + src/lib/i18n/locales/bn-BD/translation.json | 1 + src/lib/i18n/locales/ca-ES/translation.json | 1 + src/lib/i18n/locales/ceb-PH/translation.json | 1 + src/lib/i18n/locales/de-DE/translation.json | 1 + src/lib/i18n/locales/dg-DG/translation.json | 1 + src/lib/i18n/locales/en-GB/translation.json | 1 + src/lib/i18n/locales/en-US/translation.json | 1 + src/lib/i18n/locales/es-ES/translation.json | 1 + src/lib/i18n/locales/fa-IR/translation.json | 1 + src/lib/i18n/locales/fi-FI/translation.json | 1 + src/lib/i18n/locales/fr-CA/translation.json | 1 + src/lib/i18n/locales/fr-FR/translation.json | 1 + src/lib/i18n/locales/he-IL/translation.json | 1 + src/lib/i18n/locales/hi-IN/translation.json | 1 + src/lib/i18n/locales/hr-HR/translation.json | 1 + src/lib/i18n/locales/id-ID/translation.json | 1 + src/lib/i18n/locales/it-IT/translation.json | 1 + src/lib/i18n/locales/ja-JP/translation.json | 1 + src/lib/i18n/locales/ka-GE/translation.json | 1 + src/lib/i18n/locales/ko-KR/translation.json | 1 + src/lib/i18n/locales/lt-LT/translation.json | 1 + src/lib/i18n/locales/nb-NO/translation.json | 1 + src/lib/i18n/locales/nl-NL/translation.json | 1 + src/lib/i18n/locales/pa-IN/translation.json | 1 + src/lib/i18n/locales/pl-PL/translation.json | 1 + src/lib/i18n/locales/pt-BR/translation.json | 1 + src/lib/i18n/locales/pt-PT/translation.json | 1 + src/lib/i18n/locales/ru-RU/translation.json | 1 + src/lib/i18n/locales/sr-RS/translation.json | 1 + src/lib/i18n/locales/sv-SE/translation.json | 1 + src/lib/i18n/locales/tk-TW/translation.json | 1 + src/lib/i18n/locales/tr-TR/translation.json | 1 + src/lib/i18n/locales/uk-UA/translation.json | 1 + src/lib/i18n/locales/vi-VN/translation.json | 1 + src/lib/i18n/locales/zh-CN/translation.json | 1 + src/lib/i18n/locales/zh-TW/translation.json | 1 + .../(app)/workspace/functions/create/+page.svelte | 10 +++++++++- src/routes/(app)/workspace/functions/edit/+page.svelte | 10 +++++++++- src/routes/(app)/workspace/tools/create/+page.svelte | 10 +++++++++- src/routes/(app)/workspace/tools/edit/+page.svelte | 10 +++++++++- 42 files changed, 74 insertions(+), 4 deletions(-) diff --git a/src/lib/i18n/locales/ar-BH/translation.json b/src/lib/i18n/locales/ar-BH/translation.json index 6da4f2734..bfc9a4f09 100644 --- a/src/lib/i18n/locales/ar-BH/translation.json +++ b/src/lib/i18n/locales/ar-BH/translation.json @@ -409,6 +409,7 @@ "Open": "فتح", "Open AI (Dall-E)": "AI (Dall-E) فتح", "Open new chat": "فتح محادثة جديده", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", "OpenAI": "OpenAI", "OpenAI API": "OpenAI API", "OpenAI API Config": "OpenAI API إعدادات", diff --git a/src/lib/i18n/locales/bg-BG/translation.json b/src/lib/i18n/locales/bg-BG/translation.json index 79e0507ef..2410dff0e 100644 --- a/src/lib/i18n/locales/bg-BG/translation.json +++ b/src/lib/i18n/locales/bg-BG/translation.json @@ -409,6 +409,7 @@ "Open": "Отвори", "Open AI (Dall-E)": "Open AI (Dall-E)", "Open new chat": "Отвори нов чат", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", "OpenAI": "OpenAI", "OpenAI API": "OpenAI API", "OpenAI API Config": "OpenAI API Config", diff --git a/src/lib/i18n/locales/bn-BD/translation.json b/src/lib/i18n/locales/bn-BD/translation.json index 191ae82a3..aa305b11f 100644 --- a/src/lib/i18n/locales/bn-BD/translation.json +++ b/src/lib/i18n/locales/bn-BD/translation.json @@ -409,6 +409,7 @@ "Open": "খোলা", "Open AI (Dall-E)": "Open AI (Dall-E)", "Open new chat": "নতুন চ্যাট খুলুন", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", "OpenAI": "OpenAI", "OpenAI API": "OpenAI এপিআই", "OpenAI API Config": "OpenAI এপিআই কনফিগ", diff --git a/src/lib/i18n/locales/ca-ES/translation.json b/src/lib/i18n/locales/ca-ES/translation.json index c4d5d7a5c..f66907566 100644 --- a/src/lib/i18n/locales/ca-ES/translation.json +++ b/src/lib/i18n/locales/ca-ES/translation.json @@ -409,6 +409,7 @@ "Open": "Obre", "Open AI (Dall-E)": "Open AI (Dall-E)", "Open new chat": "Obre un xat nou", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", "OpenAI": "OpenAI", "OpenAI API": "API d'OpenAI", "OpenAI API Config": "Configuració de l'API d'OpenAI", diff --git a/src/lib/i18n/locales/ceb-PH/translation.json b/src/lib/i18n/locales/ceb-PH/translation.json index 69eedfb2c..41552d33a 100644 --- a/src/lib/i18n/locales/ceb-PH/translation.json +++ b/src/lib/i18n/locales/ceb-PH/translation.json @@ -409,6 +409,7 @@ "Open": "Bukas", "Open AI (Dall-E)": "Buksan ang AI (Dall-E)", "Open new chat": "Ablihi ang bag-ong diskusyon", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", "OpenAI": "", "OpenAI API": "OpenAI API", "OpenAI API Config": "", diff --git a/src/lib/i18n/locales/de-DE/translation.json b/src/lib/i18n/locales/de-DE/translation.json index 3ce56c06d..42e691d21 100644 --- a/src/lib/i18n/locales/de-DE/translation.json +++ b/src/lib/i18n/locales/de-DE/translation.json @@ -409,6 +409,7 @@ "Open": "Öffne", "Open AI (Dall-E)": "Open AI (Dall-E)", "Open new chat": "Neuen Chat öffnen", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", "OpenAI": "OpenAI", "OpenAI API": "OpenAI-API", "OpenAI API Config": "OpenAI-API-Konfiguration", diff --git a/src/lib/i18n/locales/dg-DG/translation.json b/src/lib/i18n/locales/dg-DG/translation.json index a6a13056f..31a7ad861 100644 --- a/src/lib/i18n/locales/dg-DG/translation.json +++ b/src/lib/i18n/locales/dg-DG/translation.json @@ -409,6 +409,7 @@ "Open": "Open", "Open AI (Dall-E)": "Open AI (Dall-E)", "Open new chat": "Open new bark", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", "OpenAI": "", "OpenAI API": "OpenAI API", "OpenAI API Config": "", diff --git a/src/lib/i18n/locales/en-GB/translation.json b/src/lib/i18n/locales/en-GB/translation.json index 3f3634f5b..78525e22f 100644 --- a/src/lib/i18n/locales/en-GB/translation.json +++ b/src/lib/i18n/locales/en-GB/translation.json @@ -409,6 +409,7 @@ "Open": "", "Open AI (Dall-E)": "", "Open new chat": "", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", "OpenAI": "", "OpenAI API": "", "OpenAI API Config": "", diff --git a/src/lib/i18n/locales/en-US/translation.json b/src/lib/i18n/locales/en-US/translation.json index 3f3634f5b..78525e22f 100644 --- a/src/lib/i18n/locales/en-US/translation.json +++ b/src/lib/i18n/locales/en-US/translation.json @@ -409,6 +409,7 @@ "Open": "", "Open AI (Dall-E)": "", "Open new chat": "", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", "OpenAI": "", "OpenAI API": "", "OpenAI API Config": "", diff --git a/src/lib/i18n/locales/es-ES/translation.json b/src/lib/i18n/locales/es-ES/translation.json index aae8934d1..37a306f80 100644 --- a/src/lib/i18n/locales/es-ES/translation.json +++ b/src/lib/i18n/locales/es-ES/translation.json @@ -409,6 +409,7 @@ "Open": "Abrir", "Open AI (Dall-E)": "Abrir AI (Dall-E)", "Open new chat": "Abrir nuevo chat", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", "OpenAI": "OpenAI", "OpenAI API": "OpenAI API", "OpenAI API Config": "OpenAI API Config", diff --git a/src/lib/i18n/locales/fa-IR/translation.json b/src/lib/i18n/locales/fa-IR/translation.json index 7b9ba9e53..aaacb9a74 100644 --- a/src/lib/i18n/locales/fa-IR/translation.json +++ b/src/lib/i18n/locales/fa-IR/translation.json @@ -409,6 +409,7 @@ "Open": "باز", "Open AI (Dall-E)": "Open AI (Dall-E)", "Open new chat": "باز کردن گپ جدید", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", "OpenAI": "OpenAI", "OpenAI API": "OpenAI API", "OpenAI API Config": "OpenAI API Config", diff --git a/src/lib/i18n/locales/fi-FI/translation.json b/src/lib/i18n/locales/fi-FI/translation.json index 1752afa79..cbb9d8f9d 100644 --- a/src/lib/i18n/locales/fi-FI/translation.json +++ b/src/lib/i18n/locales/fi-FI/translation.json @@ -409,6 +409,7 @@ "Open": "Avaa", "Open AI (Dall-E)": "Open AI (Dall-E)", "Open new chat": "Avaa uusi keskustelu", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", "OpenAI": "OpenAI", "OpenAI API": "OpenAI API", "OpenAI API Config": "OpenAI API -asetukset", diff --git a/src/lib/i18n/locales/fr-CA/translation.json b/src/lib/i18n/locales/fr-CA/translation.json index a5754f724..32415078f 100644 --- a/src/lib/i18n/locales/fr-CA/translation.json +++ b/src/lib/i18n/locales/fr-CA/translation.json @@ -409,6 +409,7 @@ "Open": "Ouvrir", "Open AI (Dall-E)": "Open AI (Dall-E)", "Open new chat": "Ouvrir une nouvelle discussion", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", "OpenAI": "OpenAI", "OpenAI API": "API OpenAI", "OpenAI API Config": "Configuration API OpenAI", diff --git a/src/lib/i18n/locales/fr-FR/translation.json b/src/lib/i18n/locales/fr-FR/translation.json index b088eeb30..9b64ae056 100644 --- a/src/lib/i18n/locales/fr-FR/translation.json +++ b/src/lib/i18n/locales/fr-FR/translation.json @@ -409,6 +409,7 @@ "Open": "Ouvrir", "Open AI (Dall-E)": "Open AI (Dall-E)", "Open new chat": "Ouvrir un nouveau chat", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", "OpenAI": "OpenAI", "OpenAI API": "API OpenAI", "OpenAI API Config": "Config API OpenAI", diff --git a/src/lib/i18n/locales/he-IL/translation.json b/src/lib/i18n/locales/he-IL/translation.json index 9dc2284cd..981d6990c 100644 --- a/src/lib/i18n/locales/he-IL/translation.json +++ b/src/lib/i18n/locales/he-IL/translation.json @@ -409,6 +409,7 @@ "Open": "פתח", "Open AI (Dall-E)": "Open AI (Dall-E)", "Open new chat": "פתח צ'אט חדש", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", "OpenAI": "OpenAI", "OpenAI API": "API של OpenAI", "OpenAI API Config": "תצורת API של OpenAI", diff --git a/src/lib/i18n/locales/hi-IN/translation.json b/src/lib/i18n/locales/hi-IN/translation.json index f2d1164f3..c787e7c79 100644 --- a/src/lib/i18n/locales/hi-IN/translation.json +++ b/src/lib/i18n/locales/hi-IN/translation.json @@ -409,6 +409,7 @@ "Open": "खोलें", "Open AI (Dall-E)": "Open AI (Dall-E)", "Open new chat": "नई चैट खोलें", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", "OpenAI": "OpenAI", "OpenAI API": "OpenAI API", "OpenAI API Config": "OpenAI API कॉन्फिग", diff --git a/src/lib/i18n/locales/hr-HR/translation.json b/src/lib/i18n/locales/hr-HR/translation.json index daba47fc2..a167635bc 100644 --- a/src/lib/i18n/locales/hr-HR/translation.json +++ b/src/lib/i18n/locales/hr-HR/translation.json @@ -409,6 +409,7 @@ "Open": "Otvoreno", "Open AI (Dall-E)": "Open AI (Dall-E)", "Open new chat": "Otvorite novi razgovor", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", "OpenAI": "OpenAI", "OpenAI API": "OpenAI API", "OpenAI API Config": "OpenAI API konfiguracija", diff --git a/src/lib/i18n/locales/id-ID/translation.json b/src/lib/i18n/locales/id-ID/translation.json index 9d0340a85..e06aeab54 100644 --- a/src/lib/i18n/locales/id-ID/translation.json +++ b/src/lib/i18n/locales/id-ID/translation.json @@ -409,6 +409,7 @@ "Open": "Buka", "Open AI (Dall-E)": "Buka AI (Dall-E)", "Open new chat": "Buka obrolan baru", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", "OpenAI": "OpenAI", "OpenAI API": "API OpenAI", "OpenAI API Config": "Konfigurasi API OpenAI", diff --git a/src/lib/i18n/locales/it-IT/translation.json b/src/lib/i18n/locales/it-IT/translation.json index 0e01b7db1..804e7ec33 100644 --- a/src/lib/i18n/locales/it-IT/translation.json +++ b/src/lib/i18n/locales/it-IT/translation.json @@ -409,6 +409,7 @@ "Open": "Apri", "Open AI (Dall-E)": "Open AI (Dall-E)", "Open new chat": "Apri nuova chat", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", "OpenAI": "OpenAI", "OpenAI API": "API OpenAI", "OpenAI API Config": "Configurazione API OpenAI", diff --git a/src/lib/i18n/locales/ja-JP/translation.json b/src/lib/i18n/locales/ja-JP/translation.json index aabca814b..81c8d6733 100644 --- a/src/lib/i18n/locales/ja-JP/translation.json +++ b/src/lib/i18n/locales/ja-JP/translation.json @@ -409,6 +409,7 @@ "Open": "開く", "Open AI (Dall-E)": "Open AI (Dall-E)", "Open new chat": "新しいチャットを開く", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", "OpenAI": "OpenAI", "OpenAI API": "OpenAI API", "OpenAI API Config": "OpenAI API 設定", diff --git a/src/lib/i18n/locales/ka-GE/translation.json b/src/lib/i18n/locales/ka-GE/translation.json index ec2888f0e..f1ed7c61a 100644 --- a/src/lib/i18n/locales/ka-GE/translation.json +++ b/src/lib/i18n/locales/ka-GE/translation.json @@ -409,6 +409,7 @@ "Open": "ღია", "Open AI (Dall-E)": "Open AI (Dall-E)", "Open new chat": "ახალი მიმოწერის გახსნა", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", "OpenAI": "OpenAI", "OpenAI API": "OpenAI API", "OpenAI API Config": "OpenAI API პარამეტრები", diff --git a/src/lib/i18n/locales/ko-KR/translation.json b/src/lib/i18n/locales/ko-KR/translation.json index 0a4e227c9..b23318175 100644 --- a/src/lib/i18n/locales/ko-KR/translation.json +++ b/src/lib/i18n/locales/ko-KR/translation.json @@ -409,6 +409,7 @@ "Open": "열기", "Open AI (Dall-E)": "OpenAI(Dall-E)", "Open new chat": "새 채팅 열기", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", "OpenAI": "OpenAI", "OpenAI API": "OpenAI API", "OpenAI API Config": "OpenAI API 설정", diff --git a/src/lib/i18n/locales/lt-LT/translation.json b/src/lib/i18n/locales/lt-LT/translation.json index 7066e19c2..ddf31b1e0 100644 --- a/src/lib/i18n/locales/lt-LT/translation.json +++ b/src/lib/i18n/locales/lt-LT/translation.json @@ -409,6 +409,7 @@ "Open": "Atverti", "Open AI (Dall-E)": "Open AI (Dall-E)", "Open new chat": "Atverti naują pokalbį", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", "OpenAI": "OpenAI", "OpenAI API": "OpenAI API", "OpenAI API Config": "Open AI API nustatymai", diff --git a/src/lib/i18n/locales/nb-NO/translation.json b/src/lib/i18n/locales/nb-NO/translation.json index 4d75dfff1..1fb084f0e 100644 --- a/src/lib/i18n/locales/nb-NO/translation.json +++ b/src/lib/i18n/locales/nb-NO/translation.json @@ -409,6 +409,7 @@ "Open": "Åpne", "Open AI (Dall-E)": "Open AI (Dall-E)", "Open new chat": "Åpne ny chat", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", "OpenAI": "OpenAI", "OpenAI API": "OpenAI API", "OpenAI API Config": "OpenAI API-konfigurasjon", diff --git a/src/lib/i18n/locales/nl-NL/translation.json b/src/lib/i18n/locales/nl-NL/translation.json index 0f0309fec..403428bf7 100644 --- a/src/lib/i18n/locales/nl-NL/translation.json +++ b/src/lib/i18n/locales/nl-NL/translation.json @@ -409,6 +409,7 @@ "Open": "Open", "Open AI (Dall-E)": "Open AI (Dall-E)", "Open new chat": "Open nieuwe chat", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", "OpenAI": "OpenAI", "OpenAI API": "OpenAI API", "OpenAI API Config": "OpenAI API Config", diff --git a/src/lib/i18n/locales/pa-IN/translation.json b/src/lib/i18n/locales/pa-IN/translation.json index 7480d99f9..08204a7d7 100644 --- a/src/lib/i18n/locales/pa-IN/translation.json +++ b/src/lib/i18n/locales/pa-IN/translation.json @@ -409,6 +409,7 @@ "Open": "ਖੋਲ੍ਹੋ", "Open AI (Dall-E)": "ਓਪਨ ਏਆਈ (ਡਾਲ-ਈ)", "Open new chat": "ਨਵੀਂ ਗੱਲਬਾਤ ਖੋਲ੍ਹੋ", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", "OpenAI": "ਓਪਨਏਆਈ", "OpenAI API": "ਓਪਨਏਆਈ API", "OpenAI API Config": "ਓਪਨਏਆਈ API ਕਨਫਿਗ", diff --git a/src/lib/i18n/locales/pl-PL/translation.json b/src/lib/i18n/locales/pl-PL/translation.json index e6c7266a3..14dc5de75 100644 --- a/src/lib/i18n/locales/pl-PL/translation.json +++ b/src/lib/i18n/locales/pl-PL/translation.json @@ -409,6 +409,7 @@ "Open": "Otwórz", "Open AI (Dall-E)": "Open AI (Dall-E)", "Open new chat": "Otwórz nowy czat", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", "OpenAI": "OpenAI", "OpenAI API": "OpenAI API", "OpenAI API Config": "Konfiguracja OpenAI API", diff --git a/src/lib/i18n/locales/pt-BR/translation.json b/src/lib/i18n/locales/pt-BR/translation.json index d625cb633..8760a1bee 100644 --- a/src/lib/i18n/locales/pt-BR/translation.json +++ b/src/lib/i18n/locales/pt-BR/translation.json @@ -409,6 +409,7 @@ "Open": "Abrir", "Open AI (Dall-E)": "OpenAI (Dall-E)", "Open new chat": "Abrir novo bate-papo", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", "OpenAI": "OpenAI", "OpenAI API": "API OpenAI", "OpenAI API Config": "Configuração da API OpenAI", diff --git a/src/lib/i18n/locales/pt-PT/translation.json b/src/lib/i18n/locales/pt-PT/translation.json index efe368d4d..8ca4d5e1a 100644 --- a/src/lib/i18n/locales/pt-PT/translation.json +++ b/src/lib/i18n/locales/pt-PT/translation.json @@ -409,6 +409,7 @@ "Open": "Abrir", "Open AI (Dall-E)": "Open AI (Dall-E)", "Open new chat": "Abrir nova conversa", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", "OpenAI": "OpenAI", "OpenAI API": "API OpenAI", "OpenAI API Config": "Configuração da API OpenAI", diff --git a/src/lib/i18n/locales/ru-RU/translation.json b/src/lib/i18n/locales/ru-RU/translation.json index 877543d1b..5fafca9b9 100644 --- a/src/lib/i18n/locales/ru-RU/translation.json +++ b/src/lib/i18n/locales/ru-RU/translation.json @@ -409,6 +409,7 @@ "Open": "Открыть", "Open AI (Dall-E)": "Open AI (Dall-E)", "Open new chat": "Открыть новый чат", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", "OpenAI": "Open AI", "OpenAI API": "API OpenAI", "OpenAI API Config": "Конфигурация API OpenAI", diff --git a/src/lib/i18n/locales/sr-RS/translation.json b/src/lib/i18n/locales/sr-RS/translation.json index f7793bb2d..87addf040 100644 --- a/src/lib/i18n/locales/sr-RS/translation.json +++ b/src/lib/i18n/locales/sr-RS/translation.json @@ -409,6 +409,7 @@ "Open": "Отвори", "Open AI (Dall-E)": "Open AI (Dall-E)", "Open new chat": "Покрени ново ћаскање", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", "OpenAI": "OpenAI", "OpenAI API": "OpenAI API", "OpenAI API Config": "Подешавање OpenAI API-ја", diff --git a/src/lib/i18n/locales/sv-SE/translation.json b/src/lib/i18n/locales/sv-SE/translation.json index e47a5da3e..14aededbd 100644 --- a/src/lib/i18n/locales/sv-SE/translation.json +++ b/src/lib/i18n/locales/sv-SE/translation.json @@ -409,6 +409,7 @@ "Open": "Öppna", "Open AI (Dall-E)": "Öppna AI (Dall-E)", "Open new chat": "Öppna ny chatt", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", "OpenAI": "OpenAI", "OpenAI API": "OpenAI API", "OpenAI API Config": "OpenAI API-konfig", diff --git a/src/lib/i18n/locales/tk-TW/translation.json b/src/lib/i18n/locales/tk-TW/translation.json index 3f3634f5b..78525e22f 100644 --- a/src/lib/i18n/locales/tk-TW/translation.json +++ b/src/lib/i18n/locales/tk-TW/translation.json @@ -409,6 +409,7 @@ "Open": "", "Open AI (Dall-E)": "", "Open new chat": "", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", "OpenAI": "", "OpenAI API": "", "OpenAI API Config": "", diff --git a/src/lib/i18n/locales/tr-TR/translation.json b/src/lib/i18n/locales/tr-TR/translation.json index 827e7bc3b..981cfb829 100644 --- a/src/lib/i18n/locales/tr-TR/translation.json +++ b/src/lib/i18n/locales/tr-TR/translation.json @@ -409,6 +409,7 @@ "Open": "Aç", "Open AI (Dall-E)": "Open AI (Dall-E)", "Open new chat": "Yeni sohbet aç", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", "OpenAI": "OpenAI", "OpenAI API": "OpenAI API", "OpenAI API Config": "OpenAI API Konfigürasyonu", diff --git a/src/lib/i18n/locales/uk-UA/translation.json b/src/lib/i18n/locales/uk-UA/translation.json index 07a6748a9..5443d21a8 100644 --- a/src/lib/i18n/locales/uk-UA/translation.json +++ b/src/lib/i18n/locales/uk-UA/translation.json @@ -409,6 +409,7 @@ "Open": "Відкрити", "Open AI (Dall-E)": "Open AI (Dall-E)", "Open new chat": "Відкрити новий чат", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", "OpenAI": "OpenAI", "OpenAI API": "OpenAI API", "OpenAI API Config": "Конфігурація OpenAI API", diff --git a/src/lib/i18n/locales/vi-VN/translation.json b/src/lib/i18n/locales/vi-VN/translation.json index 4f8374797..8f433a034 100644 --- a/src/lib/i18n/locales/vi-VN/translation.json +++ b/src/lib/i18n/locales/vi-VN/translation.json @@ -409,6 +409,7 @@ "Open": "Mở", "Open AI (Dall-E)": "Open AI (Dall-E)", "Open new chat": "Mở nội dung chat mới", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", "OpenAI": "OpenAI", "OpenAI API": "API OpenAI", "OpenAI API Config": "Cấu hình API OpenAI", diff --git a/src/lib/i18n/locales/zh-CN/translation.json b/src/lib/i18n/locales/zh-CN/translation.json index f4c1e03ce..0cf513ac0 100644 --- a/src/lib/i18n/locales/zh-CN/translation.json +++ b/src/lib/i18n/locales/zh-CN/translation.json @@ -409,6 +409,7 @@ "Open": "打开", "Open AI (Dall-E)": "Open AI (Dall-E)", "Open new chat": "打开新对话", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", "OpenAI": "OpenAI", "OpenAI API": "OpenAI API", "OpenAI API Config": "OpenAI API 配置", diff --git a/src/lib/i18n/locales/zh-TW/translation.json b/src/lib/i18n/locales/zh-TW/translation.json index 584987795..0f555eb65 100644 --- a/src/lib/i18n/locales/zh-TW/translation.json +++ b/src/lib/i18n/locales/zh-TW/translation.json @@ -409,6 +409,7 @@ "Open": "開啟", "Open AI (Dall-E)": "Open AI (Dall-E)", "Open new chat": "開啟新聊天", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", "OpenAI": "OpenAI", "OpenAI API": "OpenAI API", "OpenAI API Config": "OpenAI API 設定", diff --git a/src/routes/(app)/workspace/functions/create/+page.svelte b/src/routes/(app)/workspace/functions/create/+page.svelte index 02e108e55..deee266e5 100644 --- a/src/routes/(app)/workspace/functions/create/+page.svelte +++ b/src/routes/(app)/workspace/functions/create/+page.svelte @@ -22,7 +22,15 @@ const manifest = extractFrontmatter(data.content); if (compareVersion(manifest?.required_open_webui_version ?? '0.0.0', WEBUI_VERSION)) { console.log('Version is lower than required'); - toast.error($i18n.t('Open WebUI version is lower than required version')); + toast.error( + $i18n.t( + 'Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})', + { + OPEN_WEBUI_VERSION: WEBUI_VERSION, + REQUIRED_VERSION: manifest?.required_open_webui_version ?? '0.0.0' + } + ) + ); return; } diff --git a/src/routes/(app)/workspace/functions/edit/+page.svelte b/src/routes/(app)/workspace/functions/edit/+page.svelte index 7c60e70c6..366890c04 100644 --- a/src/routes/(app)/workspace/functions/edit/+page.svelte +++ b/src/routes/(app)/workspace/functions/edit/+page.svelte @@ -23,7 +23,15 @@ const manifest = extractFrontmatter(data.content); if (compareVersion(manifest?.required_open_webui_version ?? '0.0.0', WEBUI_VERSION)) { console.log('Version is lower than required'); - toast.error($i18n.t('Open WebUI version is lower than required version')); + toast.error( + $i18n.t( + 'Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})', + { + OPEN_WEBUI_VERSION: WEBUI_VERSION, + REQUIRED_VERSION: manifest?.required_open_webui_version ?? '0.0.0' + } + ) + ); return; } diff --git a/src/routes/(app)/workspace/tools/create/+page.svelte b/src/routes/(app)/workspace/tools/create/+page.svelte index 0f0b90cbd..29a285d22 100644 --- a/src/routes/(app)/workspace/tools/create/+page.svelte +++ b/src/routes/(app)/workspace/tools/create/+page.svelte @@ -20,7 +20,15 @@ const manifest = extractFrontmatter(data.content); if (compareVersion(manifest?.required_open_webui_version ?? '0.0.0', WEBUI_VERSION)) { console.log('Version is lower than required'); - toast.error($i18n.t('Open WebUI version is lower than required version')); + toast.error( + $i18n.t( + 'Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})', + { + OPEN_WEBUI_VERSION: WEBUI_VERSION, + REQUIRED_VERSION: manifest?.required_open_webui_version ?? '0.0.0' + } + ) + ); return; } diff --git a/src/routes/(app)/workspace/tools/edit/+page.svelte b/src/routes/(app)/workspace/tools/edit/+page.svelte index 48803db54..ebd47701e 100644 --- a/src/routes/(app)/workspace/tools/edit/+page.svelte +++ b/src/routes/(app)/workspace/tools/edit/+page.svelte @@ -20,7 +20,15 @@ const manifest = extractFrontmatter(data.content); if (compareVersion(manifest?.required_open_webui_version ?? '0.0.0', WEBUI_VERSION)) { console.log('Version is lower than required'); - toast.error($i18n.t('Open WebUI version is lower than required version')); + toast.error( + $i18n.t( + 'Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})', + { + OPEN_WEBUI_VERSION: WEBUI_VERSION, + REQUIRED_VERSION: manifest?.required_open_webui_version ?? '0.0.0' + } + ) + ); return; } From a392865615863292f23626e0ef87dfa21ad516bb Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Mon, 1 Jul 2024 17:11:09 -0700 Subject: [PATCH 056/181] refac --- backend/apps/rag/main.py | 33 ++++++++++--------- backend/config.py | 10 +++--- src/lib/apis/rag/index.ts | 4 +-- .../admin/Settings/Documents.svelte | 30 ++++++++--------- 4 files changed, 40 insertions(+), 37 deletions(-) diff --git a/backend/apps/rag/main.py b/backend/apps/rag/main.py index e3bb2bf5e..24b4a7453 100644 --- a/backend/apps/rag/main.py +++ b/backend/apps/rag/main.py @@ -91,7 +91,7 @@ from config import ( SRC_LOG_LEVELS, UPLOAD_DIR, DOCS_DIR, - TEXT_EXTRACTION_ENGINE, + CONTENT_EXTRACTION_ENGINE, TIKA_SERVER_URL, RAG_TOP_K, RAG_RELEVANCE_THRESHOLD, @@ -148,7 +148,7 @@ app.state.config.ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION = ( ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION ) -app.state.config.TEXT_EXTRACTION_ENGINE = TEXT_EXTRACTION_ENGINE +app.state.config.CONTENT_EXTRACTION_ENGINE = CONTENT_EXTRACTION_ENGINE app.state.config.TIKA_SERVER_URL = TIKA_SERVER_URL app.state.config.CHUNK_SIZE = CHUNK_SIZE @@ -395,8 +395,8 @@ async def get_rag_config(user=Depends(get_admin_user)): return { "status": True, "pdf_extract_images": app.state.config.PDF_EXTRACT_IMAGES, - "text_extraction": { - "engine": app.state.config.TEXT_EXTRACTION_ENGINE, + "content_extraction": { + "engine": app.state.config.CONTENT_EXTRACTION_ENGINE, "tika_server_url": app.state.config.TIKA_SERVER_URL, }, "chunk": { @@ -428,7 +428,7 @@ async def get_rag_config(user=Depends(get_admin_user)): } -class TextExtractionConfig(BaseModel): +class ContentExtractionConfig(BaseModel): engine: str = "" tika_server_url: Optional[str] = None @@ -466,7 +466,7 @@ class WebConfig(BaseModel): class ConfigUpdateForm(BaseModel): pdf_extract_images: Optional[bool] = None - text_extraction: Optional[TextExtractionConfig] = None + content_extraction: Optional[ContentExtractionConfig] = None chunk: Optional[ChunkParamUpdateForm] = None youtube: Optional[YoutubeLoaderConfig] = None web: Optional[WebConfig] = None @@ -480,10 +480,10 @@ async def update_rag_config(form_data: ConfigUpdateForm, user=Depends(get_admin_ else app.state.config.PDF_EXTRACT_IMAGES ) - if form_data.text_extraction is not None: - log.info(f"Updating text settings: {form_data.text_extraction}") - app.state.config.TEXT_EXTRACTION_ENGINE = form_data.text_extraction.engine - app.state.config.TIKA_SERVER_URL = form_data.text_extraction.tika_server_url + if form_data.content_extraction is not None: + log.info(f"Updating text settings: {form_data.content_extraction}") + app.state.config.CONTENT_EXTRACTION_ENGINE = form_data.content_extraction.engine + app.state.config.TIKA_SERVER_URL = form_data.content_extraction.tika_server_url if form_data.chunk is not None: app.state.config.CHUNK_SIZE = form_data.chunk.chunk_size @@ -521,8 +521,8 @@ async def update_rag_config(form_data: ConfigUpdateForm, user=Depends(get_admin_ return { "status": True, "pdf_extract_images": app.state.config.PDF_EXTRACT_IMAGES, - "text_extraction": { - "engine": app.state.config.TEXT_EXTRACTION_ENGINE, + "content_extraction": { + "engine": app.state.config.CONTENT_EXTRACTION_ENGINE, "tika_server_url": app.state.config.TIKA_SERVER_URL, }, "chunk": { @@ -1017,7 +1017,7 @@ class TikaLoader: self.mime_type = mime_type def load(self) -> List[Document]: - with (open(self.file_path, "rb") as f): + with open(self.file_path, "rb") as f: data = f.read() if self.mime_type is not None: @@ -1096,9 +1096,12 @@ def get_loader(filename: str, file_content_type: str, file_path: str): "msg", ] - if app.state.config.TEXT_EXTRACTION_ENGINE == "tika" and app.state.config.TIKA_SERVER_URL: + if ( + app.state.config.CONTENT_EXTRACTION_ENGINE == "tika" + and app.state.config.TIKA_SERVER_URL + ): if file_ext in known_source_ext or ( - file_content_type and file_content_type.find("text/") >= 0 + file_content_type and file_content_type.find("text/") >= 0 ): loader = TextLoader(file_path, autodetect_encoding=True) else: diff --git a/backend/config.py b/backend/config.py index 064ddff33..d6efde563 100644 --- a/backend/config.py +++ b/backend/config.py @@ -886,13 +886,13 @@ if WEBUI_AUTH and WEBUI_SECRET_KEY == "": raise ValueError(ERROR_MESSAGES.ENV_VAR_NOT_FOUND) #################################### -# RAG document text extraction +# RAG document content extraction #################################### -TEXT_EXTRACTION_ENGINE = PersistentConfig( - "TEXT_EXTRACTION_ENGINE", - "rag.text_extraction_engine", - os.environ.get("TEXT_EXTRACTION_ENGINE", "").lower() +CONTENT_EXTRACTION_ENGINE = PersistentConfig( + "CONTENT_EXTRACTION_ENGINE", + "rag.CONTENT_EXTRACTION_ENGINE", + os.environ.get("CONTENT_EXTRACTION_ENGINE", "").lower(), ) TIKA_SERVER_URL = PersistentConfig( diff --git a/src/lib/apis/rag/index.ts b/src/lib/apis/rag/index.ts index 4047c419a..b32e544ee 100644 --- a/src/lib/apis/rag/index.ts +++ b/src/lib/apis/rag/index.ts @@ -32,7 +32,7 @@ type ChunkConfigForm = { chunk_overlap: number; }; -type TextExtractConfigForm = { +type ContentExtractConfigForm = { engine: string; tika_server_url: string | null; }; @@ -45,7 +45,7 @@ type YoutubeConfigForm = { type RAGConfigForm = { pdf_extract_images?: boolean; chunk?: ChunkConfigForm; - text_extraction?: TextExtractConfigForm; + content_extraction?: ContentExtractConfigForm; web_loader_ssl_verification?: boolean; youtube?: YoutubeConfigForm; }; diff --git a/src/lib/components/admin/Settings/Documents.svelte b/src/lib/components/admin/Settings/Documents.svelte index 1377eb5bb..2094d0421 100644 --- a/src/lib/components/admin/Settings/Documents.svelte +++ b/src/lib/components/admin/Settings/Documents.svelte @@ -37,7 +37,7 @@ let embeddingModel = ''; let rerankingModel = ''; - let textExtractionEngine = 'default'; + let contentExtractionEngine = 'default'; let tikaServerUrl = ''; let showTikaServerUrl = false; @@ -167,7 +167,7 @@ rerankingModelUpdateHandler(); } - if (textExtractionEngine === 'tika' && tikaServerUrl === '') { + if (contentExtractionEngine === 'tika' && tikaServerUrl === '') { toast.error($i18n.t('Tika Server URL required.')); return; } @@ -178,8 +178,8 @@ chunk_overlap: chunkOverlap, chunk_size: chunkSize }, - text_extraction: { - engine: textExtractionEngine, + content_extraction: { + engine: contentExtractionEngine, tika_server_url: tikaServerUrl } }); @@ -227,9 +227,9 @@ chunkSize = res.chunk.chunk_size; chunkOverlap = res.chunk.chunk_overlap; - textExtractionEngine = res.text_extraction.engine; - tikaServerUrl = res.text_extraction.tika_server_url; - showTikaServerUrl = textExtractionEngine === 'tika'; + contentExtractionEngine = res.content_extraction.engine; + tikaServerUrl = res.content_extraction.tika_server_url; + showTikaServerUrl = contentExtractionEngine === 'tika'; } }); @@ -414,11 +414,11 @@
{$i18n.t('Engine')}
From b62d2a9b2880b65567730134102880ba9c33cfdb Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Mon, 1 Jul 2024 17:15:10 -0700 Subject: [PATCH 057/181] refac --- .../admin/Settings/Documents.svelte | 66 +++++++++---------- src/lib/i18n/locales/ar-BH/translation.json | 5 ++ src/lib/i18n/locales/bg-BG/translation.json | 5 ++ src/lib/i18n/locales/bn-BD/translation.json | 5 ++ src/lib/i18n/locales/ca-ES/translation.json | 5 ++ src/lib/i18n/locales/ceb-PH/translation.json | 5 ++ src/lib/i18n/locales/de-DE/translation.json | 5 ++ src/lib/i18n/locales/dg-DG/translation.json | 5 ++ src/lib/i18n/locales/en-GB/translation.json | 5 ++ src/lib/i18n/locales/en-US/translation.json | 5 ++ src/lib/i18n/locales/es-ES/translation.json | 5 ++ src/lib/i18n/locales/fa-IR/translation.json | 5 ++ src/lib/i18n/locales/fi-FI/translation.json | 5 ++ src/lib/i18n/locales/fr-CA/translation.json | 5 ++ src/lib/i18n/locales/fr-FR/translation.json | 5 ++ src/lib/i18n/locales/he-IL/translation.json | 5 ++ src/lib/i18n/locales/hi-IN/translation.json | 5 ++ src/lib/i18n/locales/hr-HR/translation.json | 5 ++ src/lib/i18n/locales/id-ID/translation.json | 5 ++ src/lib/i18n/locales/it-IT/translation.json | 5 ++ src/lib/i18n/locales/ja-JP/translation.json | 5 ++ src/lib/i18n/locales/ka-GE/translation.json | 5 ++ src/lib/i18n/locales/ko-KR/translation.json | 5 ++ src/lib/i18n/locales/lt-LT/translation.json | 5 ++ src/lib/i18n/locales/nb-NO/translation.json | 5 ++ src/lib/i18n/locales/nl-NL/translation.json | 5 ++ src/lib/i18n/locales/pa-IN/translation.json | 5 ++ src/lib/i18n/locales/pl-PL/translation.json | 5 ++ src/lib/i18n/locales/pt-BR/translation.json | 5 ++ src/lib/i18n/locales/pt-PT/translation.json | 5 ++ src/lib/i18n/locales/ru-RU/translation.json | 5 ++ src/lib/i18n/locales/sr-RS/translation.json | 5 ++ src/lib/i18n/locales/sv-SE/translation.json | 5 ++ src/lib/i18n/locales/tk-TW/translation.json | 5 ++ src/lib/i18n/locales/tr-TR/translation.json | 5 ++ src/lib/i18n/locales/uk-UA/translation.json | 5 ++ src/lib/i18n/locales/vi-VN/translation.json | 5 ++ src/lib/i18n/locales/zh-CN/translation.json | 5 ++ src/lib/i18n/locales/zh-TW/translation.json | 5 ++ 39 files changed, 223 insertions(+), 33 deletions(-) diff --git a/src/lib/components/admin/Settings/Documents.svelte b/src/lib/components/admin/Settings/Documents.svelte index 2094d0421..8ad08b285 100644 --- a/src/lib/components/admin/Settings/Documents.svelte +++ b/src/lib/components/admin/Settings/Documents.svelte @@ -407,39 +407,6 @@
-
-
{$i18n.t('Text Extraction')}
- -
-
{$i18n.t('Engine')}
-
- -
-
- - {#if showTikaServerUrl} -
-
- -
-
- {/if} -
-
-
{$i18n.t('Embedding Model')}
@@ -612,6 +579,39 @@
+
+
{$i18n.t('Content Extraction')}
+ +
+
{$i18n.t('Engine')}
+
+ +
+
+ + {#if showTikaServerUrl} +
+
+ +
+
+ {/if} +
+
+
{$i18n.t('Query Params')}
diff --git a/src/lib/i18n/locales/ar-BH/translation.json b/src/lib/i18n/locales/ar-BH/translation.json index bfc9a4f09..b2bb2484f 100644 --- a/src/lib/i18n/locales/ar-BH/translation.json +++ b/src/lib/i18n/locales/ar-BH/translation.json @@ -126,6 +126,7 @@ "Connections": "اتصالات", "Contact Admin for WebUI Access": "", "Content": "الاتصال", + "Content Extraction": "", "Context Length": "طول السياق", "Continue Response": "متابعة الرد", "Continue with {{provider}}": "", @@ -212,6 +213,7 @@ "Enable Community Sharing": "تمكين مشاركة المجتمع", "Enable New Sign Ups": "تفعيل عمليات التسجيل الجديدة", "Enable Web Search": "تمكين بحث الويب", + "Engine": "", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "تأكد من أن ملف CSV الخاص بك يتضمن 4 أعمدة بهذا الترتيب: Name, Email, Password, Role.", "Enter {{role}} message here": "أدخل رسالة {{role}} هنا", "Enter a detail about yourself for your LLMs to recall": "ادخل معلومات عنك تريد أن يتذكرها الموديل", @@ -233,6 +235,7 @@ "Enter Serpstack API Key": "أدخل مفتاح واجهة برمجة تطبيقات Serpstack", "Enter stop sequence": "أدخل تسلسل التوقف", "Enter Tavily API Key": "", + "Enter Tika Server URL": "", "Enter Top K": "أدخل Top K", "Enter URL (e.g. http://127.0.0.1:7860/)": "الرابط (e.g. http://127.0.0.1:7860/)", "Enter URL (e.g. http://localhost:11434)": "URL (e.g. http://localhost:11434)", @@ -578,6 +581,8 @@ "This setting does not sync across browsers or devices.": "لا تتم مزامنة هذا الإعداد عبر المتصفحات أو الأجهزة.", "This will delete": "", "Thorough explanation": "شرح شامل", + "Tika": "", + "Tika Server URL required.": "", "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "ملاحضة: قم بتحديث عدة فتحات متغيرة على التوالي عن طريق الضغط على مفتاح tab في مدخلات الدردشة بعد كل استبدال.", "Title": "العنوان", "Title (e.g. Tell me a fun fact)": "(e.g. Tell me a fun fact) العناون", diff --git a/src/lib/i18n/locales/bg-BG/translation.json b/src/lib/i18n/locales/bg-BG/translation.json index 2410dff0e..837d724a1 100644 --- a/src/lib/i18n/locales/bg-BG/translation.json +++ b/src/lib/i18n/locales/bg-BG/translation.json @@ -126,6 +126,7 @@ "Connections": "Връзки", "Contact Admin for WebUI Access": "", "Content": "Съдържание", + "Content Extraction": "", "Context Length": "Дължина на Контекста", "Continue Response": "Продължи отговора", "Continue with {{provider}}": "", @@ -212,6 +213,7 @@ "Enable Community Sharing": "Разрешаване на споделяне в общност", "Enable New Sign Ups": "Вклюване на Нови Потребители", "Enable Web Search": "Разрешаване на търсене в уеб", + "Engine": "", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Уверете се, че вашият CSV файл включва 4 колони в следния ред: Име, Имейл, Парола, Роля.", "Enter {{role}} message here": "Въведете съобщение за {{role}} тук", "Enter a detail about yourself for your LLMs to recall": "Въведете подробности за себе си, за да се herinnerат вашите LLMs", @@ -233,6 +235,7 @@ "Enter Serpstack API Key": "Въведете Serpstack API ключ", "Enter stop sequence": "Въведете стоп последователност", "Enter Tavily API Key": "", + "Enter Tika Server URL": "", "Enter Top K": "Въведете Top K", "Enter URL (e.g. http://127.0.0.1:7860/)": "Въведете URL (напр. http://127.0.0.1:7860/)", "Enter URL (e.g. http://localhost:11434)": "Въведете URL (напр. http://localhost:11434)", @@ -574,6 +577,8 @@ "This setting does not sync across browsers or devices.": "Тази настройка не се синхронизира между браузъри или устройства.", "This will delete": "", "Thorough explanation": "Това е подробно описание.", + "Tika": "", + "Tika Server URL required.": "", "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Съвет: Актуализирайте няколко слота за променливи последователно, като натискате клавиша Tab в чат входа след всяка подмяна.", "Title": "Заглавие", "Title (e.g. Tell me a fun fact)": "Заглавие (напр. Моля, кажете ми нещо забавно)", diff --git a/src/lib/i18n/locales/bn-BD/translation.json b/src/lib/i18n/locales/bn-BD/translation.json index aa305b11f..e4c565af2 100644 --- a/src/lib/i18n/locales/bn-BD/translation.json +++ b/src/lib/i18n/locales/bn-BD/translation.json @@ -126,6 +126,7 @@ "Connections": "কানেকশনগুলো", "Contact Admin for WebUI Access": "", "Content": "বিষয়বস্তু", + "Content Extraction": "", "Context Length": "কনটেক্সটের দৈর্ঘ্য", "Continue Response": "যাচাই করুন", "Continue with {{provider}}": "", @@ -212,6 +213,7 @@ "Enable Community Sharing": "সম্প্রদায় শেয়ারকরণ সক্ষম করুন", "Enable New Sign Ups": "নতুন সাইনআপ চালু করুন", "Enable Web Search": "ওয়েব অনুসন্ধান সক্ষম করুন", + "Engine": "", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "আপনার সিএসভি ফাইলটিতে এই ক্রমে 4 টি কলাম অন্তর্ভুক্ত রয়েছে তা নিশ্চিত করুন: নাম, ইমেল, পাসওয়ার্ড, ভূমিকা।.", "Enter {{role}} message here": "{{role}} মেসেজ এখানে লিখুন", "Enter a detail about yourself for your LLMs to recall": "আপনার এলএলএমগুলি স্মরণ করার জন্য নিজের সম্পর্কে একটি বিশদ লিখুন", @@ -233,6 +235,7 @@ "Enter Serpstack API Key": "Serpstack API কী লিখুন", "Enter stop sequence": "স্টপ সিকোয়েন্স লিখুন", "Enter Tavily API Key": "", + "Enter Tika Server URL": "", "Enter Top K": "Top K লিখুন", "Enter URL (e.g. http://127.0.0.1:7860/)": "ইউআরএল দিন (যেমন http://127.0.0.1:7860/)", "Enter URL (e.g. http://localhost:11434)": "ইউআরএল দিন (যেমন http://localhost:11434)", @@ -574,6 +577,8 @@ "This setting does not sync across browsers or devices.": "এই সেটিং অন্যন্য ব্রাউজার বা ডিভাইসের সাথে সিঙ্ক্রোনাইজ নয় না।", "This will delete": "", "Thorough explanation": "পুঙ্খানুপুঙ্খ ব্যাখ্যা", + "Tika": "", + "Tika Server URL required.": "", "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "পরামর্শ: একাধিক ভেরিয়েবল স্লট একের পর এক রিপ্লেস করার জন্য চ্যাট ইনপুটে কিবোর্ডের Tab বাটন ব্যবহার করুন।", "Title": "শিরোনাম", "Title (e.g. Tell me a fun fact)": "শিরোনাম (একটি উপস্থিতি বিবরণ জানান)", diff --git a/src/lib/i18n/locales/ca-ES/translation.json b/src/lib/i18n/locales/ca-ES/translation.json index f66907566..e03b989aa 100644 --- a/src/lib/i18n/locales/ca-ES/translation.json +++ b/src/lib/i18n/locales/ca-ES/translation.json @@ -126,6 +126,7 @@ "Connections": "Connexions", "Contact Admin for WebUI Access": "Posat en contacte amb l'administrador per accedir a WebUI", "Content": "Contingut", + "Content Extraction": "", "Context Length": "Mida del context", "Continue Response": "Continuar la resposta", "Continue with {{provider}}": "Continuar amb {{provider}}", @@ -212,6 +213,7 @@ "Enable Community Sharing": "Activar l'ús compartit amb la comunitat", "Enable New Sign Ups": "Permetre nous registres", "Enable Web Search": "Activar la cerca web", + "Engine": "", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Assegura't que els teus fitxers CSV inclouen 4 columnes en aquest ordre: Nom, Correu electrònic, Contrasenya, Rol.", "Enter {{role}} message here": "Introdueix aquí el missatge de {{role}}", "Enter a detail about yourself for your LLMs to recall": "Introdueix un detall sobre tu què els teus models de llenguatge puguin recordar", @@ -233,6 +235,7 @@ "Enter Serpstack API Key": "Introdueix la clau API Serpstack", "Enter stop sequence": "Introdueix la seqüència de parada", "Enter Tavily API Key": "Introdueix la clau API de Tavily", + "Enter Tika Server URL": "", "Enter Top K": "Introdueix Top K", "Enter URL (e.g. http://127.0.0.1:7860/)": "Introdueix l'URL (p. ex. http://127.0.0.1:7860/)", "Enter URL (e.g. http://localhost:11434)": "Introdueix l'URL (p. ex. http://localhost:11434)", @@ -575,6 +578,8 @@ "This setting does not sync across browsers or devices.": "Aquesta preferència no es sincronitza entre navegadors ni dispositius.", "This will delete": "Això eliminarà", "Thorough explanation": "Explicació en detall", + "Tika": "", + "Tika Server URL required.": "", "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Consell: Actualitza les diverses variables consecutivament prement la tecla de tabulació en l'entrada del xat després de cada reemplaçament.", "Title": "Títol", "Title (e.g. Tell me a fun fact)": "Títol (p. ex. Digues-me quelcom divertit)", diff --git a/src/lib/i18n/locales/ceb-PH/translation.json b/src/lib/i18n/locales/ceb-PH/translation.json index 41552d33a..15130f59b 100644 --- a/src/lib/i18n/locales/ceb-PH/translation.json +++ b/src/lib/i18n/locales/ceb-PH/translation.json @@ -126,6 +126,7 @@ "Connections": "Mga koneksyon", "Contact Admin for WebUI Access": "", "Content": "Kontento", + "Content Extraction": "", "Context Length": "Ang gitas-on sa konteksto", "Continue Response": "", "Continue with {{provider}}": "", @@ -212,6 +213,7 @@ "Enable Community Sharing": "", "Enable New Sign Ups": "I-enable ang bag-ong mga rehistro", "Enable Web Search": "", + "Engine": "", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "", "Enter {{role}} message here": "Pagsulod sa mensahe {{role}} dinhi", "Enter a detail about yourself for your LLMs to recall": "", @@ -233,6 +235,7 @@ "Enter Serpstack API Key": "", "Enter stop sequence": "Pagsulod sa katapusan nga han-ay", "Enter Tavily API Key": "", + "Enter Tika Server URL": "", "Enter Top K": "Pagsulod sa Top K", "Enter URL (e.g. http://127.0.0.1:7860/)": "Pagsulod sa URL (e.g. http://127.0.0.1:7860/)", "Enter URL (e.g. http://localhost:11434)": "", @@ -574,6 +577,8 @@ "This setting does not sync across browsers or devices.": "Kini nga setting wala mag-sync tali sa mga browser o device.", "This will delete": "", "Thorough explanation": "", + "Tika": "", + "Tika Server URL required.": "", "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Sugyot: Pag-update sa daghang variable nga lokasyon nga sunud-sunod pinaagi sa pagpindot sa tab key sa chat entry pagkahuman sa matag puli.", "Title": "Titulo", "Title (e.g. Tell me a fun fact)": "", diff --git a/src/lib/i18n/locales/de-DE/translation.json b/src/lib/i18n/locales/de-DE/translation.json index 42e691d21..3e41b435b 100644 --- a/src/lib/i18n/locales/de-DE/translation.json +++ b/src/lib/i18n/locales/de-DE/translation.json @@ -126,6 +126,7 @@ "Connections": "Verbindungen", "Contact Admin for WebUI Access": "Kontaktieren Sie den Administrator für den Zugriff auf die Weboberfläche", "Content": "Info", + "Content Extraction": "", "Context Length": "Kontextlänge", "Continue Response": "Antwort fortsetzen", "Continue with {{provider}}": "Mit {{Anbieter}} fortfahren", @@ -212,6 +213,7 @@ "Enable Community Sharing": "Community-Freigabe aktivieren", "Enable New Sign Ups": "Registrierung erlauben", "Enable Web Search": "Websuche aktivieren", + "Engine": "", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Stellen Sie sicher, dass Ihre CSV-Datei 4 Spalten in dieser Reihenfolge enthält: Name, E-Mail, Passwort, Rolle.", "Enter {{role}} message here": "Geben Sie die {{role}}-Nachricht hier ein", "Enter a detail about yourself for your LLMs to recall": "Geben Sie ein Detail über sich selbst ein, das Ihre Sprachmodelle (LLMs) sich merken sollen", @@ -233,6 +235,7 @@ "Enter Serpstack API Key": "Geben Sie den Serpstack-API-Schlüssel ein", "Enter stop sequence": "Stop-Sequenz eingeben", "Enter Tavily API Key": "Geben Sie den Tavily-API-Schlüssel ein", + "Enter Tika Server URL": "", "Enter Top K": "Geben Sie Top K ein", "Enter URL (e.g. http://127.0.0.1:7860/)": "Geben Sie die URL ein (z. B. http://127.0.0.1:7860/)", "Enter URL (e.g. http://localhost:11434)": "Geben Sie die URL ein (z. B. http://localhost:11434)", @@ -574,6 +577,8 @@ "This setting does not sync across browsers or devices.": "Diese Einstellung wird nicht zwischen Browsern oder Geräten synchronisiert.", "This will delete": "Dies löscht", "Thorough explanation": "Ausführliche Erklärung", + "Tika": "", + "Tika Server URL required.": "", "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Tipp: Aktualisieren Sie mehrere Variablenfelder nacheinander, indem Sie nach jedem Ersetzen die Tabulatortaste im Eingabefeld der Unterhaltung drücken.", "Title": "Titel", "Title (e.g. Tell me a fun fact)": "Titel (z. B. Erzähl mir einen lustigen Fakt)", diff --git a/src/lib/i18n/locales/dg-DG/translation.json b/src/lib/i18n/locales/dg-DG/translation.json index 31a7ad861..1fb100e41 100644 --- a/src/lib/i18n/locales/dg-DG/translation.json +++ b/src/lib/i18n/locales/dg-DG/translation.json @@ -126,6 +126,7 @@ "Connections": "Connections", "Contact Admin for WebUI Access": "", "Content": "Content", + "Content Extraction": "", "Context Length": "Context Length", "Continue Response": "", "Continue with {{provider}}": "", @@ -212,6 +213,7 @@ "Enable Community Sharing": "", "Enable New Sign Ups": "Enable New Bark Ups", "Enable Web Search": "", + "Engine": "", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "", "Enter {{role}} message here": "Enter {{role}} bork here", "Enter a detail about yourself for your LLMs to recall": "", @@ -233,6 +235,7 @@ "Enter Serpstack API Key": "", "Enter stop sequence": "Enter stop bark", "Enter Tavily API Key": "", + "Enter Tika Server URL": "", "Enter Top K": "Enter Top Wow", "Enter URL (e.g. http://127.0.0.1:7860/)": "Enter URL (e.g. http://127.0.0.1:7860/)", "Enter URL (e.g. http://localhost:11434)": "", @@ -576,6 +579,8 @@ "This setting does not sync across browsers or devices.": "This setting does not sync across browsers or devices. Very not sync.", "This will delete": "", "Thorough explanation": "", + "Tika": "", + "Tika Server URL required.": "", "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. Much tip!", "Title": "Title very title", "Title (e.g. Tell me a fun fact)": "", diff --git a/src/lib/i18n/locales/en-GB/translation.json b/src/lib/i18n/locales/en-GB/translation.json index 78525e22f..ec5e205cb 100644 --- a/src/lib/i18n/locales/en-GB/translation.json +++ b/src/lib/i18n/locales/en-GB/translation.json @@ -126,6 +126,7 @@ "Connections": "", "Contact Admin for WebUI Access": "", "Content": "", + "Content Extraction": "", "Context Length": "", "Continue Response": "", "Continue with {{provider}}": "", @@ -212,6 +213,7 @@ "Enable Community Sharing": "", "Enable New Sign Ups": "", "Enable Web Search": "", + "Engine": "", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "", "Enter {{role}} message here": "", "Enter a detail about yourself for your LLMs to recall": "", @@ -233,6 +235,7 @@ "Enter Serpstack API Key": "", "Enter stop sequence": "", "Enter Tavily API Key": "", + "Enter Tika Server URL": "", "Enter Top K": "", "Enter URL (e.g. http://127.0.0.1:7860/)": "", "Enter URL (e.g. http://localhost:11434)": "", @@ -574,6 +577,8 @@ "This setting does not sync across browsers or devices.": "", "This will delete": "", "Thorough explanation": "", + "Tika": "", + "Tika Server URL required.": "", "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "", "Title": "", "Title (e.g. Tell me a fun fact)": "", diff --git a/src/lib/i18n/locales/en-US/translation.json b/src/lib/i18n/locales/en-US/translation.json index 78525e22f..ec5e205cb 100644 --- a/src/lib/i18n/locales/en-US/translation.json +++ b/src/lib/i18n/locales/en-US/translation.json @@ -126,6 +126,7 @@ "Connections": "", "Contact Admin for WebUI Access": "", "Content": "", + "Content Extraction": "", "Context Length": "", "Continue Response": "", "Continue with {{provider}}": "", @@ -212,6 +213,7 @@ "Enable Community Sharing": "", "Enable New Sign Ups": "", "Enable Web Search": "", + "Engine": "", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "", "Enter {{role}} message here": "", "Enter a detail about yourself for your LLMs to recall": "", @@ -233,6 +235,7 @@ "Enter Serpstack API Key": "", "Enter stop sequence": "", "Enter Tavily API Key": "", + "Enter Tika Server URL": "", "Enter Top K": "", "Enter URL (e.g. http://127.0.0.1:7860/)": "", "Enter URL (e.g. http://localhost:11434)": "", @@ -574,6 +577,8 @@ "This setting does not sync across browsers or devices.": "", "This will delete": "", "Thorough explanation": "", + "Tika": "", + "Tika Server URL required.": "", "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "", "Title": "", "Title (e.g. Tell me a fun fact)": "", diff --git a/src/lib/i18n/locales/es-ES/translation.json b/src/lib/i18n/locales/es-ES/translation.json index 37a306f80..85ee652b1 100644 --- a/src/lib/i18n/locales/es-ES/translation.json +++ b/src/lib/i18n/locales/es-ES/translation.json @@ -126,6 +126,7 @@ "Connections": "Conexiones", "Contact Admin for WebUI Access": "Contacta el administrador para obtener acceso al WebUI", "Content": "Contenido", + "Content Extraction": "", "Context Length": "Longitud del contexto", "Continue Response": "Continuar Respuesta", "Continue with {{provider}}": "Continuar con {{provider}}", @@ -212,6 +213,7 @@ "Enable Community Sharing": "Habilitar el uso compartido de la comunidad", "Enable New Sign Ups": "Habilitar Nuevos Registros", "Enable Web Search": "Habilitar la búsqueda web", + "Engine": "", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Asegúrese de que su archivo CSV incluya 4 columnas en este orden: Nombre, Correo Electrónico, Contraseña, Rol.", "Enter {{role}} message here": "Ingrese el mensaje {{role}} aquí", "Enter a detail about yourself for your LLMs to recall": "Ingrese un detalle sobre usted para que sus LLMs recuerden", @@ -233,6 +235,7 @@ "Enter Serpstack API Key": "Ingrese la clave API de Serpstack", "Enter stop sequence": "Ingrese la secuencia de parada", "Enter Tavily API Key": "Ingrese la clave API de Tavily", + "Enter Tika Server URL": "", "Enter Top K": "Ingrese el Top K", "Enter URL (e.g. http://127.0.0.1:7860/)": "Ingrese la URL (p.ej., http://127.0.0.1:7860/)", "Enter URL (e.g. http://localhost:11434)": "Ingrese la URL (p.ej., http://localhost:11434)", @@ -575,6 +578,8 @@ "This setting does not sync across browsers or devices.": "Esta configuración no se sincroniza entre navegadores o dispositivos.", "This will delete": "Esto eliminará", "Thorough explanation": "Explicación exhaustiva", + "Tika": "", + "Tika Server URL required.": "", "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Consejo: Actualice múltiples variables consecutivamente presionando la tecla tab en la entrada del chat después de cada reemplazo.", "Title": "Título", "Title (e.g. Tell me a fun fact)": "Título (por ejemplo, cuéntame una curiosidad)", diff --git a/src/lib/i18n/locales/fa-IR/translation.json b/src/lib/i18n/locales/fa-IR/translation.json index aaacb9a74..f7aca0eda 100644 --- a/src/lib/i18n/locales/fa-IR/translation.json +++ b/src/lib/i18n/locales/fa-IR/translation.json @@ -126,6 +126,7 @@ "Connections": "ارتباطات", "Contact Admin for WebUI Access": "", "Content": "محتوا", + "Content Extraction": "", "Context Length": "طول زمینه", "Continue Response": "ادامه پاسخ", "Continue with {{provider}}": "", @@ -212,6 +213,7 @@ "Enable Community Sharing": "فعالسازی اشتراک انجمن", "Enable New Sign Ups": "فعال کردن ثبت نام\u200cهای جدید", "Enable Web Search": "فعالسازی جستجوی وب", + "Engine": "", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "اطمینان حاصل کنید که فایل CSV شما شامل چهار ستون در این ترتیب است: نام، ایمیل، رمز عبور، نقش.", "Enter {{role}} message here": "پیام {{role}} را اینجا وارد کنید", "Enter a detail about yourself for your LLMs to recall": "برای ذخیره سازی اطلاعات خود، یک توضیح کوتاه درباره خود را وارد کنید", @@ -233,6 +235,7 @@ "Enter Serpstack API Key": "کلید API Serpstack را وارد کنید", "Enter stop sequence": "توالی توقف را وارد کنید", "Enter Tavily API Key": "", + "Enter Tika Server URL": "", "Enter Top K": "مقدار Top K را وارد کنید", "Enter URL (e.g. http://127.0.0.1:7860/)": "مقدار URL را وارد کنید (مثال http://127.0.0.1:7860/)", "Enter URL (e.g. http://localhost:11434)": "مقدار URL را وارد کنید (مثال http://localhost:11434)", @@ -574,6 +577,8 @@ "This setting does not sync across browsers or devices.": "این تنظیم در مرورگرها یا دستگاه\u200cها همگام\u200cسازی نمی\u200cشود.", "This will delete": "", "Thorough explanation": "توضیح کامل", + "Tika": "", + "Tika Server URL required.": "", "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "با فشردن کلید Tab در ورودی چت پس از هر بار تعویض، چندین متغیر را به صورت متوالی به روزرسانی کنید.", "Title": "عنوان", "Title (e.g. Tell me a fun fact)": "عنوان (برای مثال: به من بگوید چیزی که دوست دارید)", diff --git a/src/lib/i18n/locales/fi-FI/translation.json b/src/lib/i18n/locales/fi-FI/translation.json index cbb9d8f9d..73c5c9e0c 100644 --- a/src/lib/i18n/locales/fi-FI/translation.json +++ b/src/lib/i18n/locales/fi-FI/translation.json @@ -126,6 +126,7 @@ "Connections": "Yhteydet", "Contact Admin for WebUI Access": "", "Content": "Sisältö", + "Content Extraction": "", "Context Length": "Kontekstin pituus", "Continue Response": "Jatka vastausta", "Continue with {{provider}}": "", @@ -212,6 +213,7 @@ "Enable Community Sharing": "Ota yhteisön jakaminen käyttöön", "Enable New Sign Ups": "Salli uudet rekisteröitymiset", "Enable Web Search": "Ota verkkohaku käyttöön", + "Engine": "", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Varmista, että CSV-tiedostossasi on 4 saraketta seuraavassa järjestyksessä: Nimi, Sähköposti, Salasana, Rooli.", "Enter {{role}} message here": "Kirjoita {{role}} viesti tähän", "Enter a detail about yourself for your LLMs to recall": "Kirjoita tieto itseestäsi LLM:ien muistamiseksi", @@ -233,6 +235,7 @@ "Enter Serpstack API Key": "Anna Serpstack API -avain", "Enter stop sequence": "Syötä lopetussekvenssi", "Enter Tavily API Key": "", + "Enter Tika Server URL": "", "Enter Top K": "Syötä Top K", "Enter URL (e.g. http://127.0.0.1:7860/)": "Syötä URL (esim. http://127.0.0.1:7860/)", "Enter URL (e.g. http://localhost:11434)": "Syötä URL (esim. http://localhost:11434)", @@ -574,6 +577,8 @@ "This setting does not sync across browsers or devices.": "Tämä asetus ei synkronoidu selainten tai laitteiden välillä.", "This will delete": "", "Thorough explanation": "Perusteellinen selitys", + "Tika": "", + "Tika Server URL required.": "", "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Vinkki: Päivitä useita muuttujapaikkoja peräkkäin painamalla tabulaattoria keskustelusyötteessä jokaisen korvauksen jälkeen.", "Title": "Otsikko", "Title (e.g. Tell me a fun fact)": "Otsikko (esim. Kerro hauska fakta)", diff --git a/src/lib/i18n/locales/fr-CA/translation.json b/src/lib/i18n/locales/fr-CA/translation.json index 32415078f..4fcfd5630 100644 --- a/src/lib/i18n/locales/fr-CA/translation.json +++ b/src/lib/i18n/locales/fr-CA/translation.json @@ -126,6 +126,7 @@ "Connections": "Connexions", "Contact Admin for WebUI Access": "", "Content": "Contenu", + "Content Extraction": "", "Context Length": "Longueur du contexte", "Continue Response": "Continuer la réponse", "Continue with {{provider}}": "", @@ -212,6 +213,7 @@ "Enable Community Sharing": "Permettre le partage communautaire", "Enable New Sign Ups": "Activer les nouvelles inscriptions", "Enable Web Search": "Activer la recherche sur le Web", + "Engine": "", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Assurez-vous que votre fichier CSV inclut 4 colonnes dans cet ordre : Nom, Email, Mot de passe, Rôle.", "Enter {{role}} message here": "Entrez le message {{role}} ici", "Enter a detail about yourself for your LLMs to recall": "Entrez un détail sur vous pour que vos LLMs puissent le rappeler", @@ -233,6 +235,7 @@ "Enter Serpstack API Key": "Entrez dans la clé API Serpstack", "Enter stop sequence": "Entrez la séquence de fin", "Enter Tavily API Key": "", + "Enter Tika Server URL": "", "Enter Top K": "Entrez Top K", "Enter URL (e.g. http://127.0.0.1:7860/)": "Entrez l'URL (p. ex. http://127.0.0.1:7860/)", "Enter URL (e.g. http://localhost:11434)": "Entrez l'URL (p. ex. http://localhost:11434)", @@ -575,6 +578,8 @@ "This setting does not sync across browsers or devices.": "Ce réglage ne se synchronise pas entre les navigateurs ou les appareils.", "This will delete": "", "Thorough explanation": "Explication approfondie", + "Tika": "", + "Tika Server URL required.": "", "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Astuce : Mettez à jour plusieurs emplacements de variables consécutivement en appuyant sur la touche tabulation dans l'entrée de chat après chaque remplacement.", "Title": "Titre", "Title (e.g. Tell me a fun fact)": "Titre (par exemple, Dites-moi un fait amusant)", diff --git a/src/lib/i18n/locales/fr-FR/translation.json b/src/lib/i18n/locales/fr-FR/translation.json index 9b64ae056..073bbac0e 100644 --- a/src/lib/i18n/locales/fr-FR/translation.json +++ b/src/lib/i18n/locales/fr-FR/translation.json @@ -126,6 +126,7 @@ "Connections": "Connexions", "Contact Admin for WebUI Access": "", "Content": "Contenu", + "Content Extraction": "", "Context Length": "Longueur du contexte", "Continue Response": "Continuer la Réponse", "Continue with {{provider}}": "", @@ -212,6 +213,7 @@ "Enable Community Sharing": "Activer le partage de communauté", "Enable New Sign Ups": "Activer les nouvelles inscriptions", "Enable Web Search": "Activer la recherche sur le Web", + "Engine": "", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Vérifiez que le fichier CSV contienne 4 colonnes dans cet ordre : Name (Nom), Email, Password (Mot de passe), Role (Rôle).", "Enter {{role}} message here": "Entrez le message {{role}} ici", "Enter a detail about yourself for your LLMs to recall": "Saisissez une donnée vous concernant pour que vos LLMs s'en souviennent", @@ -233,6 +235,7 @@ "Enter Serpstack API Key": "Entrez la clé API Serpstack", "Enter stop sequence": "Entrez la séquence de fin", "Enter Tavily API Key": "", + "Enter Tika Server URL": "", "Enter Top K": "Entrez Top K", "Enter URL (e.g. http://127.0.0.1:7860/)": "Entrez l'URL (p. ex. http://127.0.0.1:7860/)", "Enter URL (e.g. http://localhost:11434)": "Entrez l'URL (p. ex. http://localhost:11434)", @@ -575,6 +578,8 @@ "This setting does not sync across browsers or devices.": "Ce paramètre ne se synchronise pas entre les navigateurs ou les appareils.", "This will delete": "", "Thorough explanation": "Explication détaillée", + "Tika": "", + "Tika Server URL required.": "", "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Conseil : Mettez à jour plusieurs emplacements de variables consécutivement en appuyant sur la touche tab dans l'entrée de chat après chaque remplacement", "Title": "Titre", "Title (e.g. Tell me a fun fact)": "Titre (p. ex. Donne moi un fait amusant)", diff --git a/src/lib/i18n/locales/he-IL/translation.json b/src/lib/i18n/locales/he-IL/translation.json index 981d6990c..7344026c8 100644 --- a/src/lib/i18n/locales/he-IL/translation.json +++ b/src/lib/i18n/locales/he-IL/translation.json @@ -126,6 +126,7 @@ "Connections": "חיבורים", "Contact Admin for WebUI Access": "", "Content": "תוכן", + "Content Extraction": "", "Context Length": "אורך הקשר", "Continue Response": "המשך תגובה", "Continue with {{provider}}": "", @@ -212,6 +213,7 @@ "Enable Community Sharing": "הפיכת שיתוף קהילה לזמין", "Enable New Sign Ups": "אפשר הרשמות חדשות", "Enable Web Search": "הפיכת חיפוש באינטרנט לזמין", + "Engine": "", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "ודא שקובץ ה-CSV שלך כולל 4 עמודות בסדר הבא: שם, דוא\"ל, סיסמה, תפקיד.", "Enter {{role}} message here": "הזן הודעת {{role}} כאן", "Enter a detail about yourself for your LLMs to recall": "הזן פרטים על עצמך כדי שLLMs יזכור", @@ -233,6 +235,7 @@ "Enter Serpstack API Key": "הזן מפתח API של Serpstack", "Enter stop sequence": "הזן רצף עצירה", "Enter Tavily API Key": "", + "Enter Tika Server URL": "", "Enter Top K": "הזן Top K", "Enter URL (e.g. http://127.0.0.1:7860/)": "הזן כתובת URL (למשל http://127.0.0.1:7860/)", "Enter URL (e.g. http://localhost:11434)": "הזן כתובת URL (למשל http://localhost:11434)", @@ -575,6 +578,8 @@ "This setting does not sync across browsers or devices.": "הגדרה זו אינה מסתנכרנת בין דפדפנים או מכשירים.", "This will delete": "", "Thorough explanation": "תיאור מפורט", + "Tika": "", + "Tika Server URL required.": "", "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "טיפ: עדכן חריצים משתנים מרובים ברציפות על-ידי לחיצה על מקש Tab בקלט הצ'אט לאחר כל החלפה.", "Title": "שם", "Title (e.g. Tell me a fun fact)": "שם (לדוגמה: תרגום)", diff --git a/src/lib/i18n/locales/hi-IN/translation.json b/src/lib/i18n/locales/hi-IN/translation.json index c787e7c79..2beae3bd7 100644 --- a/src/lib/i18n/locales/hi-IN/translation.json +++ b/src/lib/i18n/locales/hi-IN/translation.json @@ -126,6 +126,7 @@ "Connections": "सम्बन्ध", "Contact Admin for WebUI Access": "", "Content": "सामग्री", + "Content Extraction": "", "Context Length": "प्रसंग की लंबाई", "Continue Response": "प्रतिक्रिया जारी रखें", "Continue with {{provider}}": "", @@ -212,6 +213,7 @@ "Enable Community Sharing": "समुदाय साझाकरण सक्षम करें", "Enable New Sign Ups": "नए साइन अप सक्रिय करें", "Enable Web Search": "वेब खोज सक्षम करें", + "Engine": "", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "सुनिश्चित करें कि आपकी CSV फ़ाइल में इस क्रम में 4 कॉलम शामिल हैं: नाम, ईमेल, पासवर्ड, भूमिका।", "Enter {{role}} message here": "यहां {{role}} संदेश दर्ज करें", "Enter a detail about yourself for your LLMs to recall": "अपने एलएलएम को याद करने के लिए अपने बारे में एक विवरण दर्ज करें", @@ -233,6 +235,7 @@ "Enter Serpstack API Key": "सर्पस्टैक एपीआई कुंजी दर्ज करें", "Enter stop sequence": "स्टॉप अनुक्रम दर्ज करें", "Enter Tavily API Key": "", + "Enter Tika Server URL": "", "Enter Top K": "शीर्ष K दर्ज करें", "Enter URL (e.g. http://127.0.0.1:7860/)": "यूआरएल दर्ज करें (उदा. http://127.0.0.1:7860/)", "Enter URL (e.g. http://localhost:11434)": "यूआरएल दर्ज करें (उदा. http://localhost:11434)", @@ -574,6 +577,8 @@ "This setting does not sync across browsers or devices.": "यह सेटिंग सभी ब्राउज़रों या डिवाइसों में समन्वयित नहीं होती है", "This will delete": "", "Thorough explanation": "विस्तृत व्याख्या", + "Tika": "", + "Tika Server URL required.": "", "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "टिप: प्रत्येक प्रतिस्थापन के बाद चैट इनपुट में टैब कुंजी दबाकर लगातार कई वैरिएबल स्लॉट अपडेट करें।", "Title": "शीर्षक", "Title (e.g. Tell me a fun fact)": "शीर्षक (उदा. मुझे एक मज़ेदार तथ्य बताएं)", diff --git a/src/lib/i18n/locales/hr-HR/translation.json b/src/lib/i18n/locales/hr-HR/translation.json index a167635bc..24d52fa33 100644 --- a/src/lib/i18n/locales/hr-HR/translation.json +++ b/src/lib/i18n/locales/hr-HR/translation.json @@ -126,6 +126,7 @@ "Connections": "Povezivanja", "Contact Admin for WebUI Access": "Kontaktirajte admina za WebUI pristup", "Content": "Sadržaj", + "Content Extraction": "", "Context Length": "Dužina konteksta", "Continue Response": "Nastavi odgovor", "Continue with {{provider}}": "", @@ -212,6 +213,7 @@ "Enable Community Sharing": "Omogući zajedničko korištenje zajednice", "Enable New Sign Ups": "Omogući nove prijave", "Enable Web Search": "Omogući pretraživanje weba", + "Engine": "", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Provjerite da vaša CSV datoteka uključuje 4 stupca u ovom redoslijedu: Name, Email, Password, Role.", "Enter {{role}} message here": "Unesite {{role}} poruku ovdje", "Enter a detail about yourself for your LLMs to recall": "Unesite pojedinosti o sebi da bi učitali memoriju u LLM", @@ -233,6 +235,7 @@ "Enter Serpstack API Key": "Unesite Serpstack API ključ", "Enter stop sequence": "Unesite sekvencu zaustavljanja", "Enter Tavily API Key": "", + "Enter Tika Server URL": "", "Enter Top K": "Unesite Top K", "Enter URL (e.g. http://127.0.0.1:7860/)": "Unesite URL (npr. http://127.0.0.1:7860/)", "Enter URL (e.g. http://localhost:11434)": "Unesite URL (npr. http://localhost:11434)", @@ -575,6 +578,8 @@ "This setting does not sync across browsers or devices.": "Ova postavka se ne sinkronizira između preglednika ili uređaja.", "This will delete": "", "Thorough explanation": "Detaljno objašnjenje", + "Tika": "", + "Tika Server URL required.": "", "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Savjet: Ažurirajte više mjesta za varijable uzastopno pritiskom na tipku tab u unosu razgovora nakon svake zamjene.", "Title": "Naslov", "Title (e.g. Tell me a fun fact)": "Naslov (npr. Reci mi zanimljivost)", diff --git a/src/lib/i18n/locales/id-ID/translation.json b/src/lib/i18n/locales/id-ID/translation.json index e06aeab54..aa8f26bd7 100644 --- a/src/lib/i18n/locales/id-ID/translation.json +++ b/src/lib/i18n/locales/id-ID/translation.json @@ -126,6 +126,7 @@ "Connections": "Koneksi", "Contact Admin for WebUI Access": "Hubungi Admin untuk Akses WebUI", "Content": "Konten", + "Content Extraction": "", "Context Length": "Panjang Konteks", "Continue Response": "Lanjutkan Tanggapan", "Continue with {{provider}}": "Lanjutkan dengan {{penyedia}}", @@ -212,6 +213,7 @@ "Enable Community Sharing": "Aktifkan Berbagi Komunitas", "Enable New Sign Ups": "Aktifkan Pendaftaran Baru", "Enable Web Search": "Aktifkan Pencarian Web", + "Engine": "", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Pastikan file CSV Anda menyertakan 4 kolom dengan urutan sebagai berikut: Nama, Email, Kata Sandi, Peran.", "Enter {{role}} message here": "Masukkan pesan {{role}} di sini", "Enter a detail about yourself for your LLMs to recall": "Masukkan detail tentang diri Anda untuk diingat oleh LLM Anda", @@ -233,6 +235,7 @@ "Enter Serpstack API Key": "Masukkan Kunci API Serpstack", "Enter stop sequence": "Masukkan urutan berhenti", "Enter Tavily API Key": "Masukkan Kunci API Tavily", + "Enter Tika Server URL": "", "Enter Top K": "Masukkan Top K", "Enter URL (e.g. http://127.0.0.1:7860/)": "Masukkan URL (mis. http://127.0.0.1:7860/)", "Enter URL (e.g. http://localhost:11434)": "Masukkan URL (mis. http://localhost:11434)", @@ -574,6 +577,8 @@ "This setting does not sync across browsers or devices.": "Pengaturan ini tidak disinkronkan di seluruh browser atau perangkat.", "This will delete": "Ini akan menghapus", "Thorough explanation": "Penjelasan menyeluruh", + "Tika": "", + "Tika Server URL required.": "", "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Tips: Perbarui beberapa slot variabel secara berurutan dengan menekan tombol tab di input obrolan setelah setiap penggantian.", "Title": "Judul", "Title (e.g. Tell me a fun fact)": "Judul (misalnya, Ceritakan sebuah fakta menarik)", diff --git a/src/lib/i18n/locales/it-IT/translation.json b/src/lib/i18n/locales/it-IT/translation.json index 804e7ec33..00d127f7d 100644 --- a/src/lib/i18n/locales/it-IT/translation.json +++ b/src/lib/i18n/locales/it-IT/translation.json @@ -126,6 +126,7 @@ "Connections": "Connessioni", "Contact Admin for WebUI Access": "", "Content": "Contenuto", + "Content Extraction": "", "Context Length": "Lunghezza contesto", "Continue Response": "Continua risposta", "Continue with {{provider}}": "", @@ -212,6 +213,7 @@ "Enable Community Sharing": "Abilita la condivisione della community", "Enable New Sign Ups": "Abilita nuove iscrizioni", "Enable Web Search": "Abilita ricerca Web", + "Engine": "", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Assicurati che il tuo file CSV includa 4 colonne in questo ordine: Nome, Email, Password, Ruolo.", "Enter {{role}} message here": "Inserisci il messaggio per {{role}} qui", "Enter a detail about yourself for your LLMs to recall": "Inserisci un dettaglio su di te per che i LLM possano ricordare", @@ -233,6 +235,7 @@ "Enter Serpstack API Key": "Inserisci la chiave API Serpstack", "Enter stop sequence": "Inserisci la sequenza di arresto", "Enter Tavily API Key": "", + "Enter Tika Server URL": "", "Enter Top K": "Inserisci Top K", "Enter URL (e.g. http://127.0.0.1:7860/)": "Inserisci URL (ad esempio http://127.0.0.1:7860/)", "Enter URL (e.g. http://localhost:11434)": "Inserisci URL (ad esempio http://localhost:11434)", @@ -575,6 +578,8 @@ "This setting does not sync across browsers or devices.": "Questa impostazione non si sincronizza tra browser o dispositivi.", "This will delete": "", "Thorough explanation": "Spiegazione dettagliata", + "Tika": "", + "Tika Server URL required.": "", "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Suggerimento: aggiorna più slot di variabili consecutivamente premendo il tasto tab nell'input della chat dopo ogni sostituzione.", "Title": "Titolo", "Title (e.g. Tell me a fun fact)": "Titolo (ad esempio Dimmi un fatto divertente)", diff --git a/src/lib/i18n/locales/ja-JP/translation.json b/src/lib/i18n/locales/ja-JP/translation.json index 81c8d6733..de8eaea56 100644 --- a/src/lib/i18n/locales/ja-JP/translation.json +++ b/src/lib/i18n/locales/ja-JP/translation.json @@ -126,6 +126,7 @@ "Connections": "接続", "Contact Admin for WebUI Access": "", "Content": "コンテンツ", + "Content Extraction": "", "Context Length": "コンテキストの長さ", "Continue Response": "続きの応答", "Continue with {{provider}}": "", @@ -212,6 +213,7 @@ "Enable Community Sharing": "コミュニティ共有の有効化", "Enable New Sign Ups": "新規登録を有効化", "Enable Web Search": "Web 検索を有効にする", + "Engine": "", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "CSVファイルに4つの列が含まれていることを確認してください: Name, Email, Password, Role.", "Enter {{role}} message here": "{{role}} メッセージをここに入力してください", "Enter a detail about yourself for your LLMs to recall": "LLM が記憶するために、自分についての詳細を入力してください", @@ -233,6 +235,7 @@ "Enter Serpstack API Key": "Serpstack APIキーの入力", "Enter stop sequence": "ストップシーケンスを入力してください", "Enter Tavily API Key": "", + "Enter Tika Server URL": "", "Enter Top K": "トップ K を入力してください", "Enter URL (e.g. http://127.0.0.1:7860/)": "URL を入力してください (例: http://127.0.0.1:7860/)", "Enter URL (e.g. http://localhost:11434)": "URL を入力してください (例: http://localhost:11434)", @@ -573,6 +576,8 @@ "This setting does not sync across browsers or devices.": "この設定は、ブラウザやデバイス間で同期されません。", "This will delete": "", "Thorough explanation": "詳細な説明", + "Tika": "", + "Tika Server URL required.": "", "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "ヒント: 各置換後にチャット入力で Tab キーを押すことで、複数の変数スロットを連続して更新できます。", "Title": "タイトル", "Title (e.g. Tell me a fun fact)": "タイトル (例: 楽しい事を教えて)", diff --git a/src/lib/i18n/locales/ka-GE/translation.json b/src/lib/i18n/locales/ka-GE/translation.json index f1ed7c61a..06dfddf9f 100644 --- a/src/lib/i18n/locales/ka-GE/translation.json +++ b/src/lib/i18n/locales/ka-GE/translation.json @@ -126,6 +126,7 @@ "Connections": "კავშირები", "Contact Admin for WebUI Access": "", "Content": "კონტენტი", + "Content Extraction": "", "Context Length": "კონტექსტის სიგრძე", "Continue Response": "პასუხის გაგრძელება", "Continue with {{provider}}": "", @@ -212,6 +213,7 @@ "Enable Community Sharing": "საზოგადოების გაზიარების ჩართვა", "Enable New Sign Ups": "ახალი რეგისტრაციების ჩართვა", "Enable Web Search": "ვებ ძიების ჩართვა", + "Engine": "", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "გთხოვთ, უზრუნველყოთ, რომთქვევის CSV-ფაილი შეიცავს 4 ველი, ჩაწერილი ორივე ველი უდრის პირველი ველით.", "Enter {{role}} message here": "შეიყვანე {{role}} შეტყობინება აქ", "Enter a detail about yourself for your LLMs to recall": "შეიყვანე დეტალი ჩემთათვის, რომ ჩვენი LLMs-ს შეიძლოს აღაქვს", @@ -233,6 +235,7 @@ "Enter Serpstack API Key": "შეიყვანეთ Serpstack API Key", "Enter stop sequence": "შეიყვანეთ ტოპ თანმიმდევრობა", "Enter Tavily API Key": "", + "Enter Tika Server URL": "", "Enter Top K": "შეიყვანეთ Top K", "Enter URL (e.g. http://127.0.0.1:7860/)": "შეიყვანეთ მისამართი (მაგალითად http://127.0.0.1:7860/)", "Enter URL (e.g. http://localhost:11434)": "შეიყვანეთ მისამართი (მაგალითად http://localhost:11434)", @@ -574,6 +577,8 @@ "This setting does not sync across browsers or devices.": "ეს პარამეტრი არ სინქრონიზდება ბრაუზერებსა და მოწყობილობებში", "This will delete": "", "Thorough explanation": "ვრცლად აღწერა", + "Tika": "", + "Tika Server URL required.": "", "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "რჩევა: განაახლეთ რამდენიმე ცვლადი სლოტი თანმიმდევრულად, ყოველი ჩანაცვლების შემდეგ ჩატის ღილაკზე დაჭერით.", "Title": "სათაური", "Title (e.g. Tell me a fun fact)": "სათაური (მაგ. გაიხსნე რაღაც ხარისხი)", diff --git a/src/lib/i18n/locales/ko-KR/translation.json b/src/lib/i18n/locales/ko-KR/translation.json index b23318175..4f85b749d 100644 --- a/src/lib/i18n/locales/ko-KR/translation.json +++ b/src/lib/i18n/locales/ko-KR/translation.json @@ -126,6 +126,7 @@ "Connections": "연결", "Contact Admin for WebUI Access": "WebUI 접속을 위해서는 관리자에게 연락 필요", "Content": "내용", + "Content Extraction": "", "Context Length": "내용 길이", "Continue Response": "대화 계속", "Continue with {{provider}}": "", @@ -212,6 +213,7 @@ "Enable Community Sharing": "커뮤니티 공유 활성화", "Enable New Sign Ups": "새 회원가입 활성화", "Enable Web Search": "웹 검색 활성화", + "Engine": "", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "CSV 파일에 이름, 이메일, 비밀번호, 역할 4개의 컬럼이 순서대로 포함되어 있는지 확인하세요.", "Enter {{role}} message here": "여기에 {{role}} 메시지 입력", "Enter a detail about yourself for your LLMs to recall": "자신에 대한 세부사항을 입력하여 LLM들이 기억할 수 있도록 하세요.", @@ -233,6 +235,7 @@ "Enter Serpstack API Key": "Serpstack API 키 입력", "Enter stop sequence": "중지 시퀀스 입력", "Enter Tavily API Key": "Tavily API 키 입력", + "Enter Tika Server URL": "", "Enter Top K": "Top K 입력", "Enter URL (e.g. http://127.0.0.1:7860/)": "URL 입력(예: http://127.0.0.1:7860/)", "Enter URL (e.g. http://localhost:11434)": "URL 입력(예: http://localhost:11434)", @@ -574,6 +577,8 @@ "This setting does not sync across browsers or devices.": "이 설정은 브라우저 또는 장치 간에 동기화되지 않습니다.", "This will delete": "이것은 다음을 삭제합니다.", "Thorough explanation": "완전한 설명", + "Tika": "", + "Tika Server URL required.": "", "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "팁: 각 대체 후 채팅 입력에서 탭 키를 눌러 여러 개의 변수 슬롯을 연속적으로 업데이트하세요.", "Title": "제목", "Title (e.g. Tell me a fun fact)": "제목 (예: 재미있는 사실을 알려주세요)", diff --git a/src/lib/i18n/locales/lt-LT/translation.json b/src/lib/i18n/locales/lt-LT/translation.json index ddf31b1e0..6ca0cdfdb 100644 --- a/src/lib/i18n/locales/lt-LT/translation.json +++ b/src/lib/i18n/locales/lt-LT/translation.json @@ -126,6 +126,7 @@ "Connections": "Ryšiai", "Contact Admin for WebUI Access": "", "Content": "Turinys", + "Content Extraction": "", "Context Length": "Konteksto ilgis", "Continue Response": "Tęsti atsakymą", "Continue with {{provider}}": "", @@ -212,6 +213,7 @@ "Enable Community Sharing": "", "Enable New Sign Ups": "Aktyvuoti naujas registracijas", "Enable Web Search": "", + "Engine": "", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Įsitikinkite, kad CSV failas turi 4 kolonas šiuo eiliškumu: Name, Email, Password, Role.", "Enter {{role}} message here": "Įveskite {{role}} žinutę čia", "Enter a detail about yourself for your LLMs to recall": "", @@ -233,6 +235,7 @@ "Enter Serpstack API Key": "", "Enter stop sequence": "Įveskite pabaigos sekvenciją", "Enter Tavily API Key": "", + "Enter Tika Server URL": "", "Enter Top K": "Įveskite Top K", "Enter URL (e.g. http://127.0.0.1:7860/)": "Įveskite nuorodą (pvz. http://127.0.0.1:7860/)", "Enter URL (e.g. http://localhost:11434)": "Įveskite nuorododą (pvz. http://localhost:11434", @@ -576,6 +579,8 @@ "This setting does not sync across browsers or devices.": "Šis parametras nesisinchronizuoja su skirtingomis naršyklėmis ir įrankiais.", "This will delete": "", "Thorough explanation": "Platus paaiškinimas", + "Tika": "", + "Tika Server URL required.": "", "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Jei norite pakeisti keletą kintamųjų vieną po kitos, spauskite Tab", "Title": "Pavadinimas", "Title (e.g. Tell me a fun fact)": "Pavadinimas", diff --git a/src/lib/i18n/locales/nb-NO/translation.json b/src/lib/i18n/locales/nb-NO/translation.json index 1fb084f0e..096da47ce 100644 --- a/src/lib/i18n/locales/nb-NO/translation.json +++ b/src/lib/i18n/locales/nb-NO/translation.json @@ -126,6 +126,7 @@ "Connections": "Tilkoblinger", "Contact Admin for WebUI Access": "", "Content": "Innhold", + "Content Extraction": "", "Context Length": "Kontekstlengde", "Continue Response": "Fortsett svar", "Continue with {{provider}}": "", @@ -212,6 +213,7 @@ "Enable Community Sharing": "Aktiver deling i fellesskap", "Enable New Sign Ups": "Aktiver nye registreringer", "Enable Web Search": "Aktiver websøk", + "Engine": "", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Sørg for at CSV-filen din inkluderer 4 kolonner i denne rekkefølgen: Navn, E-post, Passord, Rolle.", "Enter {{role}} message here": "Skriv inn {{role}} melding her", "Enter a detail about yourself for your LLMs to recall": "Skriv inn en detalj om deg selv som LLM-ene dine kan huske", @@ -233,6 +235,7 @@ "Enter Serpstack API Key": "Skriv inn Serpstack API-nøkkel", "Enter stop sequence": "Skriv inn stoppsekvens", "Enter Tavily API Key": "", + "Enter Tika Server URL": "", "Enter Top K": "Skriv inn Top K", "Enter URL (e.g. http://127.0.0.1:7860/)": "Skriv inn URL (f.eks. http://127.0.0.1:7860/)", "Enter URL (e.g. http://localhost:11434)": "Skriv inn URL (f.eks. http://localhost:11434)", @@ -574,6 +577,8 @@ "This setting does not sync across browsers or devices.": "Denne innstillingen synkroniseres ikke mellom nettlesere eller enheter.", "This will delete": "", "Thorough explanation": "Grundig forklaring", + "Tika": "", + "Tika Server URL required.": "", "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Tips: Oppdater flere variabelplasser etter hverandre ved å trykke på tab-tasten i chatinputen etter hver erstatning.", "Title": "Tittel", "Title (e.g. Tell me a fun fact)": "Tittel (f.eks. Fortell meg en morsom fakta)", diff --git a/src/lib/i18n/locales/nl-NL/translation.json b/src/lib/i18n/locales/nl-NL/translation.json index 403428bf7..a19fda4dd 100644 --- a/src/lib/i18n/locales/nl-NL/translation.json +++ b/src/lib/i18n/locales/nl-NL/translation.json @@ -126,6 +126,7 @@ "Connections": "Verbindingen", "Contact Admin for WebUI Access": "", "Content": "Inhoud", + "Content Extraction": "", "Context Length": "Context Lengte", "Continue Response": "Doorgaan met Antwoord", "Continue with {{provider}}": "", @@ -212,6 +213,7 @@ "Enable Community Sharing": "Delen via de community inschakelen", "Enable New Sign Ups": "Schakel Nieuwe Registraties in", "Enable Web Search": "Zoeken op het web inschakelen", + "Engine": "", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Zorg ervoor dat uw CSV-bestand de volgende vier kolommen in deze volgorde bevat: Naam, E-mail, Wachtwoord, Rol.", "Enter {{role}} message here": "Voeg {{role}} bericht hier toe", "Enter a detail about yourself for your LLMs to recall": "Voer een detail over jezelf in voor je LLMs om het her te onthouden", @@ -233,6 +235,7 @@ "Enter Serpstack API Key": "Voer de Serpstack API-sleutel in", "Enter stop sequence": "Zet stop sequentie", "Enter Tavily API Key": "", + "Enter Tika Server URL": "", "Enter Top K": "Voeg Top K toe", "Enter URL (e.g. http://127.0.0.1:7860/)": "Zet URL (Bijv. http://127.0.0.1:7860/)", "Enter URL (e.g. http://localhost:11434)": "Zet URL (Bijv. http://localhost:11434)", @@ -574,6 +577,8 @@ "This setting does not sync across browsers or devices.": "Deze instelling wordt niet gesynchroniseerd tussen browsers of apparaten.", "This will delete": "", "Thorough explanation": "Gevorderde uitleg", + "Tika": "", + "Tika Server URL required.": "", "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Tip: Werk meerdere variabele slots achtereenvolgens bij door op de tab-toets te drukken in de chat input na elke vervanging.", "Title": "Titel", "Title (e.g. Tell me a fun fact)": "Titel (bv. Vertel me een leuke gebeurtenis)", diff --git a/src/lib/i18n/locales/pa-IN/translation.json b/src/lib/i18n/locales/pa-IN/translation.json index 08204a7d7..76cbb93f1 100644 --- a/src/lib/i18n/locales/pa-IN/translation.json +++ b/src/lib/i18n/locales/pa-IN/translation.json @@ -126,6 +126,7 @@ "Connections": "ਕਨੈਕਸ਼ਨ", "Contact Admin for WebUI Access": "", "Content": "ਸਮੱਗਰੀ", + "Content Extraction": "", "Context Length": "ਸੰਦਰਭ ਲੰਬਾਈ", "Continue Response": "ਜਵਾਬ ਜਾਰੀ ਰੱਖੋ", "Continue with {{provider}}": "", @@ -212,6 +213,7 @@ "Enable Community Sharing": "ਕਮਿਊਨਿਟੀ ਸ਼ੇਅਰਿੰਗ ਨੂੰ ਸਮਰੱਥ ਕਰੋ", "Enable New Sign Ups": "ਨਵੇਂ ਸਾਈਨ ਅਪ ਯੋਗ ਕਰੋ", "Enable Web Search": "ਵੈੱਬ ਖੋਜ ਨੂੰ ਸਮਰੱਥ ਕਰੋ", + "Engine": "", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "ਸੁਨਿਸ਼ਚਿਤ ਕਰੋ ਕਿ ਤੁਹਾਡੀ CSV ਫਾਈਲ ਵਿੱਚ ਇਸ ਕ੍ਰਮ ਵਿੱਚ 4 ਕਾਲਮ ਹਨ: ਨਾਮ, ਈਮੇਲ, ਪਾਸਵਰਡ, ਭੂਮਿਕਾ।", "Enter {{role}} message here": "{{role}} ਸੁਨੇਹਾ ਇੱਥੇ ਦਰਜ ਕਰੋ", "Enter a detail about yourself for your LLMs to recall": "ਤੁਹਾਡੇ LLMs ਨੂੰ ਸੁਨੇਹਾ ਕਰਨ ਲਈ ਸੁਨੇਹਾ ਇੱਥੇ ਦਰਜ ਕਰੋ", @@ -233,6 +235,7 @@ "Enter Serpstack API Key": "Serpstack API ਕੁੰਜੀ ਦਾਖਲ ਕਰੋ", "Enter stop sequence": "ਰੋਕਣ ਦਾ ਕ੍ਰਮ ਦਰਜ ਕਰੋ", "Enter Tavily API Key": "", + "Enter Tika Server URL": "", "Enter Top K": "ਸਿਖਰ K ਦਰਜ ਕਰੋ", "Enter URL (e.g. http://127.0.0.1:7860/)": "URL ਦਰਜ ਕਰੋ (ਉਦਾਹਰਣ ਲਈ http://127.0.0.1:7860/)", "Enter URL (e.g. http://localhost:11434)": "URL ਦਰਜ ਕਰੋ (ਉਦਾਹਰਣ ਲਈ http://localhost:11434)", @@ -574,6 +577,8 @@ "This setting does not sync across browsers or devices.": "ਇਹ ਸੈਟਿੰਗ ਬ੍ਰਾਊਜ਼ਰ ਜਾਂ ਡਿਵਾਈਸਾਂ ਵਿੱਚ ਸਿੰਕ ਨਹੀਂ ਹੁੰਦੀ।", "This will delete": "", "Thorough explanation": "ਵਿਸਥਾਰ ਨਾਲ ਵਿਆਖਿਆ", + "Tika": "", + "Tika Server URL required.": "", "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "ਸਲਾਹ: ਹਰ ਬਦਲਾਅ ਦੇ ਬਾਅਦ ਗੱਲਬਾਤ ਇਨਪੁਟ ਵਿੱਚ ਟੈਬ ਕੀ ਦਬਾ ਕੇ ਲਗਾਤਾਰ ਕਈ ਵੈਰੀਏਬਲ ਸਲਾਟਾਂ ਨੂੰ ਅੱਪਡੇਟ ਕਰੋ।", "Title": "ਸਿਰਲੇਖ", "Title (e.g. Tell me a fun fact)": "ਸਿਰਲੇਖ (ਉਦਾਹਰਣ ਲਈ ਮੈਨੂੰ ਇੱਕ ਮਜ਼ੇਦਾਰ ਤੱਥ ਦੱਸੋ)", diff --git a/src/lib/i18n/locales/pl-PL/translation.json b/src/lib/i18n/locales/pl-PL/translation.json index 14dc5de75..3d5047b8d 100644 --- a/src/lib/i18n/locales/pl-PL/translation.json +++ b/src/lib/i18n/locales/pl-PL/translation.json @@ -126,6 +126,7 @@ "Connections": "Połączenia", "Contact Admin for WebUI Access": "", "Content": "Zawartość", + "Content Extraction": "", "Context Length": "Długość kontekstu", "Continue Response": "Kontynuuj odpowiedź", "Continue with {{provider}}": "", @@ -212,6 +213,7 @@ "Enable Community Sharing": "Włączanie udostępniania społecznościowego", "Enable New Sign Ups": "Włącz nowe rejestracje", "Enable Web Search": "Włączanie wyszukiwania w Internecie", + "Engine": "", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Upewnij się, że twój plik CSV zawiera 4 kolumny w następującym porządku: Nazwa, Email, Hasło, Rola.", "Enter {{role}} message here": "Wprowadź wiadomość {{role}} tutaj", "Enter a detail about yourself for your LLMs to recall": "Wprowadź szczegóły o sobie, aby LLMs mogli pamiętać", @@ -233,6 +235,7 @@ "Enter Serpstack API Key": "Wprowadź klucz API Serpstack", "Enter stop sequence": "Wprowadź sekwencję zatrzymania", "Enter Tavily API Key": "", + "Enter Tika Server URL": "", "Enter Top K": "Wprowadź Top K", "Enter URL (e.g. http://127.0.0.1:7860/)": "Wprowadź adres URL (np. http://127.0.0.1:7860/)", "Enter URL (e.g. http://localhost:11434)": "Wprowadź adres URL (np. http://localhost:11434/)", @@ -576,6 +579,8 @@ "This setting does not sync across browsers or devices.": "To ustawienie nie synchronizuje się między przeglądarkami ani urządzeniami.", "This will delete": "", "Thorough explanation": "Dokładne wyjaśnienie", + "Tika": "", + "Tika Server URL required.": "", "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Porada: Aktualizuj wiele zmiennych kolejno, naciskając klawisz tabulatora w polu wprowadzania czatu po każdej zmianie.", "Title": "Tytuł", "Title (e.g. Tell me a fun fact)": "Tytuł (np. Powiedz mi jakiś zabawny fakt)", diff --git a/src/lib/i18n/locales/pt-BR/translation.json b/src/lib/i18n/locales/pt-BR/translation.json index 8760a1bee..285fee2d2 100644 --- a/src/lib/i18n/locales/pt-BR/translation.json +++ b/src/lib/i18n/locales/pt-BR/translation.json @@ -126,6 +126,7 @@ "Connections": "Conexões", "Contact Admin for WebUI Access": "", "Content": "Conteúdo", + "Content Extraction": "", "Context Length": "Comprimento do Contexto", "Continue Response": "Continuar resposta", "Continue with {{provider}}": "", @@ -212,6 +213,7 @@ "Enable Community Sharing": "Habilitar o compartilhamento da comunidade", "Enable New Sign Ups": "Ativar Novas Inscrições", "Enable Web Search": "Habilitar a Pesquisa na Web", + "Engine": "", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Garanta que seu arquivo CSV inclua 4 colunas nesta ordem: Nome, E-mail, Senha, Função.", "Enter {{role}} message here": "Digite a mensagem de {{role}} aqui", "Enter a detail about yourself for your LLMs to recall": "Digite um detalhe sobre você para que seus LLMs possam lembrar", @@ -233,6 +235,7 @@ "Enter Serpstack API Key": "Digite a chave da API Serpstack", "Enter stop sequence": "Digite a sequência de parada", "Enter Tavily API Key": "", + "Enter Tika Server URL": "", "Enter Top K": "Digite o Top K", "Enter URL (e.g. http://127.0.0.1:7860/)": "Digite a URL (por exemplo, http://127.0.0.1:7860/)", "Enter URL (e.g. http://localhost:11434)": "Digite a URL (por exemplo, http://localhost:11434)", @@ -575,6 +578,8 @@ "This setting does not sync across browsers or devices.": "Esta configuração não sincroniza entre navegadores ou dispositivos.", "This will delete": "", "Thorough explanation": "Explicação Completa", + "Tika": "", + "Tika Server URL required.": "", "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Dica: Atualize vários slots de variáveis consecutivamente pressionando a tecla Tab na entrada de bate-papo após cada substituição.", "Title": "Título", "Title (e.g. Tell me a fun fact)": "Título (ex.: Dê-me um fatídico fatídico)", diff --git a/src/lib/i18n/locales/pt-PT/translation.json b/src/lib/i18n/locales/pt-PT/translation.json index 8ca4d5e1a..13a8e9255 100644 --- a/src/lib/i18n/locales/pt-PT/translation.json +++ b/src/lib/i18n/locales/pt-PT/translation.json @@ -126,6 +126,7 @@ "Connections": "Conexões", "Contact Admin for WebUI Access": "Contatar Admin para acesso ao WebUI", "Content": "Conteúdo", + "Content Extraction": "", "Context Length": "Comprimento do Contexto", "Continue Response": "Continuar resposta", "Continue with {{provider}}": "", @@ -212,6 +213,7 @@ "Enable Community Sharing": "Active a Partilha da Comunidade", "Enable New Sign Ups": "Ativar Novas Inscrições", "Enable Web Search": "Ativar pesquisa na Web", + "Engine": "", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Confirme que o seu ficheiro CSV inclui 4 colunas nesta ordem: Nome, E-mail, Senha, Função.", "Enter {{role}} message here": "Escreva a mensagem de {{role}} aqui", "Enter a detail about yourself for your LLMs to recall": "Escreva um detalhe sobre você para que os seus LLMs possam lembrar-se", @@ -233,6 +235,7 @@ "Enter Serpstack API Key": "Escreva a chave da API Serpstack", "Enter stop sequence": "Escreva a sequência de paragem", "Enter Tavily API Key": "", + "Enter Tika Server URL": "", "Enter Top K": "Escreva o Top K", "Enter URL (e.g. http://127.0.0.1:7860/)": "Escreva o URL (por exemplo, http://127.0.0.1:7860/)", "Enter URL (e.g. http://localhost:11434)": "Escreva o URL (por exemplo, http://localhost:11434)", @@ -575,6 +578,8 @@ "This setting does not sync across browsers or devices.": "Esta configuração não sincroniza entre navegadores ou dispositivos.", "This will delete": "", "Thorough explanation": "Explicação Minuciosa", + "Tika": "", + "Tika Server URL required.": "", "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Dica: Atualize vários slots de variáveis consecutivamente pressionando a tecla Tab na entrada da conversa após cada substituição.", "Title": "Título", "Title (e.g. Tell me a fun fact)": "Título (ex.: Diz-me um facto divertido)", diff --git a/src/lib/i18n/locales/ru-RU/translation.json b/src/lib/i18n/locales/ru-RU/translation.json index 5fafca9b9..eca3b47a7 100644 --- a/src/lib/i18n/locales/ru-RU/translation.json +++ b/src/lib/i18n/locales/ru-RU/translation.json @@ -126,6 +126,7 @@ "Connections": "Соединение", "Contact Admin for WebUI Access": "", "Content": "Содержание", + "Content Extraction": "", "Context Length": "Длина контексту", "Continue Response": "Продолжить ответ", "Continue with {{provider}}": "", @@ -212,6 +213,7 @@ "Enable Community Sharing": "Включить общий доступ к сообществу", "Enable New Sign Ups": "Разрешить новые регистрации", "Enable Web Search": "Включить поиск в Интернете", + "Engine": "", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Убедитесь, что ваш CSV-файл включает в себя 4 столбца в следующем порядке: Имя, Электронная почта, Пароль, Роль.", "Enter {{role}} message here": "Введите сообщение {{role}} здесь", "Enter a detail about yourself for your LLMs to recall": "Введите детали о себе, чтобы LLMs могли запомнить", @@ -233,6 +235,7 @@ "Enter Serpstack API Key": "Введите ключ API Serpstack", "Enter stop sequence": "Введите последовательность остановки", "Enter Tavily API Key": "", + "Enter Tika Server URL": "", "Enter Top K": "Введите Top K", "Enter URL (e.g. http://127.0.0.1:7860/)": "Введите URL-адрес (например, http://127.0.0.1:7860/)", "Enter URL (e.g. http://localhost:11434)": "Введите URL-адрес (например, http://localhost:11434)", @@ -576,6 +579,8 @@ "This setting does not sync across browsers or devices.": "Эта настройка не синхронизируется между браузерами или устройствами.", "This will delete": "", "Thorough explanation": "Повнимательнее", + "Tika": "", + "Tika Server URL required.": "", "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Совет: Обновляйте несколько переменных подряд, нажимая клавишу Tab в поле ввода чата после каждой замены.", "Title": "Заголовок", "Title (e.g. Tell me a fun fact)": "Заголовок (например. Расскажи мне интересную факт)", diff --git a/src/lib/i18n/locales/sr-RS/translation.json b/src/lib/i18n/locales/sr-RS/translation.json index 87addf040..afb64601f 100644 --- a/src/lib/i18n/locales/sr-RS/translation.json +++ b/src/lib/i18n/locales/sr-RS/translation.json @@ -126,6 +126,7 @@ "Connections": "Везе", "Contact Admin for WebUI Access": "", "Content": "Садржај", + "Content Extraction": "", "Context Length": "Дужина контекста", "Continue Response": "Настави одговор", "Continue with {{provider}}": "", @@ -212,6 +213,7 @@ "Enable Community Sharing": "Омогући дељење заједнице", "Enable New Sign Ups": "Омогући нове пријаве", "Enable Web Search": "Омогући Wеб претрагу", + "Engine": "", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Уверите се да ваша CSV датотека укључује 4 колоне у овом редоследу: Име, Е-пошта, Лозинка, Улога.", "Enter {{role}} message here": "Унесите {{role}} поруку овде", "Enter a detail about yourself for your LLMs to recall": "Унесите детаље за себе да ће LLMs преузимати", @@ -233,6 +235,7 @@ "Enter Serpstack API Key": "Унесите Серпстацк АПИ кључ", "Enter stop sequence": "Унесите секвенцу заустављања", "Enter Tavily API Key": "", + "Enter Tika Server URL": "", "Enter Top K": "Унесите Топ К", "Enter URL (e.g. http://127.0.0.1:7860/)": "Унесите адресу (нпр. http://127.0.0.1:7860/)", "Enter URL (e.g. http://localhost:11434)": "Унесите адресу (нпр. http://localhost:11434)", @@ -575,6 +578,8 @@ "This setting does not sync across browsers or devices.": "Ово подешавање се не усклађује преко прегледача или уређаја.", "This will delete": "", "Thorough explanation": "Детаљно објашњење", + "Tika": "", + "Tika Server URL required.": "", "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Савет: ажурирајте више променљивих слотова узастопно притиском на тастер Таб у уносу ћаскања након сваке замене.", "Title": "Наслов", "Title (e.g. Tell me a fun fact)": "Наслов (нпр. „реци ми занимљивост“)", diff --git a/src/lib/i18n/locales/sv-SE/translation.json b/src/lib/i18n/locales/sv-SE/translation.json index 14aededbd..d05ef1a43 100644 --- a/src/lib/i18n/locales/sv-SE/translation.json +++ b/src/lib/i18n/locales/sv-SE/translation.json @@ -126,6 +126,7 @@ "Connections": "Anslutningar", "Contact Admin for WebUI Access": "Kontakta administratören för att få åtkomst till WebUI", "Content": "Innehåll", + "Content Extraction": "", "Context Length": "Kontextlängd", "Continue Response": "Fortsätt svar", "Continue with {{provider}}": "", @@ -212,6 +213,7 @@ "Enable Community Sharing": "Aktivera community-delning", "Enable New Sign Ups": "Aktivera nya registreringar", "Enable Web Search": "Aktivera webbsökning", + "Engine": "", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Se till att din CSV-fil innehåller fyra kolumner i denna ordning: Name, Email, Password, Role.", "Enter {{role}} message here": "Skriv {{role}} meddelande här", "Enter a detail about yourself for your LLMs to recall": "Skriv en detalj om dig själv för att dina LLMs ska komma ihåg", @@ -233,6 +235,7 @@ "Enter Serpstack API Key": "Ange Serpstack API-nyckel", "Enter stop sequence": "Ange stoppsekvens", "Enter Tavily API Key": "", + "Enter Tika Server URL": "", "Enter Top K": "Ange Top K", "Enter URL (e.g. http://127.0.0.1:7860/)": "Ange URL (t.ex. http://127.0.0.1:7860/)", "Enter URL (e.g. http://localhost:11434)": "Ange URL (t.ex. http://localhost:11434)", @@ -574,6 +577,8 @@ "This setting does not sync across browsers or devices.": "Denna inställning synkroniseras inte mellan webbläsare eller enheter.", "This will delete": "", "Thorough explanation": "Djupare förklaring", + "Tika": "", + "Tika Server URL required.": "", "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Tips: Uppdatera fler variabler genom att trycka på tabb-tangenten i chattinmatningen efter varje ersättning.", "Title": "Titel", "Title (e.g. Tell me a fun fact)": "Titel (t.ex. Berätta en kuriosa)", diff --git a/src/lib/i18n/locales/tk-TW/translation.json b/src/lib/i18n/locales/tk-TW/translation.json index 78525e22f..ec5e205cb 100644 --- a/src/lib/i18n/locales/tk-TW/translation.json +++ b/src/lib/i18n/locales/tk-TW/translation.json @@ -126,6 +126,7 @@ "Connections": "", "Contact Admin for WebUI Access": "", "Content": "", + "Content Extraction": "", "Context Length": "", "Continue Response": "", "Continue with {{provider}}": "", @@ -212,6 +213,7 @@ "Enable Community Sharing": "", "Enable New Sign Ups": "", "Enable Web Search": "", + "Engine": "", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "", "Enter {{role}} message here": "", "Enter a detail about yourself for your LLMs to recall": "", @@ -233,6 +235,7 @@ "Enter Serpstack API Key": "", "Enter stop sequence": "", "Enter Tavily API Key": "", + "Enter Tika Server URL": "", "Enter Top K": "", "Enter URL (e.g. http://127.0.0.1:7860/)": "", "Enter URL (e.g. http://localhost:11434)": "", @@ -574,6 +577,8 @@ "This setting does not sync across browsers or devices.": "", "This will delete": "", "Thorough explanation": "", + "Tika": "", + "Tika Server URL required.": "", "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "", "Title": "", "Title (e.g. Tell me a fun fact)": "", diff --git a/src/lib/i18n/locales/tr-TR/translation.json b/src/lib/i18n/locales/tr-TR/translation.json index 981cfb829..d6859ce49 100644 --- a/src/lib/i18n/locales/tr-TR/translation.json +++ b/src/lib/i18n/locales/tr-TR/translation.json @@ -126,6 +126,7 @@ "Connections": "Bağlantılar", "Contact Admin for WebUI Access": "WebUI Erişimi için Yöneticiyle İletişime Geçin", "Content": "İçerik", + "Content Extraction": "", "Context Length": "Bağlam Uzunluğu", "Continue Response": "Yanıta Devam Et", "Continue with {{provider}}": "{{provider}} ile devam et", @@ -212,6 +213,7 @@ "Enable Community Sharing": "Topluluk Paylaşımını Etkinleştir", "Enable New Sign Ups": "Yeni Kayıtları Etkinleştir", "Enable Web Search": "Web Aramasını Etkinleştir", + "Engine": "", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "CSV dosyanızın şu sırayla 4 sütun içerdiğinden emin olun: İsim, E-posta, Şifre, Rol.", "Enter {{role}} message here": "Buraya {{role}} mesajını girin", "Enter a detail about yourself for your LLMs to recall": "LLM'lerinizin hatırlaması için kendiniz hakkında bir bilgi girin", @@ -233,6 +235,7 @@ "Enter Serpstack API Key": "Serpstack API Anahtarını Girin", "Enter stop sequence": "Durdurma dizisini girin", "Enter Tavily API Key": "Tavily API Anahtarını Girin", + "Enter Tika Server URL": "", "Enter Top K": "Top K'yı girin", "Enter URL (e.g. http://127.0.0.1:7860/)": "URL'yi Girin (örn. http://127.0.0.1:7860/)", "Enter URL (e.g. http://localhost:11434)": "URL'yi Girin (e.g. http://localhost:11434)", @@ -574,6 +577,8 @@ "This setting does not sync across browsers or devices.": "Bu ayar tarayıcılar veya cihazlar arasında senkronize edilmez.", "This will delete": "Bu silinecek", "Thorough explanation": "Kapsamlı açıklama", + "Tika": "", + "Tika Server URL required.": "", "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "İpucu: Her değiştirmeden sonra sohbet girişinde tab tuşuna basarak birden fazla değişken yuvasını art arda güncelleyin.", "Title": "Başlık", "Title (e.g. Tell me a fun fact)": "Başlık (e.g. Bana ilginç bir bilgi ver)", diff --git a/src/lib/i18n/locales/uk-UA/translation.json b/src/lib/i18n/locales/uk-UA/translation.json index 5443d21a8..123ad9bf9 100644 --- a/src/lib/i18n/locales/uk-UA/translation.json +++ b/src/lib/i18n/locales/uk-UA/translation.json @@ -126,6 +126,7 @@ "Connections": "З'єднання", "Contact Admin for WebUI Access": "Зверніться до адміна для отримання доступу до WebUI", "Content": "Зміст", + "Content Extraction": "", "Context Length": "Довжина контексту", "Continue Response": "Продовжити відповідь", "Continue with {{provider}}": "Продовжити з {{provider}}", @@ -212,6 +213,7 @@ "Enable Community Sharing": "Увімкнути спільний доступ", "Enable New Sign Ups": "Дозволити нові реєстрації", "Enable Web Search": "Увімкнути веб-пошук", + "Engine": "", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Переконайтеся, що ваш CSV-файл містить 4 колонки в такому порядку: Ім'я, Email, Пароль, Роль.", "Enter {{role}} message here": "Введіть повідомлення {{role}} тут", "Enter a detail about yourself for your LLMs to recall": "Введіть відомості про себе для запам'ятовування вашими LLM.", @@ -233,6 +235,7 @@ "Enter Serpstack API Key": "Введіть ключ API Serpstack", "Enter stop sequence": "Введіть символ зупинки", "Enter Tavily API Key": "Введіть ключ API Tavily", + "Enter Tika Server URL": "", "Enter Top K": "Введіть Top K", "Enter URL (e.g. http://127.0.0.1:7860/)": "Введіть URL-адресу (напр., http://127.0.0.1:7860/)", "Enter URL (e.g. http://localhost:11434)": "Введіть URL-адресу (напр., http://localhost:11434)", @@ -576,6 +579,8 @@ "This setting does not sync across browsers or devices.": "Це налаштування не синхронізується між браузерами або пристроями.", "This will delete": "Це призведе до видалення", "Thorough explanation": "Детальне пояснення", + "Tika": "", + "Tika Server URL required.": "", "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Порада: Оновіть кілька слотів змінних послідовно, натискаючи клавішу табуляції у вікні чату після кожної заміни.", "Title": "Заголовок", "Title (e.g. Tell me a fun fact)": "Заголовок (напр., Розкажіть мені цікавий факт)", diff --git a/src/lib/i18n/locales/vi-VN/translation.json b/src/lib/i18n/locales/vi-VN/translation.json index 8f433a034..b1f3de93c 100644 --- a/src/lib/i18n/locales/vi-VN/translation.json +++ b/src/lib/i18n/locales/vi-VN/translation.json @@ -126,6 +126,7 @@ "Connections": "Kết nối", "Contact Admin for WebUI Access": "Liên hệ với Quản trị viên để được cấp quyền truy cập", "Content": "Nội dung", + "Content Extraction": "", "Context Length": "Độ dài ngữ cảnh (Context Length)", "Continue Response": "Tiếp tục trả lời", "Continue with {{provider}}": "", @@ -212,6 +213,7 @@ "Enable Community Sharing": "Kích hoạt Chia sẻ Cộng đồng", "Enable New Sign Ups": "Cho phép đăng ký mới", "Enable Web Search": "Kích hoạt tìm kiếm Web", + "Engine": "", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Đảm bảo tệp CSV của bạn bao gồm 4 cột theo thứ tự sau: Name, Email, Password, Role.", "Enter {{role}} message here": "Nhập yêu cầu của {{role}} ở đây", "Enter a detail about yourself for your LLMs to recall": "Nhập chi tiết về bản thân của bạn để LLMs có thể nhớ", @@ -233,6 +235,7 @@ "Enter Serpstack API Key": "Nhập Serpstack API Key", "Enter stop sequence": "Nhập stop sequence", "Enter Tavily API Key": "Nhập Tavily API Key", + "Enter Tika Server URL": "", "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 URL (e.g. http://localhost:11434)": "Nhập URL (vd: http://localhost:11434)", @@ -573,6 +576,8 @@ "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ị.", "This will delete": "Chat này sẽ bị xóa", "Thorough explanation": "Giải thích kỹ lưỡng", + "Tika": "", + "Tika Server URL required.": "", "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 (e.g. Tell me a fun fact)": "Tiêu đề (ví dụ: Hãy kể cho tôi một sự thật thú vị về...)", diff --git a/src/lib/i18n/locales/zh-CN/translation.json b/src/lib/i18n/locales/zh-CN/translation.json index 0cf513ac0..5662bcb6e 100644 --- a/src/lib/i18n/locales/zh-CN/translation.json +++ b/src/lib/i18n/locales/zh-CN/translation.json @@ -126,6 +126,7 @@ "Connections": "外部连接", "Contact Admin for WebUI Access": "请联系管理员以获取访问权限", "Content": "内容", + "Content Extraction": "", "Context Length": "上下文长度", "Continue Response": "继续生成", "Continue with {{provider}}": "使用 {{provider}} 继续", @@ -212,6 +213,7 @@ "Enable Community Sharing": "启用分享至社区", "Enable New Sign Ups": "允许新用户注册", "Enable Web Search": "启用网络搜索", + "Engine": "", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "确保您的 CSV 文件按以下顺序包含 4 列: 姓名、电子邮箱、密码、角色。", "Enter {{role}} message here": "在此处输入 {{role}} 信息", "Enter a detail about yourself for your LLMs to recall": "输入一个关于你自己的详细信息,方便你的大语言模型记住这些内容", @@ -233,6 +235,7 @@ "Enter Serpstack API Key": "输入 Serpstack API 密钥", "Enter stop sequence": "输入停止序列 (Stop Sequence)", "Enter Tavily API Key": "输入 Tavily API 密钥", + "Enter Tika Server URL": "", "Enter Top K": "输入 Top K", "Enter URL (e.g. http://127.0.0.1:7860/)": "输入地址 (例如:http://127.0.0.1:7860/)", "Enter URL (e.g. http://localhost:11434)": "输入地址 (例如:http://localhost:11434)", @@ -573,6 +576,8 @@ "This setting does not sync across browsers or devices.": "此设置不会在浏览器或设备之间同步。", "This will delete": "这将删除", "Thorough explanation": "解释较为详细", + "Tika": "", + "Tika Server URL required.": "", "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "提示:在每次替换后,在对话输入中按 Tab 键可以连续更新多个变量。", "Title": "标题", "Title (e.g. Tell me a fun fact)": "标题(例如 给我讲一个有趣的事实)", diff --git a/src/lib/i18n/locales/zh-TW/translation.json b/src/lib/i18n/locales/zh-TW/translation.json index 0f555eb65..6e24104d8 100644 --- a/src/lib/i18n/locales/zh-TW/translation.json +++ b/src/lib/i18n/locales/zh-TW/translation.json @@ -126,6 +126,7 @@ "Connections": "連線", "Contact Admin for WebUI Access": "聯絡管理員以取得 WebUI 存取權", "Content": "內容", + "Content Extraction": "", "Context Length": "上下文長度", "Continue Response": "繼續回答", "Continue with {{provider}}": "", @@ -212,6 +213,7 @@ "Enable Community Sharing": "啟用社群分享", "Enable New Sign Ups": "允許註冊新帳號", "Enable Web Search": "啟用網頁搜尋", + "Engine": "", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "請確保您的 CSV 檔案包含這四個欄位,並按照此順序:名稱、電子郵件、密碼、角色。", "Enter {{role}} message here": "在這裡輸入 {{role}} 訊息", "Enter a detail about yourself for your LLMs to recall": "輸入 LLM 記憶的詳細內容", @@ -233,6 +235,7 @@ "Enter Serpstack API Key": "輸入 Serpstack API 金鑰", "Enter stop sequence": "輸入停止序列", "Enter Tavily API Key": "輸入 Tavily API 金鑰", + "Enter Tika Server URL": "", "Enter Top K": "輸入 Top K", "Enter URL (e.g. http://127.0.0.1:7860/)": "輸入 URL(例如 http://127.0.0.1:7860/)", "Enter URL (e.g. http://localhost:11434)": "輸入 URL(例如 http://localhost:11434)", @@ -573,6 +576,8 @@ "This setting does not sync across browsers or devices.": "此設定不會在瀏覽器或裝置間同步。", "This will delete": "", "Thorough explanation": "詳細說明", + "Tika": "", + "Tika Server URL required.": "", "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "提示:透過在每次替換後在聊天輸入框中按 Tab 鍵連續更新多個變數。", "Title": "標題", "Title (e.g. Tell me a fun fact)": "標題(例如:告訴我一個有趣的事)", From c7a9b5ccfab963803916e63df68058a3938d511b Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Mon, 1 Jul 2024 19:33:58 -0700 Subject: [PATCH 058/181] refac: chat completion middleware --- backend/apps/rag/utils.py | 14 +- backend/main.py | 501 ++++++++++++++++------------ src/lib/components/chat/Chat.svelte | 12 +- 3 files changed, 304 insertions(+), 223 deletions(-) diff --git a/backend/apps/rag/utils.py b/backend/apps/rag/utils.py index 3a3dad4a2..fde89b069 100644 --- a/backend/apps/rag/utils.py +++ b/backend/apps/rag/utils.py @@ -294,14 +294,16 @@ def get_rag_context( extracted_collections.extend(collection_names) - context_string = "" - + contexts = [] citations = [] + for context in relevant_contexts: try: if "documents" in context: - context_string += "\n\n".join( - [text for text in context["documents"][0] if text is not None] + contexts.append( + "\n\n".join( + [text for text in context["documents"][0] if text is not None] + ) ) if "metadatas" in context: @@ -315,9 +317,7 @@ def get_rag_context( except Exception as e: log.exception(e) - context_string = context_string.strip() - - return context_string, citations + return contexts, citations def get_model_path(model: str, update_model: bool = False): diff --git a/backend/main.py b/backend/main.py index e1172f026..00e08676b 100644 --- a/backend/main.py +++ b/backend/main.py @@ -213,7 +213,7 @@ origins = ["*"] async def get_function_call_response( - messages, files, tool_id, template, task_model_id, user + messages, files, tool_id, template, task_model_id, user, model ): tool = Tools.get_tool_by_id(tool_id) tools_specs = json.dumps(tool.specs, indent=2) @@ -373,233 +373,308 @@ async def get_function_call_response( return None, None, False +def get_task_model_id(default_model_id): + # Set the task model + task_model_id = default_model_id + # Check if the user has a custom task model and use that model + if app.state.MODELS[task_model_id]["owned_by"] == "ollama": + if ( + app.state.config.TASK_MODEL + and app.state.config.TASK_MODEL in app.state.MODELS + ): + task_model_id = app.state.config.TASK_MODEL + else: + if ( + app.state.config.TASK_MODEL_EXTERNAL + and app.state.config.TASK_MODEL_EXTERNAL in app.state.MODELS + ): + task_model_id = app.state.config.TASK_MODEL_EXTERNAL + + return task_model_id + + +def get_filter_function_ids(model): + def get_priority(function_id): + function = Functions.get_function_by_id(function_id) + if function is not None and hasattr(function, "valves"): + return (function.valves if function.valves else {}).get("priority", 0) + return 0 + + filter_ids = [function.id for function in Functions.get_global_filter_functions()] + if "info" in model and "meta" in model["info"]: + filter_ids.extend(model["info"]["meta"].get("filterIds", [])) + filter_ids = list(set(filter_ids)) + + enabled_filter_ids = [ + function.id + for function in Functions.get_functions_by_type("filter", active_only=True) + ] + + filter_ids = [ + filter_id for filter_id in filter_ids if filter_id in enabled_filter_ids + ] + + filter_ids.sort(key=get_priority) + return filter_ids + + +async def chat_completion_functions_handler(body, model, user): + skip_files = None + + filter_ids = get_filter_function_ids(model) + for filter_id in filter_ids: + filter = Functions.get_function_by_id(filter_id) + if filter: + if filter_id in webui_app.state.FUNCTIONS: + function_module = webui_app.state.FUNCTIONS[filter_id] + else: + function_module, function_type, frontmatter = ( + load_function_module_by_id(filter_id) + ) + webui_app.state.FUNCTIONS[filter_id] = function_module + + # Check if the function has a file_handler variable + if hasattr(function_module, "file_handler"): + skip_files = function_module.file_handler + + if hasattr(function_module, "valves") and hasattr( + function_module, "Valves" + ): + valves = Functions.get_function_valves_by_id(filter_id) + function_module.valves = function_module.Valves( + **(valves if valves else {}) + ) + + try: + if hasattr(function_module, "inlet"): + inlet = function_module.inlet + + # Get the signature of the function + sig = inspect.signature(inlet) + params = {"body": body} + + if "__user__" in sig.parameters: + __user__ = { + "id": user.id, + "email": user.email, + "name": user.name, + "role": user.role, + } + + try: + if hasattr(function_module, "UserValves"): + __user__["valves"] = function_module.UserValves( + **Functions.get_user_valves_by_id_and_user_id( + filter_id, user.id + ) + ) + except Exception as e: + print(e) + + params = {**params, "__user__": __user__} + + if "__id__" in sig.parameters: + params = { + **params, + "__id__": filter_id, + } + + if "__model__" in sig.parameters: + params = { + **params, + "__model__": model, + } + + if inspect.iscoroutinefunction(inlet): + body = await inlet(**params) + else: + body = inlet(**params) + + except Exception as e: + print(f"Error: {e}") + raise e + + if skip_files: + if "files" in body: + del body["files"] + + return body, {} + + +async def chat_completion_tools_handler(body, model, user): + skip_files = None + + contexts = [] + citations = None + + task_model_id = get_task_model_id(body["model"]) + + # If tool_ids field is present, call the functions + if "tool_ids" in body: + print(body["tool_ids"]) + for tool_id in body["tool_ids"]: + print(tool_id) + try: + response, citation, file_handler = await get_function_call_response( + messages=body["messages"], + files=body.get("files", []), + tool_id=tool_id, + template=app.state.config.TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE, + task_model_id=task_model_id, + user=user, + model=model, + ) + + print(file_handler) + if isinstance(response, str): + contexts.append(response) + + if citation: + if citations is None: + citations = [citation] + else: + citations.append(citation) + + if file_handler: + skip_files = True + + except Exception as e: + print(f"Error: {e}") + del body["tool_ids"] + print(f"tool_contexts: {contexts}") + + if skip_files: + if "files" in body: + del body["files"] + + return body, { + **({"contexts": contexts} if contexts is not None else {}), + **({"citations": citations} if citations is not None else {}), + } + + +async def chat_completion_files_handler(body): + contexts = [] + citations = None + + if "files" in body: + files = body["files"] + del body["files"] + + contexts, citations = get_rag_context( + files=files, + messages=body["messages"], + embedding_function=rag_app.state.EMBEDDING_FUNCTION, + k=rag_app.state.config.TOP_K, + reranking_function=rag_app.state.sentence_transformer_rf, + r=rag_app.state.config.RELEVANCE_THRESHOLD, + hybrid_search=rag_app.state.config.ENABLE_RAG_HYBRID_SEARCH, + ) + + log.debug(f"rag_contexts: {contexts}, citations: {citations}") + + return body, { + **({"contexts": contexts} if contexts is not None else {}), + **({"citations": citations} if citations is not None else {}), + } + + +async def get_body_and_model_and_user(request): + # Read the original request body + body = await request.body() + body_str = body.decode("utf-8") + body = json.loads(body_str) if body_str else {} + + model_id = body["model"] + if model_id not in app.state.MODELS: + raise "Model not found" + model = app.state.MODELS[model_id] + + user = get_current_user( + request, + get_http_authorization_cred(request.headers.get("Authorization")), + ) + + return body, model, user + + class ChatCompletionMiddleware(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next): - data_items = [] - - show_citations = False - citations = [] - if request.method == "POST" and any( endpoint in request.url.path for endpoint in ["/ollama/api/chat", "/chat/completions"] ): log.debug(f"request.url.path: {request.url.path}") - # Read the original request body - body = await request.body() - body_str = body.decode("utf-8") - data = json.loads(body_str) if body_str else {} - - user = get_current_user( - request, - get_http_authorization_cred(request.headers.get("Authorization")), - ) - # Flag to skip RAG completions if file_handler is present in tools/functions - skip_files = False - if data.get("citations"): - show_citations = True - del data["citations"] - - model_id = data["model"] - if model_id not in app.state.MODELS: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Model not found", + try: + body, model, user = await get_body_and_model_and_user(request) + except Exception as e: + return JSONResponse( + status_code=status.HTTP_400_BAD_REQUEST, + content={"detail": str(e)}, ) - model = app.state.MODELS[model_id] - def get_priority(function_id): - function = Functions.get_function_by_id(function_id) - if function is not None and hasattr(function, "valves"): - return (function.valves if function.valves else {}).get( - "priority", 0 - ) - return 0 + # Extract chat_id and message_id from the request body + chat_id = None + if "chat_id" in body: + chat_id = body["chat_id"] + del body["chat_id"] + message_id = None + if "id" in body: + message_id = body["id"] + del body["id"] - filter_ids = [ - function.id for function in Functions.get_global_filter_functions() - ] - if "info" in model and "meta" in model["info"]: - filter_ids.extend(model["info"]["meta"].get("filterIds", [])) - filter_ids = list(set(filter_ids)) + # Initialize data_items to store additional data to be sent to the client + data_items = [] - enabled_filter_ids = [ - function.id - for function in Functions.get_functions_by_type( - "filter", active_only=True + # Initialize context, and citations + contexts = [] + citations = [] + + print(body) + + try: + body, flags = await chat_completion_functions_handler(body, model, user) + except Exception as e: + return JSONResponse( + status_code=status.HTTP_400_BAD_REQUEST, + content={"detail": str(e)}, ) - ] - filter_ids = [ - filter_id for filter_id in filter_ids if filter_id in enabled_filter_ids - ] - filter_ids.sort(key=get_priority) - for filter_id in filter_ids: - filter = Functions.get_function_by_id(filter_id) - if filter: - if filter_id in webui_app.state.FUNCTIONS: - function_module = webui_app.state.FUNCTIONS[filter_id] - else: - function_module, function_type, frontmatter = ( - load_function_module_by_id(filter_id) - ) - webui_app.state.FUNCTIONS[filter_id] = function_module + try: + body, flags = await chat_completion_tools_handler(body, model, user) - # Check if the function has a file_handler variable - if hasattr(function_module, "file_handler"): - skip_files = function_module.file_handler + contexts.extend(flags.get("contexts", [])) + citations.extend(flags.get("citations", [])) + except Exception as e: + print(e) + pass - if hasattr(function_module, "valves") and hasattr( - function_module, "Valves" - ): - valves = Functions.get_function_valves_by_id(filter_id) - function_module.valves = function_module.Valves( - **(valves if valves else {}) - ) + try: + body, flags = await chat_completion_files_handler(body) - try: - if hasattr(function_module, "inlet"): - inlet = function_module.inlet + contexts.extend(flags.get("contexts", [])) + citations.extend(flags.get("citations", [])) + except Exception as e: + print(e) + pass - # Get the signature of the function - sig = inspect.signature(inlet) - params = {"body": data} + # If context is not empty, insert it into the messages + if len(contexts) > 0: + context_string = "/n".join(contexts).strip() + prompt = get_last_user_message(body["messages"]) + body["messages"] = add_or_update_system_message( + rag_template( + rag_app.state.config.RAG_TEMPLATE, context_string, prompt + ), + body["messages"], + ) - if "__user__" in sig.parameters: - __user__ = { - "id": user.id, - "email": user.email, - "name": user.name, - "role": user.role, - } - - try: - if hasattr(function_module, "UserValves"): - __user__["valves"] = function_module.UserValves( - **Functions.get_user_valves_by_id_and_user_id( - filter_id, user.id - ) - ) - except Exception as e: - print(e) - - params = {**params, "__user__": __user__} - - if "__id__" in sig.parameters: - params = { - **params, - "__id__": filter_id, - } - - if "__model__" in sig.parameters: - params = { - **params, - "__model__": model, - } - - if inspect.iscoroutinefunction(inlet): - data = await inlet(**params) - else: - data = inlet(**params) - - except Exception as e: - print(f"Error: {e}") - return JSONResponse( - status_code=status.HTTP_400_BAD_REQUEST, - content={"detail": str(e)}, - ) - - # Set the task model - task_model_id = data["model"] - # Check if the user has a custom task model and use that model - if app.state.MODELS[task_model_id]["owned_by"] == "ollama": - if ( - app.state.config.TASK_MODEL - and app.state.config.TASK_MODEL in app.state.MODELS - ): - task_model_id = app.state.config.TASK_MODEL - else: - if ( - app.state.config.TASK_MODEL_EXTERNAL - and app.state.config.TASK_MODEL_EXTERNAL in app.state.MODELS - ): - task_model_id = app.state.config.TASK_MODEL_EXTERNAL - - prompt = get_last_user_message(data["messages"]) - context = "" - - # If tool_ids field is present, call the functions - if "tool_ids" in data: - print(data["tool_ids"]) - for tool_id in data["tool_ids"]: - print(tool_id) - try: - response, citation, file_handler = ( - await get_function_call_response( - messages=data["messages"], - files=data.get("files", []), - tool_id=tool_id, - template=app.state.config.TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE, - task_model_id=task_model_id, - user=user, - ) - ) - - print(file_handler) - if isinstance(response, str): - context += ("\n" if context != "" else "") + response - - if citation: - citations.append(citation) - show_citations = True - - if file_handler: - skip_files = True - - except Exception as e: - print(f"Error: {e}") - del data["tool_ids"] - - print(f"tool_context: {context}") - - # If files field is present, generate RAG completions - # If skip_files is True, skip the RAG completions - if "files" in data: - if not skip_files: - data = {**data} - rag_context, rag_citations = get_rag_context( - files=data["files"], - messages=data["messages"], - embedding_function=rag_app.state.EMBEDDING_FUNCTION, - k=rag_app.state.config.TOP_K, - reranking_function=rag_app.state.sentence_transformer_rf, - r=rag_app.state.config.RELEVANCE_THRESHOLD, - hybrid_search=rag_app.state.config.ENABLE_RAG_HYBRID_SEARCH, - ) - if rag_context: - context += ("\n" if context != "" else "") + rag_context - - log.debug(f"rag_context: {rag_context}, citations: {citations}") - - if rag_citations: - citations.extend(rag_citations) - - del data["files"] - - if show_citations and len(citations) > 0: + # If there are citations, add them to the data_items + if len(citations) > 0: data_items.append({"citations": citations}) - if context != "": - system_prompt = rag_template( - rag_app.state.config.RAG_TEMPLATE, context, prompt - ) - print(system_prompt) - data["messages"] = add_or_update_system_message( - system_prompt, data["messages"] - ) - - modified_body_bytes = json.dumps(data).encode("utf-8") + modified_body_bytes = json.dumps(body).encode("utf-8") # Replace the request body with the modified one request._body = modified_body_bytes # Set custom header to ensure content-length matches new body length @@ -721,9 +796,6 @@ def filter_pipeline(payload, user): pass if "pipeline" not in app.state.MODELS[model_id]: - if "chat_id" in payload: - del payload["chat_id"] - if "title" in payload: del payload["title"] @@ -1225,6 +1297,9 @@ async def generate_title(form_data: dict, user=Depends(get_verified_user)): content={"detail": e.args[1]}, ) + if "chat_id" in payload: + del payload["chat_id"] + return await generate_chat_completions(form_data=payload, user=user) @@ -1285,6 +1360,9 @@ async def generate_search_query(form_data: dict, user=Depends(get_verified_user) content={"detail": e.args[1]}, ) + if "chat_id" in payload: + del payload["chat_id"] + return await generate_chat_completions(form_data=payload, user=user) @@ -1349,6 +1427,9 @@ Message: """{{prompt}}""" content={"detail": e.args[1]}, ) + if "chat_id" in payload: + del payload["chat_id"] + return await generate_chat_completions(form_data=payload, user=user) diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte index 1f8fd9827..056432d42 100644 --- a/src/lib/components/chat/Chat.svelte +++ b/src/lib/components/chat/Chat.svelte @@ -665,6 +665,7 @@ await tick(); const [res, controller] = await generateChatCompletion(localStorage.token, { + stream: true, model: model.id, messages: messagesBody, options: { @@ -682,8 +683,8 @@ keep_alive: $settings.keepAlive ?? undefined, tool_ids: selectedToolIds.length > 0 ? selectedToolIds : undefined, files: files.length > 0 ? files : undefined, - citations: files.length > 0 ? true : undefined, - chat_id: $chatId + chat_id: $chatId, + id: responseMessageId }); if (res && res.ok) { @@ -912,8 +913,8 @@ const [res, controller] = await generateOpenAIChatCompletion( localStorage.token, { - model: model.id, stream: true, + model: model.id, stream_options: model.info?.meta?.capabilities?.usage ?? false ? { @@ -983,9 +984,8 @@ max_tokens: $settings?.params?.max_tokens ?? undefined, tool_ids: selectedToolIds.length > 0 ? selectedToolIds : undefined, files: files.length > 0 ? files : undefined, - citations: files.length > 0 ? true : undefined, - - chat_id: $chatId + chat_id: $chatId, + id: responseMessageId }, `${WEBUI_BASE_URL}/api` ); From e5895af7a06e389bc8daacd0b0c5b45715fd454c Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Mon, 1 Jul 2024 19:37:54 -0700 Subject: [PATCH 059/181] refac --- backend/main.py | 128 ++++++++++++++++++++++++------------------------ 1 file changed, 64 insertions(+), 64 deletions(-) diff --git a/backend/main.py b/backend/main.py index 00e08676b..09d80f012 100644 --- a/backend/main.py +++ b/backend/main.py @@ -212,6 +212,70 @@ origins = ["*"] ################################## +async def get_body_and_model_and_user(request): + # Read the original request body + body = await request.body() + body_str = body.decode("utf-8") + body = json.loads(body_str) if body_str else {} + + model_id = body["model"] + if model_id not in app.state.MODELS: + raise "Model not found" + model = app.state.MODELS[model_id] + + user = get_current_user( + request, + get_http_authorization_cred(request.headers.get("Authorization")), + ) + + return body, model, user + + +def get_task_model_id(default_model_id): + # Set the task model + task_model_id = default_model_id + # Check if the user has a custom task model and use that model + if app.state.MODELS[task_model_id]["owned_by"] == "ollama": + if ( + app.state.config.TASK_MODEL + and app.state.config.TASK_MODEL in app.state.MODELS + ): + task_model_id = app.state.config.TASK_MODEL + else: + if ( + app.state.config.TASK_MODEL_EXTERNAL + and app.state.config.TASK_MODEL_EXTERNAL in app.state.MODELS + ): + task_model_id = app.state.config.TASK_MODEL_EXTERNAL + + return task_model_id + + +def get_filter_function_ids(model): + def get_priority(function_id): + function = Functions.get_function_by_id(function_id) + if function is not None and hasattr(function, "valves"): + return (function.valves if function.valves else {}).get("priority", 0) + return 0 + + filter_ids = [function.id for function in Functions.get_global_filter_functions()] + if "info" in model and "meta" in model["info"]: + filter_ids.extend(model["info"]["meta"].get("filterIds", [])) + filter_ids = list(set(filter_ids)) + + enabled_filter_ids = [ + function.id + for function in Functions.get_functions_by_type("filter", active_only=True) + ] + + filter_ids = [ + filter_id for filter_id in filter_ids if filter_id in enabled_filter_ids + ] + + filter_ids.sort(key=get_priority) + return filter_ids + + async def get_function_call_response( messages, files, tool_id, template, task_model_id, user, model ): @@ -373,51 +437,6 @@ async def get_function_call_response( return None, None, False -def get_task_model_id(default_model_id): - # Set the task model - task_model_id = default_model_id - # Check if the user has a custom task model and use that model - if app.state.MODELS[task_model_id]["owned_by"] == "ollama": - if ( - app.state.config.TASK_MODEL - and app.state.config.TASK_MODEL in app.state.MODELS - ): - task_model_id = app.state.config.TASK_MODEL - else: - if ( - app.state.config.TASK_MODEL_EXTERNAL - and app.state.config.TASK_MODEL_EXTERNAL in app.state.MODELS - ): - task_model_id = app.state.config.TASK_MODEL_EXTERNAL - - return task_model_id - - -def get_filter_function_ids(model): - def get_priority(function_id): - function = Functions.get_function_by_id(function_id) - if function is not None and hasattr(function, "valves"): - return (function.valves if function.valves else {}).get("priority", 0) - return 0 - - filter_ids = [function.id for function in Functions.get_global_filter_functions()] - if "info" in model and "meta" in model["info"]: - filter_ids.extend(model["info"]["meta"].get("filterIds", [])) - filter_ids = list(set(filter_ids)) - - enabled_filter_ids = [ - function.id - for function in Functions.get_functions_by_type("filter", active_only=True) - ] - - filter_ids = [ - filter_id for filter_id in filter_ids if filter_id in enabled_filter_ids - ] - - filter_ids.sort(key=get_priority) - return filter_ids - - async def chat_completion_functions_handler(body, model, user): skip_files = None @@ -579,25 +598,6 @@ async def chat_completion_files_handler(body): } -async def get_body_and_model_and_user(request): - # Read the original request body - body = await request.body() - body_str = body.decode("utf-8") - body = json.loads(body_str) if body_str else {} - - model_id = body["model"] - if model_id not in app.state.MODELS: - raise "Model not found" - model = app.state.MODELS[model_id] - - user = get_current_user( - request, - get_http_authorization_cred(request.headers.get("Authorization")), - ) - - return body, model, user - - class ChatCompletionMiddleware(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next): if request.method == "POST" and any( From a07051f51bf54ce638cbef3781dffcea34696f14 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Mon, 1 Jul 2024 20:05:02 -0700 Subject: [PATCH 060/181] feat: __event_emitter__ --- backend/main.py | 55 ++++++++++++++++++++++++----- src/lib/components/chat/Chat.svelte | 8 +++++ 2 files changed, 54 insertions(+), 9 deletions(-) diff --git a/backend/main.py b/backend/main.py index 09d80f012..8b8cac2aa 100644 --- a/backend/main.py +++ b/backend/main.py @@ -33,7 +33,7 @@ from starlette.middleware.sessions import SessionMiddleware from starlette.responses import StreamingResponse, Response, RedirectResponse -from apps.socket.main import app as socket_app +from apps.socket.main import sio, app as socket_app from apps.ollama.main import ( app as ollama_app, OpenAIChatCompletionForm, @@ -277,7 +277,14 @@ def get_filter_function_ids(model): async def get_function_call_response( - messages, files, tool_id, template, task_model_id, user, model + messages, + files, + tool_id, + template, + task_model_id, + user, + model, + __event_emitter__=None, ): tool = Tools.get_tool_by_id(tool_id) tools_specs = json.dumps(tool.specs, indent=2) @@ -414,6 +421,13 @@ async def get_function_call_response( "__id__": tool_id, } + if "__event_emitter__" in sig.parameters: + # Call the function with the '__event_emitter__' parameter included + params = { + **params, + "__event_emitter__": model, + } + if inspect.iscoroutinefunction(function): function_result = await function(**params) else: @@ -437,7 +451,7 @@ async def get_function_call_response( return None, None, False -async def chat_completion_functions_handler(body, model, user): +async def chat_completion_functions_handler(body, model, user, __event_emitter__): skip_files = None filter_ids = get_filter_function_ids(model) @@ -503,6 +517,11 @@ async def chat_completion_functions_handler(body, model, user): **params, "__model__": model, } + if "__event_emitter__" in sig.parameters: + params = { + **params, + "__event_emitter__": __event_emitter__, + } if inspect.iscoroutinefunction(inlet): body = await inlet(**params) @@ -520,7 +539,7 @@ async def chat_completion_functions_handler(body, model, user): return body, {} -async def chat_completion_tools_handler(body, model, user): +async def chat_completion_tools_handler(body, model, user, __event_emitter__): skip_files = None contexts = [] @@ -542,6 +561,7 @@ async def chat_completion_tools_handler(body, model, user): task_model_id=task_model_id, user=user, model=model, + __event_emitter__=__event_emitter__, ) print(file_handler) @@ -614,7 +634,11 @@ class ChatCompletionMiddleware(BaseHTTPMiddleware): content={"detail": str(e)}, ) - # Extract chat_id and message_id from the request body + # Extract session_id, chat_id and message_id from the request body + session_id = None + if "session_id" in body: + session_id = body["session_id"] + del body["session_id"] chat_id = None if "chat_id" in body: chat_id = body["chat_id"] @@ -624,6 +648,17 @@ class ChatCompletionMiddleware(BaseHTTPMiddleware): message_id = body["id"] del body["id"] + async def __event_emitter__(data): + await sio.emit( + "chat-events", + { + "chat_id": chat_id, + "message_id": message_id, + "data": data, + }, + to=session_id, + ) + # Initialize data_items to store additional data to be sent to the client data_items = [] @@ -631,10 +666,10 @@ class ChatCompletionMiddleware(BaseHTTPMiddleware): contexts = [] citations = [] - print(body) - try: - body, flags = await chat_completion_functions_handler(body, model, user) + body, flags = await chat_completion_functions_handler( + body, model, user, __event_emitter__ + ) except Exception as e: return JSONResponse( status_code=status.HTTP_400_BAD_REQUEST, @@ -642,7 +677,9 @@ class ChatCompletionMiddleware(BaseHTTPMiddleware): ) try: - body, flags = await chat_completion_tools_handler(body, model, user) + body, flags = await chat_completion_tools_handler( + body, model, user, __event_emitter__ + ) contexts.extend(flags.get("contexts", [])) citations.extend(flags.get("citations", [])) diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte index 056432d42..769046367 100644 --- a/src/lib/components/chat/Chat.svelte +++ b/src/lib/components/chat/Chat.svelte @@ -163,6 +163,10 @@ }; window.addEventListener('message', onMessageHandler); + $socket.on('chat-events', async (data) => { + console.log(data); + }); + if (!$chatId) { chatId.subscribe(async (value) => { if (!value) { @@ -177,6 +181,8 @@ return () => { window.removeEventListener('message', onMessageHandler); + + $socket.off('chat-events'); }; }); @@ -683,6 +689,7 @@ keep_alive: $settings.keepAlive ?? undefined, tool_ids: selectedToolIds.length > 0 ? selectedToolIds : undefined, files: files.length > 0 ? files : undefined, + session_id: $socket?.id, chat_id: $chatId, id: responseMessageId }); @@ -984,6 +991,7 @@ max_tokens: $settings?.params?.max_tokens ?? undefined, tool_ids: selectedToolIds.length > 0 ? selectedToolIds : undefined, files: files.length > 0 ? files : undefined, + session_id: $socket?.id, chat_id: $chatId, id: responseMessageId }, From d6dbd73ec9b7f0ef03b856191719f96a2bd4b2b2 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Mon, 1 Jul 2024 20:15:27 -0700 Subject: [PATCH 061/181] fix --- backend/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/main.py b/backend/main.py index 8b8cac2aa..7731df2f8 100644 --- a/backend/main.py +++ b/backend/main.py @@ -425,7 +425,7 @@ async def get_function_call_response( # Call the function with the '__event_emitter__' parameter included params = { **params, - "__event_emitter__": model, + "__event_emitter__": __event_emitter__, } if inspect.iscoroutinefunction(function): From 52cae406b43f08847250ca1326e286a0e13e376a Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Mon, 1 Jul 2024 20:53:24 -0700 Subject: [PATCH 062/181] feat: chat event handler --- src/lib/components/chat/Chat.svelte | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte index 769046367..f077833fb 100644 --- a/src/lib/components/chat/Chat.svelte +++ b/src/lib/components/chat/Chat.svelte @@ -126,6 +126,27 @@ })(); } + const chatEventHandler = async (data) => { + if (data.chat_id === $chatId) { + await tick(); + console.log(data); + let message = history.messages[data.message_id]; + + const status = { + done: data?.data?.done ?? null, + description: data?.data?.status ?? null + }; + + if (message.statusHistory) { + message.statusHistory.push(status); + } else { + message.statusHistory = [status]; + } + + messages = messages; + } + }; + onMount(async () => { const onMessageHandler = async (event) => { if (event.origin === window.origin) { @@ -163,9 +184,7 @@ }; window.addEventListener('message', onMessageHandler); - $socket.on('chat-events', async (data) => { - console.log(data); - }); + $socket.on('chat-events', chatEventHandler); if (!$chatId) { chatId.subscribe(async (value) => { From d97a4d687ebc065ae4ec2a45b24c775e052ab38c Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Mon, 1 Jul 2024 21:41:44 -0700 Subject: [PATCH 063/181] refac --- backend/main.py | 17 +++++++++++++++++ src/lib/components/chat/Chat.svelte | 10 ++++++---- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/backend/main.py b/backend/main.py index 7731df2f8..0e3986f21 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1123,6 +1123,17 @@ async def chat_completed(form_data: dict, user=Depends(get_verified_user)): else: pass + async def __event_emitter__(data): + await sio.emit( + "chat-events", + { + "chat_id": data["chat_id"], + "message_id": data["id"], + "data": data, + }, + to=data["session_id"], + ) + def get_priority(function_id): function = Functions.get_function_by_id(function_id) if function is not None and hasattr(function, "valves"): @@ -1204,6 +1215,12 @@ async def chat_completed(form_data: dict, user=Depends(get_verified_user)): "__model__": model, } + if "__event_emitter__" in sig.parameters: + params = { + **params, + "__event_emitter__": __event_emitter__, + } + if inspect.iscoroutinefunction(outlet): data = await outlet(**params) else: diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte index f077833fb..3d03246b7 100644 --- a/src/lib/components/chat/Chat.svelte +++ b/src/lib/components/chat/Chat.svelte @@ -327,7 +327,7 @@ } }; - const chatCompletedHandler = async (modelId, messages) => { + const chatCompletedHandler = async (modelId, responseMessageId, messages) => { await mermaid.run({ querySelector: '.mermaid' }); @@ -341,7 +341,9 @@ info: m.info ? m.info : undefined, timestamp: m.timestamp })), - chat_id: $chatId + chat_id: $chatId, + session_id: $socket?.id, + id: responseMessageId }).catch((error) => { toast.error(error); messages.at(-1).error = { content: error }; @@ -731,7 +733,7 @@ controller.abort('User: Stop Response'); } else { const messages = createMessagesList(responseMessageId); - await chatCompletedHandler(model.id, messages); + await chatCompletedHandler(model.id, responseMessageId, messages); } _response = responseMessage.content; @@ -1041,7 +1043,7 @@ } else { const messages = createMessagesList(responseMessageId); - await chatCompletedHandler(model.id, messages); + await chatCompletedHandler(model.id, responseMessageId, messages); } _response = responseMessage.content; From 044a0fbbbebb75fd9506018f3e744f2b72787d69 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Mon, 1 Jul 2024 22:14:09 -0700 Subject: [PATCH 064/181] enh: bool, literal valves --- .../components/chat/Settings/Valves.svelte | 46 +++++++++++++++---- .../workspace/common/ValvesModal.svelte | 46 +++++++++++++++---- 2 files changed, 74 insertions(+), 18 deletions(-) diff --git a/src/lib/components/chat/Settings/Valves.svelte b/src/lib/components/chat/Settings/Valves.svelte index eb4554871..ca97ade05 100644 --- a/src/lib/components/chat/Settings/Valves.svelte +++ b/src/lib/components/chat/Settings/Valves.svelte @@ -18,6 +18,7 @@ import ManageModal from './Personalization/ManageModal.svelte'; import Tooltip from '$lib/components/common/Tooltip.svelte'; import Spinner from '$lib/components/common/Spinner.svelte'; + import Switch from '$lib/components/common/Switch.svelte'; const dispatch = createEventDispatcher(); @@ -185,7 +186,10 @@ class="p-1 px-3 text-xs flex rounded transition" type="button" on:click={() => { - valves[property] = (valves[property] ?? null) === null ? '' : null; + valves[property] = + (valves[property] ?? null) === null + ? valvesSpec.properties[property]?.default ?? '' + : null; }} > {#if (valves[property] ?? null) === null} @@ -203,16 +207,40 @@
{#if (valves[property] ?? null) !== null} +
- + {#if valvesSpec.properties[property]?.enum ?? null} + + {:else if (valvesSpec.properties[property]?.type ?? null) === 'boolean'} +
+
+ {valves[property] ? 'Enabled' : 'Disabled'} +
+ +
+ +
+
+ {:else} + + {/if}
{/if} diff --git a/src/lib/components/workspace/common/ValvesModal.svelte b/src/lib/components/workspace/common/ValvesModal.svelte index e4bdfb567..12315f9e6 100644 --- a/src/lib/components/workspace/common/ValvesModal.svelte +++ b/src/lib/components/workspace/common/ValvesModal.svelte @@ -12,6 +12,7 @@ } from '$lib/apis/functions'; import { getToolValvesById, getToolValvesSpecById, updateToolValvesById } from '$lib/apis/tools'; import Spinner from '../../common/Spinner.svelte'; + import Switch from '$lib/components/common/Switch.svelte'; const i18n = getContext('i18n'); const dispatch = createEventDispatcher(); @@ -142,7 +143,10 @@ class="p-1 px-3 text-xs flex rounded transition" type="button" on:click={() => { - valves[property] = (valves[property] ?? null) === null ? '' : null; + valves[property] = + (valves[property] ?? null) === null + ? valvesSpec.properties[property]?.default ?? '' + : null; }} > {#if (valves[property] ?? null) === null} @@ -160,16 +164,40 @@
{#if (valves[property] ?? null) !== null} +
- + {#if valvesSpec.properties[property]?.enum ?? null} + + {:else if (valvesSpec.properties[property]?.type ?? null) === 'boolean'} +
+
+ {valves[property] ? 'Enabled' : 'Disabled'} +
+ +
+ +
+
+ {:else} + + {/if}
{/if} From 439ab7a33568c51250cc3ce40edfffb05090875e Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Mon, 1 Jul 2024 22:18:45 -0700 Subject: [PATCH 065/181] refac --- .../admin/Settings/Pipelines.svelte | 42 +++++++++++++++---- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/src/lib/components/admin/Settings/Pipelines.svelte b/src/lib/components/admin/Settings/Pipelines.svelte index 6826cb92b..22ebc707c 100644 --- a/src/lib/components/admin/Settings/Pipelines.svelte +++ b/src/lib/components/admin/Settings/Pipelines.svelte @@ -19,6 +19,7 @@ } from '$lib/apis'; import Spinner from '$lib/components/common/Spinner.svelte'; + import Switch from '$lib/components/common/Switch.svelte'; const i18n: Writable = getContext('i18n'); @@ -476,15 +477,40 @@
{#if (valves[property] ?? null) !== null} -
+ +
- + {#if valvesSpec.properties[property]?.enum ?? null} + + {:else if (valvesSpec.properties[property]?.type ?? null) === 'boolean'} +
+
+ {valves[property] ? 'Enabled' : 'Disabled'} +
+ +
+ +
+
+ {:else} + + {/if}
{/if} From 05ec71beb982ae9cdb98c2f7d19248f89cbde2ee Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Mon, 1 Jul 2024 23:08:01 -0700 Subject: [PATCH 066/181] enh: pinned chats support --- src/lib/components/chat/Tags.svelte | 19 ++- src/lib/components/icons/Bookmark.svelte | 19 +++ src/lib/components/icons/BookmarkSlash.svelte | 19 +++ src/lib/components/icons/ChatMenu.svelte | 124 ++++++++++++++++++ src/lib/components/icons/Star.svelte | 19 +++ src/lib/components/layout/Sidebar.svelte | 42 +++++- .../components/layout/Sidebar/ChatItem.svelte | 9 +- .../components/layout/Sidebar/ChatMenu.svelte | 44 ++++++- src/lib/stores/index.ts | 1 + 9 files changed, 285 insertions(+), 11 deletions(-) create mode 100644 src/lib/components/icons/Bookmark.svelte create mode 100644 src/lib/components/icons/BookmarkSlash.svelte create mode 100644 src/lib/components/icons/ChatMenu.svelte create mode 100644 src/lib/components/icons/Star.svelte diff --git a/src/lib/components/chat/Tags.svelte b/src/lib/components/chat/Tags.svelte index 47e63198f..cb4e54641 100644 --- a/src/lib/components/chat/Tags.svelte +++ b/src/lib/components/chat/Tags.svelte @@ -8,7 +8,7 @@ getTagsById, updateChatById } from '$lib/apis/chats'; - import { tags as _tags, chats } from '$lib/stores'; + import { tags as _tags, chats, pinnedChats } from '$lib/stores'; import { createEventDispatcher, onMount } from 'svelte'; const dispatch = createEventDispatcher(); @@ -19,9 +19,11 @@ let tags = []; const getTags = async () => { - return await getTagsById(localStorage.token, chatId).catch(async (error) => { - return []; - }); + return ( + await getTagsById(localStorage.token, chatId).catch(async (error) => { + return []; + }) + ).filter((tag) => tag.name !== 'pinned'); }; const addTag = async (tagName) => { @@ -33,6 +35,7 @@ }); _tags.set(await getAllChatTags(localStorage.token)); + await pinnedChats.set(await getChatListByTagName(localStorage.token, 'pinned')); }; const deleteTag = async (tagName) => { @@ -44,19 +47,23 @@ }); console.log($_tags); - await _tags.set(await getAllChatTags(localStorage.token)); console.log($_tags); if ($_tags.map((t) => t.name).includes(tagName)) { - await chats.set(await getChatListByTagName(localStorage.token, tagName)); + if (tagName === 'pinned') { + await pinnedChats.set(await getChatListByTagName(localStorage.token, 'pinned')); + } else { + await chats.set(await getChatListByTagName(localStorage.token, tagName)); + } if ($chats.find((chat) => chat.id === chatId)) { dispatch('close'); } } else { await chats.set(await getChatList(localStorage.token)); + await pinnedChats.set(await getChatListByTagName(localStorage.token, 'pinned')); } }; diff --git a/src/lib/components/icons/Bookmark.svelte b/src/lib/components/icons/Bookmark.svelte new file mode 100644 index 000000000..ea8028457 --- /dev/null +++ b/src/lib/components/icons/Bookmark.svelte @@ -0,0 +1,19 @@ + + + + + diff --git a/src/lib/components/icons/BookmarkSlash.svelte b/src/lib/components/icons/BookmarkSlash.svelte new file mode 100644 index 000000000..6b80ea3ca --- /dev/null +++ b/src/lib/components/icons/BookmarkSlash.svelte @@ -0,0 +1,19 @@ + + + + + diff --git a/src/lib/components/icons/ChatMenu.svelte b/src/lib/components/icons/ChatMenu.svelte new file mode 100644 index 000000000..673e643b1 --- /dev/null +++ b/src/lib/components/icons/ChatMenu.svelte @@ -0,0 +1,124 @@ + + + { + if (e.detail === false) { + onClose(); + } + }} +> + + + + +
+ + { + pinHandler(); + }} + > + +
{$i18n.t('Pin')}
+
+ + { + renameHandler(); + }} + > + +
{$i18n.t('Rename')}
+
+ + { + cloneChatHandler(); + }} + > + +
{$i18n.t('Clone')}
+
+ + { + archiveChatHandler(); + }} + > + +
{$i18n.t('Archive')}
+
+ + { + shareHandler(); + }} + > + +
{$i18n.t('Share')}
+
+ + { + deleteHandler(); + }} + > + +
{$i18n.t('Delete')}
+
+ +
+ +
+ { + show = false; + onClose(); + }} + /> +
+
+
+
diff --git a/src/lib/components/icons/Star.svelte b/src/lib/components/icons/Star.svelte new file mode 100644 index 000000000..45faf808b --- /dev/null +++ b/src/lib/components/icons/Star.svelte @@ -0,0 +1,19 @@ + + + + + diff --git a/src/lib/components/layout/Sidebar.svelte b/src/lib/components/layout/Sidebar.svelte index 0cd6afa28..193fe41ff 100644 --- a/src/lib/components/layout/Sidebar.svelte +++ b/src/lib/components/layout/Sidebar.svelte @@ -10,7 +10,8 @@ tags, showSidebar, mobile, - showArchivedChats + showArchivedChats, + pinnedChats } from '$lib/stores'; import { onMount, getContext, tick } from 'svelte'; @@ -46,6 +47,7 @@ let showDeleteConfirm = false; let showDropdown = false; + let filteredChatList = []; $: filteredChatList = $chats.filter((chat) => { @@ -80,6 +82,8 @@ }); showSidebar.set(window.innerWidth > BREAKPOINT); + + await pinnedChats.set(await getChatListByTagName(localStorage.token, 'pinned')); await chats.set(await getChatList(localStorage.token)); let touchstart; @@ -412,7 +416,7 @@
- {#if $tags.length > 0} + {#if $tags.filter((t) => t.name !== 'pinned').length > 0}
- {#each $tags as tag} + {#each $tags.filter((t) => t.name !== 'pinned') as tag}
{/if} + {#if $pinnedChats.length > 0} +
+
+
+ {$i18n.t('Pinned')} +
+ + {#each $pinnedChats as chat, idx} + { + selectedChatId = chat.id; + }} + on:unselect={() => { + selectedChatId = null; + }} + on:delete={(e) => { + if ((e?.detail ?? '') === 'shift') { + deleteChatHandler(chat.id); + } else { + deleteChat = chat; + showDeleteConfirm = true; + } + }} + /> + {/each} +
+
+ {/if} +
{#each filteredChatList as chat, idx} {#if idx === 0 || (idx > 0 && chat.time_range !== filteredChatList[idx - 1].time_range)} diff --git a/src/lib/components/layout/Sidebar/ChatItem.svelte b/src/lib/components/layout/Sidebar/ChatItem.svelte index 742c69a61..129c653fa 100644 --- a/src/lib/components/layout/Sidebar/ChatItem.svelte +++ b/src/lib/components/layout/Sidebar/ChatItem.svelte @@ -11,9 +11,10 @@ cloneChatById, deleteChatById, getChatList, + getChatListByTagName, updateChatById } from '$lib/apis/chats'; - import { chatId, chats, mobile, showSidebar } from '$lib/stores'; + import { chatId, chats, mobile, pinnedChats, showSidebar } from '$lib/stores'; import ChatMenu from './ChatMenu.svelte'; import ShareChatModal from '$lib/components/chat/ShareChatModal.svelte'; @@ -40,6 +41,7 @@ title: _title }); await chats.set(await getChatList(localStorage.token)); + await pinnedChats.set(await getChatListByTagName(localStorage.token, 'pinned')); } }; @@ -52,12 +54,14 @@ if (res) { goto(`/c/${res.id}`); await chats.set(await getChatList(localStorage.token)); + await pinnedChats.set(await getChatListByTagName(localStorage.token, 'pinned')); } }; const archiveChatHandler = async (id) => { await archiveChatById(localStorage.token, id); await chats.set(await getChatList(localStorage.token)); + await pinnedChats.set(await getChatListByTagName(localStorage.token, 'pinned')); }; const focusEdit = async (node: HTMLInputElement) => { @@ -233,6 +237,9 @@ onClose={() => { dispatch('unselect'); }} + on:change={async () => { + await pinnedChats.set(await getChatListByTagName(localStorage.token, 'pinned')); + }} > {:else if prompt.split(' ')?.at(0)?.substring(1).startsWith('http')} +
+ +
+
\ No newline at end of file From 2389c36a70d55ee6da4164b9e085a322e488a194 Mon Sep 17 00:00:00 2001 From: rdavis Date: Thu, 4 Jul 2024 13:55:37 +0000 Subject: [PATCH 099/181] refactor: Update WebSearchResults.svelte to use new CollapsibleComponent --- .../ResponseMessage/WebSearchResults.svelte | 146 +++++++++--------- 1 file changed, 73 insertions(+), 73 deletions(-) diff --git a/src/lib/components/chat/Messages/ResponseMessage/WebSearchResults.svelte b/src/lib/components/chat/Messages/ResponseMessage/WebSearchResults.svelte index 528108036..25001730e 100644 --- a/src/lib/components/chat/Messages/ResponseMessage/WebSearchResults.svelte +++ b/src/lib/components/chat/Messages/ResponseMessage/WebSearchResults.svelte @@ -2,17 +2,18 @@ import ChevronDown from '$lib/components/icons/ChevronDown.svelte'; import ChevronUp from '$lib/components/icons/ChevronUp.svelte'; import MagnifyingGlass from '$lib/components/icons/MagnifyingGlass.svelte'; - import { Collapsible } from 'bits-ui'; - import { slide } from 'svelte/transition'; + import Collapsible from '$lib/components/common/Collapsible.svelte'; + export let status = { urls: [], query: '' }; let state = false; - - +
+
@@ -22,76 +23,75 @@ {/if}
- - - - {#if status?.query} - -
- - -
- {status.query} + - -
- - - - -
-
- {/if} - - {#each status.urls as url, urlIdx} - -
- {url} -
- -
+ + + +
+
+ {/if} + + {#each status.urls as url, urlIdx} + - - + {url} +
+ +
- - -
-
- {/each} - - + + + + +
+ + {/each} +
+ + \ No newline at end of file From d5c0876a0b180cfc413a7dfb55ae4fe34f2f5d52 Mon Sep 17 00:00:00 2001 From: rdavis Date: Thu, 4 Jul 2024 14:02:26 +0000 Subject: [PATCH 100/181] refactor: fixed new Collapsible Component to allow passed in classes chore: format --- .../ResponseMessage/WebSearchResults.svelte | 160 +++++++++--------- src/lib/components/common/Collapsible.svelte | 31 ++-- 2 files changed, 92 insertions(+), 99 deletions(-) diff --git a/src/lib/components/chat/Messages/ResponseMessage/WebSearchResults.svelte b/src/lib/components/chat/Messages/ResponseMessage/WebSearchResults.svelte index 25001730e..4523c8482 100644 --- a/src/lib/components/chat/Messages/ResponseMessage/WebSearchResults.svelte +++ b/src/lib/components/chat/Messages/ResponseMessage/WebSearchResults.svelte @@ -4,94 +4,88 @@ import MagnifyingGlass from '$lib/components/icons/MagnifyingGlass.svelte'; import Collapsible from '$lib/components/common/Collapsible.svelte'; - export let status = { urls: [], query: '' }; let state = false; -
- -
- + +
+ + + {#if state} + + {:else} + + {/if} +
+
+ {#if status?.query} + +
+ - {#if state} - - {:else} - - {/if} -
-
\ No newline at end of file + + +
+ + {/if} + + {#each status.urls as url, urlIdx} + +
+ {url} +
+ +
+ + + + +
+
+ {/each} +
+
diff --git a/src/lib/components/common/Collapsible.svelte b/src/lib/components/common/Collapsible.svelte index c87ffe8ba..b681143a6 100644 --- a/src/lib/components/common/Collapsible.svelte +++ b/src/lib/components/common/Collapsible.svelte @@ -2,11 +2,11 @@ import { afterUpdate } from 'svelte'; export let open = false; - + export let className = ''; // Manage the max-height of the collapsible content for snappy transitions let contentElement: HTMLElement; - let maxHeight = '0px'; // Initial max-height + let maxHeight = '0px'; // Initial max-height // After any state update, adjust the max-height for the transition afterUpdate(() => { if (open) { @@ -15,23 +15,22 @@ } else { maxHeight = '0px'; } - }); - + }); +
+ +
+ +
+
+ - -
- -
- -
-
\ No newline at end of file From db58bb5f0f51521fa5c52e1b4e8107e6275904ad Mon Sep 17 00:00:00 2001 From: rdavis Date: Thu, 4 Jul 2024 14:15:16 +0000 Subject: [PATCH 101/181] refactor: Removed dependency --- src/lib/components/common/Collapsible.svelte | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/lib/components/common/Collapsible.svelte b/src/lib/components/common/Collapsible.svelte index b681143a6..0a140d9dd 100644 --- a/src/lib/components/common/Collapsible.svelte +++ b/src/lib/components/common/Collapsible.svelte @@ -1,21 +1,19 @@
From 78ba18a680f9cce4c895279282ecf60fc581f382 Mon Sep 17 00:00:00 2001 From: rdavis Date: Thu, 4 Jul 2024 14:55:48 +0000 Subject: [PATCH 102/181] refactor: Update Collapsible component to include dynamic margin for open state --- src/lib/components/common/Collapsible.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/components/common/Collapsible.svelte b/src/lib/components/common/Collapsible.svelte index 0a140d9dd..14e5785a4 100644 --- a/src/lib/components/common/Collapsible.svelte +++ b/src/lib/components/common/Collapsible.svelte @@ -20,7 +20,7 @@ -
+
@@ -28,7 +28,7 @@ From f611533764ece12128d5a3daaa4a0ee53e0e3b64 Mon Sep 17 00:00:00 2001 From: Karl Lee <61072264+KarlLee830@users.noreply.github.com> Date: Thu, 4 Jul 2024 22:57:32 +0800 Subject: [PATCH 103/181] i18n: Update Chinese translation --- src/lib/i18n/locales/zh-CN/translation.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/lib/i18n/locales/zh-CN/translation.json b/src/lib/i18n/locales/zh-CN/translation.json index d5887e2ff..366b717f0 100644 --- a/src/lib/i18n/locales/zh-CN/translation.json +++ b/src/lib/i18n/locales/zh-CN/translation.json @@ -126,7 +126,7 @@ "Connections": "外部连接", "Contact Admin for WebUI Access": "请联系管理员以获取访问权限", "Content": "内容", - "Content Extraction": "", + "Content Extraction": "内容提取", "Context Length": "上下文长度", "Continue Response": "继续生成", "Continue with {{provider}}": "使用 {{provider}} 继续", @@ -213,7 +213,7 @@ "Enable Community Sharing": "启用分享至社区", "Enable New Sign Ups": "允许新用户注册", "Enable Web Search": "启用网络搜索", - "Engine": "", + "Engine": "引擎", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "确保您的 CSV 文件按以下顺序包含 4 列: 姓名、电子邮箱、密码、角色。", "Enter {{role}} message here": "在此处输入 {{role}} 信息", "Enter a detail about yourself for your LLMs to recall": "输入一个关于你自己的详细信息,方便你的大语言模型记住这些内容", @@ -235,7 +235,7 @@ "Enter Serpstack API Key": "输入 Serpstack API 密钥", "Enter stop sequence": "输入停止序列 (Stop Sequence)", "Enter Tavily API Key": "输入 Tavily API 密钥", - "Enter Tika Server URL": "", + "Enter Tika Server URL": "输入 Tika 服务器地址", "Enter Top K": "输入 Top K", "Enter URL (e.g. http://127.0.0.1:7860/)": "输入地址 (例如:http://127.0.0.1:7860/)", "Enter URL (e.g. http://localhost:11434)": "输入地址 (例如:http://localhost:11434)", @@ -412,7 +412,7 @@ "Open": "打开", "Open AI (Dall-E)": "Open AI (Dall-E)", "Open new chat": "打开新对话", - "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "当前 Open WebUI 版本 (v{{OPEN_WEBUI_VERSION}}) 低于所需的版本 (v{{REQUIRED_VERSION}})", "OpenAI": "OpenAI", "OpenAI API": "OpenAI API", "OpenAI API Config": "OpenAI API 配置", @@ -428,8 +428,8 @@ "Permission denied when accessing microphone": "申请麦克风权限被拒绝", "Permission denied when accessing microphone: {{error}}": "申请麦克风权限被拒绝:{{error}}", "Personalization": "个性化", - "Pin": "", - "Pinned": "", + "Pin": "置顶", + "Pinned": "已置顶", "Pipeline deleted successfully": "Pipeline 删除成功", "Pipeline downloaded successfully": "Pipeline 下载成功", "Pipelines": "Pipeline", @@ -578,8 +578,8 @@ "This setting does not sync across browsers or devices.": "此设置不会在浏览器或设备之间同步。", "This will delete": "这将删除", "Thorough explanation": "解释较为详细", - "Tika": "", - "Tika Server URL required.": "", + "Tika": "Tika", + "Tika Server URL required.": "请输入 Tika 服务器地址。", "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "提示:在每次替换后,在对话输入中按 Tab 键可以连续更新多个变量。", "Title": "标题", "Title (e.g. Tell me a fun fact)": "标题(例如 给我讲一个有趣的事实)", @@ -614,7 +614,7 @@ "Uh-oh! There was an issue connecting to {{provider}}.": "糟糕!连接到 {{provider}} 时出现问题。", "UI": "界面", "Unknown file type '{{file_type}}'. Proceeding with the file upload anyway.": "未知文件类型“{{file_type}}”,将无视继续上传文件。", - "Unpin": "", + "Unpin": "取消置顶", "Update": "更新", "Update and Copy Link": "更新和复制链接", "Update password": "更新密码", From ca3f8e6cb52231a21f7c157fd1e38504665b1793 Mon Sep 17 00:00:00 2001 From: rdavis Date: Thu, 4 Jul 2024 15:18:21 +0000 Subject: [PATCH 104/181] chore: format --- src/lib/components/common/Collapsible.svelte | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/components/common/Collapsible.svelte b/src/lib/components/common/Collapsible.svelte index 14e5785a4..8a3ef9690 100644 --- a/src/lib/components/common/Collapsible.svelte +++ b/src/lib/components/common/Collapsible.svelte @@ -20,7 +20,11 @@ -
+
From 55b7c30028c96dc58e14b563dcd26780dbea34cb Mon Sep 17 00:00:00 2001 From: Michael Poluektov Date: Thu, 4 Jul 2024 18:50:09 +0100 Subject: [PATCH 105/181] simplify citation API --- src/lib/components/chat/Chat.svelte | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte index de64d2681..a087e76ed 100644 --- a/src/lib/components/chat/Chat.svelte +++ b/src/lib/components/chat/Chat.svelte @@ -139,7 +139,7 @@ return; } const status_keys = ["done", "description"]; - const citation_keys = ["document", "metadata", "source"]; + const citation_keys = ["document", "url", "title"]; if (type === "status" && status_keys.every(key => key in payload)) { if (message.statusHistory) { message.statusHistory.push(payload); @@ -147,10 +147,15 @@ message.statusHistory = [payload]; } } else if (type === "citation" && citation_keys.every(key => key in payload)) { + const citation = { + document: [payload.document], + metadata: [{source: payload.url}], + source: {name: payload.title} + }; if (message.citations) { - message.citations.push(payload); + message.citations.push(citation); } else { - message.citations = [payload]; + message.citations = [citation]; } } else { console.log("Unknown message type", data); From 67c2ab006d06e442c4ca7cc4e0293e119f67f715 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Thu, 4 Jul 2024 13:41:18 -0700 Subject: [PATCH 106/181] fix: pipe custom model --- backend/apps/webui/main.py | 76 ++++++++++++++++++++++++++++++++++++++ backend/main.py | 6 ++- 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/backend/apps/webui/main.py b/backend/apps/webui/main.py index 552edf7fa..745157ac6 100644 --- a/backend/apps/webui/main.py +++ b/backend/apps/webui/main.py @@ -19,8 +19,13 @@ from apps.webui.routers import ( functions, ) from apps.webui.models.functions import Functions +from apps.webui.models.models import Models + from apps.webui.utils import load_function_module_by_id + from utils.misc import stream_message_template +from utils.task import prompt_template + from config import ( WEBUI_BUILD_HASH, @@ -186,6 +191,77 @@ async def get_pipe_models(): async def generate_function_chat_completion(form_data, user): + model_id = form_data.get("model") + model_info = Models.get_model_by_id(model_id) + + if model_info: + if model_info.base_model_id: + form_data["model"] = model_info.base_model_id + + model_info.params = model_info.params.model_dump() + + if model_info.params: + if model_info.params.get("temperature", None) is not None: + form_data["temperature"] = float(model_info.params.get("temperature")) + + if model_info.params.get("top_p", None): + form_data["top_p"] = int(model_info.params.get("top_p", None)) + + if model_info.params.get("max_tokens", None): + form_data["max_tokens"] = int(model_info.params.get("max_tokens", None)) + + if model_info.params.get("frequency_penalty", None): + form_data["frequency_penalty"] = int( + model_info.params.get("frequency_penalty", None) + ) + + if model_info.params.get("seed", None): + form_data["seed"] = model_info.params.get("seed", None) + + if model_info.params.get("stop", None): + form_data["stop"] = ( + [ + bytes(stop, "utf-8").decode("unicode_escape") + for stop in model_info.params["stop"] + ] + if model_info.params.get("stop", None) + else None + ) + + system = model_info.params.get("system", None) + if system: + system = prompt_template( + system, + **( + { + "user_name": user.name, + "user_location": ( + user.info.get("location") if user.info else None + ), + } + if user + else {} + ), + ) + # Check if the payload already has a system message + # If not, add a system message to the payload + if form_data.get("messages"): + for message in form_data["messages"]: + if message.get("role") == "system": + message["content"] = system + message["content"] + break + else: + form_data["messages"].insert( + 0, + { + "role": "system", + "content": system, + }, + ) + + else: + pass + async def job(): pipe_id = form_data["model"] if "." in pipe_id: diff --git a/backend/main.py b/backend/main.py index 8f818c85b..f2019b30f 100644 --- a/backend/main.py +++ b/backend/main.py @@ -975,12 +975,16 @@ async def get_all_models(): model["info"] = custom_model.model_dump() else: owned_by = "openai" + pipe = None + for model in models: if ( custom_model.base_model_id == model["id"] or custom_model.base_model_id == model["id"].split(":")[0] ): owned_by = model["owned_by"] + if "pipe" in model: + pipe = model["pipe"] break models.append( @@ -992,11 +996,11 @@ async def get_all_models(): "owned_by": owned_by, "info": custom_model.model_dump(), "preset": True, + **({"pipe": pipe} if pipe is not None else {}), } ) app.state.MODELS = {model["id"]: model for model in models} - webui_app.state.MODELS = app.state.MODELS return models From 838134637818ae64127bcb27a9208b0466b438d4 Mon Sep 17 00:00:00 2001 From: Peter De-Ath Date: Fri, 5 Jul 2024 02:05:59 +0100 Subject: [PATCH 107/181] enh: add sideways scrolling to settings tabs container --- src/lib/components/admin/Settings.svelte | 17 ++++++++++-- src/lib/components/chat/SettingsModal.svelte | 29 +++++++++++++++++++- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/lib/components/admin/Settings.svelte b/src/lib/components/admin/Settings.svelte index 5538a11cf..24cf595a7 100644 --- a/src/lib/components/admin/Settings.svelte +++ b/src/lib/components/admin/Settings.svelte @@ -1,5 +1,5 @@
-
- -
-
- + {#if open} +
+ +
+ {/if} +
From 1436bb7c61b1df4dba2b5b383ecb8c86ec452f37 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Fri, 5 Jul 2024 23:38:53 -0700 Subject: [PATCH 112/181] enh: handle peewee migration --- backend/apps/webui/internal/db.py | 36 +++++++++++-- backend/apps/webui/internal/wrappers.py | 72 +++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 backend/apps/webui/internal/wrappers.py diff --git a/backend/apps/webui/internal/db.py b/backend/apps/webui/internal/db.py index 333e215ea..8437ae4fa 100644 --- a/backend/apps/webui/internal/db.py +++ b/backend/apps/webui/internal/db.py @@ -2,6 +2,10 @@ import os import logging import json from contextlib import contextmanager + +from peewee_migrate import Router +from apps.webui.internal.wrappers import register_connection + from typing import Optional, Any from typing_extensions import Self @@ -46,6 +50,35 @@ if os.path.exists(f"{DATA_DIR}/ollama.db"): else: pass + +# Workaround to handle the peewee migration +# This is required to ensure the peewee migration is handled before the alembic migration +def handle_peewee_migration(): + try: + db = register_connection(DATABASE_URL) + migrate_dir = BACKEND_DIR / "apps" / "webui" / "internal" / "migrations" + router = Router(db, logger=log, migrate_dir=migrate_dir) + router.run() + db.close() + + # check if db connection has been closed + + except Exception as e: + log.error(f"Failed to initialize the database connection: {e}") + raise + + finally: + # Properly closing the database connection + if db and not db.is_closed(): + db.close() + + # Assert if db connection has been closed + assert db.is_closed(), "Database connection is still open." + + +handle_peewee_migration() + + SQLALCHEMY_DATABASE_URL = DATABASE_URL if "sqlite" in SQLALCHEMY_DATABASE_URL: engine = create_engine( @@ -62,9 +95,6 @@ Base = declarative_base() Session = scoped_session(SessionLocal) -from contextlib import contextmanager - - # Dependency def get_session(): db = SessionLocal() diff --git a/backend/apps/webui/internal/wrappers.py b/backend/apps/webui/internal/wrappers.py new file mode 100644 index 000000000..2b5551ce2 --- /dev/null +++ b/backend/apps/webui/internal/wrappers.py @@ -0,0 +1,72 @@ +from contextvars import ContextVar +from peewee import * +from peewee import PostgresqlDatabase, InterfaceError as PeeWeeInterfaceError + +import logging +from playhouse.db_url import connect, parse +from playhouse.shortcuts import ReconnectMixin + +from config import SRC_LOG_LEVELS + +log = logging.getLogger(__name__) +log.setLevel(SRC_LOG_LEVELS["DB"]) + +db_state_default = {"closed": None, "conn": None, "ctx": None, "transactions": None} +db_state = ContextVar("db_state", default=db_state_default.copy()) + + +class PeeweeConnectionState(object): + def __init__(self, **kwargs): + super().__setattr__("_state", db_state) + super().__init__(**kwargs) + + def __setattr__(self, name, value): + self._state.get()[name] = value + + def __getattr__(self, name): + value = self._state.get()[name] + return value + + +class CustomReconnectMixin(ReconnectMixin): + reconnect_errors = ( + # psycopg2 + (OperationalError, "termin"), + (InterfaceError, "closed"), + # peewee + (PeeWeeInterfaceError, "closed"), + ) + + +class ReconnectingPostgresqlDatabase(CustomReconnectMixin, PostgresqlDatabase): + pass + + +def register_connection(db_url): + db = connect(db_url) + if isinstance(db, PostgresqlDatabase): + # Enable autoconnect for SQLite databases, managed by Peewee + db.autoconnect = True + db.reuse_if_open = True + log.info("Connected to PostgreSQL database") + + # Get the connection details + connection = parse(db_url) + + # Use our custom database class that supports reconnection + db = ReconnectingPostgresqlDatabase( + connection["database"], + user=connection["user"], + password=connection["password"], + host=connection["host"], + port=connection["port"], + ) + db.connect(reuse_if_open=True) + elif isinstance(db, SqliteDatabase): + # Enable autoconnect for SQLite databases, managed by Peewee + db.autoconnect = True + db.reuse_if_open = True + log.info("Connected to SQLite database") + else: + raise ValueError("Unsupported database connection") + return db From d5716ae751f2ea24bda45fad810750b5c7c72b29 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Fri, 5 Jul 2024 23:48:53 -0700 Subject: [PATCH 113/181] chore: format --- src/lib/components/admin/Settings.svelte | 3 ++- src/lib/i18n/locales/fr-CA/translation.json | 16 +++++++++------- src/lib/i18n/locales/fr-FR/translation.json | 20 +++++++++++--------- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/lib/components/admin/Settings.svelte b/src/lib/components/admin/Settings.svelte index 24cf595a7..661401443 100644 --- a/src/lib/components/admin/Settings.svelte +++ b/src/lib/components/admin/Settings.svelte @@ -38,7 +38,8 @@