diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 1ec947b4e0..9a19bfa6b4 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,4 +1,4 @@ - By submitting this pull request, I confirm that I have read and fully agree to the [Contributor License Agreement (CLA)](https://github.com/open-webui/open-webui/blob/main/CONTRIBUTOR_LICENSE_AGREEMENT), and I am providing my contributions under its terms. > [!NOTE] -> Deleting the CLA section will lead to immediate closure of your PR and it will not be merged in. \ No newline at end of file +> Deleting the CLA section will lead to immediate closure of your PR and it will not be merged in. diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index f2c65ccef8..e2db276e1e 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -2287,7 +2287,9 @@ WEAVIATE_GRPC_PORT = int(os.environ.get("WEAVIATE_GRPC_PORT", "50051")) WEAVIATE_API_KEY = os.environ.get("WEAVIATE_API_KEY") WEAVIATE_HTTP_SECURE = os.environ.get("WEAVIATE_HTTP_SECURE", "false").lower() == "true" WEAVIATE_GRPC_SECURE = os.environ.get("WEAVIATE_GRPC_SECURE", "false").lower() == "true" -WEAVIATE_SKIP_INIT_CHECKS = os.environ.get("WEAVIATE_SKIP_INIT_CHECKS", "false").lower() == "true" +WEAVIATE_SKIP_INIT_CHECKS = ( + os.environ.get("WEAVIATE_SKIP_INIT_CHECKS", "false").lower() == "true" +) # OpenSearch OPENSEARCH_URI = os.environ.get("OPENSEARCH_URI", "https://localhost:9200") @@ -3486,10 +3488,14 @@ IMAGE_GENERATION_MODEL = PersistentConfig( ) # Regex pattern for models that support IMAGE_SIZE = "auto". -IMAGE_AUTO_SIZE_MODELS_REGEX_PATTERN = os.getenv("IMAGE_AUTO_SIZE_MODELS_REGEX_PATTERN", "^gpt-image") +IMAGE_AUTO_SIZE_MODELS_REGEX_PATTERN = os.getenv( + "IMAGE_AUTO_SIZE_MODELS_REGEX_PATTERN", "^gpt-image" +) # Regex pattern for models that return URLs instead of base64 data. -IMAGE_URL_RESPONSE_MODELS_REGEX_PATTERN = os.getenv("IMAGE_URL_RESPONSE_MODELS_REGEX_PATTERN", "^gpt-image") +IMAGE_URL_RESPONSE_MODELS_REGEX_PATTERN = os.getenv( + "IMAGE_URL_RESPONSE_MODELS_REGEX_PATTERN", "^gpt-image" +) IMAGE_SIZE = PersistentConfig( "IMAGE_SIZE", "image_generation.size", os.getenv("IMAGE_SIZE", "512x512") diff --git a/backend/open_webui/env.py b/backend/open_webui/env.py index 4289475c04..d57f25dae6 100644 --- a/backend/open_webui/env.py +++ b/backend/open_webui/env.py @@ -195,13 +195,23 @@ ENABLE_FORWARD_USER_INFO_HEADERS = ( ) # Header names for user info forwarding (customizable via environment variables) -FORWARD_USER_INFO_HEADER_USER_NAME = os.environ.get("FORWARD_USER_INFO_HEADER_USER_NAME", "X-OpenWebUI-User-Name") -FORWARD_USER_INFO_HEADER_USER_ID = os.environ.get("FORWARD_USER_INFO_HEADER_USER_ID", "X-OpenWebUI-User-Id") -FORWARD_USER_INFO_HEADER_USER_EMAIL = os.environ.get("FORWARD_USER_INFO_HEADER_USER_EMAIL", "X-OpenWebUI-User-Email") -FORWARD_USER_INFO_HEADER_USER_ROLE = os.environ.get("FORWARD_USER_INFO_HEADER_USER_ROLE", "X-OpenWebUI-User-Role") +FORWARD_USER_INFO_HEADER_USER_NAME = os.environ.get( + "FORWARD_USER_INFO_HEADER_USER_NAME", "X-OpenWebUI-User-Name" +) +FORWARD_USER_INFO_HEADER_USER_ID = os.environ.get( + "FORWARD_USER_INFO_HEADER_USER_ID", "X-OpenWebUI-User-Id" +) +FORWARD_USER_INFO_HEADER_USER_EMAIL = os.environ.get( + "FORWARD_USER_INFO_HEADER_USER_EMAIL", "X-OpenWebUI-User-Email" +) +FORWARD_USER_INFO_HEADER_USER_ROLE = os.environ.get( + "FORWARD_USER_INFO_HEADER_USER_ROLE", "X-OpenWebUI-User-Role" +) # Header name for chat ID forwarding (customizable via environment variable) -FORWARD_SESSION_INFO_HEADER_CHAT_ID = os.environ.get("FORWARD_SESSION_INFO_HEADER_CHAT_ID", "X-OpenWebUI-Chat-Id") +FORWARD_SESSION_INFO_HEADER_CHAT_ID = os.environ.get( + "FORWARD_SESSION_INFO_HEADER_CHAT_ID", "X-OpenWebUI-Chat-Id" +) # Experimental feature, may be removed in future ENABLE_STAR_SESSIONS_MIDDLEWARE = ( @@ -401,18 +411,14 @@ try: REDIS_SOCKET_CONNECT_TIMEOUT = float(REDIS_SOCKET_CONNECT_TIMEOUT) except ValueError: REDIS_SOCKET_CONNECT_TIMEOUT = None - -REDIS_RECONNECT_DELAY = os.environ.get( - "REDIS_RECONNECT_DELAY", "" -) + +REDIS_RECONNECT_DELAY = os.environ.get("REDIS_RECONNECT_DELAY", "") if REDIS_RECONNECT_DELAY == "": REDIS_RECONNECT_DELAY = None else: try: - REDIS_RECONNECT_DELAY = float( - REDIS_RECONNECT_DELAY - ) + REDIS_RECONNECT_DELAY = float(REDIS_RECONNECT_DELAY) if REDIS_RECONNECT_DELAY < 0: REDIS_RECONNECT_DELAY = None except Exception: @@ -580,15 +586,11 @@ LICENSE_PUBLIC_KEY = os.environ.get("LICENSE_PUBLIC_KEY", "") pk = None if LICENSE_PUBLIC_KEY: - pk = serialization.load_pem_public_key( - f""" + pk = serialization.load_pem_public_key(f""" -----BEGIN PUBLIC KEY----- {LICENSE_PUBLIC_KEY} -----END PUBLIC KEY----- -""".encode( - "utf-8" - ) - ) +""".encode("utf-8")) #################################### diff --git a/backend/open_webui/functions.py b/backend/open_webui/functions.py index 5381daf0cc..364d2a889b 100644 --- a/backend/open_webui/functions.py +++ b/backend/open_webui/functions.py @@ -50,7 +50,6 @@ from open_webui.utils.payload import ( apply_system_prompt_to_body, ) - logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL) log = logging.getLogger(__name__) diff --git a/backend/open_webui/internal/migrations/001_initial_schema.py b/backend/open_webui/internal/migrations/001_initial_schema.py index 93f278f15b..0df2249b21 100644 --- a/backend/open_webui/internal/migrations/001_initial_schema.py +++ b/backend/open_webui/internal/migrations/001_initial_schema.py @@ -29,7 +29,6 @@ from contextlib import suppress import peewee as pw from peewee_migrate import Migrator - with suppress(ImportError): import playhouse.postgres_ext as pw_pext diff --git a/backend/open_webui/internal/migrations/002_add_local_sharing.py b/backend/open_webui/internal/migrations/002_add_local_sharing.py index e93501aeec..a01862d103 100644 --- a/backend/open_webui/internal/migrations/002_add_local_sharing.py +++ b/backend/open_webui/internal/migrations/002_add_local_sharing.py @@ -29,7 +29,6 @@ from contextlib import suppress import peewee as pw from peewee_migrate import Migrator - with suppress(ImportError): import playhouse.postgres_ext as pw_pext diff --git a/backend/open_webui/internal/migrations/003_add_auth_api_key.py b/backend/open_webui/internal/migrations/003_add_auth_api_key.py index 07144f3aca..23cba26383 100644 --- a/backend/open_webui/internal/migrations/003_add_auth_api_key.py +++ b/backend/open_webui/internal/migrations/003_add_auth_api_key.py @@ -29,7 +29,6 @@ from contextlib import suppress import peewee as pw from peewee_migrate import Migrator - with suppress(ImportError): import playhouse.postgres_ext as pw_pext diff --git a/backend/open_webui/internal/migrations/004_add_archived.py b/backend/open_webui/internal/migrations/004_add_archived.py index d01c06b4e6..11108a3e0b 100644 --- a/backend/open_webui/internal/migrations/004_add_archived.py +++ b/backend/open_webui/internal/migrations/004_add_archived.py @@ -29,7 +29,6 @@ from contextlib import suppress import peewee as pw from peewee_migrate import Migrator - with suppress(ImportError): import playhouse.postgres_ext as pw_pext diff --git a/backend/open_webui/internal/migrations/005_add_updated_at.py b/backend/open_webui/internal/migrations/005_add_updated_at.py index 950866ef02..f7fc69a5db 100644 --- a/backend/open_webui/internal/migrations/005_add_updated_at.py +++ b/backend/open_webui/internal/migrations/005_add_updated_at.py @@ -29,7 +29,6 @@ from contextlib import suppress import peewee as pw from peewee_migrate import Migrator - with suppress(ImportError): import playhouse.postgres_ext as pw_pext diff --git a/backend/open_webui/internal/migrations/006_migrate_timestamps_and_charfields.py b/backend/open_webui/internal/migrations/006_migrate_timestamps_and_charfields.py index caca14d323..abe7016c57 100644 --- a/backend/open_webui/internal/migrations/006_migrate_timestamps_and_charfields.py +++ b/backend/open_webui/internal/migrations/006_migrate_timestamps_and_charfields.py @@ -29,7 +29,6 @@ from contextlib import suppress import peewee as pw from peewee_migrate import Migrator - with suppress(ImportError): import playhouse.postgres_ext as pw_pext diff --git a/backend/open_webui/internal/migrations/007_add_user_last_active_at.py b/backend/open_webui/internal/migrations/007_add_user_last_active_at.py index dd176ba73e..3f89a5f59f 100644 --- a/backend/open_webui/internal/migrations/007_add_user_last_active_at.py +++ b/backend/open_webui/internal/migrations/007_add_user_last_active_at.py @@ -29,7 +29,6 @@ from contextlib import suppress import peewee as pw from peewee_migrate import Migrator - with suppress(ImportError): import playhouse.postgres_ext as pw_pext diff --git a/backend/open_webui/internal/migrations/008_add_memory.py b/backend/open_webui/internal/migrations/008_add_memory.py index 9307aa4d5c..96be907eba 100644 --- a/backend/open_webui/internal/migrations/008_add_memory.py +++ b/backend/open_webui/internal/migrations/008_add_memory.py @@ -29,7 +29,6 @@ from contextlib import suppress import peewee as pw from peewee_migrate import Migrator - with suppress(ImportError): import playhouse.postgres_ext as pw_pext diff --git a/backend/open_webui/internal/migrations/009_add_models.py b/backend/open_webui/internal/migrations/009_add_models.py index 548ec7cdca..0a8d73bd3b 100644 --- a/backend/open_webui/internal/migrations/009_add_models.py +++ b/backend/open_webui/internal/migrations/009_add_models.py @@ -29,7 +29,6 @@ from contextlib import suppress import peewee as pw from peewee_migrate import Migrator - with suppress(ImportError): import playhouse.postgres_ext as pw_pext diff --git a/backend/open_webui/internal/migrations/011_add_user_settings.py b/backend/open_webui/internal/migrations/011_add_user_settings.py index a1620dcada..c3b9ab6edc 100644 --- a/backend/open_webui/internal/migrations/011_add_user_settings.py +++ b/backend/open_webui/internal/migrations/011_add_user_settings.py @@ -29,7 +29,6 @@ from contextlib import suppress import peewee as pw from peewee_migrate import Migrator - with suppress(ImportError): import playhouse.postgres_ext as pw_pext diff --git a/backend/open_webui/internal/migrations/012_add_tools.py b/backend/open_webui/internal/migrations/012_add_tools.py index 4a68eea552..ac3cd8bfec 100644 --- a/backend/open_webui/internal/migrations/012_add_tools.py +++ b/backend/open_webui/internal/migrations/012_add_tools.py @@ -29,7 +29,6 @@ from contextlib import suppress import peewee as pw from peewee_migrate import Migrator - with suppress(ImportError): import playhouse.postgres_ext as pw_pext diff --git a/backend/open_webui/internal/migrations/013_add_user_info.py b/backend/open_webui/internal/migrations/013_add_user_info.py index 0f68669cca..6fafa951f0 100644 --- a/backend/open_webui/internal/migrations/013_add_user_info.py +++ b/backend/open_webui/internal/migrations/013_add_user_info.py @@ -29,7 +29,6 @@ from contextlib import suppress import peewee as pw from peewee_migrate import Migrator - with suppress(ImportError): import playhouse.postgres_ext as pw_pext diff --git a/backend/open_webui/internal/migrations/014_add_files.py b/backend/open_webui/internal/migrations/014_add_files.py index 5e1acf0ad8..655b00d238 100644 --- a/backend/open_webui/internal/migrations/014_add_files.py +++ b/backend/open_webui/internal/migrations/014_add_files.py @@ -29,7 +29,6 @@ from contextlib import suppress import peewee as pw from peewee_migrate import Migrator - with suppress(ImportError): import playhouse.postgres_ext as pw_pext diff --git a/backend/open_webui/internal/migrations/015_add_functions.py b/backend/open_webui/internal/migrations/015_add_functions.py index 8316a9333b..84d2843839 100644 --- a/backend/open_webui/internal/migrations/015_add_functions.py +++ b/backend/open_webui/internal/migrations/015_add_functions.py @@ -29,7 +29,6 @@ from contextlib import suppress import peewee as pw from peewee_migrate import Migrator - with suppress(ImportError): import playhouse.postgres_ext as pw_pext diff --git a/backend/open_webui/internal/migrations/016_add_valves_and_is_active.py b/backend/open_webui/internal/migrations/016_add_valves_and_is_active.py index e3af521b7e..fadf964e46 100644 --- a/backend/open_webui/internal/migrations/016_add_valves_and_is_active.py +++ b/backend/open_webui/internal/migrations/016_add_valves_and_is_active.py @@ -29,7 +29,6 @@ from contextlib import suppress import peewee as pw from peewee_migrate import Migrator - with suppress(ImportError): import playhouse.postgres_ext as pw_pext diff --git a/backend/open_webui/internal/migrations/017_add_user_oauth_sub.py b/backend/open_webui/internal/migrations/017_add_user_oauth_sub.py index eaa3fa5fe5..67a36b4889 100644 --- a/backend/open_webui/internal/migrations/017_add_user_oauth_sub.py +++ b/backend/open_webui/internal/migrations/017_add_user_oauth_sub.py @@ -25,7 +25,6 @@ from contextlib import suppress import peewee as pw from peewee_migrate import Migrator - with suppress(ImportError): import playhouse.postgres_ext as pw_pext diff --git a/backend/open_webui/internal/migrations/018_add_function_is_global.py b/backend/open_webui/internal/migrations/018_add_function_is_global.py index 04cdab7059..1e932ed710 100644 --- a/backend/open_webui/internal/migrations/018_add_function_is_global.py +++ b/backend/open_webui/internal/migrations/018_add_function_is_global.py @@ -29,7 +29,6 @@ from contextlib import suppress import peewee as pw from peewee_migrate import Migrator - with suppress(ImportError): import playhouse.postgres_ext as pw_pext diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index 4d838d251c..ce3ec4f562 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -551,7 +551,6 @@ from open_webui.utils.redis import get_sentinels_from_env from open_webui.constants import ERROR_MESSAGES - if SAFE_MODE: print("SAFE MODE ENABLED") Functions.deactivate_all_functions() @@ -575,8 +574,7 @@ class SPAStaticFiles(StaticFiles): raise ex -print( - rf""" +print(rf""" ██████╗ ██████╗ ███████╗███╗ ██╗ ██╗ ██╗███████╗██████╗ ██╗ ██╗██╗ ██╔═══██╗██╔══██╗██╔════╝████╗ ██║ ██║ ██║██╔════╝██╔══██╗██║ ██║██║ ██║ ██║██████╔╝█████╗ ██╔██╗ ██║ ██║ █╗ ██║█████╗ ██████╔╝██║ ██║██║ @@ -588,8 +586,7 @@ print( v{VERSION} - building the best AI user interface. {f"Commit: {WEBUI_BUILD_HASH}" if WEBUI_BUILD_HASH != "dev-build" else ""} https://github.com/open-webui/open-webui -""" -) +""") @asynccontextmanager @@ -1845,9 +1842,7 @@ async def chat_completion( # Emit chat:active=true when task starts event_emitter = get_event_emitter(metadata, update_db=False) if event_emitter: - await event_emitter( - {"type": "chat:active", "data": {"active": True}} - ) + await event_emitter({"type": "chat:active", "data": {"active": True}}) return {"status": True, "task_id": task_id} else: return await process_chat(request, form_data, user, metadata, model) diff --git a/backend/open_webui/migrations/versions/2f1211949ecc_update_message_and_channel_member_table.py b/backend/open_webui/migrations/versions/2f1211949ecc_update_message_and_channel_member_table.py index 2d72583ebe..1a4ae73180 100644 --- a/backend/open_webui/migrations/versions/2f1211949ecc_update_message_and_channel_member_table.py +++ b/backend/open_webui/migrations/versions/2f1211949ecc_update_message_and_channel_member_table.py @@ -12,7 +12,6 @@ from alembic import op import sqlalchemy as sa import open_webui.internal.db - # revision identifiers, used by Alembic. revision: str = "2f1211949ecc" down_revision: Union[str, None] = "37f288994c47" diff --git a/backend/open_webui/migrations/versions/374d2f66af06_add_prompt_history_table.py b/backend/open_webui/migrations/versions/374d2f66af06_add_prompt_history_table.py index c61196fcb0..57bc8748e3 100644 --- a/backend/open_webui/migrations/versions/374d2f66af06_add_prompt_history_table.py +++ b/backend/open_webui/migrations/versions/374d2f66af06_add_prompt_history_table.py @@ -12,7 +12,6 @@ import uuid from alembic import op import sqlalchemy as sa - revision: str = "374d2f66af06" down_revision: Union[str, None] = "c440947495f3" branch_labels: Union[str, Sequence[str], None] = None diff --git a/backend/open_webui/migrations/versions/37f288994c47_add_group_member_table.py b/backend/open_webui/migrations/versions/37f288994c47_add_group_member_table.py index 0c5cec1941..229bb8cffb 100644 --- a/backend/open_webui/migrations/versions/37f288994c47_add_group_member_table.py +++ b/backend/open_webui/migrations/versions/37f288994c47_add_group_member_table.py @@ -14,7 +14,6 @@ from typing import Sequence, Union from alembic import op import sqlalchemy as sa - # revision identifiers, used by Alembic. revision: str = "37f288994c47" down_revision: Union[str, None] = "a5c220713937" diff --git a/backend/open_webui/migrations/versions/38d63c18f30f_add_oauth_session_table.py b/backend/open_webui/migrations/versions/38d63c18f30f_add_oauth_session_table.py index 264ce13b41..af8340a3cb 100644 --- a/backend/open_webui/migrations/versions/38d63c18f30f_add_oauth_session_table.py +++ b/backend/open_webui/migrations/versions/38d63c18f30f_add_oauth_session_table.py @@ -11,7 +11,6 @@ from typing import Sequence, Union from alembic import op import sqlalchemy as sa - # revision identifiers, used by Alembic. revision: str = "38d63c18f30f" down_revision: Union[str, None] = "3af16a1c9fb6" diff --git a/backend/open_webui/migrations/versions/6283dc0e4d8d_add_channel_file_table.py b/backend/open_webui/migrations/versions/6283dc0e4d8d_add_channel_file_table.py index 59fe57a421..f3ef62fd64 100644 --- a/backend/open_webui/migrations/versions/6283dc0e4d8d_add_channel_file_table.py +++ b/backend/open_webui/migrations/versions/6283dc0e4d8d_add_channel_file_table.py @@ -12,7 +12,6 @@ from alembic import op import sqlalchemy as sa import open_webui.internal.db - # revision identifiers, used by Alembic. revision: str = "6283dc0e4d8d" down_revision: Union[str, None] = "3e0e00844bb0" diff --git a/backend/open_webui/migrations/versions/6a39f3d8e55c_add_knowledge_table.py b/backend/open_webui/migrations/versions/6a39f3d8e55c_add_knowledge_table.py index 881e6ae641..d6083d7177 100644 --- a/backend/open_webui/migrations/versions/6a39f3d8e55c_add_knowledge_table.py +++ b/backend/open_webui/migrations/versions/6a39f3d8e55c_add_knowledge_table.py @@ -11,7 +11,6 @@ import sqlalchemy as sa from sqlalchemy.sql import table, column, select import json - revision = "6a39f3d8e55c" down_revision = "c0fbf31ca0db" branch_labels = None diff --git a/backend/open_webui/migrations/versions/81cc2ce44d79_update_channel_file_and_knowledge_table.py b/backend/open_webui/migrations/versions/81cc2ce44d79_update_channel_file_and_knowledge_table.py index 181b280666..3853ec50d9 100644 --- a/backend/open_webui/migrations/versions/81cc2ce44d79_update_channel_file_and_knowledge_table.py +++ b/backend/open_webui/migrations/versions/81cc2ce44d79_update_channel_file_and_knowledge_table.py @@ -12,7 +12,6 @@ from alembic import op import sqlalchemy as sa import open_webui.internal.db - # revision identifiers, used by Alembic. revision: str = "81cc2ce44d79" down_revision: Union[str, None] = "6283dc0e4d8d" diff --git a/backend/open_webui/migrations/versions/8452d01d26d7_add_chat_message_table.py b/backend/open_webui/migrations/versions/8452d01d26d7_add_chat_message_table.py index 28b5340d77..c8a3647aec 100644 --- a/backend/open_webui/migrations/versions/8452d01d26d7_add_chat_message_table.py +++ b/backend/open_webui/migrations/versions/8452d01d26d7_add_chat_message_table.py @@ -165,7 +165,9 @@ def upgrade() -> None: log.warning(f"Failed to insert message {message_id}: {e}") continue - log.info(f"Backfilled {messages_inserted} messages into chat_message table ({messages_failed} failed)") + log.info( + f"Backfilled {messages_inserted} messages into chat_message table ({messages_failed} failed)" + ) def downgrade() -> None: diff --git a/backend/open_webui/migrations/versions/90ef40d4714e_update_channel_and_channel_members_table.py b/backend/open_webui/migrations/versions/90ef40d4714e_update_channel_and_channel_members_table.py index 8c52a4b22a..8b9e338309 100644 --- a/backend/open_webui/migrations/versions/90ef40d4714e_update_channel_and_channel_members_table.py +++ b/backend/open_webui/migrations/versions/90ef40d4714e_update_channel_and_channel_members_table.py @@ -12,7 +12,6 @@ from alembic import op import sqlalchemy as sa import open_webui.internal.db - # revision identifiers, used by Alembic. revision: str = "90ef40d4714e" down_revision: Union[str, None] = "b10670c03dd5" diff --git a/backend/open_webui/migrations/versions/b10670c03dd5_update_user_table.py b/backend/open_webui/migrations/versions/b10670c03dd5_update_user_table.py index f35a382645..0472c08616 100644 --- a/backend/open_webui/migrations/versions/b10670c03dd5_update_user_table.py +++ b/backend/open_webui/migrations/versions/b10670c03dd5_update_user_table.py @@ -173,12 +173,10 @@ def upgrade() -> None: for uid, api_key in users_with_keys: if api_key: conn.execute( - sa.text( - """ + sa.text(""" INSERT INTO api_key (id, user_id, key, created_at, updated_at) VALUES (:id, :user_id, :key, :created_at, :updated_at) - """ - ), + """), { "id": f"key_{uid}", "user_id": uid, diff --git a/backend/open_webui/migrations/versions/c29facfe716b_update_file_table_path.py b/backend/open_webui/migrations/versions/c29facfe716b_update_file_table_path.py index de82854b88..7786de425f 100644 --- a/backend/open_webui/migrations/versions/c29facfe716b_update_file_table_path.py +++ b/backend/open_webui/migrations/versions/c29facfe716b_update_file_table_path.py @@ -12,7 +12,6 @@ import json from sqlalchemy.sql import table, column from sqlalchemy import String, Text, JSON, and_ - revision = "c29facfe716b" down_revision = "c69f45358db4" branch_labels = None diff --git a/backend/open_webui/migrations/versions/c440947495f3_add_chat_file_table.py b/backend/open_webui/migrations/versions/c440947495f3_add_chat_file_table.py index 20f4a6d7b6..fa818e1f08 100644 --- a/backend/open_webui/migrations/versions/c440947495f3_add_chat_file_table.py +++ b/backend/open_webui/migrations/versions/c440947495f3_add_chat_file_table.py @@ -11,7 +11,6 @@ from typing import Sequence, Union from alembic import op import sqlalchemy as sa - # revision identifiers, used by Alembic. revision: str = "c440947495f3" down_revision: Union[str, None] = "81cc2ce44d79" diff --git a/backend/open_webui/migrations/versions/f1e2d3c4b5a6_add_access_grant_table.py b/backend/open_webui/migrations/versions/f1e2d3c4b5a6_add_access_grant_table.py index 1b76e67c31..5569718dd8 100644 --- a/backend/open_webui/migrations/versions/f1e2d3c4b5a6_add_access_grant_table.py +++ b/backend/open_webui/migrations/versions/f1e2d3c4b5a6_add_access_grant_table.py @@ -98,26 +98,27 @@ def upgrade() -> None: # Could be Python None (SQL NULL) or string "null" (JSON null) # EXCEPTION: files with NULL are PRIVATE (owner-only), not public is_null = ( - access_control_json is None or - access_control_json == "null" or - (isinstance(access_control_json, str) and access_control_json.strip().lower() == "null") + access_control_json is None + or access_control_json == "null" + or ( + isinstance(access_control_json, str) + and access_control_json.strip().lower() == "null" + ) ) if is_null: # Files: NULL = private (no entry needed, owner has implicit access) # Other resources: NULL = public (insert user:* for read) if resource_type == "file": continue # Private - no entry needed - + key = (resource_type, resource_id, "user", "*", "read") if key not in inserted: try: conn.execute( - sa.text( - """ + sa.text(""" INSERT INTO access_grant (id, resource_type, resource_id, principal_type, principal_id, permission, created_at) VALUES (:id, :resource_type, :resource_id, :principal_type, :principal_id, :permission, :created_at) - """ - ), + """), { "id": str(uuid.uuid4()), "resource_type": resource_type, @@ -174,12 +175,10 @@ def upgrade() -> None: continue try: conn.execute( - sa.text( - """ + sa.text(""" INSERT INTO access_grant (id, resource_type, resource_id, principal_type, principal_id, permission, created_at) VALUES (:id, :resource_type, :resource_id, :principal_type, :principal_id, :permission, :created_at) - """ - ), + """), { "id": str(uuid.uuid4()), "resource_type": resource_type, @@ -200,12 +199,10 @@ def upgrade() -> None: continue try: conn.execute( - sa.text( - """ + sa.text(""" INSERT INTO access_grant (id, resource_type, resource_id, principal_type, principal_id, permission, created_at) VALUES (:id, :resource_type, :resource_id, :principal_type, :principal_id, :permission, :created_at) - """ - ), + """), { "id": str(uuid.uuid4()), "resource_type": resource_type, @@ -233,9 +230,9 @@ def upgrade() -> None: def downgrade() -> None: import json - + conn = op.get_bind() - + # Resource tables mapping: (table_name, resource_type) resource_tables = [ ("knowledge", "knowledge"), @@ -265,7 +262,7 @@ def downgrade() -> None: FROM access_grant WHERE resource_type = :resource_type """), - {"resource_type": resource_type} + {"resource_type": resource_type}, ) rows = result.fetchall() except Exception: @@ -287,39 +284,61 @@ def downgrade() -> None: } # Handle public access (user:* for read) - if principal_type == "user" and principal_id == "*" and permission == "read": + if ( + principal_type == "user" + and principal_id == "*" + and permission == "read" + ): resource_grants[resource_id]["is_public"] = True continue # Add to appropriate list if permission in ["read", "write"]: if principal_type == "group": - if principal_id not in resource_grants[resource_id][permission]["group_ids"]: - resource_grants[resource_id][permission]["group_ids"].append(principal_id) + if ( + principal_id + not in resource_grants[resource_id][permission]["group_ids"] + ): + resource_grants[resource_id][permission]["group_ids"].append( + principal_id + ) elif principal_type == "user": - if principal_id not in resource_grants[resource_id][permission]["user_ids"]: - resource_grants[resource_id][permission]["user_ids"].append(principal_id) + if ( + principal_id + not in resource_grants[resource_id][permission]["user_ids"] + ): + resource_grants[resource_id][permission]["user_ids"].append( + principal_id + ) # Step 3: Update each resource with reconstructed JSON for resource_id, grants in resource_grants.items(): if grants["is_public"]: # Public = NULL access_control_value = None - elif (not grants["read"]["group_ids"] and not grants["read"]["user_ids"] and - not grants["write"]["group_ids"] and not grants["write"]["user_ids"]): + elif ( + not grants["read"]["group_ids"] + and not grants["read"]["user_ids"] + and not grants["write"]["group_ids"] + and not grants["write"]["user_ids"] + ): # No grants = should not happen (would mean no entries), default to {} access_control_value = json.dumps({}) else: # Custom permissions - access_control_value = json.dumps({ - "read": grants["read"], - "write": grants["write"], - }) + access_control_value = json.dumps( + { + "read": grants["read"], + "write": grants["write"], + } + ) try: conn.execute( - sa.text(f'UPDATE "{table_name}" SET access_control = :access_control WHERE id = :id'), - {"access_control": access_control_value, "id": resource_id} + sa.text( + f'UPDATE "{table_name}" SET access_control = :access_control WHERE id = :id' + ), + {"access_control": access_control_value, "id": resource_id}, ) except Exception: pass @@ -330,15 +349,15 @@ def downgrade() -> None: if resource_type != "file": try: conn.execute( - sa.text(f''' + sa.text(f""" UPDATE "{table_name}" SET access_control = :private_value WHERE id NOT IN ( SELECT DISTINCT resource_id FROM access_grant WHERE resource_type = :resource_type ) AND access_control IS NULL - '''), - {"private_value": json.dumps({}), "resource_type": resource_type} + """), + {"private_value": json.dumps({}), "resource_type": resource_type}, ) except Exception: pass diff --git a/backend/open_webui/models/access_grants.py b/backend/open_webui/models/access_grants.py index aac475f3e1..fa6e79a8db 100644 --- a/backend/open_webui/models/access_grants.py +++ b/backend/open_webui/models/access_grants.py @@ -22,10 +22,14 @@ class AccessGrant(Base): __tablename__ = "access_grant" id = Column(Text, primary_key=True) - resource_type = Column(Text, nullable=False) # "knowledge", "model", "prompt", "tool", "note", "channel", "file" + resource_type = Column( + Text, nullable=False + ) # "knowledge", "model", "prompt", "tool", "note", "channel", "file" resource_id = Column(Text, nullable=False) principal_type = Column(Text, nullable=False) # "user" or "group" - principal_id = Column(Text, nullable=False) # user_id, group_id, or "*" (wildcard for public) + principal_id = Column( + Text, nullable=False + ) # user_id, group_id, or "*" (wildcard for public) permission = Column(Text, nullable=False) # "read" or "write" created_at = Column(BigInteger, nullable=False) @@ -173,9 +177,11 @@ def normalize_access_grants(access_grants: Optional[list]) -> list[dict]: key = (principal_type, principal_id, permission) deduped[key] = { - "id": grant.get("id") - if isinstance(grant.get("id"), str) and grant.get("id") - else str(uuid.uuid4()), + "id": ( + grant.get("id") + if isinstance(grant.get("id"), str) and grant.get("id") + else str(uuid.uuid4()) + ), "principal_type": principal_type, "principal_id": principal_id, "permission": permission, diff --git a/backend/open_webui/models/channels.py b/backend/open_webui/models/channels.py index 3ff6fb7554..8a55da9345 100644 --- a/backend/open_webui/models/channels.py +++ b/backend/open_webui/models/channels.py @@ -263,7 +263,9 @@ class ChannelTable: def _to_channel_model( self, channel: Channel, db: Optional[Session] = None ) -> ChannelModel: - channel_data = ChannelModel.model_validate(channel).model_dump(exclude={"access_grants"}) + channel_data = ChannelModel.model_validate(channel).model_dump( + exclude={"access_grants"} + ) access_grants = self._get_access_grants(channel_data["id"], db=db) channel_data["access_grants"] = access_grants return ChannelModel.model_validate(channel_data) @@ -770,9 +772,7 @@ class ChannelTable: .first() ) if membership: - allowed_channels.append( - self._to_channel_model(channel, db=db) - ) + allowed_channels.append(self._to_channel_model(channel, db=db)) continue # --- Case B: standard channel => rely on ACL permissions --- diff --git a/backend/open_webui/models/chat_messages.py b/backend/open_webui/models/chat_messages.py index c00c6f750f..fe3539f9cd 100644 --- a/backend/open_webui/models/chat_messages.py +++ b/backend/open_webui/models/chat_messages.py @@ -332,7 +332,11 @@ class ChatMessageTable: if end_date: query = query.filter(ChatMessage.created_at <= end_date) if group_id: - group_users = db.query(GroupMember.user_id).filter(GroupMember.group_id == group_id).subquery() + group_users = ( + db.query(GroupMember.user_id) + .filter(GroupMember.group_id == group_id) + .subquery() + ) query = query.filter(ChatMessage.user_id.in_(group_users)) results = query.group_by(ChatMessage.model_id).all() @@ -362,10 +366,12 @@ class ChatMessageTable: elif dialect == "postgresql": # Use json_extract_path_text for PostgreSQL JSON columns input_tokens = cast( - func.json_extract_path_text(ChatMessage.usage, "input_tokens"), Integer + func.json_extract_path_text(ChatMessage.usage, "input_tokens"), + Integer, ) output_tokens = cast( - func.json_extract_path_text(ChatMessage.usage, "output_tokens"), Integer + func.json_extract_path_text(ChatMessage.usage, "output_tokens"), + Integer, ) else: raise NotImplementedError(f"Unsupported dialect: {dialect}") @@ -387,7 +393,11 @@ class ChatMessageTable: if end_date: query = query.filter(ChatMessage.created_at <= end_date) if group_id: - group_users = db.query(GroupMember.user_id).filter(GroupMember.group_id == group_id).subquery() + group_users = ( + db.query(GroupMember.user_id) + .filter(GroupMember.group_id == group_id) + .subquery() + ) query = query.filter(ChatMessage.user_id.in_(group_users)) results = query.group_by(ChatMessage.model_id).all() @@ -424,10 +434,12 @@ class ChatMessageTable: elif dialect == "postgresql": # Use json_extract_path_text for PostgreSQL JSON columns input_tokens = cast( - func.json_extract_path_text(ChatMessage.usage, "input_tokens"), Integer + func.json_extract_path_text(ChatMessage.usage, "input_tokens"), + Integer, ) output_tokens = cast( - func.json_extract_path_text(ChatMessage.usage, "output_tokens"), Integer + func.json_extract_path_text(ChatMessage.usage, "output_tokens"), + Integer, ) else: raise NotImplementedError(f"Unsupported dialect: {dialect}") @@ -481,7 +493,11 @@ class ChatMessageTable: if end_date: query = query.filter(ChatMessage.created_at <= end_date) if group_id: - group_users = db.query(GroupMember.user_id).filter(GroupMember.group_id == group_id).subquery() + group_users = ( + db.query(GroupMember.user_id) + .filter(GroupMember.group_id == group_id) + .subquery() + ) query = query.filter(ChatMessage.user_id.in_(group_users)) results = query.group_by(ChatMessage.user_id).all() @@ -507,7 +523,11 @@ class ChatMessageTable: if end_date: query = query.filter(ChatMessage.created_at <= end_date) if group_id: - group_users = db.query(GroupMember.user_id).filter(GroupMember.group_id == group_id).subquery() + group_users = ( + db.query(GroupMember.user_id) + .filter(GroupMember.group_id == group_id) + .subquery() + ) query = query.filter(ChatMessage.user_id.in_(group_users)) results = query.group_by(ChatMessage.chat_id).all() @@ -536,7 +556,11 @@ class ChatMessageTable: if end_date: query = query.filter(ChatMessage.created_at <= end_date) if group_id: - group_users = db.query(GroupMember.user_id).filter(GroupMember.group_id == group_id).subquery() + group_users = ( + db.query(GroupMember.user_id) + .filter(GroupMember.group_id == group_id) + .subquery() + ) query = query.filter(ChatMessage.user_id.in_(group_users)) results = query.all() @@ -544,10 +568,14 @@ class ChatMessageTable: # Group by date -> model -> count daily_counts: dict[str, dict[str, int]] = {} for timestamp, model_id in results: - date_str = datetime.fromtimestamp(_normalize_timestamp(timestamp)).strftime("%Y-%m-%d") + date_str = datetime.fromtimestamp( + _normalize_timestamp(timestamp) + ).strftime("%Y-%m-%d") if date_str not in daily_counts: daily_counts[date_str] = {} - daily_counts[date_str][model_id] = daily_counts[date_str].get(model_id, 0) + 1 + daily_counts[date_str][model_id] = ( + daily_counts[date_str].get(model_id, 0) + 1 + ) # Fill in missing days if start_date and end_date: @@ -587,14 +615,20 @@ class ChatMessageTable: # Group by hour -> model -> count hourly_counts: dict[str, dict[str, int]] = {} for timestamp, model_id in results: - hour_str = datetime.fromtimestamp(_normalize_timestamp(timestamp)).strftime("%Y-%m-%d %H:00") + hour_str = datetime.fromtimestamp( + _normalize_timestamp(timestamp) + ).strftime("%Y-%m-%d %H:00") if hour_str not in hourly_counts: hourly_counts[hour_str] = {} - hourly_counts[hour_str][model_id] = hourly_counts[hour_str].get(model_id, 0) + 1 + hourly_counts[hour_str][model_id] = ( + hourly_counts[hour_str].get(model_id, 0) + 1 + ) # Fill in missing hours if start_date and end_date: - current = datetime.fromtimestamp(_normalize_timestamp(start_date)).replace(minute=0, second=0, microsecond=0) + current = datetime.fromtimestamp( + _normalize_timestamp(start_date) + ).replace(minute=0, second=0, microsecond=0) end_dt = datetime.fromtimestamp(_normalize_timestamp(end_date)) while current <= end_dt: hour_str = current.strftime("%Y-%m-%d %H:00") diff --git a/backend/open_webui/models/chats.py b/backend/open_webui/models/chats.py index 51a714cea7..6040050fc3 100644 --- a/backend/open_webui/models/chats.py +++ b/backend/open_webui/models/chats.py @@ -329,7 +329,9 @@ class ChatTable: data=message, ) except Exception as e: - log.warning(f"Failed to write initial messages to chat_message table: {e}") + log.warning( + f"Failed to write initial messages to chat_message table: {e}" + ) return ChatModel.model_validate(chat_item) if chat_item else None @@ -388,7 +390,9 @@ class ChatTable: data=message, ) except Exception as e: - log.warning(f"Failed to write imported messages to chat_message table: {e}") + log.warning( + f"Failed to write imported messages to chat_message table: {e}" + ) return [ChatModel.model_validate(chat) for chat in chats] @@ -739,8 +743,10 @@ class ChatTable: ) -> list[ChatModel]: with get_db_context(db) as db: - query = db.query(Chat).filter_by(user_id=user_id).filter( - Chat.share_id.isnot(None) + query = ( + db.query(Chat) + .filter_by(user_id=user_id) + .filter(Chat.share_id.isnot(None)) ) if filter: @@ -1110,29 +1116,23 @@ class ChatTable: # Check if there are any tags to filter, it should have all the tags if "none" in tag_ids: - query = query.filter( - text( - """ + query = query.filter(text(""" NOT EXISTS ( SELECT 1 FROM json_each(Chat.meta, '$.tags') AS tag ) - """ - ) - ) + """)) elif tag_ids: query = query.filter( and_( *[ - text( - f""" + text(f""" EXISTS ( SELECT 1 FROM json_each(Chat.meta, '$.tags') AS tag WHERE tag.value = :tag_id_{tag_idx} ) - """ - ).params(**{f"tag_id_{tag_idx}": tag_id}) + """).params(**{f"tag_id_{tag_idx}": tag_id}) for tag_idx, tag_id in enumerate(tag_ids) ] ) @@ -1168,29 +1168,23 @@ class ChatTable: # Check if there are any tags to filter, it should have all the tags if "none" in tag_ids: - query = query.filter( - text( - """ + query = query.filter(text(""" NOT EXISTS ( SELECT 1 FROM json_array_elements_text(Chat.meta->'tags') AS tag ) - """ - ) - ) + """)) elif tag_ids: query = query.filter( and_( *[ - text( - f""" + text(f""" EXISTS ( SELECT 1 FROM json_array_elements_text(Chat.meta->'tags') AS tag WHERE tag = :tag_id_{tag_idx} ) - """ - ).params(**{f"tag_id_{tag_idx}": tag_id}) + """).params(**{f"tag_id_{tag_idx}": tag_id}) for tag_idx, tag_id in enumerate(tag_ids) ] ) diff --git a/backend/open_webui/models/files.py b/backend/open_webui/models/files.py index 67f2891605..09060f9bde 100644 --- a/backend/open_webui/models/files.py +++ b/backend/open_webui/models/files.py @@ -65,7 +65,7 @@ class FileMeta(BaseModel): """Sanitize metadata fields to handle malformed legacy data.""" if not isinstance(data, dict): return data - + # Handle content_type that may be a list like ['application/pdf', None] content_type = data.get("content_type") if isinstance(content_type, list): @@ -75,7 +75,7 @@ class FileMeta(BaseModel): ) elif content_type is not None and not isinstance(content_type, str): data["content_type"] = None - + return data diff --git a/backend/open_webui/models/folders.py b/backend/open_webui/models/folders.py index 3455208944..24a872bcc4 100644 --- a/backend/open_webui/models/folders.py +++ b/backend/open_webui/models/folders.py @@ -11,7 +11,6 @@ from sqlalchemy.orm import Session from open_webui.internal.db import Base, JSONField, get_db, get_db_context - log = logging.getLogger(__name__) diff --git a/backend/open_webui/models/functions.py b/backend/open_webui/models/functions.py index c41b328317..fdbfac5e7c 100644 --- a/backend/open_webui/models/functions.py +++ b/backend/open_webui/models/functions.py @@ -214,7 +214,6 @@ class FunctionsTable: except Exception: return [] - def get_functions( self, active_only=False, include_valves=False, db: Optional[Session] = None ) -> list[FunctionModel | FunctionWithValvesModel]: diff --git a/backend/open_webui/models/groups.py b/backend/open_webui/models/groups.py index 0859c053aa..73d562dba6 100644 --- a/backend/open_webui/models/groups.py +++ b/backend/open_webui/models/groups.py @@ -25,7 +25,6 @@ from sqlalchemy import ( select, ) - log = logging.getLogger(__name__) #################### @@ -182,12 +181,12 @@ class GroupTable: if share_value: # Groups open to anyone: data is null, config.share is null, or share is true # Use case-insensitive string comparison to handle variations like "True", "TRUE" - # Handle potential JSON boolean to string casting issues by checking for both string 'true' and boolean equivalence if possible, + # Handle potential JSON boolean to string casting issues by checking for both string 'true' and boolean equivalence if possible, anyone_can_share = or_( Group.data.is_(None), json_share_str.is_(None), json_share_lower == "true", - json_share_lower == "1", # Handle SQLite boolean true + json_share_lower == "1", # Handle SQLite boolean true ) if member_id: diff --git a/backend/open_webui/models/knowledge.py b/backend/open_webui/models/knowledge.py index 817cab5caf..1d21d5d910 100644 --- a/backend/open_webui/models/knowledge.py +++ b/backend/open_webui/models/knowledge.py @@ -30,7 +30,6 @@ from sqlalchemy import ( or_, ) - log = logging.getLogger(__name__) #################### @@ -402,9 +401,7 @@ class KnowledgeTable: try: with get_db_context(db) as db: knowledge = db.query(Knowledge).filter_by(id=id).first() - return ( - self._to_knowledge_model(knowledge, db=db) if knowledge else None - ) + return self._to_knowledge_model(knowledge, db=db) if knowledge else None except Exception: return None @@ -443,7 +440,10 @@ class KnowledgeTable: .filter(KnowledgeFile.file_id == file_id) .all() ) - return [self._to_knowledge_model(knowledge, db=db) for knowledge in knowledges] + return [ + self._to_knowledge_model(knowledge, db=db) + for knowledge in knowledges + ] except Exception: return [] @@ -484,11 +484,17 @@ class KnowledgeTable: is_asc = direction == "asc" if order_by == "name": - primary_sort = File.filename.asc() if is_asc else File.filename.desc() + primary_sort = ( + File.filename.asc() if is_asc else File.filename.desc() + ) elif order_by == "created_at": - primary_sort = File.created_at.asc() if is_asc else File.created_at.desc() + primary_sort = ( + File.created_at.asc() if is_asc else File.created_at.desc() + ) elif order_by == "updated_at": - primary_sort = File.updated_at.asc() if is_asc else File.updated_at.desc() + primary_sort = ( + File.updated_at.asc() if is_asc else File.updated_at.desc() + ) # Apply sort with secondary key for deterministic pagination query = query.order_by(primary_sort, File.id.asc()) diff --git a/backend/open_webui/models/models.py b/backend/open_webui/models/models.py index d523ae0fc1..cfece00e35 100755 --- a/backend/open_webui/models/models.py +++ b/backend/open_webui/models/models.py @@ -18,7 +18,6 @@ from sqlalchemy.dialects import postgresql, sqlite from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy import BigInteger, Column, Text, Boolean - log = logging.getLogger(__name__) @@ -182,7 +181,9 @@ class ModelsTable: def get_all_models(self, db: Optional[Session] = None) -> list[ModelModel]: with get_db_context(db) as db: - return [self._to_model_model(model, db=db) for model in db.query(Model).all()] + return [ + self._to_model_model(model, db=db) for model in db.query(Model).all() + ] def get_models(self, db: Optional[Session] = None) -> list[ModelUserResponse]: with get_db_context(db) as db: diff --git a/backend/open_webui/models/prompt_history.py b/backend/open_webui/models/prompt_history.py index 0f5e7cea87..91ca4cb445 100644 --- a/backend/open_webui/models/prompt_history.py +++ b/backend/open_webui/models/prompt_history.py @@ -13,7 +13,6 @@ from open_webui.models.users import Users, UserResponse from pydantic import BaseModel, ConfigDict from sqlalchemy import BigInteger, Column, Text, JSON, Index - #################### # PromptHistory DB Schema #################### diff --git a/backend/open_webui/models/prompts.py b/backend/open_webui/models/prompts.py index 7bc1f9dee4..3ab7a496ab 100644 --- a/backend/open_webui/models/prompts.py +++ b/backend/open_webui/models/prompts.py @@ -13,7 +13,6 @@ from open_webui.models.access_grants import AccessGrantModel, AccessGrants from pydantic import BaseModel, ConfigDict, Field from sqlalchemy import BigInteger, Boolean, Column, String, Text, JSON, or_, func, cast - #################### # Prompts DB Schema #################### @@ -146,7 +145,9 @@ class PromptsTable: "data": form_data.data or {}, "meta": form_data.meta or {}, "tags": form_data.tags or [], - "access_grants": [grant.model_dump() for grant in current_access_grants], + "access_grants": [ + grant.model_dump() for grant in current_access_grants + ], } history_entry = PromptHistories.create_history_entry( @@ -345,7 +346,6 @@ class PromptsTable: return PromptListResponse(items=prompts, total=total) def update_prompt_by_command( - self, command: str, form_data: PromptForm, @@ -450,7 +450,7 @@ class PromptsTable: prompt.content = form_data.content prompt.data = form_data.data or prompt.data prompt.meta = form_data.meta or prompt.meta - + if form_data.tags is not None: prompt.tags = form_data.tags @@ -459,7 +459,7 @@ class PromptsTable: "prompt", prompt.id, form_data.access_grants, db=db ) current_access_grants = self._get_access_grants(prompt.id, db=db) - + prompt.updated_at = int(time.time()) db.commit() @@ -510,16 +510,16 @@ class PromptsTable: prompt = db.query(Prompt).filter_by(id=prompt_id).first() if not prompt: return None - + prompt.name = name prompt.command = command - + if tags is not None: prompt.tags = tags - + prompt.updated_at = int(time.time()) db.commit() - + return self._to_prompt_model(prompt, db=db) except Exception: return None diff --git a/backend/open_webui/models/skills.py b/backend/open_webui/models/skills.py index 00239e6afd..71e8f97b31 100644 --- a/backend/open_webui/models/skills.py +++ b/backend/open_webui/models/skills.py @@ -11,7 +11,6 @@ from open_webui.models.access_grants import AccessGrantModel, AccessGrants from pydantic import BaseModel, ConfigDict, Field from sqlalchemy import BigInteger, Boolean, Column, String, Text, or_ - log = logging.getLogger(__name__) #################### @@ -112,7 +111,9 @@ class SkillsTable: return AccessGrants.get_grants_by_resource("skill", skill_id, db=db) def _to_skill_model(self, skill: Skill, db: Optional[Session] = None) -> SkillModel: - skill_data = SkillModel.model_validate(skill).model_dump(exclude={"access_grants"}) + skill_data = SkillModel.model_validate(skill).model_dump( + exclude={"access_grants"} + ) skill_data["access_grants"] = self._get_access_grants(skill_data["id"], db=db) return SkillModel.model_validate(skill_data) @@ -223,9 +224,7 @@ class SkillsTable: from open_webui.models.users import User, UserModel # Join with User table for user filtering - query = db.query(Skill, User).outerjoin( - User, User.id == Skill.user_id - ) + query = db.query(Skill, User).outerjoin(User, User.id == Skill.user_id) if filter: query_key = filter.get("query") diff --git a/backend/open_webui/models/tools.py b/backend/open_webui/models/tools.py index da439161e9..eaac4c385d 100644 --- a/backend/open_webui/models/tools.py +++ b/backend/open_webui/models/tools.py @@ -11,7 +11,6 @@ from open_webui.models.access_grants import AccessGrantModel, AccessGrants from pydantic import BaseModel, ConfigDict, Field from sqlalchemy import BigInteger, Column, String, Text - log = logging.getLogger(__name__) #################### diff --git a/backend/open_webui/retrieval/models/external.py b/backend/open_webui/retrieval/models/external.py index 095143d20d..cd24dc6af2 100644 --- a/backend/open_webui/retrieval/models/external.py +++ b/backend/open_webui/retrieval/models/external.py @@ -8,7 +8,6 @@ from open_webui.env import ENABLE_FORWARD_USER_INFO_HEADERS, REQUESTS_VERIFY from open_webui.retrieval.models.base_reranker import BaseReranker from open_webui.utils.headers import include_user_info_headers - log = logging.getLogger(__name__) diff --git a/backend/open_webui/retrieval/utils.py b/backend/open_webui/retrieval/utils.py index fe7049a100..96fd9d3f89 100644 --- a/backend/open_webui/retrieval/utils.py +++ b/backend/open_webui/retrieval/utils.py @@ -695,7 +695,9 @@ async def agenerate_azure_openai_batch_embeddings( trust_env=True, timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT) ) as session: async with session.post( - full_url, headers=headers, json=form_data, + full_url, + headers=headers, + json=form_data, ssl=AIOHTTP_CLIENT_SESSION_SSL, ) as r: r.raise_for_status() diff --git a/backend/open_webui/retrieval/vector/dbs/oracle23ai.py b/backend/open_webui/retrieval/vector/dbs/oracle23ai.py index 9f16f82bc9..f4258c9eff 100644 --- a/backend/open_webui/retrieval/vector/dbs/oracle23ai.py +++ b/backend/open_webui/retrieval/vector/dbs/oracle23ai.py @@ -256,8 +256,7 @@ class Oracle23aiClient(VectorDBBase): with connection.cursor() as cursor: try: log.info("Creating Table document_chunk") - cursor.execute( - """ + cursor.execute(""" BEGIN EXECUTE IMMEDIATE ' CREATE TABLE IF NOT EXISTS document_chunk ( @@ -274,12 +273,10 @@ class Oracle23aiClient(VectorDBBase): RAISE; END IF; END; - """ - ) + """) log.info("Creating Index document_chunk_collection_name_idx") - cursor.execute( - """ + cursor.execute(""" BEGIN EXECUTE IMMEDIATE ' CREATE INDEX IF NOT EXISTS document_chunk_collection_name_idx @@ -291,12 +288,10 @@ class Oracle23aiClient(VectorDBBase): RAISE; END IF; END; - """ - ) + """) log.info("Creating VECTOR INDEX document_chunk_vector_ivf_idx") - cursor.execute( - """ + cursor.execute(""" BEGIN EXECUTE IMMEDIATE ' CREATE VECTOR INDEX IF NOT EXISTS document_chunk_vector_ivf_idx @@ -312,8 +307,7 @@ class Oracle23aiClient(VectorDBBase): RAISE; END IF; END; - """ - ) + """) connection.commit() log.info("Database initialization completed successfully.") diff --git a/backend/open_webui/retrieval/vector/dbs/pgvector.py b/backend/open_webui/retrieval/vector/dbs/pgvector.py index 15430db114..481f9d92fc 100644 --- a/backend/open_webui/retrieval/vector/dbs/pgvector.py +++ b/backend/open_webui/retrieval/vector/dbs/pgvector.py @@ -51,7 +51,6 @@ from open_webui.config import ( PGVECTOR_USE_HALFVEC, ) - VECTOR_LENGTH = PGVECTOR_INITIALIZE_MAX_VECTOR_LENGTH USE_HALFVEC = PGVECTOR_USE_HALFVEC @@ -121,34 +120,26 @@ class PgvectorClient(VectorDBBase): # Ensure the pgvector extension is available # Use a conditional check to avoid permission issues on Azure PostgreSQL if PGVECTOR_CREATE_EXTENSION: - self.session.execute( - text( - """ + self.session.execute(text(""" DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'vector') THEN CREATE EXTENSION IF NOT EXISTS vector; END IF; END $$; - """ - ) - ) + """)) if PGVECTOR_PGCRYPTO: # Ensure the pgcrypto extension is available for encryption # Use a conditional check to avoid permission issues on Azure PostgreSQL - self.session.execute( - text( - """ + self.session.execute(text(""" DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pgcrypto') THEN CREATE EXTENSION IF NOT EXISTS pgcrypto; END IF; END $$; - """ - ) - ) + """)) if not PGVECTOR_PGCRYPTO_KEY: raise ValueError( @@ -216,15 +207,13 @@ class PgvectorClient(VectorDBBase): def _ensure_vector_index(self, index_method: str, index_options: str) -> None: index_name = "idx_document_chunk_vector" existing_index_def = self.session.execute( - text( - """ + text(""" SELECT indexdef FROM pg_indexes WHERE schemaname = current_schema() AND tablename = 'document_chunk' AND indexname = :index_name - """ - ), + """), {"index_name": index_name}, ).scalar() @@ -310,8 +299,7 @@ class PgvectorClient(VectorDBBase): # Ensure metadata is converted to its JSON text representation json_metadata = json.dumps(item["metadata"]) self.session.execute( - text( - """ + text(""" INSERT INTO document_chunk (id, vector, collection_name, text, vmetadata) VALUES ( @@ -320,8 +308,7 @@ class PgvectorClient(VectorDBBase): pgp_sym_encrypt(:metadata_text, :key) ) ON CONFLICT (id) DO NOTHING - """ - ), + """), { "id": item["id"], "vector": vector, @@ -363,8 +350,7 @@ class PgvectorClient(VectorDBBase): vector = self.adjust_vector_length(item["vector"]) json_metadata = json.dumps(item["metadata"]) self.session.execute( - text( - """ + text(""" INSERT INTO document_chunk (id, vector, collection_name, text, vmetadata) VALUES ( @@ -377,8 +363,7 @@ class PgvectorClient(VectorDBBase): collection_name = EXCLUDED.collection_name, text = EXCLUDED.text, vmetadata = EXCLUDED.vmetadata - """ - ), + """), { "id": item["id"], "vector": vector, diff --git a/backend/open_webui/retrieval/vector/dbs/pinecone.py b/backend/open_webui/retrieval/vector/dbs/pinecone.py index fc3c98f8cf..156894bc9e 100644 --- a/backend/open_webui/retrieval/vector/dbs/pinecone.py +++ b/backend/open_webui/retrieval/vector/dbs/pinecone.py @@ -33,7 +33,6 @@ from open_webui.config import ( ) from open_webui.retrieval.vector.utils import process_metadata - NO_LIMIT = 10000 # Reasonable limit to avoid overwhelming the system BATCH_SIZE = 100 # Recommended batch size for Pinecone operations diff --git a/backend/open_webui/retrieval/web/external.py b/backend/open_webui/retrieval/web/external.py index 1dd63273ae..e8cf72e9f0 100644 --- a/backend/open_webui/retrieval/web/external.py +++ b/backend/open_webui/retrieval/web/external.py @@ -10,7 +10,6 @@ from open_webui.retrieval.web.main import SearchResult, get_filtered_results from open_webui.utils.headers import include_user_info_headers from open_webui.env import FORWARD_SESSION_INFO_HEADER_CHAT_ID - log = logging.getLogger(__name__) diff --git a/backend/open_webui/retrieval/web/firecrawl.py b/backend/open_webui/retrieval/web/firecrawl.py index 82635aa8ca..e6e96992a1 100644 --- a/backend/open_webui/retrieval/web/firecrawl.py +++ b/backend/open_webui/retrieval/web/firecrawl.py @@ -3,7 +3,6 @@ from typing import Optional, List from open_webui.retrieval.web.main import SearchResult, get_filtered_results - log = logging.getLogger(__name__) diff --git a/backend/open_webui/retrieval/web/perplexity_search.py b/backend/open_webui/retrieval/web/perplexity_search.py index 5c591ff64f..744a505c05 100644 --- a/backend/open_webui/retrieval/web/perplexity_search.py +++ b/backend/open_webui/retrieval/web/perplexity_search.py @@ -5,7 +5,6 @@ import requests from open_webui.retrieval.web.main import SearchResult, get_filtered_results from open_webui.utils.headers import include_user_info_headers - log = logging.getLogger(__name__) diff --git a/backend/open_webui/retrieval/web/yandex.py b/backend/open_webui/retrieval/web/yandex.py index fd1bc7274a..fba4ee482e 100644 --- a/backend/open_webui/retrieval/web/yandex.py +++ b/backend/open_webui/retrieval/web/yandex.py @@ -31,14 +31,14 @@ def xml_element_contents_to_string(element: Element) -> str: def search_yandex( - request: Request, - yandex_search_url: str, - yandex_search_api_key: str, - yandex_search_config: str, - query: str, - count: int, - filter_list: Optional[List[str]] = None, - user=None, + request: Request, + yandex_search_url: str, + yandex_search_api_key: str, + yandex_search_config: str, + query: str, + count: int, + filter_list: Optional[List[str]] = None, + user=None, ) -> List[SearchResult]: try: headers = { @@ -73,7 +73,11 @@ def search_yandex( payload["groupSpec"]["docsInGroup"] = 1 response = requests.post( - "https://searchapi.api.cloud.yandex.net/v2/web/search" if yandex_search_url == "" else yandex_search_url, + ( + "https://searchapi.api.cloud.yandex.net/v2/web/search" + if yandex_search_url == "" + else yandex_search_url + ), headers=headers, json=payload, ) @@ -84,18 +88,28 @@ def search_yandex( if "rawData" not in response_body: raise Exception(f"No `rawData` in response body: {response_body}") - search_result_body_bytes = base64.decodebytes(bytes(response_body["rawData"], "utf-8")) + search_result_body_bytes = base64.decodebytes( + bytes(response_body["rawData"], "utf-8") + ) doc_root = ET.parse(io.BytesIO(search_result_body_bytes)) results = [] for group in doc_root.findall("response/results/grouping/group"): - results.append({ - "url": xml_element_contents_to_string(group.find("doc/url")).strip("\n"), - "title": xml_element_contents_to_string(group.find("doc/title")).strip("\n"), - "snippet": xml_element_contents_to_string(group.find("doc/passages/passage")), - }) + results.append( + { + "url": xml_element_contents_to_string(group.find("doc/url")).strip( + "\n" + ), + "title": xml_element_contents_to_string( + group.find("doc/title") + ).strip("\n"), + "snippet": xml_element_contents_to_string( + group.find("doc/passages/passage") + ), + } + ) results = get_filtered_results(results, filter_list) @@ -140,7 +154,9 @@ if __name__ == "__main__": ), os.environ.get("YANDEX_WEB_SEARCH_URL", ""), os.environ.get("YANDEX_WEB_SEARCH_API_KEY", ""), - os.environ.get("YANDEX_WEB_SEARCH_CONFIG", "{\"query\": {\"searchType\": \"SEARCH_TYPE_COM\"}}"), + os.environ.get( + "YANDEX_WEB_SEARCH_CONFIG", '{"query": {"searchType": "SEARCH_TYPE_COM"}}' + ), "TOP movies of the past year", 3, ) diff --git a/backend/open_webui/routers/analytics.py b/backend/open_webui/routers/analytics.py index a78431f497..61aec66332 100644 --- a/backend/open_webui/routers/analytics.py +++ b/backend/open_webui/routers/analytics.py @@ -88,25 +88,29 @@ async def get_user_analytics( token_usage = ChatMessages.get_token_usage_by_user( start_date=start_date, end_date=end_date, db=db ) - + # Get user info for top users - top_user_ids = [uid for uid, _ in sorted(counts.items(), key=lambda x: -x[1])[:limit]] + top_user_ids = [ + uid for uid, _ in sorted(counts.items(), key=lambda x: -x[1])[:limit] + ] user_info = {u.id: u for u in Users.get_users_by_user_ids(top_user_ids, db=db)} - + users = [] for user_id in top_user_ids: u = user_info.get(user_id) tokens = token_usage.get(user_id, {}) - users.append(UserAnalyticsEntry( - user_id=user_id, - name=u.name if u else None, - email=u.email if u else None, - count=counts[user_id], - input_tokens=tokens.get("input_tokens", 0), - output_tokens=tokens.get("output_tokens", 0), - total_tokens=tokens.get("total_tokens", 0), - )) - + users.append( + UserAnalyticsEntry( + user_id=user_id, + name=u.name if u else None, + email=u.email if u else None, + count=counts[user_id], + input_tokens=tokens.get("input_tokens", 0), + output_tokens=tokens.get("output_tokens", 0), + total_tokens=tokens.get("total_tokens", 0), + ) + ) + return UserAnalyticsResponse(users=users) @@ -168,7 +172,7 @@ async def get_summary( chat_counts = ChatMessages.get_message_count_by_chat( start_date=start_date, end_date=end_date, group_id=group_id, db=db ) - + return SummaryResponse( total_messages=sum(model_counts.values()), total_chats=len(chat_counts), @@ -317,9 +321,7 @@ async def get_model_chats( if isinstance(content, str): first_message = content[:200] elif isinstance(content, list): - text_parts = [ - b.get("text", "") for b in content if isinstance(b, dict) - ] + text_parts = [b.get("text", "") for b in content if isinstance(b, dict)] first_message = " ".join(text_parts)[:200] # Get user info @@ -331,7 +333,6 @@ async def get_model_chats( # Timestamps from messages updated_at = max(m.created_at for m in messages) if messages else 0 - chats_data.append( ModelChatEntry( chat_id=chat_id, @@ -387,24 +388,24 @@ async def get_model_overview( # Get feedback history per day history_counts: dict[str, dict] = defaultdict(lambda: {"won": 0, "lost": 0}) - + # Calculate start date for history now = datetime.now() start_dt = None if days > 0: start_dt = now - timedelta(days=days) - + for chat_id in chat_ids: feedbacks = Feedbacks.get_feedbacks_by_chat_id(chat_id, db=db) for fb in feedbacks: if fb.data and "rating" in fb.data: rating = fb.data["rating"] fb_date = datetime.fromtimestamp(fb.created_at) - + # Filter by date range if start_dt and fb_date < start_dt: continue - + date_str = fb_date.strftime("%Y-%m-%d") if rating == 1: history_counts[date_str]["won"] += 1 @@ -423,15 +424,17 @@ async def get_model_overview( current = datetime.strptime(min_date, "%Y-%m-%d") else: current = now - + while current <= end_dt: date_str = current.strftime("%Y-%m-%d") counts = history_counts.get(date_str, {"won": 0, "lost": 0}) - history.append(HistoryEntry( - date=date_str, - won=counts["won"], - lost=counts["lost"], - )) + history.append( + HistoryEntry( + date=date_str, + won=counts["won"], + lost=counts["lost"], + ) + ) current += timedelta(days=1) # Get chat tags diff --git a/backend/open_webui/routers/audio.py b/backend/open_webui/routers/audio.py index 52e0182cad..fbaae2da4a 100644 --- a/backend/open_webui/routers/audio.py +++ b/backend/open_webui/routers/audio.py @@ -57,7 +57,6 @@ from open_webui.env import ( ENABLE_FORWARD_USER_INFO_HEADERS, ) - router = APIRouter() # Constants diff --git a/backend/open_webui/routers/auths.py b/backend/open_webui/routers/auths.py index 586fc66ec2..387a0181fe 100644 --- a/backend/open_webui/routers/auths.py +++ b/backend/open_webui/routers/auths.py @@ -98,7 +98,7 @@ def create_session_response( """ Create JWT token and build session response for a user. Shared helper for signin, signup, ldap_auth, add_user, and token_exchange endpoints. - + Args: request: FastAPI request object user: User object @@ -558,7 +558,9 @@ async def ldap_auth( except Exception as e: log.error(f"Failed to sync groups for user {user.id}: {e}") - return create_session_response(request, user, db, response, set_cookie=True) + return create_session_response( + request, user, db, response, set_cookie=True + ) else: raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED) else: diff --git a/backend/open_webui/routers/channels.py b/backend/open_webui/routers/channels.py index 4713e369ad..c21b1b8060 100644 --- a/backend/open_webui/routers/channels.py +++ b/backend/open_webui/routers/channels.py @@ -1633,9 +1633,7 @@ async def update_message_by_id( if ( user.role != "admin" and message.user_id != user.id - and not channel_has_access( - user.id, channel, permission="read", db=db - ) + and not channel_has_access(user.id, channel, permission="read", db=db) ): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT() diff --git a/backend/open_webui/routers/folders.py b/backend/open_webui/routers/folders.py index 1c9b2229cf..ab0326c882 100644 --- a/backend/open_webui/routers/folders.py +++ b/backend/open_webui/routers/folders.py @@ -33,7 +33,6 @@ from fastapi.responses import FileResponse, StreamingResponse from open_webui.utils.auth import get_admin_user, get_verified_user from open_webui.utils.access_control import has_permission - log = logging.getLogger(__name__) diff --git a/backend/open_webui/routers/functions.py b/backend/open_webui/routers/functions.py index a31b958e21..3af3b1664a 100644 --- a/backend/open_webui/routers/functions.py +++ b/backend/open_webui/routers/functions.py @@ -29,7 +29,6 @@ from pydantic import BaseModel, HttpUrl from open_webui.internal.db import get_session from sqlalchemy.orm import Session - log = logging.getLogger(__name__) diff --git a/backend/open_webui/routers/groups.py b/backend/open_webui/routers/groups.py index a46e05473f..3711a52ab4 100755 --- a/backend/open_webui/routers/groups.py +++ b/backend/open_webui/routers/groups.py @@ -22,7 +22,6 @@ from sqlalchemy.orm import Session from open_webui.utils.auth import get_admin_user, get_verified_user - log = logging.getLogger(__name__) router = APIRouter() diff --git a/backend/open_webui/routers/images.py b/backend/open_webui/routers/images.py index 05afa63bb6..942848bc2f 100644 --- a/backend/open_webui/routers/images.py +++ b/backend/open_webui/routers/images.py @@ -14,7 +14,11 @@ import requests from fastapi import APIRouter, Depends, HTTPException, Request, UploadFile from fastapi.responses import FileResponse -from open_webui.config import CACHE_DIR, IMAGE_AUTO_SIZE_MODELS_REGEX_PATTERN, IMAGE_URL_RESPONSE_MODELS_REGEX_PATTERN +from open_webui.config import ( + CACHE_DIR, + IMAGE_AUTO_SIZE_MODELS_REGEX_PATTERN, + IMAGE_URL_RESPONSE_MODELS_REGEX_PATTERN, +) from open_webui.constants import ERROR_MESSAGES from open_webui.retrieval.web.utils import validate_url from open_webui.env import ENABLE_FORWARD_USER_INFO_HEADERS @@ -199,9 +203,8 @@ async def update_config( request.app.state.config.IMAGE_GENERATION_ENGINE = form_data.IMAGE_GENERATION_ENGINE set_image_model(request, form_data.IMAGE_GENERATION_MODEL) - if ( - form_data.IMAGE_SIZE == "auto" - and not re.match(IMAGE_AUTO_SIZE_MODELS_REGEX_PATTERN, form_data.IMAGE_GENERATION_MODEL) + if form_data.IMAGE_SIZE == "auto" and not re.match( + IMAGE_AUTO_SIZE_MODELS_REGEX_PATTERN, form_data.IMAGE_GENERATION_MODEL ): raise HTTPException( status_code=400, @@ -610,7 +613,10 @@ async def image_generations( ), **( {} - if re.match(IMAGE_URL_RESPONSE_MODELS_REGEX_PATTERN, request.app.state.config.IMAGE_GENERATION_MODEL) + if re.match( + IMAGE_URL_RESPONSE_MODELS_REGEX_PATTERN, + request.app.state.config.IMAGE_GENERATION_MODEL, + ) else {"response_format": "b64_json"} ), **( @@ -912,7 +918,9 @@ async def image_edits( form_data.image = await load_url_image(form_data.image) elif isinstance(form_data.image, list): # Load all images in parallel for better performance - form_data.image = list(await asyncio.gather(*[load_url_image(img) for img in form_data.image])) + form_data.image = list( + await asyncio.gather(*[load_url_image(img) for img in form_data.image]) + ) except Exception as e: raise HTTPException(status_code=400, detail=ERROR_MESSAGES.DEFAULT(e)) @@ -947,7 +955,10 @@ async def image_edits( **({"size": size} if size else {}), **( {} - if re.match(IMAGE_URL_RESPONSE_MODELS_REGEX_PATTERN, request.app.state.config.IMAGE_EDIT_MODEL) + if re.match( + IMAGE_URL_RESPONSE_MODELS_REGEX_PATTERN, + request.app.state.config.IMAGE_EDIT_MODEL, + ) else {"response_format": "b64_json"} ), } diff --git a/backend/open_webui/routers/knowledge.py b/backend/open_webui/routers/knowledge.py index ff4b924149..eab00aa19b 100644 --- a/backend/open_webui/routers/knowledge.py +++ b/backend/open_webui/routers/knowledge.py @@ -36,7 +36,6 @@ from open_webui.models.access_grants import AccessGrants, has_public_read_access from open_webui.config import BYPASS_ADMIN_ACCESS_CONTROL from open_webui.models.models import Models, ModelForm - log = logging.getLogger(__name__) router = APIRouter() @@ -358,7 +357,7 @@ async def reindex_knowledge_base_metadata_embeddings( user=Depends(get_admin_user), ): """Batch embed all existing knowledge bases. Admin only. - + NOTE: We intentionally do NOT use Depends(get_session) here. This endpoint loops through ALL knowledge bases and calls embed_knowledge_base_metadata() for each one, making N external embedding API calls. Holding a session during @@ -540,9 +539,7 @@ async def update_knowledge_access_by_id( detail=ERROR_MESSAGES.ACCESS_PROHIBITED, ) - AccessGrants.set_access_grants( - "knowledge", id, form_data.access_grants, db=db - ) + AccessGrants.set_access_grants("knowledge", id, form_data.access_grants, db=db) return KnowledgeFilesResponse( **Knowledges.get_knowledge_by_id(id=id, db=db).model_dump(), diff --git a/backend/open_webui/routers/notes.py b/backend/open_webui/routers/notes.py index 659d9459fd..04841e87cc 100644 --- a/backend/open_webui/routers/notes.py +++ b/backend/open_webui/routers/notes.py @@ -345,9 +345,7 @@ async def update_note_access_by_id( status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT() ) - AccessGrants.set_access_grants( - "note", id, form_data.access_grants, db=db - ) + AccessGrants.set_access_grants("note", id, form_data.access_grants, db=db) return Notes.get_note_by_id(id, db=db) diff --git a/backend/open_webui/routers/openai.py b/backend/open_webui/routers/openai.py index d8ab50221f..eeb9f0bf21 100644 --- a/backend/open_webui/routers/openai.py +++ b/backend/open_webui/routers/openai.py @@ -54,7 +54,6 @@ from open_webui.utils.misc import ( from open_webui.utils.auth import get_admin_user, get_verified_user from open_webui.utils.headers import include_user_info_headers - log = logging.getLogger(__name__) @@ -805,67 +804,77 @@ def convert_to_azure_payload(url, payload: dict, api_version: str): def convert_to_responses_payload(payload: dict) -> dict: """ Convert Chat Completions payload to Responses API format. - + Chat Completions: { messages: [{role, content}], ... } Responses API: { input: [{type: "message", role, content: [...]}], instructions: "system" } """ messages = payload.pop("messages", []) - + system_content = "" input_items = [] - + for msg in messages: role = msg.get("role", "user") content = msg.get("content", "") - + # Check for stored output items (from previous Responses API turn) stored_output = msg.get("output") if stored_output and isinstance(stored_output, list): input_items.extend(stored_output) continue - + if role == "system": if isinstance(content, str): system_content = content elif isinstance(content, list): - system_content = "\n".join(p.get("text", "") for p in content if p.get("type") == "text") + system_content = "\n".join( + p.get("text", "") for p in content if p.get("type") == "text" + ) continue - + # Convert content format text_type = "output_text" if role == "assistant" else "input_text" - + if isinstance(content, str): content_parts = [{"type": text_type, "text": content}] elif isinstance(content, list): content_parts = [] for part in content: if part.get("type") == "text": - content_parts.append({"type": text_type, "text": part.get("text", "")}) + content_parts.append( + {"type": text_type, "text": part.get("text", "")} + ) elif part.get("type") == "image_url": url_data = part.get("image_url", {}) - url = url_data.get("url", "") if isinstance(url_data, dict) else url_data + url = ( + url_data.get("url", "") + if isinstance(url_data, dict) + else url_data + ) content_parts.append({"type": "input_image", "image_url": url}) else: content_parts = [{"type": text_type, "text": str(content)}] - - input_items.append({ - "type": "message", - "role": role, - "content": content_parts - }) - + + input_items.append({"type": "message", "role": role, "content": content_parts}) + responses_payload = {**payload, "input": input_items} - + if system_content: responses_payload["instructions"] = system_content - + if "max_tokens" in responses_payload: responses_payload["max_output_tokens"] = responses_payload.pop("max_tokens") - + # Remove Chat Completions-only parameters not supported by the Responses API - for unsupported_key in ("stream_options", "logit_bias", "frequency_penalty", "presence_penalty", "stop"): + for unsupported_key in ( + "stream_options", + "logit_bias", + "frequency_penalty", + "presence_penalty", + "stop", + ): responses_payload.pop(unsupported_key, None) - + # Convert Chat Completions tools format to Responses API format # Chat Completions: {"type": "function", "function": {"name": ..., "description": ..., "parameters": ...}} # Responses API: {"type": "function", "name": ..., "description": ..., "parameters": ...} @@ -888,9 +897,8 @@ def convert_to_responses_payload(payload: dict) -> dict: # Already in correct format or unknown format, pass through converted_tools.append(tool) responses_payload["tools"] = converted_tools - - return responses_payload + return responses_payload def convert_responses_result(response: dict) -> dict: @@ -1036,7 +1044,7 @@ async def generate_chat_completion( headers["api-key"] = key headers["api-version"] = api_version - + if is_responses: payload = convert_to_responses_payload(payload) request_url = f"{request_url}/responses?api-version={api_version}" diff --git a/backend/open_webui/routers/prompts.py b/backend/open_webui/routers/prompts.py index 1bec0950c8..e8d4660f03 100644 --- a/backend/open_webui/routers/prompts.py +++ b/backend/open_webui/routers/prompts.py @@ -107,7 +107,9 @@ async def get_prompt_list( filter["user_id"] = user.id - result = Prompts.search_prompts(user.id, filter=filter, skip=skip, limit=limit, db=db) + result = Prompts.search_prompts( + user.id, filter=filter, skip=skip, limit=limit, db=db + ) return PromptAccessListResponse( items=[ @@ -313,9 +315,7 @@ async def update_prompt_by_id( ) # Use the ID from the found prompt - updated_prompt = Prompts.update_prompt_by_id( - prompt.id, form_data, user.id, db=db - ) + updated_prompt = Prompts.update_prompt_by_id(prompt.id, form_data, user.id, db=db) if updated_prompt: return updated_prompt else: @@ -464,9 +464,7 @@ async def update_prompt_access_by_id( detail=ERROR_MESSAGES.ACCESS_PROHIBITED, ) - AccessGrants.set_access_grants( - "prompt", prompt_id, form_data.access_grants, db=db - ) + AccessGrants.set_access_grants("prompt", prompt_id, form_data.access_grants, db=db) return Prompts.get_prompt_by_id(prompt_id, db=db) @@ -522,7 +520,7 @@ async def get_prompt_history( ): """Get version history for a prompt.""" PAGE_SIZE = 20 - + prompt = Prompts.get_prompt_by_id(prompt_id, db=db) if not prompt: @@ -554,9 +552,7 @@ async def get_prompt_history( return history -@router.get( - "/id/{prompt_id}/history/{history_id}", response_model=PromptHistoryModel -) +@router.get("/id/{prompt_id}/history/{history_id}", response_model=PromptHistoryModel) async def get_prompt_history_entry( prompt_id: str, history_id: str, @@ -599,9 +595,7 @@ async def get_prompt_history_entry( return history_entry -@router.delete( - "/id/{prompt_id}/history/{history_id}", response_model=bool -) +@router.delete("/id/{prompt_id}/history/{history_id}", response_model=bool) async def delete_prompt_history_entry( prompt_id: str, history_id: str, diff --git a/backend/open_webui/routers/skills.py b/backend/open_webui/routers/skills.py index 04ba56caee..367768e61b 100644 --- a/backend/open_webui/routers/skills.py +++ b/backend/open_webui/routers/skills.py @@ -24,7 +24,6 @@ from open_webui.utils.access_control import has_access, has_permission from open_webui.config import BYPASS_ADMIN_ACCESS_CONTROL from open_webui.constants import ERROR_MESSAGES - log = logging.getLogger(__name__) PAGE_ITEM_COUNT = 30 @@ -98,9 +97,7 @@ async def get_skill_list( filter["user_id"] = user.id - result = Skills.search_skills( - user.id, filter=filter, skip=skip, limit=limit, db=db - ) + result = Skills.search_skills(user.id, filter=filter, skip=skip, limit=limit, db=db) return SkillAccessListResponse( items=[ @@ -343,9 +340,7 @@ async def update_skill_access_by_id( detail=ERROR_MESSAGES.UNAUTHORIZED, ) - AccessGrants.set_access_grants( - "skill", id, form_data.access_grants, db=db - ) + AccessGrants.set_access_grants("skill", id, form_data.access_grants, db=db) return Skills.get_skill_by_id(id, db=db) diff --git a/backend/open_webui/routers/tasks.py b/backend/open_webui/routers/tasks.py index a89e28d3af..f6404da05c 100644 --- a/backend/open_webui/routers/tasks.py +++ b/backend/open_webui/routers/tasks.py @@ -36,7 +36,6 @@ from open_webui.config import ( DEFAULT_VOICE_MODE_PROMPT_TEMPLATE, ) - log = logging.getLogger(__name__) router = APIRouter() diff --git a/backend/open_webui/routers/tools.py b/backend/open_webui/routers/tools.py index ad9cab474a..057eb509a1 100644 --- a/backend/open_webui/routers/tools.py +++ b/backend/open_webui/routers/tools.py @@ -36,7 +36,6 @@ from open_webui.utils.tools import get_tool_servers from open_webui.config import CACHE_DIR, BYPASS_ADMIN_ACCESS_CONTROL from open_webui.constants import ERROR_MESSAGES - log = logging.getLogger(__name__) @@ -555,9 +554,7 @@ async def update_tool_access_by_id( detail=ERROR_MESSAGES.UNAUTHORIZED, ) - AccessGrants.set_access_grants( - "tool", id, form_data.access_grants, db=db - ) + AccessGrants.set_access_grants("tool", id, form_data.access_grants, db=db) return Tools.get_tool_by_id(id, db=db) diff --git a/backend/open_webui/routers/users.py b/backend/open_webui/routers/users.py index 6eca1fcac2..cc5fd07d96 100644 --- a/backend/open_webui/routers/users.py +++ b/backend/open_webui/routers/users.py @@ -41,7 +41,6 @@ from open_webui.utils.auth import ( ) from open_webui.utils.access_control import get_permissions, has_permission - log = logging.getLogger(__name__) router = APIRouter() diff --git a/backend/open_webui/routers/utils.py b/backend/open_webui/routers/utils.py index 22529ab1b9..49f3a5ca55 100644 --- a/backend/open_webui/routers/utils.py +++ b/backend/open_webui/routers/utils.py @@ -15,7 +15,6 @@ from open_webui.utils.pdf_generator import PDFGenerator from open_webui.utils.auth import get_admin_user, get_verified_user from open_webui.utils.code_interpreter import execute_code_jupyter - log = logging.getLogger(__name__) router = APIRouter() diff --git a/backend/open_webui/socket/main.py b/backend/open_webui/socket/main.py index e987f9c29d..a06d062052 100644 --- a/backend/open_webui/socket/main.py +++ b/backend/open_webui/socket/main.py @@ -49,7 +49,6 @@ from open_webui.env import ( GLOBAL_LOG_LEVEL, ) - logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL) log = logging.getLogger(__name__) diff --git a/backend/open_webui/storage/provider.py b/backend/open_webui/storage/provider.py index ce02105bfa..425d10c812 100644 --- a/backend/open_webui/storage/provider.py +++ b/backend/open_webui/storage/provider.py @@ -34,7 +34,6 @@ from azure.identity import DefaultAzureCredential from azure.storage.blob import BlobServiceClient from azure.core.exceptions import ResourceNotFoundError - log = logging.getLogger(__name__) diff --git a/backend/open_webui/tasks.py b/backend/open_webui/tasks.py index cc91cde1a0..fc3d2b5fe9 100644 --- a/backend/open_webui/tasks.py +++ b/backend/open_webui/tasks.py @@ -10,7 +10,6 @@ from typing import Dict, List, Optional from open_webui.env import REDIS_KEY_PREFIX - log = logging.getLogger(__name__) # A dictionary to keep track of active tasks diff --git a/backend/open_webui/tools/builtin.py b/backend/open_webui/tools/builtin.py index 6014bd7bd6..0175ba5583 100644 --- a/backend/open_webui/tools/builtin.py +++ b/backend/open_webui/tools/builtin.py @@ -380,8 +380,8 @@ async def execute_code( # Add import blocking code if there are blocked modules if CODE_INTERPRETER_BLOCKED_MODULES: import textwrap - blocking_code = textwrap.dedent( - f""" + + blocking_code = textwrap.dedent(f""" import builtins BLOCKED_MODULES = {CODE_INTERPRETER_BLOCKED_MODULES} @@ -397,15 +397,20 @@ async def execute_code( return _real_import(name, globals, locals, fromlist, level) builtins.__import__ = restricted_import - """ - ) + """) code = blocking_code + "\n" + code - engine = getattr(__request__.app.state.config, "CODE_INTERPRETER_ENGINE", "pyodide") + engine = getattr( + __request__.app.state.config, "CODE_INTERPRETER_ENGINE", "pyodide" + ) if engine == "pyodide": # Execute via frontend pyodide using bidirectional event call if __event_call__ is None: - return json.dumps({"error": "Event call not available. WebSocket connection required for pyodide execution."}) + return json.dumps( + { + "error": "Event call not available. WebSocket connection required for pyodide execution." + } + ) output = await __event_call__( { @@ -413,7 +418,9 @@ async def execute_code( "data": { "id": str(uuid4()), "code": code, - "session_id": __metadata__.get("session_id") if __metadata__ else None, + "session_id": ( + __metadata__.get("session_id") if __metadata__ else None + ), }, } ) @@ -436,12 +443,14 @@ async def execute_code( code, ( __request__.app.state.config.CODE_INTERPRETER_JUPYTER_AUTH_TOKEN - if __request__.app.state.config.CODE_INTERPRETER_JUPYTER_AUTH == "token" + if __request__.app.state.config.CODE_INTERPRETER_JUPYTER_AUTH + == "token" else None ), ( __request__.app.state.config.CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD - if __request__.app.state.config.CODE_INTERPRETER_JUPYTER_AUTH == "password" + if __request__.app.state.config.CODE_INTERPRETER_JUPYTER_AUTH + == "password" else None ), __request__.app.state.config.CODE_INTERPRETER_JUPYTER_TIMEOUT, @@ -1921,7 +1930,9 @@ async def view_skill( # Check user access user_role = __user__.get("role", "user") if user_role != "admin" and skill.user_id != user_id: - user_group_ids = [group.id for group in Groups.get_groups_by_member_id(user_id)] + user_group_ids = [ + group.id for group in Groups.get_groups_by_member_id(user_id) + ] if not AccessGrants.has_access( user_id=user_id, resource_type="skill", @@ -1931,11 +1942,13 @@ async def view_skill( ): return json.dumps({"error": "Access denied"}) - return json.dumps({ - "name": skill.name, - "content": skill.content, - }, ensure_ascii=False) + return json.dumps( + { + "name": skill.name, + "content": skill.content, + }, + ensure_ascii=False, + ) except Exception as e: log.exception(f"view_skill error: {e}") return json.dumps({"error": str(e)}) - diff --git a/backend/open_webui/utils/access_control.py b/backend/open_webui/utils/access_control.py index 3e9d023044..b7ea9830db 100644 --- a/backend/open_webui/utils/access_control.py +++ b/backend/open_webui/utils/access_control.py @@ -139,16 +139,23 @@ def has_access( continue principal_type = grant.get("principal_type") principal_id = grant.get("principal_id") - if principal_type == "user" and (principal_id == "*" or principal_id == user_id): + if principal_type == "user" and ( + principal_id == "*" or principal_id == user_id + ): return True - if principal_type == "group" and user_group_ids and principal_id in user_group_ids: + if ( + principal_type == "group" + and user_group_ids + and principal_id in user_group_ids + ): return True - return False -def migrate_access_control(data: dict, ac_key: str = "access_control", grants_key: str = "access_grants") -> None: +def migrate_access_control( + data: dict, ac_key: str = "access_control", grants_key: str = "access_grants" +) -> None: """ Auto-migrate a config dict in-place from legacy access_control dict to access_grants list. @@ -169,17 +176,21 @@ def migrate_access_control(data: dict, ac_key: str = "access_control", grants_ke if not perm_data: continue for group_id in perm_data.get("group_ids", []): - grants.append({ - "principal_type": "group", - "principal_id": group_id, - "permission": perm, - }) + grants.append( + { + "principal_type": "group", + "principal_id": group_id, + "permission": perm, + } + ) for uid in perm_data.get("user_ids", []): - grants.append({ - "principal_type": "user", - "principal_id": uid, - "permission": perm, - }) + grants.append( + { + "principal_type": "user", + "principal_id": uid, + "permission": perm, + } + ) data[grants_key] = grants data.pop(ac_key, None) diff --git a/backend/open_webui/utils/audit.py b/backend/open_webui/utils/audit.py index 73dc140de5..c4abb445b9 100644 --- a/backend/open_webui/utils/audit.py +++ b/backend/open_webui/utils/audit.py @@ -28,7 +28,6 @@ from open_webui.env import AUDIT_LOG_LEVEL, MAX_BODY_LOG_SIZE from open_webui.utils.auth import get_current_user, get_http_authorization_cred from open_webui.models.users import UserModel - if TYPE_CHECKING: from loguru import Logger @@ -222,7 +221,9 @@ class AuditLoggingMiddleware: # Skip logging if the request is not authenticated # Check both Authorization header (API keys) and token cookie (browser sessions) - if not request.headers.get("authorization") and not request.cookies.get("token"): + if not request.headers.get("authorization") and not request.cookies.get( + "token" + ): return True # match either /api//...(for the endpoint /api/chat case) or /api/v1//... diff --git a/backend/open_webui/utils/auth.py b/backend/open_webui/utils/auth.py index ef09a6004d..27af3631e4 100644 --- a/backend/open_webui/utils/auth.py +++ b/backend/open_webui/utils/auth.py @@ -46,7 +46,6 @@ from open_webui.env import ( from fastapi import BackgroundTasks, Depends, HTTPException, Request, Response, status from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer - log = logging.getLogger(__name__) SESSION_SECRET = WEBUI_SECRET_KEY diff --git a/backend/open_webui/utils/chat.py b/backend/open_webui/utils/chat.py index 1ed34b6ca7..94d6f44267 100644 --- a/backend/open_webui/utils/chat.py +++ b/backend/open_webui/utils/chat.py @@ -57,7 +57,6 @@ from open_webui.utils.filter import ( from open_webui.env import GLOBAL_LOG_LEVEL, BYPASS_MODEL_ACCESS_CONTROL - logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL) log = logging.getLogger(__name__) @@ -341,7 +340,9 @@ async def chat_completed(request: Request, form_data: dict, user: Any): } try: - filter_ids = get_sorted_filter_ids(request, model, metadata.get("filter_ids", [])) + filter_ids = get_sorted_filter_ids( + request, model, metadata.get("filter_ids", []) + ) filter_functions = Functions.get_functions_by_ids(filter_ids) result, _ = await process_filter_functions( diff --git a/backend/open_webui/utils/code_interpreter.py b/backend/open_webui/utils/code_interpreter.py index e89b970cb6..a5de56a6c1 100644 --- a/backend/open_webui/utils/code_interpreter.py +++ b/backend/open_webui/utils/code_interpreter.py @@ -8,7 +8,6 @@ import aiohttp import websockets from pydantic import BaseModel - logger = logging.getLogger(__name__) diff --git a/backend/open_webui/utils/embeddings.py b/backend/open_webui/utils/embeddings.py index f92be44578..a2dc080cb5 100644 --- a/backend/open_webui/utils/embeddings.py +++ b/backend/open_webui/utils/embeddings.py @@ -86,4 +86,3 @@ async def generate_embeddings( form_data=form_data, user=user, ) - diff --git a/backend/open_webui/utils/logger.py b/backend/open_webui/utils/logger.py index 4af3064235..63d5fbb3ce 100644 --- a/backend/open_webui/utils/logger.py +++ b/backend/open_webui/utils/logger.py @@ -17,7 +17,6 @@ from open_webui.env import ( ENABLE_OTEL_LOGS, ) - if TYPE_CHECKING: from loguru import Record diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index d65e231302..b5b62ecf82 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -133,7 +133,6 @@ from open_webui.env import ( from open_webui.utils.headers import include_user_info_headers from open_webui.constants import TASKS - logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL) log = logging.getLogger(__name__) @@ -2106,10 +2105,15 @@ async def process_chat_payload(request, form_data, user, metadata, model): if all_skill_ids: from open_webui.models.skills import Skills as SkillsModel - accessible_skill_ids = {s.id for s in SkillsModel.get_skills_by_user_id(user.id, "read")} + accessible_skill_ids = { + s.id for s in SkillsModel.get_skills_by_user_id(user.id, "read") + } available_skills = [ - s for sid in all_skill_ids - if sid in accessible_skill_ids and (s := SkillsModel.get_skill_by_id(sid)) and s.is_active + s + for sid in all_skill_ids + if sid in accessible_skill_ids + and (s := SkillsModel.get_skill_by_id(sid)) + and s.is_active ] if available_skills: @@ -2238,7 +2242,9 @@ async def process_chat_payload(request, form_data, user, metadata, model): if ENABLE_FORWARD_USER_INFO_HEADERS and user: headers = include_user_info_headers(headers, user) if metadata and metadata.get("chat_id"): - headers[FORWARD_SESSION_INFO_HEADER_CHAT_ID] = metadata.get("chat_id") + headers[FORWARD_SESSION_INFO_HEADER_CHAT_ID] = metadata.get( + "chat_id" + ) mcp_clients[server_id] = MCPClient() await mcp_clients[server_id].connect( @@ -2812,9 +2818,7 @@ async def non_streaming_chat_response_handler(response, ctx): "id": output_id("msg"), "status": "completed", "role": "assistant", - "content": [ - {"type": "output_text", "text": content} - ], + "content": [{"type": "output_text", "text": content}], } ] @@ -2967,9 +2971,7 @@ async def streaming_chat_response_handler(response, ctx): match = re.search(start_tag_pattern, content) if match: try: - attr_content = ( - match.group(1) if match.group(1) else "" - ) + attr_content = match.group(1) if match.group(1) else "" except: attr_content = "" @@ -2982,7 +2984,7 @@ async def streaming_chat_response_handler(response, ctx): current_text = get_last_text(output) set_last_text( output, - current_text.replace(match.group(0) + after_tag, "") + current_text.replace(match.group(0) + after_tag, ""), ) if before_tag: @@ -3031,7 +3033,9 @@ async def streaming_chat_response_handler(response, ctx): "id": output_id("msg"), "status": "in_progress", "role": "assistant", - "content": [{"type": "output_text", "text": ""}], + "content": [ + {"type": "output_text", "text": ""} + ], "_tag_type": content_type, "start_tag": start_tag, "end_tag": end_tag, @@ -3059,8 +3063,14 @@ async def streaming_chat_response_handler(response, ctx): elif ( (last_type == "reasoning" and content_type == "reasoning") - or (last_type == "open_webui:code_interpreter" and content_type == "code_interpreter") - or (last_type == "message" and output[-1].get("_tag_type") == content_type) + or ( + last_type == "open_webui:code_interpreter" + and content_type == "code_interpreter" + ) + or ( + last_type == "message" + and output[-1].get("_tag_type") == content_type + ) ): item = output[-1] start_tag = item.get("start_tag", "") @@ -3178,9 +3188,7 @@ async def streaming_chat_response_handler(response, ctx): # Clean processed content start_tag_clean = rf"{re.escape(start_tag)}" if start_tag.startswith("<") and start_tag.endswith(">"): - start_tag_clean = ( - rf"<{re.escape(start_tag[1:-1])}(\s.*?)?>" - ) + start_tag_clean = rf"<{re.escape(start_tag[1:-1])}(\s.*?)?>" content = re.sub( rf"{start_tag_clean}(.|\n)*?{re.escape(end_tag)}", @@ -3231,7 +3239,6 @@ async def streaming_chat_response_handler(response, ctx): else: output = [] - usage = None reasoning_tags_param = metadata.get("params", {}).get("reasoning_tags") @@ -3514,14 +3521,19 @@ async def streaming_chat_response_handler(response, ctx): for tc in response_tool_calls: call_id = tc.get("id", "") func = tc.get("function", {}) - pending_fc_items.append({ - "type": "function_call", - "id": call_id or output_id("fc"), - "call_id": call_id, - "name": func.get("name", ""), - "arguments": func.get("arguments", "{}"), - "status": "in_progress", - }) + pending_fc_items.append( + { + "type": "function_call", + "id": call_id + or output_id("fc"), + "call_id": call_id, + "name": func.get("name", ""), + "arguments": func.get( + "arguments", "{}" + ), + "status": "in_progress", + } + ) pending_output = output + pending_fc_items await event_emitter( { @@ -3585,22 +3597,25 @@ async def streaming_chat_response_handler(response, ctx): # Append to reasoning content parts = reasoning_item.get("content", []) - if parts and parts[-1].get("type") == "output_text": + if ( + parts + and parts[-1].get("type") == "output_text" + ): parts[-1]["text"] += reasoning_content else: reasoning_item["content"] = [ - {"type": "output_text", "text": reasoning_content} + { + "type": "output_text", + "text": reasoning_content, + } ] - data = { - "content": serialize_output(output) - } + data = {"content": serialize_output(output)} if value: if ( output - and output[-1].get("type") - == "reasoning" + and output[-1].get("type") == "reasoning" and output[-1] .get("attributes", {}) .get("type") @@ -3620,7 +3635,12 @@ async def streaming_chat_response_handler(response, ctx): "id": output_id("msg"), "status": "in_progress", "role": "assistant", - "content": [{"type": "output_text", "text": ""}], + "content": [ + { + "type": "output_text", + "text": "", + } + ], } ) @@ -3650,13 +3670,22 @@ async def streaming_chat_response_handler(response, ctx): "id": output_id("msg"), "status": "in_progress", "role": "assistant", - "content": [{"type": "output_text", "text": ""}], + "content": [ + { + "type": "output_text", + "text": "", + } + ], } ) # Append value to last message item's text msg_parts = output[-1].get("content", []) - if msg_parts and msg_parts[-1].get("type") == "output_text": + if ( + msg_parts + and msg_parts[-1].get("type") + == "output_text" + ): msg_parts[-1]["text"] += value else: output[-1]["content"] = [ @@ -3664,32 +3693,26 @@ async def streaming_chat_response_handler(response, ctx): ] if DETECT_REASONING_TAGS: - content, output, _ = ( - tag_output_handler( - "reasoning", - reasoning_tags, - content, - output, - ) + content, output, _ = tag_output_handler( + "reasoning", + reasoning_tags, + content, + output, ) - content, output, _ = ( - tag_output_handler( - "solution", - DEFAULT_SOLUTION_TAGS, - content, - output, - ) + content, output, _ = tag_output_handler( + "solution", + DEFAULT_SOLUTION_TAGS, + content, + output, ) if DETECT_CODE_INTERPRETER: - content, output, end = ( - tag_output_handler( - "code_interpreter", - DEFAULT_CODE_INTERPRETER_TAGS, - content, - output, - ) + content, output, end = tag_output_handler( + "code_interpreter", + DEFAULT_CODE_INTERPRETER_TAGS, + content, + output, ) if end: @@ -3707,9 +3730,7 @@ async def streaming_chat_response_handler(response, ctx): ) else: data = { - "content": serialize_output( - output - ), + "content": serialize_output(output), } if delta: @@ -3750,7 +3771,9 @@ async def streaming_chat_response_handler(response, ctx): "id": output_id("msg"), "status": "in_progress", "role": "assistant", - "content": [{"type": "output_text", "text": ""}], + "content": [ + {"type": "output_text", "text": ""} + ], } ) @@ -3788,14 +3811,16 @@ async def streaming_chat_response_handler(response, ctx): for tc in response_tool_calls: call_id = tc.get("id", "") func = tc.get("function", {}) - output.append({ - "type": "function_call", - "id": call_id or output_id("fc"), - "call_id": call_id, - "name": func.get("name", ""), - "arguments": func.get("arguments", "{}"), - "status": "in_progress", - }) + output.append( + { + "type": "function_call", + "id": call_id or output_id("fc"), + "call_id": call_id, + "name": func.get("name", ""), + "arguments": func.get("arguments", "{}"), + "status": "in_progress", + } + ) await event_emitter( { @@ -3954,35 +3979,42 @@ async def streaming_chat_response_handler(response, ctx): call_id = tc.get("id", "") # Mark function_call as completed for item in output: - if item.get("type") == "function_call" and item.get("call_id") == call_id: + if ( + item.get("type") == "function_call" + and item.get("call_id") == call_id + ): item["status"] = "completed" # Update arguments with parsed/sanitized version - item["arguments"] = tc.get("function", {}).get("arguments", "{}") + item["arguments"] = tc.get("function", {}).get( + "arguments", "{}" + ) break for result in results: - output.append({ - "type": "function_call_output", - "id": output_id("fco"), - "call_id": result.get("tool_call_id", ""), - "output": [ - { - "type": "input_text", - "text": result.get("content", ""), - } - ], - "status": "completed", - **( - {"files": result.get("files")} - if result.get("files") - else {} - ), - **( - {"embeds": result.get("embeds")} - if result.get("embeds") - else {} - ), - }) + output.append( + { + "type": "function_call_output", + "id": output_id("fco"), + "call_id": result.get("tool_call_id", ""), + "output": [ + { + "type": "input_text", + "text": result.get("content", ""), + } + ], + "status": "completed", + **( + {"files": result.get("files")} + if result.get("files") + else {} + ), + **( + {"embeds": result.get("embeds")} + if result.get("embeds") + else {} + ), + } + ) # Append a new empty message item for the next response output.append( @@ -4079,8 +4111,7 @@ async def streaming_chat_response_handler(response, ctx): code = sanitize_code(code) if CODE_INTERPRETER_BLOCKED_MODULES: - blocking_code = textwrap.dedent( - f""" + blocking_code = textwrap.dedent(f""" import builtins BLOCKED_MODULES = {CODE_INTERPRETER_BLOCKED_MODULES} @@ -4096,8 +4127,7 @@ async def streaming_chat_response_handler(response, ctx): return _real_import(name, globals, locals, fromlist, level) builtins.__import__ = restricted_import - """ - ) + """) code = blocking_code + "\n" + code if ( diff --git a/backend/open_webui/utils/misc.py b/backend/open_webui/utils/misc.py index b2b10bf56e..756dd91615 100644 --- a/backend/open_webui/utils/misc.py +++ b/backend/open_webui/utils/misc.py @@ -151,11 +151,15 @@ def convert_output_to_messages(output: list, raw: bool = False) -> list[dict]: def flush_pending(): nonlocal pending_content, pending_tool_calls if pending_content or pending_tool_calls: - messages.append({ - "role": "assistant", - "content": "\n".join(pending_content) if pending_content else "", - **({"tool_calls": pending_tool_calls} if pending_tool_calls else {}), - }) + messages.append( + { + "role": "assistant", + "content": "\n".join(pending_content) if pending_content else "", + **( + {"tool_calls": pending_tool_calls} if pending_tool_calls else {} + ), + } + ) pending_content = [] pending_tool_calls = [] @@ -178,14 +182,16 @@ def convert_output_to_messages(output: list, raw: bool = False) -> list[dict]: # Ensure arguments is always a JSON string if not isinstance(arguments, str): arguments = json.dumps(arguments) - pending_tool_calls.append({ - "id": item.get("call_id", ""), - "type": "function", - "function": { - "name": item.get("name", ""), - "arguments": arguments, + pending_tool_calls.append( + { + "id": item.get("call_id", ""), + "type": "function", + "function": { + "name": item.get("name", ""), + "arguments": arguments, + }, } - }) + ) elif item_type == "function_call_output": # Flush any pending content/tool_calls before adding tool result @@ -198,11 +204,13 @@ def convert_output_to_messages(output: list, raw: bool = False) -> list[dict]: if part.get("type") == "input_text": content += part.get("text", "") - messages.append({ - "role": "tool", - "tool_call_id": item.get("call_id", ""), - "content": content, - }) + messages.append( + { + "role": "tool", + "tool_call_id": item.get("call_id", ""), + "content": content, + } + ) elif item_type == "reasoning": if raw: @@ -218,9 +226,7 @@ def convert_output_to_messages(output: list, raw: bool = False) -> list[dict]: if reasoning_text: start_tag = item.get("start_tag", "") end_tag = item.get("end_tag", "") - pending_content.append( - f"{start_tag}{reasoning_text}{end_tag}" - ) + pending_content.append(f"{start_tag}{reasoning_text}{end_tag}") # else: skip reasoning blocks for normal LLM messages elif item_type == "open_webui:code_interpreter": diff --git a/backend/open_webui/utils/models.py b/backend/open_webui/utils/models.py index 986c0c6014..ff3a6e0caf 100644 --- a/backend/open_webui/utils/models.py +++ b/backend/open_webui/utils/models.py @@ -32,7 +32,6 @@ from open_webui.config import ( from open_webui.env import BYPASS_MODEL_ACCESS_CONTROL, GLOBAL_LOG_LEVEL from open_webui.models.users import UserModel - logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL) log = logging.getLogger(__name__) diff --git a/backend/open_webui/utils/payload.py b/backend/open_webui/utils/payload.py index 0e8010ca34..318b8f8f88 100644 --- a/backend/open_webui/utils/payload.py +++ b/backend/open_webui/utils/payload.py @@ -289,7 +289,9 @@ def convert_payload_openai_to_ollama(openai_payload: dict) -> dict: """ # Shallow copy metadata separately (may contain non-picklable objects) metadata = openai_payload.get("metadata") - openai_payload = copy.deepcopy({k: v for k, v in openai_payload.items() if k != "metadata"}) + openai_payload = copy.deepcopy( + {k: v for k, v in openai_payload.items() if k != "metadata"} + ) if metadata is not None: openai_payload["metadata"] = dict(metadata) ollama_payload = {} @@ -421,4 +423,3 @@ def convert_embed_payload_openai_to_ollama(openai_payload: dict) -> dict: ollama_payload[optional_key] = openai_payload[optional_key] return ollama_payload - diff --git a/backend/open_webui/utils/plugin.py b/backend/open_webui/utils/plugin.py index 79a1c0f0dc..2dd49fb8ff 100644 --- a/backend/open_webui/utils/plugin.py +++ b/backend/open_webui/utils/plugin.py @@ -151,7 +151,6 @@ def resolve_valves_schema_options( return schema - def extract_frontmatter(content): """ Extract frontmatter as a dictionary from the provided content string. diff --git a/backend/open_webui/utils/response.py b/backend/open_webui/utils/response.py index 8738993c46..9d1920651e 100644 --- a/backend/open_webui/utils/response.py +++ b/backend/open_webui/utils/response.py @@ -68,7 +68,7 @@ def convert_ollama_usage_to_openai(data: dict) -> dict: input_tokens = int(data.get("prompt_eval_count", 0)) output_tokens = int(data.get("eval_count", 0)) total_tokens = input_tokens + output_tokens - + return { # Standardized fields "input_tokens": input_tokens, diff --git a/backend/open_webui/utils/sanitize.py b/backend/open_webui/utils/sanitize.py index 344a7e08e2..258b6d78fb 100644 --- a/backend/open_webui/utils/sanitize.py +++ b/backend/open_webui/utils/sanitize.py @@ -2,7 +2,9 @@ import re # ANSI escape code pattern - matches all common ANSI sequences # This includes color codes, cursor movement, and other terminal control sequences -ANSI_ESCAPE_PATTERN = re.compile(r'\x1b\[[0-9;]*[A-Za-z]|\x1b\([AB]|\x1b[PX^_].*?\x1b\\|\x1b\].*?(?:\x07|\x1b\\)') +ANSI_ESCAPE_PATTERN = re.compile( + r"\x1b\[[0-9;]*[A-Za-z]|\x1b\([AB]|\x1b[PX^_].*?\x1b\\|\x1b\].*?(?:\x07|\x1b\\)" +) def strip_ansi_codes(text: str) -> str: @@ -18,7 +20,7 @@ def strip_ansi_codes(text: str) -> str: - Reset codes: \x1b[0m, \x1b[39m - Cursor movement: \x1b[1A, \x1b[2J, etc. """ - return ANSI_ESCAPE_PATTERN.sub('', text) + return ANSI_ESCAPE_PATTERN.sub("", text) def strip_markdown_code_fences(code: str) -> str: @@ -55,4 +57,3 @@ def sanitize_code(code: str) -> str: code = strip_ansi_codes(code) code = strip_markdown_code_fences(code) return code - diff --git a/backend/open_webui/utils/task.py b/backend/open_webui/utils/task.py index 4acb2c3718..abc8920884 100644 --- a/backend/open_webui/utils/task.py +++ b/backend/open_webui/utils/task.py @@ -10,7 +10,6 @@ from open_webui.utils.misc import get_last_user_message, get_messages_content from open_webui.config import DEFAULT_RAG_TEMPLATE - log = logging.getLogger(__name__) diff --git a/backend/open_webui/utils/telemetry/instrumentors.py b/backend/open_webui/utils/telemetry/instrumentors.py index 9e251e38ae..17536b9adb 100644 --- a/backend/open_webui/utils/telemetry/instrumentors.py +++ b/backend/open_webui/utils/telemetry/instrumentors.py @@ -29,7 +29,6 @@ from fastapi import status from open_webui.utils.telemetry.constants import SPAN_REDIS_TYPE, SpanAttributes - logger = logging.getLogger(__name__) @@ -60,7 +59,7 @@ def response_hook(span: Span, request: PreparedRequest, response: Response): span.set_status(StatusCode.ERROR if response.status_code >= 400 else StatusCode.OK) -def redis_request_hook(span: Span, instance: Union[Redis|RedisCluster], args, kwargs): +def redis_request_hook(span: Span, instance: Union[Redis | RedisCluster], args, kwargs): """ Redis Request Hook """ @@ -71,7 +70,7 @@ def redis_request_hook(span: Span, instance: Union[Redis|RedisCluster], args, kw # Instead of checking the type, we check if the instance has a nodes_manager attribute. try: db = "" - if hasattr(instance, 'nodes_manager'): + if hasattr(instance, "nodes_manager"): default_node = instance.nodes_manager.default_node if not default_node: return diff --git a/backend/open_webui/utils/tools.py b/backend/open_webui/utils/tools.py index 7e8520a1ac..b350a8c679 100644 --- a/backend/open_webui/utils/tools.py +++ b/backend/open_webui/utils/tools.py @@ -352,7 +352,9 @@ async def get_tools( headers = include_user_info_headers(headers, user) metadata = extra_params.get("__metadata__", {}) if metadata and metadata.get("chat_id"): - headers[FORWARD_SESSION_INFO_HEADER_CHAT_ID] = metadata.get("chat_id") + headers[FORWARD_SESSION_INFO_HEADER_CHAT_ID] = ( + metadata.get("chat_id") + ) def make_tool_function( function_name, tool_server_data, headers @@ -416,9 +418,8 @@ def get_builtin_tools( # Helper to get model capabilities (defaults to True if not specified) def get_model_capability(name: str, default: bool = True) -> bool: - return ( - (model.get("info", {}).get("meta", {}).get("capabilities") or {}) - .get(name, default) + return (model.get("info", {}).get("meta", {}).get("capabilities") or {}).get( + name, default ) # Helper to check if a builtin tool category is enabled via meta.builtinTools @@ -491,13 +492,17 @@ def get_builtin_tools( builtin_functions.append(execute_code) # Notes tools - search, view, create, and update user's notes (if builtin category enabled AND notes enabled globally) - if is_builtin_tool_enabled("notes") and getattr(request.app.state.config, "ENABLE_NOTES", False): + if is_builtin_tool_enabled("notes") and getattr( + request.app.state.config, "ENABLE_NOTES", False + ): builtin_functions.extend( [search_notes, view_note, write_note, replace_note_content] ) # Channels tools - search channels and messages (if builtin category enabled AND channels enabled globally) - if is_builtin_tool_enabled("channels") and getattr(request.app.state.config, "ENABLE_CHANNELS", False): + if is_builtin_tool_enabled("channels") and getattr( + request.app.state.config, "ENABLE_CHANNELS", False + ): builtin_functions.extend( [ search_channels, diff --git a/backend/open_webui/utils/validate.py b/backend/open_webui/utils/validate.py index 9b50c3b793..6e62dd5416 100644 --- a/backend/open_webui/utils/validate.py +++ b/backend/open_webui/utils/validate.py @@ -36,4 +36,3 @@ def validate_profile_image_url(url: str) -> str: raise ValueError( "Invalid profile image URL: only data URIs and default avatars are allowed." ) - diff --git a/src/lib/apis/analytics/index.ts b/src/lib/apis/analytics/index.ts index f03729937c..6bab2cbf81 100644 --- a/src/lib/apis/analytics/index.ts +++ b/src/lib/apis/analytics/index.ts @@ -1,317 +1,319 @@ import { WEBUI_API_BASE_URL } from '$lib/constants'; export const getModelAnalytics = async ( - token: string = '', - startDate: number | null = null, - endDate: number | null = null, - groupId: string | null = null + token: string = '', + startDate: number | null = null, + endDate: number | null = null, + groupId: string | null = null ) => { - let error = null; + let error = null; - const searchParams = new URLSearchParams(); - if (startDate) searchParams.append('start_date', startDate.toString()); - if (endDate) searchParams.append('end_date', endDate.toString()); - if (groupId) searchParams.append('group_id', groupId); + const searchParams = new URLSearchParams(); + if (startDate) searchParams.append('start_date', startDate.toString()); + if (endDate) searchParams.append('end_date', endDate.toString()); + if (groupId) searchParams.append('group_id', groupId); - const res = await fetch(`${WEBUI_API_BASE_URL}/analytics/models?${searchParams.toString()}`, { - method: 'GET', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - authorization: `Bearer ${token}` - } - }) - .then(async (res) => { - if (!res.ok) throw await res.json(); - return res.json(); - }) - .catch((err) => { - error = err.detail; - console.error(err); - return null; - }); + const res = await fetch(`${WEBUI_API_BASE_URL}/analytics/models?${searchParams.toString()}`, { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + error = err.detail; + console.error(err); + return null; + }); - if (error) { - throw error; - } + if (error) { + throw error; + } - return res; + return res; }; export const getUserAnalytics = async ( - token: string = '', - startDate: number | null = null, - endDate: number | null = null, - limit: number = 50, - groupId: string | null = null + token: string = '', + startDate: number | null = null, + endDate: number | null = null, + limit: number = 50, + groupId: string | null = null ) => { - let error = null; + let error = null; - const searchParams = new URLSearchParams(); - if (startDate) searchParams.append('start_date', startDate.toString()); - if (endDate) searchParams.append('end_date', endDate.toString()); - if (limit) searchParams.append('limit', limit.toString()); - if (groupId) searchParams.append('group_id', groupId); + const searchParams = new URLSearchParams(); + if (startDate) searchParams.append('start_date', startDate.toString()); + if (endDate) searchParams.append('end_date', endDate.toString()); + if (limit) searchParams.append('limit', limit.toString()); + if (groupId) searchParams.append('group_id', groupId); - const res = await fetch(`${WEBUI_API_BASE_URL}/analytics/users?${searchParams.toString()}`, { - method: 'GET', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - authorization: `Bearer ${token}` - } - }) - .then(async (res) => { - if (!res.ok) throw await res.json(); - return res.json(); - }) - .catch((err) => { - error = err.detail; - console.error(err); - return null; - }); + const res = await fetch(`${WEBUI_API_BASE_URL}/analytics/users?${searchParams.toString()}`, { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + error = err.detail; + console.error(err); + return null; + }); - if (error) { - throw error; - } + if (error) { + throw error; + } - return res; + return res; }; export const getMessages = async ( - token: string = '', - modelId: string | null = null, - userId: string | null = null, - chatId: string | null = null, - startDate: number | null = null, - endDate: number | null = null, - skip: number = 0, - limit: number = 50 + token: string = '', + modelId: string | null = null, + userId: string | null = null, + chatId: string | null = null, + startDate: number | null = null, + endDate: number | null = null, + skip: number = 0, + limit: number = 50 ) => { - let error = null; + let error = null; - const searchParams = new URLSearchParams(); - if (modelId) searchParams.append('model_id', modelId); - if (userId) searchParams.append('user_id', userId); - if (chatId) searchParams.append('chat_id', chatId); - if (startDate) searchParams.append('start_date', startDate.toString()); - if (endDate) searchParams.append('end_date', endDate.toString()); - if (skip) searchParams.append('skip', skip.toString()); - if (limit) searchParams.append('limit', limit.toString()); + const searchParams = new URLSearchParams(); + if (modelId) searchParams.append('model_id', modelId); + if (userId) searchParams.append('user_id', userId); + if (chatId) searchParams.append('chat_id', chatId); + if (startDate) searchParams.append('start_date', startDate.toString()); + if (endDate) searchParams.append('end_date', endDate.toString()); + if (skip) searchParams.append('skip', skip.toString()); + if (limit) searchParams.append('limit', limit.toString()); - const res = await fetch(`${WEBUI_API_BASE_URL}/analytics/messages?${searchParams.toString()}`, { - method: 'GET', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - authorization: `Bearer ${token}` - } - }) - .then(async (res) => { - if (!res.ok) throw await res.json(); - return res.json(); - }) - .catch((err) => { - error = err.detail; - console.error(err); - return null; - }); + const res = await fetch(`${WEBUI_API_BASE_URL}/analytics/messages?${searchParams.toString()}`, { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + error = err.detail; + console.error(err); + return null; + }); - if (error) { - throw error; - } + if (error) { + throw error; + } - return res; + return res; }; export const getSummary = async ( - token: string = '', - startDate: number | null = null, - endDate: number | null = null, - groupId: string | null = null + token: string = '', + startDate: number | null = null, + endDate: number | null = null, + groupId: string | null = null ) => { - let error = null; + let error = null; - const searchParams = new URLSearchParams(); - if (startDate) searchParams.append('start_date', startDate.toString()); - if (endDate) searchParams.append('end_date', endDate.toString()); - if (groupId) searchParams.append('group_id', groupId); + const searchParams = new URLSearchParams(); + if (startDate) searchParams.append('start_date', startDate.toString()); + if (endDate) searchParams.append('end_date', endDate.toString()); + if (groupId) searchParams.append('group_id', groupId); - const res = await fetch(`${WEBUI_API_BASE_URL}/analytics/summary?${searchParams.toString()}`, { - method: 'GET', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - authorization: `Bearer ${token}` - } - }) - .then(async (res) => { - if (!res.ok) throw await res.json(); - return res.json(); - }) - .catch((err) => { - error = err.detail; - console.error(err); - return null; - }); + const res = await fetch(`${WEBUI_API_BASE_URL}/analytics/summary?${searchParams.toString()}`, { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + error = err.detail; + console.error(err); + return null; + }); - if (error) { - throw error; - } + if (error) { + throw error; + } - return res; + return res; }; export const getDailyStats = async ( - token: string = '', - startDate: number | null = null, - endDate: number | null = null, - granularity: 'hourly' | 'daily' = 'daily', - groupId: string | null = null + token: string = '', + startDate: number | null = null, + endDate: number | null = null, + granularity: 'hourly' | 'daily' = 'daily', + groupId: string | null = null ) => { - let error = null; + let error = null; - const searchParams = new URLSearchParams(); - if (startDate) searchParams.append('start_date', startDate.toString()); - if (endDate) searchParams.append('end_date', endDate.toString()); - searchParams.append('granularity', granularity); - if (groupId) searchParams.append('group_id', groupId); + const searchParams = new URLSearchParams(); + if (startDate) searchParams.append('start_date', startDate.toString()); + if (endDate) searchParams.append('end_date', endDate.toString()); + searchParams.append('granularity', granularity); + if (groupId) searchParams.append('group_id', groupId); - const res = await fetch(`${WEBUI_API_BASE_URL}/analytics/daily?${searchParams.toString()}`, { - method: 'GET', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - authorization: `Bearer ${token}` - } - }) - .then(async (res) => { - if (!res.ok) throw await res.json(); - return res.json(); - }) - .catch((err) => { - error = err.detail; - console.error(err); - return null; - }); + const res = await fetch(`${WEBUI_API_BASE_URL}/analytics/daily?${searchParams.toString()}`, { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + error = err.detail; + console.error(err); + return null; + }); - if (error) { - throw error; - } + if (error) { + throw error; + } - return res; + return res; }; export const getTokenUsage = async ( - token: string = '', - startDate: number | null = null, - endDate: number | null = null, - groupId: string | null = null + token: string = '', + startDate: number | null = null, + endDate: number | null = null, + groupId: string | null = null ) => { - let error = null; + let error = null; - const searchParams = new URLSearchParams(); - if (startDate) searchParams.append('start_date', startDate.toString()); - if (endDate) searchParams.append('end_date', endDate.toString()); - if (groupId) searchParams.append('group_id', groupId); + const searchParams = new URLSearchParams(); + if (startDate) searchParams.append('start_date', startDate.toString()); + if (endDate) searchParams.append('end_date', endDate.toString()); + if (groupId) searchParams.append('group_id', groupId); - const res = await fetch(`${WEBUI_API_BASE_URL}/analytics/tokens?${searchParams.toString()}`, { - method: 'GET', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - authorization: `Bearer ${token}` - } - }) - .then(async (res) => { - if (!res.ok) throw await res.json(); - return res.json(); - }) - .catch((err) => { - error = err.detail; - console.error(err); - return null; - }); + const res = await fetch(`${WEBUI_API_BASE_URL}/analytics/tokens?${searchParams.toString()}`, { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + error = err.detail; + console.error(err); + return null; + }); - if (error) { - throw error; - } + if (error) { + throw error; + } - return res; + return res; }; export const getModelChats = async ( - token: string = '', - modelId: string, - startDate: number | null = null, - endDate: number | null = null, - skip: number = 0, - limit: number = 50 + token: string = '', + modelId: string, + startDate: number | null = null, + endDate: number | null = null, + skip: number = 0, + limit: number = 50 ) => { - let error = null; + let error = null; - const searchParams = new URLSearchParams(); - if (startDate) searchParams.append('start_date', startDate.toString()); - if (endDate) searchParams.append('end_date', endDate.toString()); - if (skip) searchParams.append('skip', skip.toString()); - if (limit) searchParams.append('limit', limit.toString()); + const searchParams = new URLSearchParams(); + if (startDate) searchParams.append('start_date', startDate.toString()); + if (endDate) searchParams.append('end_date', endDate.toString()); + if (skip) searchParams.append('skip', skip.toString()); + if (limit) searchParams.append('limit', limit.toString()); - const res = await fetch(`${WEBUI_API_BASE_URL}/analytics/models/${encodeURIComponent(modelId)}/chats?${searchParams.toString()}`, { - method: 'GET', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - authorization: `Bearer ${token}` - } - }) - .then(async (res) => { - if (!res.ok) throw await res.json(); - return res.json(); - }) - .catch((err) => { - error = err.detail; - console.error(err); - return null; - }); + const res = await fetch( + `${WEBUI_API_BASE_URL}/analytics/models/${encodeURIComponent(modelId)}/chats?${searchParams.toString()}`, + { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + } + } + ) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + error = err.detail; + console.error(err); + return null; + }); - if (error) { - throw error; - } + if (error) { + throw error; + } - return res; + return res; }; -export const getModelOverview = async ( - token: string = '', - modelId: string, - days: number = 30 -) => { - let error = null; +export const getModelOverview = async (token: string = '', modelId: string, days: number = 30) => { + let error = null; - const searchParams = new URLSearchParams(); - searchParams.append('days', days.toString()); + const searchParams = new URLSearchParams(); + searchParams.append('days', days.toString()); - const res = await fetch(`${WEBUI_API_BASE_URL}/analytics/models/${encodeURIComponent(modelId)}/overview?${searchParams.toString()}`, { - method: 'GET', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - authorization: `Bearer ${token}` - } - }) - .then(async (res) => { - if (!res.ok) throw await res.json(); - return res.json(); - }) - .catch((err) => { - error = err.detail; - console.error(err); - return null; - }); + const res = await fetch( + `${WEBUI_API_BASE_URL}/analytics/models/${encodeURIComponent(modelId)}/overview?${searchParams.toString()}`, + { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + } + } + ) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + error = err.detail; + console.error(err); + return null; + }); - if (error) { - throw error; - } + if (error) { + throw error; + } - return res; + return res; }; diff --git a/src/lib/apis/chats/index.ts b/src/lib/apis/chats/index.ts index bfe7a16386..e16746707a 100644 --- a/src/lib/apis/chats/index.ts +++ b/src/lib/apis/chats/index.ts @@ -255,11 +255,7 @@ export const getArchivedChatList = async ( })); }; -export const getSharedChatList = async ( - token: string = '', - page: number = 1, - filter?: object -) => { +export const getSharedChatList = async (token: string = '', page: number = 1, filter?: object) => { let error = null; const searchParams = new URLSearchParams(); diff --git a/src/lib/apis/index.ts b/src/lib/apis/index.ts index 92c28e1d9d..1529600368 100644 --- a/src/lib/apis/index.ts +++ b/src/lib/apis/index.ts @@ -1675,7 +1675,7 @@ export interface ModelMeta { profile_image_url?: string; } -export interface ModelParams { } +export interface ModelParams {} export type GlobalModelConfig = ModelConfig[]; diff --git a/src/lib/apis/models/index.ts b/src/lib/apis/models/index.ts index 0151a55c19..42e77c0afa 100644 --- a/src/lib/apis/models/index.ts +++ b/src/lib/apis/models/index.ts @@ -281,11 +281,7 @@ export const updateModelById = async (token: string, id: string, model: object) return res; }; -export const updateModelAccessGrants = async ( - token: string, - id: string, - accessGrants: any[] -) => { +export const updateModelAccessGrants = async (token: string, id: string, accessGrants: any[]) => { let error = null; const res = await fetch(`${WEBUI_API_BASE_URL}/models/model/access/update`, { diff --git a/src/lib/apis/notes/index.ts b/src/lib/apis/notes/index.ts index 1780aa7905..07e249a889 100644 --- a/src/lib/apis/notes/index.ts +++ b/src/lib/apis/notes/index.ts @@ -253,11 +253,7 @@ export const updateNoteById = async (token: string, id: string, note: NoteItem) return res; }; -export const updateNoteAccessGrants = async ( - token: string, - id: string, - accessGrants: any[] -) => { +export const updateNoteAccessGrants = async (token: string, id: string, accessGrants: any[]) => { let error = null; const res = await fetch(`${WEBUI_API_BASE_URL}/notes/${id}/access/update`, { diff --git a/src/lib/apis/prompts/index.ts b/src/lib/apis/prompts/index.ts index 3c35a2cedf..1fd311c76f 100644 --- a/src/lib/apis/prompts/index.ts +++ b/src/lib/apis/prompts/index.ts @@ -1,16 +1,16 @@ import { WEBUI_API_BASE_URL } from '$lib/constants'; type PromptItem = { - id?: string; // Prompt ID + id?: string; // Prompt ID command: string; - name: string; // Changed from title + name: string; // Changed from title content: string; data?: object | null; meta?: object | null; access_grants?: object[]; - version_id?: string | null; // Active version - commit_message?: string | null; // For history tracking - is_production?: boolean; // Whether to set new version as production + version_id?: string | null; // Active version + commit_message?: string | null; // For history tracking + is_production?: boolean; // Whether to set new version as production }; type PromptHistoryItem = { @@ -196,7 +196,6 @@ export const getPromptItems = async ( }; export const getPromptList = async (token: string = '') => { - let error = null; const res = await fetch(`${WEBUI_API_BASE_URL}/prompts/list`, { @@ -472,17 +471,14 @@ export const getPromptHistory = async ( ): Promise => { let error = null; - const res = await fetch( - `${WEBUI_API_BASE_URL}/prompts/id/${promptId}/history?page=${page}`, - { - method: 'GET', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - authorization: `Bearer ${token}` - } + const res = await fetch(`${WEBUI_API_BASE_URL}/prompts/id/${promptId}/history?page=${page}`, { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` } - ) + }) .then(async (res) => { if (!res.ok) throw await res.json(); return res.json(); @@ -507,17 +503,14 @@ export const deletePromptHistoryVersion = async ( ): Promise => { let error = null; - const res = await fetch( - `${WEBUI_API_BASE_URL}/prompts/id/${promptId}/history/${historyId}`, - { - method: 'DELETE', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - authorization: `Bearer ${token}` - } + const res = await fetch(`${WEBUI_API_BASE_URL}/prompts/id/${promptId}/history/${historyId}`, { + method: 'DELETE', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` } - ) + }) .then(async (res) => { if (!res.ok) throw await res.json(); return res.json(); @@ -542,17 +535,14 @@ export const getPromptHistoryEntry = async ( ): Promise => { let error = null; - const res = await fetch( - `${WEBUI_API_BASE_URL}/prompts/id/${promptId}/history/${historyId}`, - { - method: 'GET', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - authorization: `Bearer ${token}` - } + const res = await fetch(`${WEBUI_API_BASE_URL}/prompts/id/${promptId}/history/${historyId}`, { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` } - ) + }) .then(async (res) => { if (!res.ok) throw await res.json(); return res.json(); diff --git a/src/lib/apis/skills/index.ts b/src/lib/apis/skills/index.ts index 14543046b9..24139fa042 100644 --- a/src/lib/apis/skills/index.ts +++ b/src/lib/apis/skills/index.ts @@ -227,11 +227,7 @@ export const updateSkillById = async (token: string, id: string, skill: object) return res; }; -export const updateSkillAccessGrants = async ( - token: string, - id: string, - accessGrants: any[] -) => { +export const updateSkillAccessGrants = async (token: string, id: string, accessGrants: any[]) => { let error = null; const res = await fetch(`${WEBUI_API_BASE_URL}/skills/id/${id}/access/update`, { diff --git a/src/lib/apis/tasks/index.ts b/src/lib/apis/tasks/index.ts index 83299b843b..dab6090fde 100644 --- a/src/lib/apis/tasks/index.ts +++ b/src/lib/apis/tasks/index.ts @@ -1,14 +1,14 @@ import { WEBUI_API_BASE_URL } from '$lib/constants'; export const checkActiveChats = async (token: string, chatIds: string[]) => { - const res = await fetch(`${WEBUI_API_BASE_URL}/tasks/active/chats`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${token}` - }, - body: JSON.stringify({ chat_ids: chatIds }) - }); - if (!res.ok) throw await res.json(); - return res.json(); + const res = await fetch(`${WEBUI_API_BASE_URL}/tasks/active/chats`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}` + }, + body: JSON.stringify({ chat_ids: chatIds }) + }); + if (!res.ok) throw await res.json(); + return res.json(); }; diff --git a/src/lib/apis/tools/index.ts b/src/lib/apis/tools/index.ts index 103e05f390..5d26e50fee 100644 --- a/src/lib/apis/tools/index.ts +++ b/src/lib/apis/tools/index.ts @@ -225,11 +225,7 @@ export const updateToolById = async (token: string, id: string, tool: object) => return res; }; -export const updateToolAccessGrants = async ( - token: string, - id: string, - accessGrants: any[] -) => { +export const updateToolAccessGrants = async (token: string, id: string, accessGrants: any[]) => { let error = null; const res = await fetch(`${WEBUI_API_BASE_URL}/tools/id/${id}/access/update`, { diff --git a/src/lib/apis/users/index.ts b/src/lib/apis/users/index.ts index ad669c3eb9..91b63338de 100644 --- a/src/lib/apis/users/index.ts +++ b/src/lib/apis/users/index.ts @@ -300,7 +300,6 @@ export const updateUserSettings = async (token: string, settings: object) => { return res; }; - export const getUserInfoById = async (token: string, userId: string) => { let error = null; diff --git a/src/lib/components/NotificationToast.svelte b/src/lib/components/NotificationToast.svelte index d232009b21..5a40b85b70 100644 --- a/src/lib/components/NotificationToast.svelte +++ b/src/lib/components/NotificationToast.svelte @@ -50,7 +50,10 @@ (e.currentTarget as HTMLElement).releasePointerCapture?.(e.pointerId); // Skip if clicking the close button - if (closeButtonElement && (e.target === closeButtonElement || closeButtonElement.contains(e.target as Node))) { + if ( + closeButtonElement && + (e.target === closeButtonElement || closeButtonElement.contains(e.target as Node)) + ) { return; } diff --git a/src/lib/components/admin/Analytics/AnalyticsModelModal.svelte b/src/lib/components/admin/Analytics/AnalyticsModelModal.svelte index 642ac49a68..c9eb9cf3d7 100644 --- a/src/lib/components/admin/Analytics/AnalyticsModelModal.svelte +++ b/src/lib/components/admin/Analytics/AnalyticsModelModal.svelte @@ -32,7 +32,13 @@ let loadingOverview = false; // Chats tab state - let chatList: Array<{ id: string; title: string; updated_at: number; user_id?: string; user_name?: string }> = []; + let chatList: Array<{ + id: string; + title: string; + updated_at: number; + user_id?: string; + user_name?: string; + }> = []; let chatListLoading = false; let allChatsLoaded = false; const PAGE_SIZE = 50; @@ -76,7 +82,14 @@ chatList = []; allChatsLoaded = false; try { - const res = await getModelChats(localStorage.token, model.id, startDate, endDate, 0, PAGE_SIZE); + const res = await getModelChats( + localStorage.token, + model.id, + startDate, + endDate, + 0, + PAGE_SIZE + ); const chats = res?.chats ?? []; chatList = chats.map((c: any) => ({ id: c.chat_id, @@ -99,7 +112,14 @@ chatListLoading = true; try { const skip = chatList.length; - const res = await getModelChats(localStorage.token, model.id, startDate, endDate, skip, PAGE_SIZE); + const res = await getModelChats( + localStorage.token, + model.id, + startDate, + endDate, + skip, + PAGE_SIZE + ); const chats = res?.chats ?? []; const newChats = chats.map((c: any) => ({ id: c.chat_id, @@ -157,14 +177,14 @@ {$i18n.t('Overview')} {#if $config?.features?.enable_admin_chat_access} - + {/if} @@ -175,10 +195,10 @@
-
- {$i18n.t('Feedback Activity')} -
-
+
+ {$i18n.t('Feedback Activity')} +
+
diff --git a/src/lib/components/admin/Analytics/ChartLine.svelte b/src/lib/components/admin/Analytics/ChartLine.svelte index 5c3ffabdf3..6f2f68f39a 100644 --- a/src/lib/components/admin/Analytics/ChartLine.svelte +++ b/src/lib/components/admin/Analytics/ChartLine.svelte @@ -83,7 +83,11 @@ {@const labelCount = Math.min(7, data.length)} {@const step = labelCount > 1 ? Math.floor((data.length - 1) / (labelCount - 1)) || 1 : 1} {@const isHourly = data[0]?.date?.includes(':')} - {@const dateFormat = isHourly ? 'h A' : period === 'year' || period === 'all' ? 'M/D/YY' : 'M/D'} + {@const dateFormat = isHourly + ? 'h A' + : period === 'year' || period === 'all' + ? 'M/D/YY' + : 'M/D'}
{#each Array(labelCount) as _, i} {@const idx = i === labelCount - 1 ? data.length - 1 : Math.min(i * step, data.length - 1)} diff --git a/src/lib/components/admin/Analytics/Dashboard.svelte b/src/lib/components/admin/Analytics/Dashboard.svelte index 9388761636..98877d1d43 100644 --- a/src/lib/components/admin/Analytics/Dashboard.svelte +++ b/src/lib/components/admin/Analytics/Dashboard.svelte @@ -1,7 +1,13 @@ -
+
{$i18n.t('Analytics')}
@@ -208,27 +221,54 @@ {#if !loading}
- {summary.total_messages.toLocaleString()} {$i18n.t('messages')} + {summary.total_messages.toLocaleString()} + {$i18n.t('messages')} - {formatNumber(totalTokens.total)} {$i18n.t('tokens')} + {formatNumber(totalTokens.total)} + {$i18n.t('tokens')} - {summary.total_chats.toLocaleString()} {$i18n.t('chats')} - {summary.total_users} {$i18n.t('users')} + {summary.total_chats.toLocaleString()} + {$i18n.t('chats')} + {summary.total_users} + {$i18n.t('users')}
{#if dailyStats.length > 1} - {@const allModels = [...new Set(dailyStats.flatMap(d => Object.keys(d.models || {})))]} + {@const allModels = [...new Set(dailyStats.flatMap((d) => Object.keys(d.models || {})))]} {@const topModels = allModels.slice(0, 8)} - {@const chartColors = ['#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6', '#ec4899', '#06b6d4', '#84cc16']} - {@const periodMap = { '24h': 'hour', '7d': 'week', '30d': 'month', '90d': 'year', 'all': 'all' }} + {@const chartColors = [ + '#3b82f6', + '#10b981', + '#f59e0b', + '#ef4444', + '#8b5cf6', + '#ec4899', + '#06b6d4', + '#84cc16' + ]} + {@const periodMap = { '24h': 'hour', '7d': 'week', '30d': 'month', '90d': 'year', all: 'all' }}
{$i18n.t(selectedPeriod === '24h' ? 'Hourly Messages' : 'Daily Messages')}
- - {#if modelDirection === 'asc'}{:else}{/if} + {#if modelDirection === 'asc'}{:else}{/if} {:else} @@ -278,7 +320,9 @@ {$i18n.t('Messages')} {#if modelOrderBy === 'count'} - {#if modelDirection === 'asc'}{:else}{/if} + {#if modelDirection === 'asc'}{:else}{/if} {:else} @@ -292,9 +336,12 @@ {#each sortedModels as model, idx (model.model_id)} { selectedModel = { id: model.model_id, name: model.name }; showModelModal = true; }} - > + class="bg-white dark:bg-gray-900 dark:border-gray-850 text-xs cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors" + on:click={() => { + selectedModel = { id: model.model_id, name: model.name }; + showModelModal = true; + }} + > {idx + 1}
@@ -307,14 +354,22 @@
{model.count.toLocaleString()} - {formatNumber(tokenStats[model.model_id]?.total_tokens ?? 0)} + {formatNumber(tokenStats[model.model_id]?.total_tokens ?? 0)} - {totalModelMessages > 0 ? ((model.count / totalModelMessages) * 100).toFixed(1) : 0}% + {totalModelMessages > 0 + ? ((model.count / totalModelMessages) * 100).toFixed(1) + : 0}% {/each} {#if sortedModels.length === 0} - {$i18n.t('No data')} + {$i18n.t('No data')} {/if} @@ -340,7 +395,9 @@ {$i18n.t('User')} {#if userOrderBy === 'name'} - {#if userDirection === 'asc'}{:else}{/if} + {#if userDirection === 'asc'}{:else}{/if} {:else} @@ -356,7 +413,9 @@ {$i18n.t('Messages')} {#if userOrderBy === 'count'} - {#if userDirection === 'asc'}{:else}{/if} + {#if userDirection === 'asc'}{:else}{/if} {:else} @@ -377,7 +436,9 @@ alt={user.name || 'User'} class="size-5 rounded-full object-cover shrink-0" /> - {user.name || user.email || user.user_id.substring(0, 8)} + {user.name || user.email || user.user_id.substring(0, 8)}
{user.count.toLocaleString()} @@ -385,7 +446,11 @@ {/each} {#if sortedUsers.length === 0} - {$i18n.t('No data')} + {$i18n.t('No data')} {/if} diff --git a/src/lib/components/admin/Analytics/ModelUsage.svelte b/src/lib/components/admin/Analytics/ModelUsage.svelte index d7212f721a..d24e798c88 100644 --- a/src/lib/components/admin/Analytics/ModelUsage.svelte +++ b/src/lib/components/admin/Analytics/ModelUsage.svelte @@ -41,9 +41,7 @@ $: sortedModels = [...modelStats].sort((a, b) => { if (orderBy === 'name') { - return direction === 'asc' - ? a.name.localeCompare(b.name) - : b.name.localeCompare(a.name); + return direction === 'asc' ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name); } return direction === 'asc' ? a.count - b.count : b.count - a.count; }); @@ -53,16 +51,22 @@ onMount(loadAnalytics); -
+
{$i18n.t('Model Usage')} {totalMessages} {$i18n.t('messages')}
-
+
{#if loading} -
+
{/if} @@ -70,7 +74,11 @@ {#if !modelStats.length && !loading}
{$i18n.t('No data found')}
{:else if modelStats.length} - +
@@ -82,7 +90,9 @@
{$i18n.t('Model')} {#if orderBy === 'name'} - {#if direction === 'asc'}{:else}{/if} + {#if direction === 'asc'}{:else}{/if} {:else} {/if} @@ -96,7 +106,9 @@
{$i18n.t('Messages')} {#if orderBy === 'count'} - {#if direction === 'asc'}{:else}{/if} + {#if direction === 'asc'}{:else}{/if} {:else} {/if} @@ -107,7 +119,9 @@
{#each sortedModels as model, idx (model.model_id)} - + diff --git a/src/lib/components/admin/Analytics/UserUsage.svelte b/src/lib/components/admin/Analytics/UserUsage.svelte index 015ba195d1..09be24e426 100644 --- a/src/lib/components/admin/Analytics/UserUsage.svelte +++ b/src/lib/components/admin/Analytics/UserUsage.svelte @@ -46,16 +46,22 @@ onMount(loadAnalytics); -
+
{$i18n.t('User Activity')} {userStats.length} {$i18n.t('users')}
-
+
{#if loading} -
+
{/if} @@ -63,7 +69,11 @@ {#if !userStats.length && !loading}
{$i18n.t('No data found')}
{:else if userStats.length} -
#
{idx + 1}
+
@@ -75,7 +85,9 @@
{$i18n.t('User')} {#if orderBy === 'user_id'} - {#if direction === 'asc'}{:else}{/if} + {#if direction === 'asc'}{:else}{/if} {:else} {/if} @@ -89,7 +101,9 @@
{$i18n.t('Messages')} {#if orderBy === 'count'} - {#if direction === 'asc'}{:else}{/if} + {#if direction === 'asc'}{:else}{/if} {:else} {/if} @@ -100,7 +114,9 @@
{#each sortedUsers as user, idx (user.user_id)} - + diff --git a/src/lib/components/admin/Evaluations/Feedbacks.svelte b/src/lib/components/admin/Evaluations/Feedbacks.svelte index ccf1735431..1dedb94f05 100644 --- a/src/lib/components/admin/Evaluations/Feedbacks.svelte +++ b/src/lib/components/admin/Evaluations/Feedbacks.svelte @@ -303,10 +303,12 @@
{#if feedback.data?.sibling_model_ids} -
- {feedback.data?.model_id} -
-
+
+ {feedback.data?.model_id} +
+
@@ -323,10 +325,12 @@ {:else} -
- {feedback.data?.model_id} -
-
+
+ {feedback.data?.model_id} +
+ {/if}
diff --git a/src/lib/components/admin/Evaluations/Leaderboard.svelte b/src/lib/components/admin/Evaluations/Leaderboard.svelte index 0dd501a153..abe0f952e4 100644 --- a/src/lib/components/admin/Evaluations/Leaderboard.svelte +++ b/src/lib/components/admin/Evaluations/Leaderboard.svelte @@ -183,7 +183,9 @@ class="size-5 rounded-full object-cover" /> - {model.name} + {model.name} diff --git a/src/lib/components/admin/Settings/Models.svelte b/src/lib/components/admin/Settings/Models.svelte index 6e38d00d14..2762d3a112 100644 --- a/src/lib/components/admin/Settings/Models.svelte +++ b/src/lib/components/admin/Settings/Models.svelte @@ -104,7 +104,9 @@ modelsToDisable.forEach((m) => (m.is_active = false)); models = models; // Sync with server - await Promise.all(modelsToDisable.map((model) => toggleModelById(localStorage.token, model.id))); + await Promise.all( + modelsToDisable.map((model) => toggleModelById(localStorage.token, model.id)) + ); }; const downloadModels = async (models) => { diff --git a/src/lib/components/admin/Settings/WebSearch.svelte b/src/lib/components/admin/Settings/WebSearch.svelte index 587b494f0d..4778b69ec5 100644 --- a/src/lib/components/admin/Settings/WebSearch.svelte +++ b/src/lib/components/admin/Settings/WebSearch.svelte @@ -738,7 +738,7 @@ {:else if webConfig.WEB_SEARCH_ENGINE === 'yandex'} -
+
{$i18n.t('Yandex Web Search URL')} @@ -769,20 +769,22 @@
-
{$i18n.t('Yandex Web Search config')}
+
{$i18n.t('Yandex Web Search config')}
- -
#
{idx + 1}