chore: format
This commit is contained in:
@@ -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_API_KEY = os.environ.get("WEAVIATE_API_KEY")
|
||||||
WEAVIATE_HTTP_SECURE = os.environ.get("WEAVIATE_HTTP_SECURE", "false").lower() == "true"
|
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_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
|
||||||
OPENSEARCH_URI = os.environ.get("OPENSEARCH_URI", "https://localhost:9200")
|
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".
|
# 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.
|
# 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 = PersistentConfig(
|
||||||
"IMAGE_SIZE", "image_generation.size", os.getenv("IMAGE_SIZE", "512x512")
|
"IMAGE_SIZE", "image_generation.size", os.getenv("IMAGE_SIZE", "512x512")
|
||||||
|
|||||||
+19
-17
@@ -195,13 +195,23 @@ ENABLE_FORWARD_USER_INFO_HEADERS = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Header names for user info forwarding (customizable via environment variables)
|
# 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_NAME = os.environ.get(
|
||||||
FORWARD_USER_INFO_HEADER_USER_ID = os.environ.get("FORWARD_USER_INFO_HEADER_USER_ID", "X-OpenWebUI-User-Id")
|
"FORWARD_USER_INFO_HEADER_USER_NAME", "X-OpenWebUI-User-Name"
|
||||||
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_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)
|
# 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
|
# Experimental feature, may be removed in future
|
||||||
ENABLE_STAR_SESSIONS_MIDDLEWARE = (
|
ENABLE_STAR_SESSIONS_MIDDLEWARE = (
|
||||||
@@ -402,17 +412,13 @@ try:
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
REDIS_SOCKET_CONNECT_TIMEOUT = None
|
REDIS_SOCKET_CONNECT_TIMEOUT = None
|
||||||
|
|
||||||
REDIS_RECONNECT_DELAY = os.environ.get(
|
REDIS_RECONNECT_DELAY = os.environ.get("REDIS_RECONNECT_DELAY", "")
|
||||||
"REDIS_RECONNECT_DELAY", ""
|
|
||||||
)
|
|
||||||
|
|
||||||
if REDIS_RECONNECT_DELAY == "":
|
if REDIS_RECONNECT_DELAY == "":
|
||||||
REDIS_RECONNECT_DELAY = None
|
REDIS_RECONNECT_DELAY = None
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
REDIS_RECONNECT_DELAY = float(
|
REDIS_RECONNECT_DELAY = float(REDIS_RECONNECT_DELAY)
|
||||||
REDIS_RECONNECT_DELAY
|
|
||||||
)
|
|
||||||
if REDIS_RECONNECT_DELAY < 0:
|
if REDIS_RECONNECT_DELAY < 0:
|
||||||
REDIS_RECONNECT_DELAY = None
|
REDIS_RECONNECT_DELAY = None
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -580,15 +586,11 @@ LICENSE_PUBLIC_KEY = os.environ.get("LICENSE_PUBLIC_KEY", "")
|
|||||||
|
|
||||||
pk = None
|
pk = None
|
||||||
if LICENSE_PUBLIC_KEY:
|
if LICENSE_PUBLIC_KEY:
|
||||||
pk = serialization.load_pem_public_key(
|
pk = serialization.load_pem_public_key(f"""
|
||||||
f"""
|
|
||||||
-----BEGIN PUBLIC KEY-----
|
-----BEGIN PUBLIC KEY-----
|
||||||
{LICENSE_PUBLIC_KEY}
|
{LICENSE_PUBLIC_KEY}
|
||||||
-----END PUBLIC KEY-----
|
-----END PUBLIC KEY-----
|
||||||
""".encode(
|
""".encode("utf-8"))
|
||||||
"utf-8"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
####################################
|
####################################
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ from open_webui.utils.payload import (
|
|||||||
apply_system_prompt_to_body,
|
apply_system_prompt_to_body,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL)
|
logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL)
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ from contextlib import suppress
|
|||||||
import peewee as pw
|
import peewee as pw
|
||||||
from peewee_migrate import Migrator
|
from peewee_migrate import Migrator
|
||||||
|
|
||||||
|
|
||||||
with suppress(ImportError):
|
with suppress(ImportError):
|
||||||
import playhouse.postgres_ext as pw_pext
|
import playhouse.postgres_ext as pw_pext
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ from contextlib import suppress
|
|||||||
import peewee as pw
|
import peewee as pw
|
||||||
from peewee_migrate import Migrator
|
from peewee_migrate import Migrator
|
||||||
|
|
||||||
|
|
||||||
with suppress(ImportError):
|
with suppress(ImportError):
|
||||||
import playhouse.postgres_ext as pw_pext
|
import playhouse.postgres_ext as pw_pext
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ from contextlib import suppress
|
|||||||
import peewee as pw
|
import peewee as pw
|
||||||
from peewee_migrate import Migrator
|
from peewee_migrate import Migrator
|
||||||
|
|
||||||
|
|
||||||
with suppress(ImportError):
|
with suppress(ImportError):
|
||||||
import playhouse.postgres_ext as pw_pext
|
import playhouse.postgres_ext as pw_pext
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ from contextlib import suppress
|
|||||||
import peewee as pw
|
import peewee as pw
|
||||||
from peewee_migrate import Migrator
|
from peewee_migrate import Migrator
|
||||||
|
|
||||||
|
|
||||||
with suppress(ImportError):
|
with suppress(ImportError):
|
||||||
import playhouse.postgres_ext as pw_pext
|
import playhouse.postgres_ext as pw_pext
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ from contextlib import suppress
|
|||||||
import peewee as pw
|
import peewee as pw
|
||||||
from peewee_migrate import Migrator
|
from peewee_migrate import Migrator
|
||||||
|
|
||||||
|
|
||||||
with suppress(ImportError):
|
with suppress(ImportError):
|
||||||
import playhouse.postgres_ext as pw_pext
|
import playhouse.postgres_ext as pw_pext
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ from contextlib import suppress
|
|||||||
import peewee as pw
|
import peewee as pw
|
||||||
from peewee_migrate import Migrator
|
from peewee_migrate import Migrator
|
||||||
|
|
||||||
|
|
||||||
with suppress(ImportError):
|
with suppress(ImportError):
|
||||||
import playhouse.postgres_ext as pw_pext
|
import playhouse.postgres_ext as pw_pext
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ from contextlib import suppress
|
|||||||
import peewee as pw
|
import peewee as pw
|
||||||
from peewee_migrate import Migrator
|
from peewee_migrate import Migrator
|
||||||
|
|
||||||
|
|
||||||
with suppress(ImportError):
|
with suppress(ImportError):
|
||||||
import playhouse.postgres_ext as pw_pext
|
import playhouse.postgres_ext as pw_pext
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ from contextlib import suppress
|
|||||||
import peewee as pw
|
import peewee as pw
|
||||||
from peewee_migrate import Migrator
|
from peewee_migrate import Migrator
|
||||||
|
|
||||||
|
|
||||||
with suppress(ImportError):
|
with suppress(ImportError):
|
||||||
import playhouse.postgres_ext as pw_pext
|
import playhouse.postgres_ext as pw_pext
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ from contextlib import suppress
|
|||||||
import peewee as pw
|
import peewee as pw
|
||||||
from peewee_migrate import Migrator
|
from peewee_migrate import Migrator
|
||||||
|
|
||||||
|
|
||||||
with suppress(ImportError):
|
with suppress(ImportError):
|
||||||
import playhouse.postgres_ext as pw_pext
|
import playhouse.postgres_ext as pw_pext
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ from contextlib import suppress
|
|||||||
import peewee as pw
|
import peewee as pw
|
||||||
from peewee_migrate import Migrator
|
from peewee_migrate import Migrator
|
||||||
|
|
||||||
|
|
||||||
with suppress(ImportError):
|
with suppress(ImportError):
|
||||||
import playhouse.postgres_ext as pw_pext
|
import playhouse.postgres_ext as pw_pext
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ from contextlib import suppress
|
|||||||
import peewee as pw
|
import peewee as pw
|
||||||
from peewee_migrate import Migrator
|
from peewee_migrate import Migrator
|
||||||
|
|
||||||
|
|
||||||
with suppress(ImportError):
|
with suppress(ImportError):
|
||||||
import playhouse.postgres_ext as pw_pext
|
import playhouse.postgres_ext as pw_pext
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ from contextlib import suppress
|
|||||||
import peewee as pw
|
import peewee as pw
|
||||||
from peewee_migrate import Migrator
|
from peewee_migrate import Migrator
|
||||||
|
|
||||||
|
|
||||||
with suppress(ImportError):
|
with suppress(ImportError):
|
||||||
import playhouse.postgres_ext as pw_pext
|
import playhouse.postgres_ext as pw_pext
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ from contextlib import suppress
|
|||||||
import peewee as pw
|
import peewee as pw
|
||||||
from peewee_migrate import Migrator
|
from peewee_migrate import Migrator
|
||||||
|
|
||||||
|
|
||||||
with suppress(ImportError):
|
with suppress(ImportError):
|
||||||
import playhouse.postgres_ext as pw_pext
|
import playhouse.postgres_ext as pw_pext
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ from contextlib import suppress
|
|||||||
import peewee as pw
|
import peewee as pw
|
||||||
from peewee_migrate import Migrator
|
from peewee_migrate import Migrator
|
||||||
|
|
||||||
|
|
||||||
with suppress(ImportError):
|
with suppress(ImportError):
|
||||||
import playhouse.postgres_ext as pw_pext
|
import playhouse.postgres_ext as pw_pext
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ from contextlib import suppress
|
|||||||
import peewee as pw
|
import peewee as pw
|
||||||
from peewee_migrate import Migrator
|
from peewee_migrate import Migrator
|
||||||
|
|
||||||
|
|
||||||
with suppress(ImportError):
|
with suppress(ImportError):
|
||||||
import playhouse.postgres_ext as pw_pext
|
import playhouse.postgres_ext as pw_pext
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ from contextlib import suppress
|
|||||||
import peewee as pw
|
import peewee as pw
|
||||||
from peewee_migrate import Migrator
|
from peewee_migrate import Migrator
|
||||||
|
|
||||||
|
|
||||||
with suppress(ImportError):
|
with suppress(ImportError):
|
||||||
import playhouse.postgres_ext as pw_pext
|
import playhouse.postgres_ext as pw_pext
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ from contextlib import suppress
|
|||||||
import peewee as pw
|
import peewee as pw
|
||||||
from peewee_migrate import Migrator
|
from peewee_migrate import Migrator
|
||||||
|
|
||||||
|
|
||||||
with suppress(ImportError):
|
with suppress(ImportError):
|
||||||
import playhouse.postgres_ext as pw_pext
|
import playhouse.postgres_ext as pw_pext
|
||||||
|
|
||||||
|
|||||||
@@ -551,7 +551,6 @@ from open_webui.utils.redis import get_sentinels_from_env
|
|||||||
|
|
||||||
from open_webui.constants import ERROR_MESSAGES
|
from open_webui.constants import ERROR_MESSAGES
|
||||||
|
|
||||||
|
|
||||||
if SAFE_MODE:
|
if SAFE_MODE:
|
||||||
print("SAFE MODE ENABLED")
|
print("SAFE MODE ENABLED")
|
||||||
Functions.deactivate_all_functions()
|
Functions.deactivate_all_functions()
|
||||||
@@ -575,8 +574,7 @@ class SPAStaticFiles(StaticFiles):
|
|||||||
raise ex
|
raise ex
|
||||||
|
|
||||||
|
|
||||||
print(
|
print(rf"""
|
||||||
rf"""
|
|
||||||
██████╗ ██████╗ ███████╗███╗ ██╗ ██╗ ██╗███████╗██████╗ ██╗ ██╗██╗
|
██████╗ ██████╗ ███████╗███╗ ██╗ ██╗ ██╗███████╗██████╗ ██╗ ██╗██╗
|
||||||
██╔═══██╗██╔══██╗██╔════╝████╗ ██║ ██║ ██║██╔════╝██╔══██╗██║ ██║██║
|
██╔═══██╗██╔══██╗██╔════╝████╗ ██║ ██║ ██║██╔════╝██╔══██╗██║ ██║██║
|
||||||
██║ ██║██████╔╝█████╗ ██╔██╗ ██║ ██║ █╗ ██║█████╗ ██████╔╝██║ ██║██║
|
██║ ██║██████╔╝█████╗ ██╔██╗ ██║ ██║ █╗ ██║█████╗ ██████╔╝██║ ██║██║
|
||||||
@@ -588,8 +586,7 @@ print(
|
|||||||
v{VERSION} - building the best AI user interface.
|
v{VERSION} - building the best AI user interface.
|
||||||
{f"Commit: {WEBUI_BUILD_HASH}" if WEBUI_BUILD_HASH != "dev-build" else ""}
|
{f"Commit: {WEBUI_BUILD_HASH}" if WEBUI_BUILD_HASH != "dev-build" else ""}
|
||||||
https://github.com/open-webui/open-webui
|
https://github.com/open-webui/open-webui
|
||||||
"""
|
""")
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
@@ -1845,9 +1842,7 @@ async def chat_completion(
|
|||||||
# Emit chat:active=true when task starts
|
# Emit chat:active=true when task starts
|
||||||
event_emitter = get_event_emitter(metadata, update_db=False)
|
event_emitter = get_event_emitter(metadata, update_db=False)
|
||||||
if event_emitter:
|
if event_emitter:
|
||||||
await event_emitter(
|
await event_emitter({"type": "chat:active", "data": {"active": True}})
|
||||||
{"type": "chat:active", "data": {"active": True}}
|
|
||||||
)
|
|
||||||
return {"status": True, "task_id": task_id}
|
return {"status": True, "task_id": task_id}
|
||||||
else:
|
else:
|
||||||
return await process_chat(request, form_data, user, metadata, model)
|
return await process_chat(request, form_data, user, metadata, model)
|
||||||
|
|||||||
-1
@@ -12,7 +12,6 @@ from alembic import op
|
|||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
import open_webui.internal.db
|
import open_webui.internal.db
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision: str = "2f1211949ecc"
|
revision: str = "2f1211949ecc"
|
||||||
down_revision: Union[str, None] = "37f288994c47"
|
down_revision: Union[str, None] = "37f288994c47"
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import uuid
|
|||||||
from alembic import op
|
from alembic import op
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
revision: str = "374d2f66af06"
|
revision: str = "374d2f66af06"
|
||||||
down_revision: Union[str, None] = "c440947495f3"
|
down_revision: Union[str, None] = "c440947495f3"
|
||||||
branch_labels: Union[str, Sequence[str], None] = None
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ from typing import Sequence, Union
|
|||||||
from alembic import op
|
from alembic import op
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision: str = "37f288994c47"
|
revision: str = "37f288994c47"
|
||||||
down_revision: Union[str, None] = "a5c220713937"
|
down_revision: Union[str, None] = "a5c220713937"
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ from typing import Sequence, Union
|
|||||||
from alembic import op
|
from alembic import op
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision: str = "38d63c18f30f"
|
revision: str = "38d63c18f30f"
|
||||||
down_revision: Union[str, None] = "3af16a1c9fb6"
|
down_revision: Union[str, None] = "3af16a1c9fb6"
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ from alembic import op
|
|||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
import open_webui.internal.db
|
import open_webui.internal.db
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision: str = "6283dc0e4d8d"
|
revision: str = "6283dc0e4d8d"
|
||||||
down_revision: Union[str, None] = "3e0e00844bb0"
|
down_revision: Union[str, None] = "3e0e00844bb0"
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import sqlalchemy as sa
|
|||||||
from sqlalchemy.sql import table, column, select
|
from sqlalchemy.sql import table, column, select
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
|
||||||
revision = "6a39f3d8e55c"
|
revision = "6a39f3d8e55c"
|
||||||
down_revision = "c0fbf31ca0db"
|
down_revision = "c0fbf31ca0db"
|
||||||
branch_labels = None
|
branch_labels = None
|
||||||
|
|||||||
-1
@@ -12,7 +12,6 @@ from alembic import op
|
|||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
import open_webui.internal.db
|
import open_webui.internal.db
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision: str = "81cc2ce44d79"
|
revision: str = "81cc2ce44d79"
|
||||||
down_revision: Union[str, None] = "6283dc0e4d8d"
|
down_revision: Union[str, None] = "6283dc0e4d8d"
|
||||||
|
|||||||
@@ -165,7 +165,9 @@ def upgrade() -> None:
|
|||||||
log.warning(f"Failed to insert message {message_id}: {e}")
|
log.warning(f"Failed to insert message {message_id}: {e}")
|
||||||
continue
|
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:
|
def downgrade() -> None:
|
||||||
|
|||||||
-1
@@ -12,7 +12,6 @@ from alembic import op
|
|||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
import open_webui.internal.db
|
import open_webui.internal.db
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision: str = "90ef40d4714e"
|
revision: str = "90ef40d4714e"
|
||||||
down_revision: Union[str, None] = "b10670c03dd5"
|
down_revision: Union[str, None] = "b10670c03dd5"
|
||||||
|
|||||||
@@ -173,12 +173,10 @@ def upgrade() -> None:
|
|||||||
for uid, api_key in users_with_keys:
|
for uid, api_key in users_with_keys:
|
||||||
if api_key:
|
if api_key:
|
||||||
conn.execute(
|
conn.execute(
|
||||||
sa.text(
|
sa.text("""
|
||||||
"""
|
|
||||||
INSERT INTO api_key (id, user_id, key, created_at, updated_at)
|
INSERT INTO api_key (id, user_id, key, created_at, updated_at)
|
||||||
VALUES (:id, :user_id, :key, :created_at, :updated_at)
|
VALUES (:id, :user_id, :key, :created_at, :updated_at)
|
||||||
"""
|
"""),
|
||||||
),
|
|
||||||
{
|
{
|
||||||
"id": f"key_{uid}",
|
"id": f"key_{uid}",
|
||||||
"user_id": uid,
|
"user_id": uid,
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import json
|
|||||||
from sqlalchemy.sql import table, column
|
from sqlalchemy.sql import table, column
|
||||||
from sqlalchemy import String, Text, JSON, and_
|
from sqlalchemy import String, Text, JSON, and_
|
||||||
|
|
||||||
|
|
||||||
revision = "c29facfe716b"
|
revision = "c29facfe716b"
|
||||||
down_revision = "c69f45358db4"
|
down_revision = "c69f45358db4"
|
||||||
branch_labels = None
|
branch_labels = None
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ from typing import Sequence, Union
|
|||||||
from alembic import op
|
from alembic import op
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision: str = "c440947495f3"
|
revision: str = "c440947495f3"
|
||||||
down_revision: Union[str, None] = "81cc2ce44d79"
|
down_revision: Union[str, None] = "81cc2ce44d79"
|
||||||
|
|||||||
@@ -98,9 +98,12 @@ def upgrade() -> None:
|
|||||||
# Could be Python None (SQL NULL) or string "null" (JSON null)
|
# Could be Python None (SQL NULL) or string "null" (JSON null)
|
||||||
# EXCEPTION: files with NULL are PRIVATE (owner-only), not public
|
# EXCEPTION: files with NULL are PRIVATE (owner-only), not public
|
||||||
is_null = (
|
is_null = (
|
||||||
access_control_json is None or
|
access_control_json is None
|
||||||
access_control_json == "null" or
|
or access_control_json == "null"
|
||||||
(isinstance(access_control_json, str) and access_control_json.strip().lower() == "null")
|
or (
|
||||||
|
isinstance(access_control_json, str)
|
||||||
|
and access_control_json.strip().lower() == "null"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
if is_null:
|
if is_null:
|
||||||
# Files: NULL = private (no entry needed, owner has implicit access)
|
# Files: NULL = private (no entry needed, owner has implicit access)
|
||||||
@@ -112,12 +115,10 @@ def upgrade() -> None:
|
|||||||
if key not in inserted:
|
if key not in inserted:
|
||||||
try:
|
try:
|
||||||
conn.execute(
|
conn.execute(
|
||||||
sa.text(
|
sa.text("""
|
||||||
"""
|
|
||||||
INSERT INTO access_grant (id, resource_type, resource_id, principal_type, principal_id, permission, created_at)
|
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)
|
VALUES (:id, :resource_type, :resource_id, :principal_type, :principal_id, :permission, :created_at)
|
||||||
"""
|
"""),
|
||||||
),
|
|
||||||
{
|
{
|
||||||
"id": str(uuid.uuid4()),
|
"id": str(uuid.uuid4()),
|
||||||
"resource_type": resource_type,
|
"resource_type": resource_type,
|
||||||
@@ -174,12 +175,10 @@ def upgrade() -> None:
|
|||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
conn.execute(
|
conn.execute(
|
||||||
sa.text(
|
sa.text("""
|
||||||
"""
|
|
||||||
INSERT INTO access_grant (id, resource_type, resource_id, principal_type, principal_id, permission, created_at)
|
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)
|
VALUES (:id, :resource_type, :resource_id, :principal_type, :principal_id, :permission, :created_at)
|
||||||
"""
|
"""),
|
||||||
),
|
|
||||||
{
|
{
|
||||||
"id": str(uuid.uuid4()),
|
"id": str(uuid.uuid4()),
|
||||||
"resource_type": resource_type,
|
"resource_type": resource_type,
|
||||||
@@ -200,12 +199,10 @@ def upgrade() -> None:
|
|||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
conn.execute(
|
conn.execute(
|
||||||
sa.text(
|
sa.text("""
|
||||||
"""
|
|
||||||
INSERT INTO access_grant (id, resource_type, resource_id, principal_type, principal_id, permission, created_at)
|
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)
|
VALUES (:id, :resource_type, :resource_id, :principal_type, :principal_id, :permission, :created_at)
|
||||||
"""
|
"""),
|
||||||
),
|
|
||||||
{
|
{
|
||||||
"id": str(uuid.uuid4()),
|
"id": str(uuid.uuid4()),
|
||||||
"resource_type": resource_type,
|
"resource_type": resource_type,
|
||||||
@@ -265,7 +262,7 @@ def downgrade() -> None:
|
|||||||
FROM access_grant
|
FROM access_grant
|
||||||
WHERE resource_type = :resource_type
|
WHERE resource_type = :resource_type
|
||||||
"""),
|
"""),
|
||||||
{"resource_type": resource_type}
|
{"resource_type": resource_type},
|
||||||
)
|
)
|
||||||
rows = result.fetchall()
|
rows = result.fetchall()
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -287,39 +284,61 @@ def downgrade() -> None:
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Handle public access (user:* for read)
|
# 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
|
resource_grants[resource_id]["is_public"] = True
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Add to appropriate list
|
# Add to appropriate list
|
||||||
if permission in ["read", "write"]:
|
if permission in ["read", "write"]:
|
||||||
if principal_type == "group":
|
if principal_type == "group":
|
||||||
if principal_id not in resource_grants[resource_id][permission]["group_ids"]:
|
if (
|
||||||
resource_grants[resource_id][permission]["group_ids"].append(principal_id)
|
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":
|
elif principal_type == "user":
|
||||||
if principal_id not in resource_grants[resource_id][permission]["user_ids"]:
|
if (
|
||||||
resource_grants[resource_id][permission]["user_ids"].append(principal_id)
|
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
|
# Step 3: Update each resource with reconstructed JSON
|
||||||
for resource_id, grants in resource_grants.items():
|
for resource_id, grants in resource_grants.items():
|
||||||
if grants["is_public"]:
|
if grants["is_public"]:
|
||||||
# Public = NULL
|
# Public = NULL
|
||||||
access_control_value = None
|
access_control_value = None
|
||||||
elif (not grants["read"]["group_ids"] and not grants["read"]["user_ids"] and
|
elif (
|
||||||
not grants["write"]["group_ids"] and not grants["write"]["user_ids"]):
|
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 {}
|
# No grants = should not happen (would mean no entries), default to {}
|
||||||
access_control_value = json.dumps({})
|
access_control_value = json.dumps({})
|
||||||
else:
|
else:
|
||||||
# Custom permissions
|
# Custom permissions
|
||||||
access_control_value = json.dumps({
|
access_control_value = json.dumps(
|
||||||
"read": grants["read"],
|
{
|
||||||
"write": grants["write"],
|
"read": grants["read"],
|
||||||
})
|
"write": grants["write"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn.execute(
|
conn.execute(
|
||||||
sa.text(f'UPDATE "{table_name}" SET access_control = :access_control WHERE id = :id'),
|
sa.text(
|
||||||
{"access_control": access_control_value, "id": resource_id}
|
f'UPDATE "{table_name}" SET access_control = :access_control WHERE id = :id'
|
||||||
|
),
|
||||||
|
{"access_control": access_control_value, "id": resource_id},
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
@@ -330,15 +349,15 @@ def downgrade() -> None:
|
|||||||
if resource_type != "file":
|
if resource_type != "file":
|
||||||
try:
|
try:
|
||||||
conn.execute(
|
conn.execute(
|
||||||
sa.text(f'''
|
sa.text(f"""
|
||||||
UPDATE "{table_name}"
|
UPDATE "{table_name}"
|
||||||
SET access_control = :private_value
|
SET access_control = :private_value
|
||||||
WHERE id NOT IN (
|
WHERE id NOT IN (
|
||||||
SELECT DISTINCT resource_id FROM access_grant WHERE resource_type = :resource_type
|
SELECT DISTINCT resource_id FROM access_grant WHERE resource_type = :resource_type
|
||||||
)
|
)
|
||||||
AND access_control IS NULL
|
AND access_control IS NULL
|
||||||
'''),
|
"""),
|
||||||
{"private_value": json.dumps({}), "resource_type": resource_type}
|
{"private_value": json.dumps({}), "resource_type": resource_type},
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -22,10 +22,14 @@ class AccessGrant(Base):
|
|||||||
__tablename__ = "access_grant"
|
__tablename__ = "access_grant"
|
||||||
|
|
||||||
id = Column(Text, primary_key=True)
|
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)
|
resource_id = Column(Text, nullable=False)
|
||||||
principal_type = Column(Text, nullable=False) # "user" or "group"
|
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"
|
permission = Column(Text, nullable=False) # "read" or "write"
|
||||||
created_at = Column(BigInteger, nullable=False)
|
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)
|
key = (principal_type, principal_id, permission)
|
||||||
deduped[key] = {
|
deduped[key] = {
|
||||||
"id": grant.get("id")
|
"id": (
|
||||||
if isinstance(grant.get("id"), str) and grant.get("id")
|
grant.get("id")
|
||||||
else str(uuid.uuid4()),
|
if isinstance(grant.get("id"), str) and grant.get("id")
|
||||||
|
else str(uuid.uuid4())
|
||||||
|
),
|
||||||
"principal_type": principal_type,
|
"principal_type": principal_type,
|
||||||
"principal_id": principal_id,
|
"principal_id": principal_id,
|
||||||
"permission": permission,
|
"permission": permission,
|
||||||
|
|||||||
@@ -263,7 +263,9 @@ class ChannelTable:
|
|||||||
def _to_channel_model(
|
def _to_channel_model(
|
||||||
self, channel: Channel, db: Optional[Session] = None
|
self, channel: Channel, db: Optional[Session] = None
|
||||||
) -> ChannelModel:
|
) -> 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)
|
access_grants = self._get_access_grants(channel_data["id"], db=db)
|
||||||
channel_data["access_grants"] = access_grants
|
channel_data["access_grants"] = access_grants
|
||||||
return ChannelModel.model_validate(channel_data)
|
return ChannelModel.model_validate(channel_data)
|
||||||
@@ -770,9 +772,7 @@ class ChannelTable:
|
|||||||
.first()
|
.first()
|
||||||
)
|
)
|
||||||
if membership:
|
if membership:
|
||||||
allowed_channels.append(
|
allowed_channels.append(self._to_channel_model(channel, db=db))
|
||||||
self._to_channel_model(channel, db=db)
|
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# --- Case B: standard channel => rely on ACL permissions ---
|
# --- Case B: standard channel => rely on ACL permissions ---
|
||||||
|
|||||||
@@ -332,7 +332,11 @@ class ChatMessageTable:
|
|||||||
if end_date:
|
if end_date:
|
||||||
query = query.filter(ChatMessage.created_at <= end_date)
|
query = query.filter(ChatMessage.created_at <= end_date)
|
||||||
if group_id:
|
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))
|
query = query.filter(ChatMessage.user_id.in_(group_users))
|
||||||
|
|
||||||
results = query.group_by(ChatMessage.model_id).all()
|
results = query.group_by(ChatMessage.model_id).all()
|
||||||
@@ -362,10 +366,12 @@ class ChatMessageTable:
|
|||||||
elif dialect == "postgresql":
|
elif dialect == "postgresql":
|
||||||
# Use json_extract_path_text for PostgreSQL JSON columns
|
# Use json_extract_path_text for PostgreSQL JSON columns
|
||||||
input_tokens = cast(
|
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(
|
output_tokens = cast(
|
||||||
func.json_extract_path_text(ChatMessage.usage, "output_tokens"), Integer
|
func.json_extract_path_text(ChatMessage.usage, "output_tokens"),
|
||||||
|
Integer,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError(f"Unsupported dialect: {dialect}")
|
raise NotImplementedError(f"Unsupported dialect: {dialect}")
|
||||||
@@ -387,7 +393,11 @@ class ChatMessageTable:
|
|||||||
if end_date:
|
if end_date:
|
||||||
query = query.filter(ChatMessage.created_at <= end_date)
|
query = query.filter(ChatMessage.created_at <= end_date)
|
||||||
if group_id:
|
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))
|
query = query.filter(ChatMessage.user_id.in_(group_users))
|
||||||
|
|
||||||
results = query.group_by(ChatMessage.model_id).all()
|
results = query.group_by(ChatMessage.model_id).all()
|
||||||
@@ -424,10 +434,12 @@ class ChatMessageTable:
|
|||||||
elif dialect == "postgresql":
|
elif dialect == "postgresql":
|
||||||
# Use json_extract_path_text for PostgreSQL JSON columns
|
# Use json_extract_path_text for PostgreSQL JSON columns
|
||||||
input_tokens = cast(
|
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(
|
output_tokens = cast(
|
||||||
func.json_extract_path_text(ChatMessage.usage, "output_tokens"), Integer
|
func.json_extract_path_text(ChatMessage.usage, "output_tokens"),
|
||||||
|
Integer,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError(f"Unsupported dialect: {dialect}")
|
raise NotImplementedError(f"Unsupported dialect: {dialect}")
|
||||||
@@ -481,7 +493,11 @@ class ChatMessageTable:
|
|||||||
if end_date:
|
if end_date:
|
||||||
query = query.filter(ChatMessage.created_at <= end_date)
|
query = query.filter(ChatMessage.created_at <= end_date)
|
||||||
if group_id:
|
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))
|
query = query.filter(ChatMessage.user_id.in_(group_users))
|
||||||
|
|
||||||
results = query.group_by(ChatMessage.user_id).all()
|
results = query.group_by(ChatMessage.user_id).all()
|
||||||
@@ -507,7 +523,11 @@ class ChatMessageTable:
|
|||||||
if end_date:
|
if end_date:
|
||||||
query = query.filter(ChatMessage.created_at <= end_date)
|
query = query.filter(ChatMessage.created_at <= end_date)
|
||||||
if group_id:
|
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))
|
query = query.filter(ChatMessage.user_id.in_(group_users))
|
||||||
|
|
||||||
results = query.group_by(ChatMessage.chat_id).all()
|
results = query.group_by(ChatMessage.chat_id).all()
|
||||||
@@ -536,7 +556,11 @@ class ChatMessageTable:
|
|||||||
if end_date:
|
if end_date:
|
||||||
query = query.filter(ChatMessage.created_at <= end_date)
|
query = query.filter(ChatMessage.created_at <= end_date)
|
||||||
if group_id:
|
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))
|
query = query.filter(ChatMessage.user_id.in_(group_users))
|
||||||
|
|
||||||
results = query.all()
|
results = query.all()
|
||||||
@@ -544,10 +568,14 @@ class ChatMessageTable:
|
|||||||
# Group by date -> model -> count
|
# Group by date -> model -> count
|
||||||
daily_counts: dict[str, dict[str, int]] = {}
|
daily_counts: dict[str, dict[str, int]] = {}
|
||||||
for timestamp, model_id in results:
|
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:
|
if date_str not in daily_counts:
|
||||||
daily_counts[date_str] = {}
|
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
|
# Fill in missing days
|
||||||
if start_date and end_date:
|
if start_date and end_date:
|
||||||
@@ -587,14 +615,20 @@ class ChatMessageTable:
|
|||||||
# Group by hour -> model -> count
|
# Group by hour -> model -> count
|
||||||
hourly_counts: dict[str, dict[str, int]] = {}
|
hourly_counts: dict[str, dict[str, int]] = {}
|
||||||
for timestamp, model_id in results:
|
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:
|
if hour_str not in hourly_counts:
|
||||||
hourly_counts[hour_str] = {}
|
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
|
# Fill in missing hours
|
||||||
if start_date and end_date:
|
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))
|
end_dt = datetime.fromtimestamp(_normalize_timestamp(end_date))
|
||||||
while current <= end_dt:
|
while current <= end_dt:
|
||||||
hour_str = current.strftime("%Y-%m-%d %H:00")
|
hour_str = current.strftime("%Y-%m-%d %H:00")
|
||||||
|
|||||||
@@ -329,7 +329,9 @@ class ChatTable:
|
|||||||
data=message,
|
data=message,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
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
|
return ChatModel.model_validate(chat_item) if chat_item else None
|
||||||
|
|
||||||
@@ -388,7 +390,9 @@ class ChatTable:
|
|||||||
data=message,
|
data=message,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
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]
|
return [ChatModel.model_validate(chat) for chat in chats]
|
||||||
|
|
||||||
@@ -739,8 +743,10 @@ class ChatTable:
|
|||||||
) -> list[ChatModel]:
|
) -> list[ChatModel]:
|
||||||
|
|
||||||
with get_db_context(db) as db:
|
with get_db_context(db) as db:
|
||||||
query = db.query(Chat).filter_by(user_id=user_id).filter(
|
query = (
|
||||||
Chat.share_id.isnot(None)
|
db.query(Chat)
|
||||||
|
.filter_by(user_id=user_id)
|
||||||
|
.filter(Chat.share_id.isnot(None))
|
||||||
)
|
)
|
||||||
|
|
||||||
if filter:
|
if filter:
|
||||||
@@ -1110,29 +1116,23 @@ class ChatTable:
|
|||||||
|
|
||||||
# Check if there are any tags to filter, it should have all the tags
|
# Check if there are any tags to filter, it should have all the tags
|
||||||
if "none" in tag_ids:
|
if "none" in tag_ids:
|
||||||
query = query.filter(
|
query = query.filter(text("""
|
||||||
text(
|
|
||||||
"""
|
|
||||||
NOT EXISTS (
|
NOT EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM json_each(Chat.meta, '$.tags') AS tag
|
FROM json_each(Chat.meta, '$.tags') AS tag
|
||||||
)
|
)
|
||||||
"""
|
"""))
|
||||||
)
|
|
||||||
)
|
|
||||||
elif tag_ids:
|
elif tag_ids:
|
||||||
query = query.filter(
|
query = query.filter(
|
||||||
and_(
|
and_(
|
||||||
*[
|
*[
|
||||||
text(
|
text(f"""
|
||||||
f"""
|
|
||||||
EXISTS (
|
EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM json_each(Chat.meta, '$.tags') AS tag
|
FROM json_each(Chat.meta, '$.tags') AS tag
|
||||||
WHERE tag.value = :tag_id_{tag_idx}
|
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)
|
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
|
# Check if there are any tags to filter, it should have all the tags
|
||||||
if "none" in tag_ids:
|
if "none" in tag_ids:
|
||||||
query = query.filter(
|
query = query.filter(text("""
|
||||||
text(
|
|
||||||
"""
|
|
||||||
NOT EXISTS (
|
NOT EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM json_array_elements_text(Chat.meta->'tags') AS tag
|
FROM json_array_elements_text(Chat.meta->'tags') AS tag
|
||||||
)
|
)
|
||||||
"""
|
"""))
|
||||||
)
|
|
||||||
)
|
|
||||||
elif tag_ids:
|
elif tag_ids:
|
||||||
query = query.filter(
|
query = query.filter(
|
||||||
and_(
|
and_(
|
||||||
*[
|
*[
|
||||||
text(
|
text(f"""
|
||||||
f"""
|
|
||||||
EXISTS (
|
EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM json_array_elements_text(Chat.meta->'tags') AS tag
|
FROM json_array_elements_text(Chat.meta->'tags') AS tag
|
||||||
WHERE tag = :tag_id_{tag_idx}
|
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)
|
for tag_idx, tag_id in enumerate(tag_ids)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ from sqlalchemy.orm import Session
|
|||||||
|
|
||||||
from open_webui.internal.db import Base, JSONField, get_db, get_db_context
|
from open_webui.internal.db import Base, JSONField, get_db, get_db_context
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -214,7 +214,6 @@ class FunctionsTable:
|
|||||||
except Exception:
|
except Exception:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def get_functions(
|
def get_functions(
|
||||||
self, active_only=False, include_valves=False, db: Optional[Session] = None
|
self, active_only=False, include_valves=False, db: Optional[Session] = None
|
||||||
) -> list[FunctionModel | FunctionWithValvesModel]:
|
) -> list[FunctionModel | FunctionWithValvesModel]:
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ from sqlalchemy import (
|
|||||||
select,
|
select,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
@@ -187,7 +186,7 @@ class GroupTable:
|
|||||||
Group.data.is_(None),
|
Group.data.is_(None),
|
||||||
json_share_str.is_(None),
|
json_share_str.is_(None),
|
||||||
json_share_lower == "true",
|
json_share_lower == "true",
|
||||||
json_share_lower == "1", # Handle SQLite boolean true
|
json_share_lower == "1", # Handle SQLite boolean true
|
||||||
)
|
)
|
||||||
|
|
||||||
if member_id:
|
if member_id:
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ from sqlalchemy import (
|
|||||||
or_,
|
or_,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
@@ -402,9 +401,7 @@ class KnowledgeTable:
|
|||||||
try:
|
try:
|
||||||
with get_db_context(db) as db:
|
with get_db_context(db) as db:
|
||||||
knowledge = db.query(Knowledge).filter_by(id=id).first()
|
knowledge = db.query(Knowledge).filter_by(id=id).first()
|
||||||
return (
|
return self._to_knowledge_model(knowledge, db=db) if knowledge else None
|
||||||
self._to_knowledge_model(knowledge, db=db) if knowledge else None
|
|
||||||
)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -443,7 +440,10 @@ class KnowledgeTable:
|
|||||||
.filter(KnowledgeFile.file_id == file_id)
|
.filter(KnowledgeFile.file_id == file_id)
|
||||||
.all()
|
.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:
|
except Exception:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -484,11 +484,17 @@ class KnowledgeTable:
|
|||||||
is_asc = direction == "asc"
|
is_asc = direction == "asc"
|
||||||
|
|
||||||
if order_by == "name":
|
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":
|
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":
|
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
|
# Apply sort with secondary key for deterministic pagination
|
||||||
query = query.order_by(primary_sort, File.id.asc())
|
query = query.order_by(primary_sort, File.id.asc())
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ from sqlalchemy.dialects import postgresql, sqlite
|
|||||||
from sqlalchemy.dialects.postgresql import JSONB
|
from sqlalchemy.dialects.postgresql import JSONB
|
||||||
from sqlalchemy import BigInteger, Column, Text, Boolean
|
from sqlalchemy import BigInteger, Column, Text, Boolean
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -182,7 +181,9 @@ class ModelsTable:
|
|||||||
|
|
||||||
def get_all_models(self, db: Optional[Session] = None) -> list[ModelModel]:
|
def get_all_models(self, db: Optional[Session] = None) -> list[ModelModel]:
|
||||||
with get_db_context(db) as db:
|
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]:
|
def get_models(self, db: Optional[Session] = None) -> list[ModelUserResponse]:
|
||||||
with get_db_context(db) as db:
|
with get_db_context(db) as db:
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ from open_webui.models.users import Users, UserResponse
|
|||||||
from pydantic import BaseModel, ConfigDict
|
from pydantic import BaseModel, ConfigDict
|
||||||
from sqlalchemy import BigInteger, Column, Text, JSON, Index
|
from sqlalchemy import BigInteger, Column, Text, JSON, Index
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# PromptHistory DB Schema
|
# PromptHistory DB Schema
|
||||||
####################
|
####################
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ from open_webui.models.access_grants import AccessGrantModel, AccessGrants
|
|||||||
from pydantic import BaseModel, ConfigDict, Field
|
from pydantic import BaseModel, ConfigDict, Field
|
||||||
from sqlalchemy import BigInteger, Boolean, Column, String, Text, JSON, or_, func, cast
|
from sqlalchemy import BigInteger, Boolean, Column, String, Text, JSON, or_, func, cast
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# Prompts DB Schema
|
# Prompts DB Schema
|
||||||
####################
|
####################
|
||||||
@@ -146,7 +145,9 @@ class PromptsTable:
|
|||||||
"data": form_data.data or {},
|
"data": form_data.data or {},
|
||||||
"meta": form_data.meta or {},
|
"meta": form_data.meta or {},
|
||||||
"tags": form_data.tags 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(
|
history_entry = PromptHistories.create_history_entry(
|
||||||
@@ -345,7 +346,6 @@ class PromptsTable:
|
|||||||
return PromptListResponse(items=prompts, total=total)
|
return PromptListResponse(items=prompts, total=total)
|
||||||
|
|
||||||
def update_prompt_by_command(
|
def update_prompt_by_command(
|
||||||
|
|
||||||
self,
|
self,
|
||||||
command: str,
|
command: str,
|
||||||
form_data: PromptForm,
|
form_data: PromptForm,
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ from open_webui.models.access_grants import AccessGrantModel, AccessGrants
|
|||||||
from pydantic import BaseModel, ConfigDict, Field
|
from pydantic import BaseModel, ConfigDict, Field
|
||||||
from sqlalchemy import BigInteger, Boolean, Column, String, Text, or_
|
from sqlalchemy import BigInteger, Boolean, Column, String, Text, or_
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
@@ -112,7 +111,9 @@ class SkillsTable:
|
|||||||
return AccessGrants.get_grants_by_resource("skill", skill_id, db=db)
|
return AccessGrants.get_grants_by_resource("skill", skill_id, db=db)
|
||||||
|
|
||||||
def _to_skill_model(self, skill: Skill, db: Optional[Session] = None) -> SkillModel:
|
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)
|
skill_data["access_grants"] = self._get_access_grants(skill_data["id"], db=db)
|
||||||
return SkillModel.model_validate(skill_data)
|
return SkillModel.model_validate(skill_data)
|
||||||
|
|
||||||
@@ -223,9 +224,7 @@ class SkillsTable:
|
|||||||
from open_webui.models.users import User, UserModel
|
from open_webui.models.users import User, UserModel
|
||||||
|
|
||||||
# Join with User table for user filtering
|
# Join with User table for user filtering
|
||||||
query = db.query(Skill, User).outerjoin(
|
query = db.query(Skill, User).outerjoin(User, User.id == Skill.user_id)
|
||||||
User, User.id == Skill.user_id
|
|
||||||
)
|
|
||||||
|
|
||||||
if filter:
|
if filter:
|
||||||
query_key = filter.get("query")
|
query_key = filter.get("query")
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ from open_webui.models.access_grants import AccessGrantModel, AccessGrants
|
|||||||
from pydantic import BaseModel, ConfigDict, Field
|
from pydantic import BaseModel, ConfigDict, Field
|
||||||
from sqlalchemy import BigInteger, Column, String, Text
|
from sqlalchemy import BigInteger, Column, String, Text
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|||||||
@@ -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.retrieval.models.base_reranker import BaseReranker
|
||||||
from open_webui.utils.headers import include_user_info_headers
|
from open_webui.utils.headers import include_user_info_headers
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -695,7 +695,9 @@ async def agenerate_azure_openai_batch_embeddings(
|
|||||||
trust_env=True, timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT)
|
trust_env=True, timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT)
|
||||||
) as session:
|
) as session:
|
||||||
async with session.post(
|
async with session.post(
|
||||||
full_url, headers=headers, json=form_data,
|
full_url,
|
||||||
|
headers=headers,
|
||||||
|
json=form_data,
|
||||||
ssl=AIOHTTP_CLIENT_SESSION_SSL,
|
ssl=AIOHTTP_CLIENT_SESSION_SSL,
|
||||||
) as r:
|
) as r:
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
|
|||||||
@@ -256,8 +256,7 @@ class Oracle23aiClient(VectorDBBase):
|
|||||||
with connection.cursor() as cursor:
|
with connection.cursor() as cursor:
|
||||||
try:
|
try:
|
||||||
log.info("Creating Table document_chunk")
|
log.info("Creating Table document_chunk")
|
||||||
cursor.execute(
|
cursor.execute("""
|
||||||
"""
|
|
||||||
BEGIN
|
BEGIN
|
||||||
EXECUTE IMMEDIATE '
|
EXECUTE IMMEDIATE '
|
||||||
CREATE TABLE IF NOT EXISTS document_chunk (
|
CREATE TABLE IF NOT EXISTS document_chunk (
|
||||||
@@ -274,12 +273,10 @@ class Oracle23aiClient(VectorDBBase):
|
|||||||
RAISE;
|
RAISE;
|
||||||
END IF;
|
END IF;
|
||||||
END;
|
END;
|
||||||
"""
|
""")
|
||||||
)
|
|
||||||
|
|
||||||
log.info("Creating Index document_chunk_collection_name_idx")
|
log.info("Creating Index document_chunk_collection_name_idx")
|
||||||
cursor.execute(
|
cursor.execute("""
|
||||||
"""
|
|
||||||
BEGIN
|
BEGIN
|
||||||
EXECUTE IMMEDIATE '
|
EXECUTE IMMEDIATE '
|
||||||
CREATE INDEX IF NOT EXISTS document_chunk_collection_name_idx
|
CREATE INDEX IF NOT EXISTS document_chunk_collection_name_idx
|
||||||
@@ -291,12 +288,10 @@ class Oracle23aiClient(VectorDBBase):
|
|||||||
RAISE;
|
RAISE;
|
||||||
END IF;
|
END IF;
|
||||||
END;
|
END;
|
||||||
"""
|
""")
|
||||||
)
|
|
||||||
|
|
||||||
log.info("Creating VECTOR INDEX document_chunk_vector_ivf_idx")
|
log.info("Creating VECTOR INDEX document_chunk_vector_ivf_idx")
|
||||||
cursor.execute(
|
cursor.execute("""
|
||||||
"""
|
|
||||||
BEGIN
|
BEGIN
|
||||||
EXECUTE IMMEDIATE '
|
EXECUTE IMMEDIATE '
|
||||||
CREATE VECTOR INDEX IF NOT EXISTS document_chunk_vector_ivf_idx
|
CREATE VECTOR INDEX IF NOT EXISTS document_chunk_vector_ivf_idx
|
||||||
@@ -312,8 +307,7 @@ class Oracle23aiClient(VectorDBBase):
|
|||||||
RAISE;
|
RAISE;
|
||||||
END IF;
|
END IF;
|
||||||
END;
|
END;
|
||||||
"""
|
""")
|
||||||
)
|
|
||||||
|
|
||||||
connection.commit()
|
connection.commit()
|
||||||
log.info("Database initialization completed successfully.")
|
log.info("Database initialization completed successfully.")
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ from open_webui.config import (
|
|||||||
PGVECTOR_USE_HALFVEC,
|
PGVECTOR_USE_HALFVEC,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
VECTOR_LENGTH = PGVECTOR_INITIALIZE_MAX_VECTOR_LENGTH
|
VECTOR_LENGTH = PGVECTOR_INITIALIZE_MAX_VECTOR_LENGTH
|
||||||
USE_HALFVEC = PGVECTOR_USE_HALFVEC
|
USE_HALFVEC = PGVECTOR_USE_HALFVEC
|
||||||
|
|
||||||
@@ -121,34 +120,26 @@ class PgvectorClient(VectorDBBase):
|
|||||||
# Ensure the pgvector extension is available
|
# Ensure the pgvector extension is available
|
||||||
# Use a conditional check to avoid permission issues on Azure PostgreSQL
|
# Use a conditional check to avoid permission issues on Azure PostgreSQL
|
||||||
if PGVECTOR_CREATE_EXTENSION:
|
if PGVECTOR_CREATE_EXTENSION:
|
||||||
self.session.execute(
|
self.session.execute(text("""
|
||||||
text(
|
|
||||||
"""
|
|
||||||
DO $$
|
DO $$
|
||||||
BEGIN
|
BEGIN
|
||||||
IF NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'vector') THEN
|
IF NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'vector') THEN
|
||||||
CREATE EXTENSION IF NOT EXISTS vector;
|
CREATE EXTENSION IF NOT EXISTS vector;
|
||||||
END IF;
|
END IF;
|
||||||
END $$;
|
END $$;
|
||||||
"""
|
"""))
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if PGVECTOR_PGCRYPTO:
|
if PGVECTOR_PGCRYPTO:
|
||||||
# Ensure the pgcrypto extension is available for encryption
|
# Ensure the pgcrypto extension is available for encryption
|
||||||
# Use a conditional check to avoid permission issues on Azure PostgreSQL
|
# Use a conditional check to avoid permission issues on Azure PostgreSQL
|
||||||
self.session.execute(
|
self.session.execute(text("""
|
||||||
text(
|
|
||||||
"""
|
|
||||||
DO $$
|
DO $$
|
||||||
BEGIN
|
BEGIN
|
||||||
IF NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pgcrypto') THEN
|
IF NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pgcrypto') THEN
|
||||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||||
END IF;
|
END IF;
|
||||||
END $$;
|
END $$;
|
||||||
"""
|
"""))
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if not PGVECTOR_PGCRYPTO_KEY:
|
if not PGVECTOR_PGCRYPTO_KEY:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
@@ -216,15 +207,13 @@ class PgvectorClient(VectorDBBase):
|
|||||||
def _ensure_vector_index(self, index_method: str, index_options: str) -> None:
|
def _ensure_vector_index(self, index_method: str, index_options: str) -> None:
|
||||||
index_name = "idx_document_chunk_vector"
|
index_name = "idx_document_chunk_vector"
|
||||||
existing_index_def = self.session.execute(
|
existing_index_def = self.session.execute(
|
||||||
text(
|
text("""
|
||||||
"""
|
|
||||||
SELECT indexdef
|
SELECT indexdef
|
||||||
FROM pg_indexes
|
FROM pg_indexes
|
||||||
WHERE schemaname = current_schema()
|
WHERE schemaname = current_schema()
|
||||||
AND tablename = 'document_chunk'
|
AND tablename = 'document_chunk'
|
||||||
AND indexname = :index_name
|
AND indexname = :index_name
|
||||||
"""
|
"""),
|
||||||
),
|
|
||||||
{"index_name": index_name},
|
{"index_name": index_name},
|
||||||
).scalar()
|
).scalar()
|
||||||
|
|
||||||
@@ -310,8 +299,7 @@ class PgvectorClient(VectorDBBase):
|
|||||||
# Ensure metadata is converted to its JSON text representation
|
# Ensure metadata is converted to its JSON text representation
|
||||||
json_metadata = json.dumps(item["metadata"])
|
json_metadata = json.dumps(item["metadata"])
|
||||||
self.session.execute(
|
self.session.execute(
|
||||||
text(
|
text("""
|
||||||
"""
|
|
||||||
INSERT INTO document_chunk
|
INSERT INTO document_chunk
|
||||||
(id, vector, collection_name, text, vmetadata)
|
(id, vector, collection_name, text, vmetadata)
|
||||||
VALUES (
|
VALUES (
|
||||||
@@ -320,8 +308,7 @@ class PgvectorClient(VectorDBBase):
|
|||||||
pgp_sym_encrypt(:metadata_text, :key)
|
pgp_sym_encrypt(:metadata_text, :key)
|
||||||
)
|
)
|
||||||
ON CONFLICT (id) DO NOTHING
|
ON CONFLICT (id) DO NOTHING
|
||||||
"""
|
"""),
|
||||||
),
|
|
||||||
{
|
{
|
||||||
"id": item["id"],
|
"id": item["id"],
|
||||||
"vector": vector,
|
"vector": vector,
|
||||||
@@ -363,8 +350,7 @@ class PgvectorClient(VectorDBBase):
|
|||||||
vector = self.adjust_vector_length(item["vector"])
|
vector = self.adjust_vector_length(item["vector"])
|
||||||
json_metadata = json.dumps(item["metadata"])
|
json_metadata = json.dumps(item["metadata"])
|
||||||
self.session.execute(
|
self.session.execute(
|
||||||
text(
|
text("""
|
||||||
"""
|
|
||||||
INSERT INTO document_chunk
|
INSERT INTO document_chunk
|
||||||
(id, vector, collection_name, text, vmetadata)
|
(id, vector, collection_name, text, vmetadata)
|
||||||
VALUES (
|
VALUES (
|
||||||
@@ -377,8 +363,7 @@ class PgvectorClient(VectorDBBase):
|
|||||||
collection_name = EXCLUDED.collection_name,
|
collection_name = EXCLUDED.collection_name,
|
||||||
text = EXCLUDED.text,
|
text = EXCLUDED.text,
|
||||||
vmetadata = EXCLUDED.vmetadata
|
vmetadata = EXCLUDED.vmetadata
|
||||||
"""
|
"""),
|
||||||
),
|
|
||||||
{
|
{
|
||||||
"id": item["id"],
|
"id": item["id"],
|
||||||
"vector": vector,
|
"vector": vector,
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ from open_webui.config import (
|
|||||||
)
|
)
|
||||||
from open_webui.retrieval.vector.utils import process_metadata
|
from open_webui.retrieval.vector.utils import process_metadata
|
||||||
|
|
||||||
|
|
||||||
NO_LIMIT = 10000 # Reasonable limit to avoid overwhelming the system
|
NO_LIMIT = 10000 # Reasonable limit to avoid overwhelming the system
|
||||||
BATCH_SIZE = 100 # Recommended batch size for Pinecone operations
|
BATCH_SIZE = 100 # Recommended batch size for Pinecone operations
|
||||||
|
|
||||||
|
|||||||
@@ -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.utils.headers import include_user_info_headers
|
||||||
from open_webui.env import FORWARD_SESSION_INFO_HEADER_CHAT_ID
|
from open_webui.env import FORWARD_SESSION_INFO_HEADER_CHAT_ID
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ from typing import Optional, List
|
|||||||
|
|
||||||
from open_webui.retrieval.web.main import SearchResult, get_filtered_results
|
from open_webui.retrieval.web.main import SearchResult, get_filtered_results
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import requests
|
|||||||
from open_webui.retrieval.web.main import SearchResult, get_filtered_results
|
from open_webui.retrieval.web.main import SearchResult, get_filtered_results
|
||||||
from open_webui.utils.headers import include_user_info_headers
|
from open_webui.utils.headers import include_user_info_headers
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -31,14 +31,14 @@ def xml_element_contents_to_string(element: Element) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def search_yandex(
|
def search_yandex(
|
||||||
request: Request,
|
request: Request,
|
||||||
yandex_search_url: str,
|
yandex_search_url: str,
|
||||||
yandex_search_api_key: str,
|
yandex_search_api_key: str,
|
||||||
yandex_search_config: str,
|
yandex_search_config: str,
|
||||||
query: str,
|
query: str,
|
||||||
count: int,
|
count: int,
|
||||||
filter_list: Optional[List[str]] = None,
|
filter_list: Optional[List[str]] = None,
|
||||||
user=None,
|
user=None,
|
||||||
) -> List[SearchResult]:
|
) -> List[SearchResult]:
|
||||||
try:
|
try:
|
||||||
headers = {
|
headers = {
|
||||||
@@ -73,7 +73,11 @@ def search_yandex(
|
|||||||
payload["groupSpec"]["docsInGroup"] = 1
|
payload["groupSpec"]["docsInGroup"] = 1
|
||||||
|
|
||||||
response = requests.post(
|
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,
|
headers=headers,
|
||||||
json=payload,
|
json=payload,
|
||||||
)
|
)
|
||||||
@@ -84,18 +88,28 @@ def search_yandex(
|
|||||||
if "rawData" not in response_body:
|
if "rawData" not in response_body:
|
||||||
raise Exception(f"No `rawData` in response body: {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))
|
doc_root = ET.parse(io.BytesIO(search_result_body_bytes))
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
|
|
||||||
for group in doc_root.findall("response/results/grouping/group"):
|
for group in doc_root.findall("response/results/grouping/group"):
|
||||||
results.append({
|
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"),
|
"url": xml_element_contents_to_string(group.find("doc/url")).strip(
|
||||||
"snippet": xml_element_contents_to_string(group.find("doc/passages/passage")),
|
"\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)
|
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_URL", ""),
|
||||||
os.environ.get("YANDEX_WEB_SEARCH_API_KEY", ""),
|
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",
|
"TOP movies of the past year",
|
||||||
3,
|
3,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -90,22 +90,26 @@ async def get_user_analytics(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Get user info for top users
|
# 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)}
|
user_info = {u.id: u for u in Users.get_users_by_user_ids(top_user_ids, db=db)}
|
||||||
|
|
||||||
users = []
|
users = []
|
||||||
for user_id in top_user_ids:
|
for user_id in top_user_ids:
|
||||||
u = user_info.get(user_id)
|
u = user_info.get(user_id)
|
||||||
tokens = token_usage.get(user_id, {})
|
tokens = token_usage.get(user_id, {})
|
||||||
users.append(UserAnalyticsEntry(
|
users.append(
|
||||||
user_id=user_id,
|
UserAnalyticsEntry(
|
||||||
name=u.name if u else None,
|
user_id=user_id,
|
||||||
email=u.email if u else None,
|
name=u.name if u else None,
|
||||||
count=counts[user_id],
|
email=u.email if u else None,
|
||||||
input_tokens=tokens.get("input_tokens", 0),
|
count=counts[user_id],
|
||||||
output_tokens=tokens.get("output_tokens", 0),
|
input_tokens=tokens.get("input_tokens", 0),
|
||||||
total_tokens=tokens.get("total_tokens", 0),
|
output_tokens=tokens.get("output_tokens", 0),
|
||||||
))
|
total_tokens=tokens.get("total_tokens", 0),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return UserAnalyticsResponse(users=users)
|
return UserAnalyticsResponse(users=users)
|
||||||
|
|
||||||
@@ -317,9 +321,7 @@ async def get_model_chats(
|
|||||||
if isinstance(content, str):
|
if isinstance(content, str):
|
||||||
first_message = content[:200]
|
first_message = content[:200]
|
||||||
elif isinstance(content, list):
|
elif isinstance(content, list):
|
||||||
text_parts = [
|
text_parts = [b.get("text", "") for b in content if isinstance(b, dict)]
|
||||||
b.get("text", "") for b in content if isinstance(b, dict)
|
|
||||||
]
|
|
||||||
first_message = " ".join(text_parts)[:200]
|
first_message = " ".join(text_parts)[:200]
|
||||||
|
|
||||||
# Get user info
|
# Get user info
|
||||||
@@ -331,7 +333,6 @@ async def get_model_chats(
|
|||||||
# Timestamps from messages
|
# Timestamps from messages
|
||||||
updated_at = max(m.created_at for m in messages) if messages else 0
|
updated_at = max(m.created_at for m in messages) if messages else 0
|
||||||
|
|
||||||
|
|
||||||
chats_data.append(
|
chats_data.append(
|
||||||
ModelChatEntry(
|
ModelChatEntry(
|
||||||
chat_id=chat_id,
|
chat_id=chat_id,
|
||||||
@@ -427,11 +428,13 @@ async def get_model_overview(
|
|||||||
while current <= end_dt:
|
while current <= end_dt:
|
||||||
date_str = current.strftime("%Y-%m-%d")
|
date_str = current.strftime("%Y-%m-%d")
|
||||||
counts = history_counts.get(date_str, {"won": 0, "lost": 0})
|
counts = history_counts.get(date_str, {"won": 0, "lost": 0})
|
||||||
history.append(HistoryEntry(
|
history.append(
|
||||||
date=date_str,
|
HistoryEntry(
|
||||||
won=counts["won"],
|
date=date_str,
|
||||||
lost=counts["lost"],
|
won=counts["won"],
|
||||||
))
|
lost=counts["lost"],
|
||||||
|
)
|
||||||
|
)
|
||||||
current += timedelta(days=1)
|
current += timedelta(days=1)
|
||||||
|
|
||||||
# Get chat tags
|
# Get chat tags
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ from open_webui.env import (
|
|||||||
ENABLE_FORWARD_USER_INFO_HEADERS,
|
ENABLE_FORWARD_USER_INFO_HEADERS,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
# Constants
|
# Constants
|
||||||
|
|||||||
@@ -558,7 +558,9 @@ async def ldap_auth(
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error(f"Failed to sync groups for user {user.id}: {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:
|
else:
|
||||||
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
|
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1633,9 +1633,7 @@ async def update_message_by_id(
|
|||||||
if (
|
if (
|
||||||
user.role != "admin"
|
user.role != "admin"
|
||||||
and message.user_id != user.id
|
and message.user_id != user.id
|
||||||
and not channel_has_access(
|
and not channel_has_access(user.id, channel, permission="read", db=db)
|
||||||
user.id, channel, permission="read", db=db
|
|
||||||
)
|
|
||||||
):
|
):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
|
status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
|
||||||
|
|||||||
@@ -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.auth import get_admin_user, get_verified_user
|
||||||
from open_webui.utils.access_control import has_permission
|
from open_webui.utils.access_control import has_permission
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ from pydantic import BaseModel, HttpUrl
|
|||||||
from open_webui.internal.db import get_session
|
from open_webui.internal.db import get_session
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ from sqlalchemy.orm import Session
|
|||||||
|
|
||||||
from open_webui.utils.auth import get_admin_user, get_verified_user
|
from open_webui.utils.auth import get_admin_user, get_verified_user
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|||||||
@@ -14,7 +14,11 @@ import requests
|
|||||||
from fastapi import APIRouter, Depends, HTTPException, Request, UploadFile
|
from fastapi import APIRouter, Depends, HTTPException, Request, UploadFile
|
||||||
from fastapi.responses import FileResponse
|
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.constants import ERROR_MESSAGES
|
||||||
from open_webui.retrieval.web.utils import validate_url
|
from open_webui.retrieval.web.utils import validate_url
|
||||||
from open_webui.env import ENABLE_FORWARD_USER_INFO_HEADERS
|
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
|
request.app.state.config.IMAGE_GENERATION_ENGINE = form_data.IMAGE_GENERATION_ENGINE
|
||||||
set_image_model(request, form_data.IMAGE_GENERATION_MODEL)
|
set_image_model(request, form_data.IMAGE_GENERATION_MODEL)
|
||||||
if (
|
if form_data.IMAGE_SIZE == "auto" and not re.match(
|
||||||
form_data.IMAGE_SIZE == "auto"
|
IMAGE_AUTO_SIZE_MODELS_REGEX_PATTERN, form_data.IMAGE_GENERATION_MODEL
|
||||||
and not re.match(IMAGE_AUTO_SIZE_MODELS_REGEX_PATTERN, form_data.IMAGE_GENERATION_MODEL)
|
|
||||||
):
|
):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=400,
|
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"}
|
else {"response_format": "b64_json"}
|
||||||
),
|
),
|
||||||
**(
|
**(
|
||||||
@@ -912,7 +918,9 @@ async def image_edits(
|
|||||||
form_data.image = await load_url_image(form_data.image)
|
form_data.image = await load_url_image(form_data.image)
|
||||||
elif isinstance(form_data.image, list):
|
elif isinstance(form_data.image, list):
|
||||||
# Load all images in parallel for better performance
|
# 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:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=400, detail=ERROR_MESSAGES.DEFAULT(e))
|
raise HTTPException(status_code=400, detail=ERROR_MESSAGES.DEFAULT(e))
|
||||||
|
|
||||||
@@ -947,7 +955,10 @@ async def image_edits(
|
|||||||
**({"size": size} if size else {}),
|
**({"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"}
|
else {"response_format": "b64_json"}
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.config import BYPASS_ADMIN_ACCESS_CONTROL
|
||||||
from open_webui.models.models import Models, ModelForm
|
from open_webui.models.models import Models, ModelForm
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
@@ -540,9 +539,7 @@ async def update_knowledge_access_by_id(
|
|||||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||||
)
|
)
|
||||||
|
|
||||||
AccessGrants.set_access_grants(
|
AccessGrants.set_access_grants("knowledge", id, form_data.access_grants, db=db)
|
||||||
"knowledge", id, form_data.access_grants, db=db
|
|
||||||
)
|
|
||||||
|
|
||||||
return KnowledgeFilesResponse(
|
return KnowledgeFilesResponse(
|
||||||
**Knowledges.get_knowledge_by_id(id=id, db=db).model_dump(),
|
**Knowledges.get_knowledge_by_id(id=id, db=db).model_dump(),
|
||||||
|
|||||||
@@ -345,9 +345,7 @@ async def update_note_access_by_id(
|
|||||||
status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
|
status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
|
||||||
)
|
)
|
||||||
|
|
||||||
AccessGrants.set_access_grants(
|
AccessGrants.set_access_grants("note", id, form_data.access_grants, db=db)
|
||||||
"note", id, form_data.access_grants, db=db
|
|
||||||
)
|
|
||||||
|
|
||||||
return Notes.get_note_by_id(id, db=db)
|
return Notes.get_note_by_id(id, db=db)
|
||||||
|
|
||||||
|
|||||||
@@ -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.auth import get_admin_user, get_verified_user
|
||||||
from open_webui.utils.headers import include_user_info_headers
|
from open_webui.utils.headers import include_user_info_headers
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -828,7 +827,9 @@ def convert_to_responses_payload(payload: dict) -> dict:
|
|||||||
if isinstance(content, str):
|
if isinstance(content, str):
|
||||||
system_content = content
|
system_content = content
|
||||||
elif isinstance(content, list):
|
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
|
continue
|
||||||
|
|
||||||
# Convert content format
|
# Convert content format
|
||||||
@@ -840,19 +841,21 @@ def convert_to_responses_payload(payload: dict) -> dict:
|
|||||||
content_parts = []
|
content_parts = []
|
||||||
for part in content:
|
for part in content:
|
||||||
if part.get("type") == "text":
|
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":
|
elif part.get("type") == "image_url":
|
||||||
url_data = part.get("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})
|
content_parts.append({"type": "input_image", "image_url": url})
|
||||||
else:
|
else:
|
||||||
content_parts = [{"type": text_type, "text": str(content)}]
|
content_parts = [{"type": text_type, "text": str(content)}]
|
||||||
|
|
||||||
input_items.append({
|
input_items.append({"type": "message", "role": role, "content": content_parts})
|
||||||
"type": "message",
|
|
||||||
"role": role,
|
|
||||||
"content": content_parts
|
|
||||||
})
|
|
||||||
|
|
||||||
responses_payload = {**payload, "input": input_items}
|
responses_payload = {**payload, "input": input_items}
|
||||||
|
|
||||||
@@ -863,7 +866,13 @@ def convert_to_responses_payload(payload: dict) -> dict:
|
|||||||
responses_payload["max_output_tokens"] = responses_payload.pop("max_tokens")
|
responses_payload["max_output_tokens"] = responses_payload.pop("max_tokens")
|
||||||
|
|
||||||
# Remove Chat Completions-only parameters not supported by the Responses API
|
# 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)
|
responses_payload.pop(unsupported_key, None)
|
||||||
|
|
||||||
# Convert Chat Completions tools format to Responses API format
|
# Convert Chat Completions tools format to Responses API format
|
||||||
@@ -892,7 +901,6 @@ def convert_to_responses_payload(payload: dict) -> dict:
|
|||||||
return responses_payload
|
return responses_payload
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def convert_responses_result(response: dict) -> dict:
|
def convert_responses_result(response: dict) -> dict:
|
||||||
"""
|
"""
|
||||||
Convert non-streaming Responses API result.
|
Convert non-streaming Responses API result.
|
||||||
|
|||||||
@@ -107,7 +107,9 @@ async def get_prompt_list(
|
|||||||
|
|
||||||
filter["user_id"] = user.id
|
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(
|
return PromptAccessListResponse(
|
||||||
items=[
|
items=[
|
||||||
@@ -313,9 +315,7 @@ async def update_prompt_by_id(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Use the ID from the found prompt
|
# Use the ID from the found prompt
|
||||||
updated_prompt = Prompts.update_prompt_by_id(
|
updated_prompt = Prompts.update_prompt_by_id(prompt.id, form_data, user.id, db=db)
|
||||||
prompt.id, form_data, user.id, db=db
|
|
||||||
)
|
|
||||||
if updated_prompt:
|
if updated_prompt:
|
||||||
return updated_prompt
|
return updated_prompt
|
||||||
else:
|
else:
|
||||||
@@ -464,9 +464,7 @@ async def update_prompt_access_by_id(
|
|||||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||||
)
|
)
|
||||||
|
|
||||||
AccessGrants.set_access_grants(
|
AccessGrants.set_access_grants("prompt", prompt_id, form_data.access_grants, db=db)
|
||||||
"prompt", prompt_id, form_data.access_grants, db=db
|
|
||||||
)
|
|
||||||
|
|
||||||
return Prompts.get_prompt_by_id(prompt_id, db=db)
|
return Prompts.get_prompt_by_id(prompt_id, db=db)
|
||||||
|
|
||||||
@@ -554,9 +552,7 @@ async def get_prompt_history(
|
|||||||
return history
|
return history
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get("/id/{prompt_id}/history/{history_id}", response_model=PromptHistoryModel)
|
||||||
"/id/{prompt_id}/history/{history_id}", response_model=PromptHistoryModel
|
|
||||||
)
|
|
||||||
async def get_prompt_history_entry(
|
async def get_prompt_history_entry(
|
||||||
prompt_id: str,
|
prompt_id: str,
|
||||||
history_id: str,
|
history_id: str,
|
||||||
@@ -599,9 +595,7 @@ async def get_prompt_history_entry(
|
|||||||
return history_entry
|
return history_entry
|
||||||
|
|
||||||
|
|
||||||
@router.delete(
|
@router.delete("/id/{prompt_id}/history/{history_id}", response_model=bool)
|
||||||
"/id/{prompt_id}/history/{history_id}", response_model=bool
|
|
||||||
)
|
|
||||||
async def delete_prompt_history_entry(
|
async def delete_prompt_history_entry(
|
||||||
prompt_id: str,
|
prompt_id: str,
|
||||||
history_id: str,
|
history_id: str,
|
||||||
|
|||||||
@@ -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.config import BYPASS_ADMIN_ACCESS_CONTROL
|
||||||
from open_webui.constants import ERROR_MESSAGES
|
from open_webui.constants import ERROR_MESSAGES
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
PAGE_ITEM_COUNT = 30
|
PAGE_ITEM_COUNT = 30
|
||||||
@@ -98,9 +97,7 @@ async def get_skill_list(
|
|||||||
|
|
||||||
filter["user_id"] = user.id
|
filter["user_id"] = user.id
|
||||||
|
|
||||||
result = Skills.search_skills(
|
result = Skills.search_skills(user.id, filter=filter, skip=skip, limit=limit, db=db)
|
||||||
user.id, filter=filter, skip=skip, limit=limit, db=db
|
|
||||||
)
|
|
||||||
|
|
||||||
return SkillAccessListResponse(
|
return SkillAccessListResponse(
|
||||||
items=[
|
items=[
|
||||||
@@ -343,9 +340,7 @@ async def update_skill_access_by_id(
|
|||||||
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
||||||
)
|
)
|
||||||
|
|
||||||
AccessGrants.set_access_grants(
|
AccessGrants.set_access_grants("skill", id, form_data.access_grants, db=db)
|
||||||
"skill", id, form_data.access_grants, db=db
|
|
||||||
)
|
|
||||||
|
|
||||||
return Skills.get_skill_by_id(id, db=db)
|
return Skills.get_skill_by_id(id, db=db)
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ from open_webui.config import (
|
|||||||
DEFAULT_VOICE_MODE_PROMPT_TEMPLATE,
|
DEFAULT_VOICE_MODE_PROMPT_TEMPLATE,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|||||||
@@ -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.config import CACHE_DIR, BYPASS_ADMIN_ACCESS_CONTROL
|
||||||
from open_webui.constants import ERROR_MESSAGES
|
from open_webui.constants import ERROR_MESSAGES
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -555,9 +554,7 @@ async def update_tool_access_by_id(
|
|||||||
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
||||||
)
|
)
|
||||||
|
|
||||||
AccessGrants.set_access_grants(
|
AccessGrants.set_access_grants("tool", id, form_data.access_grants, db=db)
|
||||||
"tool", id, form_data.access_grants, db=db
|
|
||||||
)
|
|
||||||
|
|
||||||
return Tools.get_tool_by_id(id, db=db)
|
return Tools.get_tool_by_id(id, db=db)
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ from open_webui.utils.auth import (
|
|||||||
)
|
)
|
||||||
from open_webui.utils.access_control import get_permissions, has_permission
|
from open_webui.utils.access_control import get_permissions, has_permission
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|||||||
@@ -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.auth import get_admin_user, get_verified_user
|
||||||
from open_webui.utils.code_interpreter import execute_code_jupyter
|
from open_webui.utils.code_interpreter import execute_code_jupyter
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ from open_webui.env import (
|
|||||||
GLOBAL_LOG_LEVEL,
|
GLOBAL_LOG_LEVEL,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL)
|
logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL)
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ from azure.identity import DefaultAzureCredential
|
|||||||
from azure.storage.blob import BlobServiceClient
|
from azure.storage.blob import BlobServiceClient
|
||||||
from azure.core.exceptions import ResourceNotFoundError
|
from azure.core.exceptions import ResourceNotFoundError
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ from typing import Dict, List, Optional
|
|||||||
|
|
||||||
from open_webui.env import REDIS_KEY_PREFIX
|
from open_webui.env import REDIS_KEY_PREFIX
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
# A dictionary to keep track of active tasks
|
# A dictionary to keep track of active tasks
|
||||||
|
|||||||
@@ -380,8 +380,8 @@ async def execute_code(
|
|||||||
# Add import blocking code if there are blocked modules
|
# Add import blocking code if there are blocked modules
|
||||||
if CODE_INTERPRETER_BLOCKED_MODULES:
|
if CODE_INTERPRETER_BLOCKED_MODULES:
|
||||||
import textwrap
|
import textwrap
|
||||||
blocking_code = textwrap.dedent(
|
|
||||||
f"""
|
blocking_code = textwrap.dedent(f"""
|
||||||
import builtins
|
import builtins
|
||||||
|
|
||||||
BLOCKED_MODULES = {CODE_INTERPRETER_BLOCKED_MODULES}
|
BLOCKED_MODULES = {CODE_INTERPRETER_BLOCKED_MODULES}
|
||||||
@@ -397,15 +397,20 @@ async def execute_code(
|
|||||||
return _real_import(name, globals, locals, fromlist, level)
|
return _real_import(name, globals, locals, fromlist, level)
|
||||||
|
|
||||||
builtins.__import__ = restricted_import
|
builtins.__import__ = restricted_import
|
||||||
"""
|
""")
|
||||||
)
|
|
||||||
code = blocking_code + "\n" + code
|
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":
|
if engine == "pyodide":
|
||||||
# Execute via frontend pyodide using bidirectional event call
|
# Execute via frontend pyodide using bidirectional event call
|
||||||
if __event_call__ is None:
|
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__(
|
output = await __event_call__(
|
||||||
{
|
{
|
||||||
@@ -413,7 +418,9 @@ async def execute_code(
|
|||||||
"data": {
|
"data": {
|
||||||
"id": str(uuid4()),
|
"id": str(uuid4()),
|
||||||
"code": code,
|
"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,
|
code,
|
||||||
(
|
(
|
||||||
__request__.app.state.config.CODE_INTERPRETER_JUPYTER_AUTH_TOKEN
|
__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
|
else None
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
__request__.app.state.config.CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD
|
__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
|
else None
|
||||||
),
|
),
|
||||||
__request__.app.state.config.CODE_INTERPRETER_JUPYTER_TIMEOUT,
|
__request__.app.state.config.CODE_INTERPRETER_JUPYTER_TIMEOUT,
|
||||||
@@ -1921,7 +1930,9 @@ async def view_skill(
|
|||||||
# Check user access
|
# Check user access
|
||||||
user_role = __user__.get("role", "user")
|
user_role = __user__.get("role", "user")
|
||||||
if user_role != "admin" and skill.user_id != user_id:
|
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(
|
if not AccessGrants.has_access(
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
resource_type="skill",
|
resource_type="skill",
|
||||||
@@ -1931,11 +1942,13 @@ async def view_skill(
|
|||||||
):
|
):
|
||||||
return json.dumps({"error": "Access denied"})
|
return json.dumps({"error": "Access denied"})
|
||||||
|
|
||||||
return json.dumps({
|
return json.dumps(
|
||||||
"name": skill.name,
|
{
|
||||||
"content": skill.content,
|
"name": skill.name,
|
||||||
}, ensure_ascii=False)
|
"content": skill.content,
|
||||||
|
},
|
||||||
|
ensure_ascii=False,
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.exception(f"view_skill error: {e}")
|
log.exception(f"view_skill error: {e}")
|
||||||
return json.dumps({"error": str(e)})
|
return json.dumps({"error": str(e)})
|
||||||
|
|
||||||
|
|||||||
@@ -139,16 +139,23 @@ def has_access(
|
|||||||
continue
|
continue
|
||||||
principal_type = grant.get("principal_type")
|
principal_type = grant.get("principal_type")
|
||||||
principal_id = grant.get("principal_id")
|
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
|
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 True
|
||||||
|
|
||||||
|
|
||||||
return False
|
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.
|
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:
|
if not perm_data:
|
||||||
continue
|
continue
|
||||||
for group_id in perm_data.get("group_ids", []):
|
for group_id in perm_data.get("group_ids", []):
|
||||||
grants.append({
|
grants.append(
|
||||||
"principal_type": "group",
|
{
|
||||||
"principal_id": group_id,
|
"principal_type": "group",
|
||||||
"permission": perm,
|
"principal_id": group_id,
|
||||||
})
|
"permission": perm,
|
||||||
|
}
|
||||||
|
)
|
||||||
for uid in perm_data.get("user_ids", []):
|
for uid in perm_data.get("user_ids", []):
|
||||||
grants.append({
|
grants.append(
|
||||||
"principal_type": "user",
|
{
|
||||||
"principal_id": uid,
|
"principal_type": "user",
|
||||||
"permission": perm,
|
"principal_id": uid,
|
||||||
})
|
"permission": perm,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
data[grants_key] = grants
|
data[grants_key] = grants
|
||||||
data.pop(ac_key, None)
|
data.pop(ac_key, None)
|
||||||
|
|||||||
@@ -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.utils.auth import get_current_user, get_http_authorization_cred
|
||||||
from open_webui.models.users import UserModel
|
from open_webui.models.users import UserModel
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from loguru import Logger
|
from loguru import Logger
|
||||||
|
|
||||||
@@ -222,7 +221,9 @@ class AuditLoggingMiddleware:
|
|||||||
|
|
||||||
# Skip logging if the request is not authenticated
|
# Skip logging if the request is not authenticated
|
||||||
# Check both Authorization header (API keys) and token cookie (browser sessions)
|
# 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
|
return True
|
||||||
|
|
||||||
# match either /api/<resource>/...(for the endpoint /api/chat case) or /api/v1/<resource>/...
|
# match either /api/<resource>/...(for the endpoint /api/chat case) or /api/v1/<resource>/...
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ from open_webui.env import (
|
|||||||
from fastapi import BackgroundTasks, Depends, HTTPException, Request, Response, status
|
from fastapi import BackgroundTasks, Depends, HTTPException, Request, Response, status
|
||||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
SESSION_SECRET = WEBUI_SECRET_KEY
|
SESSION_SECRET = WEBUI_SECRET_KEY
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ from open_webui.utils.filter import (
|
|||||||
|
|
||||||
from open_webui.env import GLOBAL_LOG_LEVEL, BYPASS_MODEL_ACCESS_CONTROL
|
from open_webui.env import GLOBAL_LOG_LEVEL, BYPASS_MODEL_ACCESS_CONTROL
|
||||||
|
|
||||||
|
|
||||||
logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL)
|
logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL)
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -341,7 +340,9 @@ async def chat_completed(request: Request, form_data: dict, user: Any):
|
|||||||
}
|
}
|
||||||
|
|
||||||
try:
|
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)
|
filter_functions = Functions.get_functions_by_ids(filter_ids)
|
||||||
|
|
||||||
result, _ = await process_filter_functions(
|
result, _ = await process_filter_functions(
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import aiohttp
|
|||||||
import websockets
|
import websockets
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -86,4 +86,3 @@ async def generate_embeddings(
|
|||||||
form_data=form_data,
|
form_data=form_data,
|
||||||
user=user,
|
user=user,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ from open_webui.env import (
|
|||||||
ENABLE_OTEL_LOGS,
|
ENABLE_OTEL_LOGS,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from loguru import Record
|
from loguru import Record
|
||||||
|
|
||||||
|
|||||||
@@ -133,7 +133,6 @@ from open_webui.env import (
|
|||||||
from open_webui.utils.headers import include_user_info_headers
|
from open_webui.utils.headers import include_user_info_headers
|
||||||
from open_webui.constants import TASKS
|
from open_webui.constants import TASKS
|
||||||
|
|
||||||
|
|
||||||
logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL)
|
logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL)
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -2106,10 +2105,15 @@ async def process_chat_payload(request, form_data, user, metadata, model):
|
|||||||
if all_skill_ids:
|
if all_skill_ids:
|
||||||
from open_webui.models.skills import Skills as SkillsModel
|
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 = [
|
available_skills = [
|
||||||
s for sid in all_skill_ids
|
s
|
||||||
if sid in accessible_skill_ids and (s := SkillsModel.get_skill_by_id(sid)) and s.is_active
|
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:
|
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:
|
if ENABLE_FORWARD_USER_INFO_HEADERS and user:
|
||||||
headers = include_user_info_headers(headers, user)
|
headers = include_user_info_headers(headers, user)
|
||||||
if metadata and metadata.get("chat_id"):
|
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()
|
mcp_clients[server_id] = MCPClient()
|
||||||
await mcp_clients[server_id].connect(
|
await mcp_clients[server_id].connect(
|
||||||
@@ -2812,9 +2818,7 @@ async def non_streaming_chat_response_handler(response, ctx):
|
|||||||
"id": output_id("msg"),
|
"id": output_id("msg"),
|
||||||
"status": "completed",
|
"status": "completed",
|
||||||
"role": "assistant",
|
"role": "assistant",
|
||||||
"content": [
|
"content": [{"type": "output_text", "text": 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)
|
match = re.search(start_tag_pattern, content)
|
||||||
if match:
|
if match:
|
||||||
try:
|
try:
|
||||||
attr_content = (
|
attr_content = match.group(1) if match.group(1) else ""
|
||||||
match.group(1) if match.group(1) else ""
|
|
||||||
)
|
|
||||||
except:
|
except:
|
||||||
attr_content = ""
|
attr_content = ""
|
||||||
|
|
||||||
@@ -2982,7 +2984,7 @@ async def streaming_chat_response_handler(response, ctx):
|
|||||||
current_text = get_last_text(output)
|
current_text = get_last_text(output)
|
||||||
set_last_text(
|
set_last_text(
|
||||||
output,
|
output,
|
||||||
current_text.replace(match.group(0) + after_tag, "")
|
current_text.replace(match.group(0) + after_tag, ""),
|
||||||
)
|
)
|
||||||
|
|
||||||
if before_tag:
|
if before_tag:
|
||||||
@@ -3031,7 +3033,9 @@ async def streaming_chat_response_handler(response, ctx):
|
|||||||
"id": output_id("msg"),
|
"id": output_id("msg"),
|
||||||
"status": "in_progress",
|
"status": "in_progress",
|
||||||
"role": "assistant",
|
"role": "assistant",
|
||||||
"content": [{"type": "output_text", "text": ""}],
|
"content": [
|
||||||
|
{"type": "output_text", "text": ""}
|
||||||
|
],
|
||||||
"_tag_type": content_type,
|
"_tag_type": content_type,
|
||||||
"start_tag": start_tag,
|
"start_tag": start_tag,
|
||||||
"end_tag": end_tag,
|
"end_tag": end_tag,
|
||||||
@@ -3059,8 +3063,14 @@ async def streaming_chat_response_handler(response, ctx):
|
|||||||
|
|
||||||
elif (
|
elif (
|
||||||
(last_type == "reasoning" and content_type == "reasoning")
|
(last_type == "reasoning" and content_type == "reasoning")
|
||||||
or (last_type == "open_webui:code_interpreter" and content_type == "code_interpreter")
|
or (
|
||||||
or (last_type == "message" and output[-1].get("_tag_type") == content_type)
|
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]
|
item = output[-1]
|
||||||
start_tag = item.get("start_tag", "")
|
start_tag = item.get("start_tag", "")
|
||||||
@@ -3178,9 +3188,7 @@ async def streaming_chat_response_handler(response, ctx):
|
|||||||
# Clean processed content
|
# Clean processed content
|
||||||
start_tag_clean = rf"{re.escape(start_tag)}"
|
start_tag_clean = rf"{re.escape(start_tag)}"
|
||||||
if start_tag.startswith("<") and start_tag.endswith(">"):
|
if start_tag.startswith("<") and start_tag.endswith(">"):
|
||||||
start_tag_clean = (
|
start_tag_clean = rf"<{re.escape(start_tag[1:-1])}(\s.*?)?>"
|
||||||
rf"<{re.escape(start_tag[1:-1])}(\s.*?)?>"
|
|
||||||
)
|
|
||||||
|
|
||||||
content = re.sub(
|
content = re.sub(
|
||||||
rf"{start_tag_clean}(.|\n)*?{re.escape(end_tag)}",
|
rf"{start_tag_clean}(.|\n)*?{re.escape(end_tag)}",
|
||||||
@@ -3231,7 +3239,6 @@ async def streaming_chat_response_handler(response, ctx):
|
|||||||
else:
|
else:
|
||||||
output = []
|
output = []
|
||||||
|
|
||||||
|
|
||||||
usage = None
|
usage = None
|
||||||
|
|
||||||
reasoning_tags_param = metadata.get("params", {}).get("reasoning_tags")
|
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:
|
for tc in response_tool_calls:
|
||||||
call_id = tc.get("id", "")
|
call_id = tc.get("id", "")
|
||||||
func = tc.get("function", {})
|
func = tc.get("function", {})
|
||||||
pending_fc_items.append({
|
pending_fc_items.append(
|
||||||
"type": "function_call",
|
{
|
||||||
"id": call_id or output_id("fc"),
|
"type": "function_call",
|
||||||
"call_id": call_id,
|
"id": call_id
|
||||||
"name": func.get("name", ""),
|
or output_id("fc"),
|
||||||
"arguments": func.get("arguments", "{}"),
|
"call_id": call_id,
|
||||||
"status": "in_progress",
|
"name": func.get("name", ""),
|
||||||
})
|
"arguments": func.get(
|
||||||
|
"arguments", "{}"
|
||||||
|
),
|
||||||
|
"status": "in_progress",
|
||||||
|
}
|
||||||
|
)
|
||||||
pending_output = output + pending_fc_items
|
pending_output = output + pending_fc_items
|
||||||
await event_emitter(
|
await event_emitter(
|
||||||
{
|
{
|
||||||
@@ -3585,22 +3597,25 @@ async def streaming_chat_response_handler(response, ctx):
|
|||||||
|
|
||||||
# Append to reasoning content
|
# Append to reasoning content
|
||||||
parts = reasoning_item.get("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
|
parts[-1]["text"] += reasoning_content
|
||||||
else:
|
else:
|
||||||
reasoning_item["content"] = [
|
reasoning_item["content"] = [
|
||||||
{"type": "output_text", "text": reasoning_content}
|
{
|
||||||
|
"type": "output_text",
|
||||||
|
"text": reasoning_content,
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
data = {
|
data = {"content": serialize_output(output)}
|
||||||
"content": serialize_output(output)
|
|
||||||
}
|
|
||||||
|
|
||||||
if value:
|
if value:
|
||||||
if (
|
if (
|
||||||
output
|
output
|
||||||
and output[-1].get("type")
|
and output[-1].get("type") == "reasoning"
|
||||||
== "reasoning"
|
|
||||||
and output[-1]
|
and output[-1]
|
||||||
.get("attributes", {})
|
.get("attributes", {})
|
||||||
.get("type")
|
.get("type")
|
||||||
@@ -3620,7 +3635,12 @@ async def streaming_chat_response_handler(response, ctx):
|
|||||||
"id": output_id("msg"),
|
"id": output_id("msg"),
|
||||||
"status": "in_progress",
|
"status": "in_progress",
|
||||||
"role": "assistant",
|
"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"),
|
"id": output_id("msg"),
|
||||||
"status": "in_progress",
|
"status": "in_progress",
|
||||||
"role": "assistant",
|
"role": "assistant",
|
||||||
"content": [{"type": "output_text", "text": ""}],
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "output_text",
|
||||||
|
"text": "",
|
||||||
|
}
|
||||||
|
],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
# Append value to last message item's text
|
# Append value to last message item's text
|
||||||
msg_parts = output[-1].get("content", [])
|
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
|
msg_parts[-1]["text"] += value
|
||||||
else:
|
else:
|
||||||
output[-1]["content"] = [
|
output[-1]["content"] = [
|
||||||
@@ -3664,32 +3693,26 @@ async def streaming_chat_response_handler(response, ctx):
|
|||||||
]
|
]
|
||||||
|
|
||||||
if DETECT_REASONING_TAGS:
|
if DETECT_REASONING_TAGS:
|
||||||
content, output, _ = (
|
content, output, _ = tag_output_handler(
|
||||||
tag_output_handler(
|
"reasoning",
|
||||||
"reasoning",
|
reasoning_tags,
|
||||||
reasoning_tags,
|
content,
|
||||||
content,
|
output,
|
||||||
output,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
content, output, _ = (
|
content, output, _ = tag_output_handler(
|
||||||
tag_output_handler(
|
"solution",
|
||||||
"solution",
|
DEFAULT_SOLUTION_TAGS,
|
||||||
DEFAULT_SOLUTION_TAGS,
|
content,
|
||||||
content,
|
output,
|
||||||
output,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if DETECT_CODE_INTERPRETER:
|
if DETECT_CODE_INTERPRETER:
|
||||||
content, output, end = (
|
content, output, end = tag_output_handler(
|
||||||
tag_output_handler(
|
"code_interpreter",
|
||||||
"code_interpreter",
|
DEFAULT_CODE_INTERPRETER_TAGS,
|
||||||
DEFAULT_CODE_INTERPRETER_TAGS,
|
content,
|
||||||
content,
|
output,
|
||||||
output,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if end:
|
if end:
|
||||||
@@ -3707,9 +3730,7 @@ async def streaming_chat_response_handler(response, ctx):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
data = {
|
data = {
|
||||||
"content": serialize_output(
|
"content": serialize_output(output),
|
||||||
output
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if delta:
|
if delta:
|
||||||
@@ -3750,7 +3771,9 @@ async def streaming_chat_response_handler(response, ctx):
|
|||||||
"id": output_id("msg"),
|
"id": output_id("msg"),
|
||||||
"status": "in_progress",
|
"status": "in_progress",
|
||||||
"role": "assistant",
|
"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:
|
for tc in response_tool_calls:
|
||||||
call_id = tc.get("id", "")
|
call_id = tc.get("id", "")
|
||||||
func = tc.get("function", {})
|
func = tc.get("function", {})
|
||||||
output.append({
|
output.append(
|
||||||
"type": "function_call",
|
{
|
||||||
"id": call_id or output_id("fc"),
|
"type": "function_call",
|
||||||
"call_id": call_id,
|
"id": call_id or output_id("fc"),
|
||||||
"name": func.get("name", ""),
|
"call_id": call_id,
|
||||||
"arguments": func.get("arguments", "{}"),
|
"name": func.get("name", ""),
|
||||||
"status": "in_progress",
|
"arguments": func.get("arguments", "{}"),
|
||||||
})
|
"status": "in_progress",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
await event_emitter(
|
await event_emitter(
|
||||||
{
|
{
|
||||||
@@ -3954,35 +3979,42 @@ async def streaming_chat_response_handler(response, ctx):
|
|||||||
call_id = tc.get("id", "")
|
call_id = tc.get("id", "")
|
||||||
# Mark function_call as completed
|
# Mark function_call as completed
|
||||||
for item in output:
|
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"
|
item["status"] = "completed"
|
||||||
# Update arguments with parsed/sanitized version
|
# Update arguments with parsed/sanitized version
|
||||||
item["arguments"] = tc.get("function", {}).get("arguments", "{}")
|
item["arguments"] = tc.get("function", {}).get(
|
||||||
|
"arguments", "{}"
|
||||||
|
)
|
||||||
break
|
break
|
||||||
|
|
||||||
for result in results:
|
for result in results:
|
||||||
output.append({
|
output.append(
|
||||||
"type": "function_call_output",
|
{
|
||||||
"id": output_id("fco"),
|
"type": "function_call_output",
|
||||||
"call_id": result.get("tool_call_id", ""),
|
"id": output_id("fco"),
|
||||||
"output": [
|
"call_id": result.get("tool_call_id", ""),
|
||||||
{
|
"output": [
|
||||||
"type": "input_text",
|
{
|
||||||
"text": result.get("content", ""),
|
"type": "input_text",
|
||||||
}
|
"text": result.get("content", ""),
|
||||||
],
|
}
|
||||||
"status": "completed",
|
],
|
||||||
**(
|
"status": "completed",
|
||||||
{"files": result.get("files")}
|
**(
|
||||||
if result.get("files")
|
{"files": result.get("files")}
|
||||||
else {}
|
if result.get("files")
|
||||||
),
|
else {}
|
||||||
**(
|
),
|
||||||
{"embeds": result.get("embeds")}
|
**(
|
||||||
if result.get("embeds")
|
{"embeds": result.get("embeds")}
|
||||||
else {}
|
if result.get("embeds")
|
||||||
),
|
else {}
|
||||||
})
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
# Append a new empty message item for the next response
|
# Append a new empty message item for the next response
|
||||||
output.append(
|
output.append(
|
||||||
@@ -4079,8 +4111,7 @@ async def streaming_chat_response_handler(response, ctx):
|
|||||||
code = sanitize_code(code)
|
code = sanitize_code(code)
|
||||||
|
|
||||||
if CODE_INTERPRETER_BLOCKED_MODULES:
|
if CODE_INTERPRETER_BLOCKED_MODULES:
|
||||||
blocking_code = textwrap.dedent(
|
blocking_code = textwrap.dedent(f"""
|
||||||
f"""
|
|
||||||
import builtins
|
import builtins
|
||||||
|
|
||||||
BLOCKED_MODULES = {CODE_INTERPRETER_BLOCKED_MODULES}
|
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)
|
return _real_import(name, globals, locals, fromlist, level)
|
||||||
|
|
||||||
builtins.__import__ = restricted_import
|
builtins.__import__ = restricted_import
|
||||||
"""
|
""")
|
||||||
)
|
|
||||||
code = blocking_code + "\n" + code
|
code = blocking_code + "\n" + code
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -151,11 +151,15 @@ def convert_output_to_messages(output: list, raw: bool = False) -> list[dict]:
|
|||||||
def flush_pending():
|
def flush_pending():
|
||||||
nonlocal pending_content, pending_tool_calls
|
nonlocal pending_content, pending_tool_calls
|
||||||
if pending_content or pending_tool_calls:
|
if pending_content or pending_tool_calls:
|
||||||
messages.append({
|
messages.append(
|
||||||
"role": "assistant",
|
{
|
||||||
"content": "\n".join(pending_content) if pending_content else "",
|
"role": "assistant",
|
||||||
**({"tool_calls": pending_tool_calls} if pending_tool_calls else {}),
|
"content": "\n".join(pending_content) if pending_content else "",
|
||||||
})
|
**(
|
||||||
|
{"tool_calls": pending_tool_calls} if pending_tool_calls else {}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
pending_content = []
|
pending_content = []
|
||||||
pending_tool_calls = []
|
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
|
# Ensure arguments is always a JSON string
|
||||||
if not isinstance(arguments, str):
|
if not isinstance(arguments, str):
|
||||||
arguments = json.dumps(arguments)
|
arguments = json.dumps(arguments)
|
||||||
pending_tool_calls.append({
|
pending_tool_calls.append(
|
||||||
"id": item.get("call_id", ""),
|
{
|
||||||
"type": "function",
|
"id": item.get("call_id", ""),
|
||||||
"function": {
|
"type": "function",
|
||||||
"name": item.get("name", ""),
|
"function": {
|
||||||
"arguments": arguments,
|
"name": item.get("name", ""),
|
||||||
|
"arguments": arguments,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
|
|
||||||
elif item_type == "function_call_output":
|
elif item_type == "function_call_output":
|
||||||
# Flush any pending content/tool_calls before adding tool result
|
# 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":
|
if part.get("type") == "input_text":
|
||||||
content += part.get("text", "")
|
content += part.get("text", "")
|
||||||
|
|
||||||
messages.append({
|
messages.append(
|
||||||
"role": "tool",
|
{
|
||||||
"tool_call_id": item.get("call_id", ""),
|
"role": "tool",
|
||||||
"content": content,
|
"tool_call_id": item.get("call_id", ""),
|
||||||
})
|
"content": content,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
elif item_type == "reasoning":
|
elif item_type == "reasoning":
|
||||||
if raw:
|
if raw:
|
||||||
@@ -218,9 +226,7 @@ def convert_output_to_messages(output: list, raw: bool = False) -> list[dict]:
|
|||||||
if reasoning_text:
|
if reasoning_text:
|
||||||
start_tag = item.get("start_tag", "<think>")
|
start_tag = item.get("start_tag", "<think>")
|
||||||
end_tag = item.get("end_tag", "</think>")
|
end_tag = item.get("end_tag", "</think>")
|
||||||
pending_content.append(
|
pending_content.append(f"{start_tag}{reasoning_text}{end_tag}")
|
||||||
f"{start_tag}{reasoning_text}{end_tag}"
|
|
||||||
)
|
|
||||||
# else: skip reasoning blocks for normal LLM messages
|
# else: skip reasoning blocks for normal LLM messages
|
||||||
|
|
||||||
elif item_type == "open_webui:code_interpreter":
|
elif item_type == "open_webui:code_interpreter":
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ from open_webui.config import (
|
|||||||
from open_webui.env import BYPASS_MODEL_ACCESS_CONTROL, GLOBAL_LOG_LEVEL
|
from open_webui.env import BYPASS_MODEL_ACCESS_CONTROL, GLOBAL_LOG_LEVEL
|
||||||
from open_webui.models.users import UserModel
|
from open_webui.models.users import UserModel
|
||||||
|
|
||||||
|
|
||||||
logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL)
|
logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL)
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
@@ -289,7 +289,9 @@ def convert_payload_openai_to_ollama(openai_payload: dict) -> dict:
|
|||||||
"""
|
"""
|
||||||
# Shallow copy metadata separately (may contain non-picklable objects)
|
# Shallow copy metadata separately (may contain non-picklable objects)
|
||||||
metadata = openai_payload.get("metadata")
|
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:
|
if metadata is not None:
|
||||||
openai_payload["metadata"] = dict(metadata)
|
openai_payload["metadata"] = dict(metadata)
|
||||||
ollama_payload = {}
|
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]
|
ollama_payload[optional_key] = openai_payload[optional_key]
|
||||||
|
|
||||||
return ollama_payload
|
return ollama_payload
|
||||||
|
|
||||||
|
|||||||
@@ -151,7 +151,6 @@ def resolve_valves_schema_options(
|
|||||||
return schema
|
return schema
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def extract_frontmatter(content):
|
def extract_frontmatter(content):
|
||||||
"""
|
"""
|
||||||
Extract frontmatter as a dictionary from the provided content string.
|
Extract frontmatter as a dictionary from the provided content string.
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ import re
|
|||||||
|
|
||||||
# ANSI escape code pattern - matches all common ANSI sequences
|
# ANSI escape code pattern - matches all common ANSI sequences
|
||||||
# This includes color codes, cursor movement, and other terminal control 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:
|
def strip_ansi_codes(text: str) -> str:
|
||||||
@@ -18,7 +20,7 @@ def strip_ansi_codes(text: str) -> str:
|
|||||||
- Reset codes: \x1b[0m, \x1b[39m
|
- Reset codes: \x1b[0m, \x1b[39m
|
||||||
- Cursor movement: \x1b[1A, \x1b[2J, etc.
|
- 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:
|
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_ansi_codes(code)
|
||||||
code = strip_markdown_code_fences(code)
|
code = strip_markdown_code_fences(code)
|
||||||
return code
|
return code
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
from open_webui.config import DEFAULT_RAG_TEMPLATE
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ from fastapi import status
|
|||||||
|
|
||||||
from open_webui.utils.telemetry.constants import SPAN_REDIS_TYPE, SpanAttributes
|
from open_webui.utils.telemetry.constants import SPAN_REDIS_TYPE, SpanAttributes
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
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)
|
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
|
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.
|
# Instead of checking the type, we check if the instance has a nodes_manager attribute.
|
||||||
try:
|
try:
|
||||||
db = ""
|
db = ""
|
||||||
if hasattr(instance, 'nodes_manager'):
|
if hasattr(instance, "nodes_manager"):
|
||||||
default_node = instance.nodes_manager.default_node
|
default_node = instance.nodes_manager.default_node
|
||||||
if not default_node:
|
if not default_node:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -352,7 +352,9 @@ async def get_tools(
|
|||||||
headers = include_user_info_headers(headers, user)
|
headers = include_user_info_headers(headers, user)
|
||||||
metadata = extra_params.get("__metadata__", {})
|
metadata = extra_params.get("__metadata__", {})
|
||||||
if metadata and metadata.get("chat_id"):
|
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(
|
def make_tool_function(
|
||||||
function_name, tool_server_data, headers
|
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)
|
# Helper to get model capabilities (defaults to True if not specified)
|
||||||
def get_model_capability(name: str, default: bool = True) -> bool:
|
def get_model_capability(name: str, default: bool = True) -> bool:
|
||||||
return (
|
return (model.get("info", {}).get("meta", {}).get("capabilities") or {}).get(
|
||||||
(model.get("info", {}).get("meta", {}).get("capabilities") or {})
|
name, default
|
||||||
.get(name, default)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Helper to check if a builtin tool category is enabled via meta.builtinTools
|
# 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)
|
builtin_functions.append(execute_code)
|
||||||
|
|
||||||
# Notes tools - search, view, create, and update user's notes (if builtin category enabled AND notes enabled globally)
|
# 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(
|
builtin_functions.extend(
|
||||||
[search_notes, view_note, write_note, replace_note_content]
|
[search_notes, view_note, write_note, replace_note_content]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Channels tools - search channels and messages (if builtin category enabled AND channels enabled globally)
|
# 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(
|
builtin_functions.extend(
|
||||||
[
|
[
|
||||||
search_channels,
|
search_channels,
|
||||||
|
|||||||
@@ -36,4 +36,3 @@ def validate_profile_image_url(url: str) -> str:
|
|||||||
raise ValueError(
|
raise ValueError(
|
||||||
"Invalid profile image URL: only data URIs and default avatars are allowed."
|
"Invalid profile image URL: only data URIs and default avatars are allowed."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+256
-254
@@ -1,317 +1,319 @@
|
|||||||
import { WEBUI_API_BASE_URL } from '$lib/constants';
|
import { WEBUI_API_BASE_URL } from '$lib/constants';
|
||||||
|
|
||||||
export const getModelAnalytics = async (
|
export const getModelAnalytics = async (
|
||||||
token: string = '',
|
token: string = '',
|
||||||
startDate: number | null = null,
|
startDate: number | null = null,
|
||||||
endDate: number | null = null,
|
endDate: number | null = null,
|
||||||
groupId: string | null = null
|
groupId: string | null = null
|
||||||
) => {
|
) => {
|
||||||
let error = null;
|
let error = null;
|
||||||
|
|
||||||
const searchParams = new URLSearchParams();
|
const searchParams = new URLSearchParams();
|
||||||
if (startDate) searchParams.append('start_date', startDate.toString());
|
if (startDate) searchParams.append('start_date', startDate.toString());
|
||||||
if (endDate) searchParams.append('end_date', endDate.toString());
|
if (endDate) searchParams.append('end_date', endDate.toString());
|
||||||
if (groupId) searchParams.append('group_id', groupId);
|
if (groupId) searchParams.append('group_id', groupId);
|
||||||
|
|
||||||
const res = await fetch(`${WEBUI_API_BASE_URL}/analytics/models?${searchParams.toString()}`, {
|
const res = await fetch(`${WEBUI_API_BASE_URL}/analytics/models?${searchParams.toString()}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
Accept: 'application/json',
|
Accept: 'application/json',
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
authorization: `Bearer ${token}`
|
authorization: `Bearer ${token}`
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(async (res) => {
|
.then(async (res) => {
|
||||||
if (!res.ok) throw await res.json();
|
if (!res.ok) throw await res.json();
|
||||||
return res.json();
|
return res.json();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
error = err.detail;
|
error = err.detail;
|
||||||
console.error(err);
|
console.error(err);
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getUserAnalytics = async (
|
export const getUserAnalytics = async (
|
||||||
token: string = '',
|
token: string = '',
|
||||||
startDate: number | null = null,
|
startDate: number | null = null,
|
||||||
endDate: number | null = null,
|
endDate: number | null = null,
|
||||||
limit: number = 50,
|
limit: number = 50,
|
||||||
groupId: string | null = null
|
groupId: string | null = null
|
||||||
) => {
|
) => {
|
||||||
let error = null;
|
let error = null;
|
||||||
|
|
||||||
const searchParams = new URLSearchParams();
|
const searchParams = new URLSearchParams();
|
||||||
if (startDate) searchParams.append('start_date', startDate.toString());
|
if (startDate) searchParams.append('start_date', startDate.toString());
|
||||||
if (endDate) searchParams.append('end_date', endDate.toString());
|
if (endDate) searchParams.append('end_date', endDate.toString());
|
||||||
if (limit) searchParams.append('limit', limit.toString());
|
if (limit) searchParams.append('limit', limit.toString());
|
||||||
if (groupId) searchParams.append('group_id', groupId);
|
if (groupId) searchParams.append('group_id', groupId);
|
||||||
|
|
||||||
const res = await fetch(`${WEBUI_API_BASE_URL}/analytics/users?${searchParams.toString()}`, {
|
const res = await fetch(`${WEBUI_API_BASE_URL}/analytics/users?${searchParams.toString()}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
Accept: 'application/json',
|
Accept: 'application/json',
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
authorization: `Bearer ${token}`
|
authorization: `Bearer ${token}`
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(async (res) => {
|
.then(async (res) => {
|
||||||
if (!res.ok) throw await res.json();
|
if (!res.ok) throw await res.json();
|
||||||
return res.json();
|
return res.json();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
error = err.detail;
|
error = err.detail;
|
||||||
console.error(err);
|
console.error(err);
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getMessages = async (
|
export const getMessages = async (
|
||||||
token: string = '',
|
token: string = '',
|
||||||
modelId: string | null = null,
|
modelId: string | null = null,
|
||||||
userId: string | null = null,
|
userId: string | null = null,
|
||||||
chatId: string | null = null,
|
chatId: string | null = null,
|
||||||
startDate: number | null = null,
|
startDate: number | null = null,
|
||||||
endDate: number | null = null,
|
endDate: number | null = null,
|
||||||
skip: number = 0,
|
skip: number = 0,
|
||||||
limit: number = 50
|
limit: number = 50
|
||||||
) => {
|
) => {
|
||||||
let error = null;
|
let error = null;
|
||||||
|
|
||||||
const searchParams = new URLSearchParams();
|
const searchParams = new URLSearchParams();
|
||||||
if (modelId) searchParams.append('model_id', modelId);
|
if (modelId) searchParams.append('model_id', modelId);
|
||||||
if (userId) searchParams.append('user_id', userId);
|
if (userId) searchParams.append('user_id', userId);
|
||||||
if (chatId) searchParams.append('chat_id', chatId);
|
if (chatId) searchParams.append('chat_id', chatId);
|
||||||
if (startDate) searchParams.append('start_date', startDate.toString());
|
if (startDate) searchParams.append('start_date', startDate.toString());
|
||||||
if (endDate) searchParams.append('end_date', endDate.toString());
|
if (endDate) searchParams.append('end_date', endDate.toString());
|
||||||
if (skip) searchParams.append('skip', skip.toString());
|
if (skip) searchParams.append('skip', skip.toString());
|
||||||
if (limit) searchParams.append('limit', limit.toString());
|
if (limit) searchParams.append('limit', limit.toString());
|
||||||
|
|
||||||
const res = await fetch(`${WEBUI_API_BASE_URL}/analytics/messages?${searchParams.toString()}`, {
|
const res = await fetch(`${WEBUI_API_BASE_URL}/analytics/messages?${searchParams.toString()}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
Accept: 'application/json',
|
Accept: 'application/json',
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
authorization: `Bearer ${token}`
|
authorization: `Bearer ${token}`
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(async (res) => {
|
.then(async (res) => {
|
||||||
if (!res.ok) throw await res.json();
|
if (!res.ok) throw await res.json();
|
||||||
return res.json();
|
return res.json();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
error = err.detail;
|
error = err.detail;
|
||||||
console.error(err);
|
console.error(err);
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSummary = async (
|
export const getSummary = async (
|
||||||
token: string = '',
|
token: string = '',
|
||||||
startDate: number | null = null,
|
startDate: number | null = null,
|
||||||
endDate: number | null = null,
|
endDate: number | null = null,
|
||||||
groupId: string | null = null
|
groupId: string | null = null
|
||||||
) => {
|
) => {
|
||||||
let error = null;
|
let error = null;
|
||||||
|
|
||||||
const searchParams = new URLSearchParams();
|
const searchParams = new URLSearchParams();
|
||||||
if (startDate) searchParams.append('start_date', startDate.toString());
|
if (startDate) searchParams.append('start_date', startDate.toString());
|
||||||
if (endDate) searchParams.append('end_date', endDate.toString());
|
if (endDate) searchParams.append('end_date', endDate.toString());
|
||||||
if (groupId) searchParams.append('group_id', groupId);
|
if (groupId) searchParams.append('group_id', groupId);
|
||||||
|
|
||||||
const res = await fetch(`${WEBUI_API_BASE_URL}/analytics/summary?${searchParams.toString()}`, {
|
const res = await fetch(`${WEBUI_API_BASE_URL}/analytics/summary?${searchParams.toString()}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
Accept: 'application/json',
|
Accept: 'application/json',
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
authorization: `Bearer ${token}`
|
authorization: `Bearer ${token}`
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(async (res) => {
|
.then(async (res) => {
|
||||||
if (!res.ok) throw await res.json();
|
if (!res.ok) throw await res.json();
|
||||||
return res.json();
|
return res.json();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
error = err.detail;
|
error = err.detail;
|
||||||
console.error(err);
|
console.error(err);
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDailyStats = async (
|
export const getDailyStats = async (
|
||||||
token: string = '',
|
token: string = '',
|
||||||
startDate: number | null = null,
|
startDate: number | null = null,
|
||||||
endDate: number | null = null,
|
endDate: number | null = null,
|
||||||
granularity: 'hourly' | 'daily' = 'daily',
|
granularity: 'hourly' | 'daily' = 'daily',
|
||||||
groupId: string | null = null
|
groupId: string | null = null
|
||||||
) => {
|
) => {
|
||||||
let error = null;
|
let error = null;
|
||||||
|
|
||||||
const searchParams = new URLSearchParams();
|
const searchParams = new URLSearchParams();
|
||||||
if (startDate) searchParams.append('start_date', startDate.toString());
|
if (startDate) searchParams.append('start_date', startDate.toString());
|
||||||
if (endDate) searchParams.append('end_date', endDate.toString());
|
if (endDate) searchParams.append('end_date', endDate.toString());
|
||||||
searchParams.append('granularity', granularity);
|
searchParams.append('granularity', granularity);
|
||||||
if (groupId) searchParams.append('group_id', groupId);
|
if (groupId) searchParams.append('group_id', groupId);
|
||||||
|
|
||||||
const res = await fetch(`${WEBUI_API_BASE_URL}/analytics/daily?${searchParams.toString()}`, {
|
const res = await fetch(`${WEBUI_API_BASE_URL}/analytics/daily?${searchParams.toString()}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
Accept: 'application/json',
|
Accept: 'application/json',
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
authorization: `Bearer ${token}`
|
authorization: `Bearer ${token}`
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(async (res) => {
|
.then(async (res) => {
|
||||||
if (!res.ok) throw await res.json();
|
if (!res.ok) throw await res.json();
|
||||||
return res.json();
|
return res.json();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
error = err.detail;
|
error = err.detail;
|
||||||
console.error(err);
|
console.error(err);
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getTokenUsage = async (
|
export const getTokenUsage = async (
|
||||||
token: string = '',
|
token: string = '',
|
||||||
startDate: number | null = null,
|
startDate: number | null = null,
|
||||||
endDate: number | null = null,
|
endDate: number | null = null,
|
||||||
groupId: string | null = null
|
groupId: string | null = null
|
||||||
) => {
|
) => {
|
||||||
let error = null;
|
let error = null;
|
||||||
|
|
||||||
const searchParams = new URLSearchParams();
|
const searchParams = new URLSearchParams();
|
||||||
if (startDate) searchParams.append('start_date', startDate.toString());
|
if (startDate) searchParams.append('start_date', startDate.toString());
|
||||||
if (endDate) searchParams.append('end_date', endDate.toString());
|
if (endDate) searchParams.append('end_date', endDate.toString());
|
||||||
if (groupId) searchParams.append('group_id', groupId);
|
if (groupId) searchParams.append('group_id', groupId);
|
||||||
|
|
||||||
const res = await fetch(`${WEBUI_API_BASE_URL}/analytics/tokens?${searchParams.toString()}`, {
|
const res = await fetch(`${WEBUI_API_BASE_URL}/analytics/tokens?${searchParams.toString()}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
Accept: 'application/json',
|
Accept: 'application/json',
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
authorization: `Bearer ${token}`
|
authorization: `Bearer ${token}`
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(async (res) => {
|
.then(async (res) => {
|
||||||
if (!res.ok) throw await res.json();
|
if (!res.ok) throw await res.json();
|
||||||
return res.json();
|
return res.json();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
error = err.detail;
|
error = err.detail;
|
||||||
console.error(err);
|
console.error(err);
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getModelChats = async (
|
export const getModelChats = async (
|
||||||
token: string = '',
|
token: string = '',
|
||||||
modelId: string,
|
modelId: string,
|
||||||
startDate: number | null = null,
|
startDate: number | null = null,
|
||||||
endDate: number | null = null,
|
endDate: number | null = null,
|
||||||
skip: number = 0,
|
skip: number = 0,
|
||||||
limit: number = 50
|
limit: number = 50
|
||||||
) => {
|
) => {
|
||||||
let error = null;
|
let error = null;
|
||||||
|
|
||||||
const searchParams = new URLSearchParams();
|
const searchParams = new URLSearchParams();
|
||||||
if (startDate) searchParams.append('start_date', startDate.toString());
|
if (startDate) searchParams.append('start_date', startDate.toString());
|
||||||
if (endDate) searchParams.append('end_date', endDate.toString());
|
if (endDate) searchParams.append('end_date', endDate.toString());
|
||||||
if (skip) searchParams.append('skip', skip.toString());
|
if (skip) searchParams.append('skip', skip.toString());
|
||||||
if (limit) searchParams.append('limit', limit.toString());
|
if (limit) searchParams.append('limit', limit.toString());
|
||||||
|
|
||||||
const res = await fetch(`${WEBUI_API_BASE_URL}/analytics/models/${encodeURIComponent(modelId)}/chats?${searchParams.toString()}`, {
|
const res = await fetch(
|
||||||
method: 'GET',
|
`${WEBUI_API_BASE_URL}/analytics/models/${encodeURIComponent(modelId)}/chats?${searchParams.toString()}`,
|
||||||
headers: {
|
{
|
||||||
Accept: 'application/json',
|
method: 'GET',
|
||||||
'Content-Type': 'application/json',
|
headers: {
|
||||||
authorization: `Bearer ${token}`
|
Accept: 'application/json',
|
||||||
}
|
'Content-Type': 'application/json',
|
||||||
})
|
authorization: `Bearer ${token}`
|
||||||
.then(async (res) => {
|
}
|
||||||
if (!res.ok) throw await res.json();
|
}
|
||||||
return res.json();
|
)
|
||||||
})
|
.then(async (res) => {
|
||||||
.catch((err) => {
|
if (!res.ok) throw await res.json();
|
||||||
error = err.detail;
|
return res.json();
|
||||||
console.error(err);
|
})
|
||||||
return null;
|
.catch((err) => {
|
||||||
});
|
error = err.detail;
|
||||||
|
console.error(err);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getModelOverview = async (
|
export const getModelOverview = async (token: string = '', modelId: string, days: number = 30) => {
|
||||||
token: string = '',
|
let error = null;
|
||||||
modelId: string,
|
|
||||||
days: number = 30
|
|
||||||
) => {
|
|
||||||
let error = null;
|
|
||||||
|
|
||||||
const searchParams = new URLSearchParams();
|
const searchParams = new URLSearchParams();
|
||||||
searchParams.append('days', days.toString());
|
searchParams.append('days', days.toString());
|
||||||
|
|
||||||
const res = await fetch(`${WEBUI_API_BASE_URL}/analytics/models/${encodeURIComponent(modelId)}/overview?${searchParams.toString()}`, {
|
const res = await fetch(
|
||||||
method: 'GET',
|
`${WEBUI_API_BASE_URL}/analytics/models/${encodeURIComponent(modelId)}/overview?${searchParams.toString()}`,
|
||||||
headers: {
|
{
|
||||||
Accept: 'application/json',
|
method: 'GET',
|
||||||
'Content-Type': 'application/json',
|
headers: {
|
||||||
authorization: `Bearer ${token}`
|
Accept: 'application/json',
|
||||||
}
|
'Content-Type': 'application/json',
|
||||||
})
|
authorization: `Bearer ${token}`
|
||||||
.then(async (res) => {
|
}
|
||||||
if (!res.ok) throw await res.json();
|
}
|
||||||
return res.json();
|
)
|
||||||
})
|
.then(async (res) => {
|
||||||
.catch((err) => {
|
if (!res.ok) throw await res.json();
|
||||||
error = err.detail;
|
return res.json();
|
||||||
console.error(err);
|
})
|
||||||
return null;
|
.catch((err) => {
|
||||||
});
|
error = err.detail;
|
||||||
|
console.error(err);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -255,11 +255,7 @@ export const getArchivedChatList = async (
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSharedChatList = async (
|
export const getSharedChatList = async (token: string = '', page: number = 1, filter?: object) => {
|
||||||
token: string = '',
|
|
||||||
page: number = 1,
|
|
||||||
filter?: object
|
|
||||||
) => {
|
|
||||||
let error = null;
|
let error = null;
|
||||||
|
|
||||||
const searchParams = new URLSearchParams();
|
const searchParams = new URLSearchParams();
|
||||||
|
|||||||
@@ -1675,7 +1675,7 @@ export interface ModelMeta {
|
|||||||
profile_image_url?: string;
|
profile_image_url?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ModelParams { }
|
export interface ModelParams {}
|
||||||
|
|
||||||
export type GlobalModelConfig = ModelConfig[];
|
export type GlobalModelConfig = ModelConfig[];
|
||||||
|
|
||||||
|
|||||||
@@ -281,11 +281,7 @@ export const updateModelById = async (token: string, id: string, model: object)
|
|||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateModelAccessGrants = async (
|
export const updateModelAccessGrants = async (token: string, id: string, accessGrants: any[]) => {
|
||||||
token: string,
|
|
||||||
id: string,
|
|
||||||
accessGrants: any[]
|
|
||||||
) => {
|
|
||||||
let error = null;
|
let error = null;
|
||||||
|
|
||||||
const res = await fetch(`${WEBUI_API_BASE_URL}/models/model/access/update`, {
|
const res = await fetch(`${WEBUI_API_BASE_URL}/models/model/access/update`, {
|
||||||
|
|||||||
@@ -253,11 +253,7 @@ export const updateNoteById = async (token: string, id: string, note: NoteItem)
|
|||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateNoteAccessGrants = async (
|
export const updateNoteAccessGrants = async (token: string, id: string, accessGrants: any[]) => {
|
||||||
token: string,
|
|
||||||
id: string,
|
|
||||||
accessGrants: any[]
|
|
||||||
) => {
|
|
||||||
let error = null;
|
let error = null;
|
||||||
|
|
||||||
const res = await fetch(`${WEBUI_API_BASE_URL}/notes/${id}/access/update`, {
|
const res = await fetch(`${WEBUI_API_BASE_URL}/notes/${id}/access/update`, {
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { WEBUI_API_BASE_URL } from '$lib/constants';
|
import { WEBUI_API_BASE_URL } from '$lib/constants';
|
||||||
|
|
||||||
type PromptItem = {
|
type PromptItem = {
|
||||||
id?: string; // Prompt ID
|
id?: string; // Prompt ID
|
||||||
command: string;
|
command: string;
|
||||||
name: string; // Changed from title
|
name: string; // Changed from title
|
||||||
content: string;
|
content: string;
|
||||||
data?: object | null;
|
data?: object | null;
|
||||||
meta?: object | null;
|
meta?: object | null;
|
||||||
access_grants?: object[];
|
access_grants?: object[];
|
||||||
version_id?: string | null; // Active version
|
version_id?: string | null; // Active version
|
||||||
commit_message?: string | null; // For history tracking
|
commit_message?: string | null; // For history tracking
|
||||||
is_production?: boolean; // Whether to set new version as production
|
is_production?: boolean; // Whether to set new version as production
|
||||||
};
|
};
|
||||||
|
|
||||||
type PromptHistoryItem = {
|
type PromptHistoryItem = {
|
||||||
@@ -196,7 +196,6 @@ export const getPromptItems = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getPromptList = async (token: string = '') => {
|
export const getPromptList = async (token: string = '') => {
|
||||||
|
|
||||||
let error = null;
|
let error = null;
|
||||||
|
|
||||||
const res = await fetch(`${WEBUI_API_BASE_URL}/prompts/list`, {
|
const res = await fetch(`${WEBUI_API_BASE_URL}/prompts/list`, {
|
||||||
@@ -472,17 +471,14 @@ export const getPromptHistory = async (
|
|||||||
): Promise<PromptHistoryItem[]> => {
|
): Promise<PromptHistoryItem[]> => {
|
||||||
let error = null;
|
let error = null;
|
||||||
|
|
||||||
const res = await fetch(
|
const res = await fetch(`${WEBUI_API_BASE_URL}/prompts/id/${promptId}/history?page=${page}`, {
|
||||||
`${WEBUI_API_BASE_URL}/prompts/id/${promptId}/history?page=${page}`,
|
method: 'GET',
|
||||||
{
|
headers: {
|
||||||
method: 'GET',
|
Accept: 'application/json',
|
||||||
headers: {
|
'Content-Type': 'application/json',
|
||||||
Accept: 'application/json',
|
authorization: `Bearer ${token}`
|
||||||
'Content-Type': 'application/json',
|
|
||||||
authorization: `Bearer ${token}`
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
})
|
||||||
.then(async (res) => {
|
.then(async (res) => {
|
||||||
if (!res.ok) throw await res.json();
|
if (!res.ok) throw await res.json();
|
||||||
return res.json();
|
return res.json();
|
||||||
@@ -507,17 +503,14 @@ export const deletePromptHistoryVersion = async (
|
|||||||
): Promise<boolean> => {
|
): Promise<boolean> => {
|
||||||
let error = null;
|
let error = null;
|
||||||
|
|
||||||
const res = await fetch(
|
const res = await fetch(`${WEBUI_API_BASE_URL}/prompts/id/${promptId}/history/${historyId}`, {
|
||||||
`${WEBUI_API_BASE_URL}/prompts/id/${promptId}/history/${historyId}`,
|
method: 'DELETE',
|
||||||
{
|
headers: {
|
||||||
method: 'DELETE',
|
Accept: 'application/json',
|
||||||
headers: {
|
'Content-Type': 'application/json',
|
||||||
Accept: 'application/json',
|
authorization: `Bearer ${token}`
|
||||||
'Content-Type': 'application/json',
|
|
||||||
authorization: `Bearer ${token}`
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
})
|
||||||
.then(async (res) => {
|
.then(async (res) => {
|
||||||
if (!res.ok) throw await res.json();
|
if (!res.ok) throw await res.json();
|
||||||
return res.json();
|
return res.json();
|
||||||
@@ -542,17 +535,14 @@ export const getPromptHistoryEntry = async (
|
|||||||
): Promise<PromptHistoryItem> => {
|
): Promise<PromptHistoryItem> => {
|
||||||
let error = null;
|
let error = null;
|
||||||
|
|
||||||
const res = await fetch(
|
const res = await fetch(`${WEBUI_API_BASE_URL}/prompts/id/${promptId}/history/${historyId}`, {
|
||||||
`${WEBUI_API_BASE_URL}/prompts/id/${promptId}/history/${historyId}`,
|
method: 'GET',
|
||||||
{
|
headers: {
|
||||||
method: 'GET',
|
Accept: 'application/json',
|
||||||
headers: {
|
'Content-Type': 'application/json',
|
||||||
Accept: 'application/json',
|
authorization: `Bearer ${token}`
|
||||||
'Content-Type': 'application/json',
|
|
||||||
authorization: `Bearer ${token}`
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
})
|
||||||
.then(async (res) => {
|
.then(async (res) => {
|
||||||
if (!res.ok) throw await res.json();
|
if (!res.ok) throw await res.json();
|
||||||
return res.json();
|
return res.json();
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user