diff --git a/backend/open_webui/env.py b/backend/open_webui/env.py index 6aa1181c1..e7f31298a 100644 --- a/backend/open_webui/env.py +++ b/backend/open_webui/env.py @@ -544,6 +544,9 @@ ENABLE_OTEL_METRICS = os.environ.get("ENABLE_OTEL_METRICS", "False").lower() == OTEL_EXPORTER_OTLP_ENDPOINT = os.environ.get( "OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317" ) +OTEL_EXPORTER_OTLP_INSECURE = ( + os.environ.get("OTEL_EXPORTER_OTLP_INSECURE", "False").lower() == "true" +) OTEL_SERVICE_NAME = os.environ.get("OTEL_SERVICE_NAME", "open-webui") OTEL_RESOURCE_ATTRIBUTES = os.environ.get( "OTEL_RESOURCE_ATTRIBUTES", "" @@ -551,6 +554,8 @@ OTEL_RESOURCE_ATTRIBUTES = os.environ.get( OTEL_TRACES_SAMPLER = os.environ.get( "OTEL_TRACES_SAMPLER", "parentbased_always_on" ).lower() +OTEL_BASIC_AUTH_USERNAME = os.environ.get("OTEL_BASIC_AUTH_USERNAME", "") +OTEL_BASIC_AUTH_PASSWORD = os.environ.get("OTEL_BASIC_AUTH_PASSWORD", "") #################################### # TOOLS/FUNCTIONS PIP OPTIONS diff --git a/backend/open_webui/retrieval/utils.py b/backend/open_webui/retrieval/utils.py index 683f42819..00dd68306 100644 --- a/backend/open_webui/retrieval/utils.py +++ b/backend/open_webui/retrieval/utils.py @@ -7,6 +7,7 @@ import hashlib from concurrent.futures import ThreadPoolExecutor import time +from urllib.parse import quote from huggingface_hub import snapshot_download from langchain.retrievers import ContextualCompressionRetriever, EnsembleRetriever from langchain_community.retrievers import BM25Retriever @@ -678,10 +679,10 @@ def generate_openai_batch_embeddings( "Authorization": f"Bearer {key}", **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS and user else {} @@ -727,10 +728,10 @@ def generate_azure_openai_batch_embeddings( "api-key": key, **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS and user else {} @@ -777,10 +778,10 @@ def generate_ollama_batch_embeddings( "Authorization": f"Bearer {key}", **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS else {} diff --git a/backend/open_webui/routers/audio.py b/backend/open_webui/routers/audio.py index 27634cec1..211f2ae85 100644 --- a/backend/open_webui/routers/audio.py +++ b/backend/open_webui/routers/audio.py @@ -15,6 +15,7 @@ import aiohttp import aiofiles import requests import mimetypes +from urllib.parse import quote from fastapi import ( Depends, @@ -343,10 +344,10 @@ async def speech(request: Request, user=Depends(get_verified_user)): "Authorization": f"Bearer {request.app.state.config.TTS_OPENAI_API_KEY}", **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS else {} @@ -919,14 +920,18 @@ def transcription( ): log.info(f"file.content_type: {file.content_type}") - supported_content_types = request.app.state.config.STT_SUPPORTED_CONTENT_TYPES or [ - "audio/*", - "video/webm", - ] + stt_supported_content_types = getattr( + request.app.state.config, "STT_SUPPORTED_CONTENT_TYPES", [] + ) if not any( fnmatch(file.content_type, content_type) - for content_type in supported_content_types + for content_type in ( + stt_supported_content_types + if stt_supported_content_types + and any(t.strip() for t in stt_supported_content_types) + else ["audio/*", "video/webm"] + ) ): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, diff --git a/backend/open_webui/routers/files.py b/backend/open_webui/routers/files.py index b9bb15c7b..bdf5780fc 100644 --- a/backend/open_webui/routers/files.py +++ b/backend/open_webui/routers/files.py @@ -155,17 +155,18 @@ def upload_file( if process: try: if file.content_type: - stt_supported_content_types = ( - request.app.state.config.STT_SUPPORTED_CONTENT_TYPES - or [ - "audio/*", - "video/webm", - ] + stt_supported_content_types = getattr( + request.app.state.config, "STT_SUPPORTED_CONTENT_TYPES", [] ) if any( fnmatch(file.content_type, content_type) - for content_type in stt_supported_content_types + for content_type in ( + stt_supported_content_types + if stt_supported_content_types + and any(t.strip() for t in stt_supported_content_types) + else ["audio/*", "video/webm"] + ) ): file_path = Storage.get_file(file_path) result = transcribe(request, file_path, file_metadata) diff --git a/backend/open_webui/routers/images.py b/backend/open_webui/routers/images.py index 52686a584..ca949fd93 100644 --- a/backend/open_webui/routers/images.py +++ b/backend/open_webui/routers/images.py @@ -8,6 +8,7 @@ import re from pathlib import Path from typing import Optional +from urllib.parse import quote import requests from fastapi import APIRouter, Depends, HTTPException, Request, UploadFile from open_webui.config import CACHE_DIR @@ -483,10 +484,10 @@ async def image_generations( headers["Content-Type"] = "application/json" if ENABLE_FORWARD_USER_INFO_HEADERS: - headers["X-OpenWebUI-User-Name"] = user.name - headers["X-OpenWebUI-User-Id"] = user.id - headers["X-OpenWebUI-User-Email"] = user.email - headers["X-OpenWebUI-User-Role"] = user.role + headers["X-OpenWebUI-User-Name"] = quote(user.name) + headers["X-OpenWebUI-User-Id"] = quote(user.id) + headers["X-OpenWebUI-User-Email"] = quote(user.email) + headers["X-OpenWebUI-User-Role"] = quote(user.role) data = { "model": ( diff --git a/backend/open_webui/routers/ollama.py b/backend/open_webui/routers/ollama.py index 135359937..9c1e1fdb0 100644 --- a/backend/open_webui/routers/ollama.py +++ b/backend/open_webui/routers/ollama.py @@ -16,6 +16,7 @@ from urllib.parse import urlparse import aiohttp from aiocache import cached import requests +from urllib.parse import quote from open_webui.models.chats import Chats from open_webui.models.users import UserModel @@ -87,10 +88,10 @@ async def send_get_request(url, key=None, user: UserModel = None): **({"Authorization": f"Bearer {key}"} if key else {}), **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS and user else {} @@ -138,10 +139,10 @@ async def send_post_request( **({"Authorization": f"Bearer {key}"} if key else {}), **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS and user else {} @@ -242,10 +243,10 @@ async def verify_connection( **({"Authorization": f"Bearer {key}"} if key else {}), **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS and user else {} @@ -462,10 +463,10 @@ async def get_ollama_tags( **({"Authorization": f"Bearer {key}"} if key else {}), **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS and user else {} @@ -824,10 +825,10 @@ async def copy_model( **({"Authorization": f"Bearer {key}"} if key else {}), **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS and user else {} @@ -890,10 +891,10 @@ async def delete_model( **({"Authorization": f"Bearer {key}"} if key else {}), **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS and user else {} @@ -949,10 +950,10 @@ async def show_model_info( **({"Authorization": f"Bearer {key}"} if key else {}), **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS and user else {} @@ -1036,10 +1037,10 @@ async def embed( **({"Authorization": f"Bearer {key}"} if key else {}), **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS and user else {} @@ -1123,10 +1124,10 @@ async def embeddings( **({"Authorization": f"Bearer {key}"} if key else {}), **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS and user else {} diff --git a/backend/open_webui/routers/openai.py b/backend/open_webui/routers/openai.py index 7649271fe..e3210ae5f 100644 --- a/backend/open_webui/routers/openai.py +++ b/backend/open_webui/routers/openai.py @@ -8,7 +8,7 @@ from typing import Literal, Optional, overload import aiohttp from aiocache import cached import requests - +from urllib.parse import quote from fastapi import Depends, FastAPI, HTTPException, Request, APIRouter from fastapi.middleware.cors import CORSMiddleware @@ -66,10 +66,10 @@ async def send_get_request(url, key=None, user: UserModel = None): **({"Authorization": f"Bearer {key}"} if key else {}), **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS and user else {} @@ -225,10 +225,10 @@ async def speech(request: Request, user=Depends(get_verified_user)): ), **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS else {} @@ -478,10 +478,10 @@ async def get_models( "Content-Type": "application/json", **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS else {} @@ -573,10 +573,10 @@ async def verify_connection( "Content-Type": "application/json", **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS else {} @@ -806,10 +806,10 @@ async def generate_chat_completion( ), **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS else {} @@ -924,10 +924,10 @@ async def embeddings(request: Request, form_data: dict, user): "Content-Type": "application/json", **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS and user else {} @@ -996,10 +996,10 @@ async def proxy(path: str, request: Request, user=Depends(get_verified_user)): "Content-Type": "application/json", **( { - "X-OpenWebUI-User-Name": user.name, - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, + "X-OpenWebUI-User-Name": quote(user.name), + "X-OpenWebUI-User-Id": quote(user.id), + "X-OpenWebUI-User-Email": quote(user.email), + "X-OpenWebUI-User-Role": quote(user.role), } if ENABLE_FORWARD_USER_INFO_HEADERS else {} diff --git a/backend/open_webui/utils/chat.py b/backend/open_webui/utils/chat.py index 268c910e3..83483f391 100644 --- a/backend/open_webui/utils/chat.py +++ b/backend/open_webui/utils/chat.py @@ -419,7 +419,7 @@ async def chat_action(request: Request, action_id: str, form_data: dict, user: A params[key] = value if "__user__" in sig.parameters: - __user__ = (user.model_dump() if isinstance(user, UserModel) else {},) + __user__ = user.model_dump() if isinstance(user, UserModel) else {} try: if hasattr(function_module, "UserValves"): diff --git a/backend/open_webui/utils/telemetry/setup.py b/backend/open_webui/utils/telemetry/setup.py index 23dbc4683..e523cf853 100644 --- a/backend/open_webui/utils/telemetry/setup.py +++ b/backend/open_webui/utils/telemetry/setup.py @@ -5,6 +5,7 @@ from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExport from opentelemetry.sdk.resources import SERVICE_NAME, Resource from opentelemetry.sdk.trace import TracerProvider from sqlalchemy import Engine +from base64 import b64encode from open_webui.utils.telemetry.exporters import LazyBatchSpanProcessor from open_webui.utils.telemetry.instrumentors import Instrumentor @@ -12,8 +13,11 @@ from open_webui.utils.telemetry.metrics import setup_metrics from open_webui.env import ( OTEL_SERVICE_NAME, OTEL_EXPORTER_OTLP_ENDPOINT, + OTEL_EXPORTER_OTLP_INSECURE, ENABLE_OTEL_METRICS, - USE_OTEL_HTTP_EXPORTER + USE_OTEL_HTTP_EXPORTER, + OTEL_BASIC_AUTH_USERNAME, + OTEL_BASIC_AUTH_PASSWORD, ) @@ -24,8 +28,27 @@ def setup(app: FastAPI, db_engine: Engine): resource=Resource.create(attributes={SERVICE_NAME: OTEL_SERVICE_NAME}) ) ) + + # Add basic auth header only if both username and password are not empty + headers = [] + if OTEL_BASIC_AUTH_USERNAME and OTEL_BASIC_AUTH_PASSWORD: + auth_string = f"{OTEL_BASIC_AUTH_USERNAME}:{OTEL_BASIC_AUTH_PASSWORD}" + auth_header = b64encode(auth_string.encode()).decode() + headers = [("authorization", f"Basic {auth_header}")] + # otlp export - exporter = if USE_OTEL_HTTP_EXPORTER HttpOTLPSpanExporter(endpoint=OTEL_EXPORTER_OTLP_ENDPOINT) else OTLPSpanExporter(endpoint=OTEL_EXPORTER_OTLP_ENDPOINT) + if USE_OTEL_HTTP_EXPORTER: + exporter = OTLPSpanExporter( + endpoint=OTEL_EXPORTER_OTLP_ENDPOINT, + insecure=OTEL_EXPORTER_OTLP_INSECURE, + headers=headers, + ) + else: + exporter = OTLPSpanExporter( + endpoint=OTEL_EXPORTER_OTLP_ENDPOINT, + insecure=OTEL_EXPORTER_OTLP_INSECURE, + headers=headers, + ) trace.get_tracer_provider().add_span_processor(LazyBatchSpanProcessor(exporter)) Instrumentor(app=app, db_engine=db_engine).instrument() diff --git a/pyproject.toml b/pyproject.toml index 9a964a8c9..86693f192 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -139,7 +139,7 @@ requires-python = ">= 3.11, < 3.13.0a1" dynamic = ["version"] classifiers = [ "Development Status :: 4 - Beta", - "License :: OSI Approved :: MIT License", + "License :: Other/Proprietary License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", diff --git a/src/lib/apis/auths/index.ts b/src/lib/apis/auths/index.ts index 842edd9c9..0475df8d0 100644 --- a/src/lib/apis/auths/index.ts +++ b/src/lib/apis/auths/index.ts @@ -347,6 +347,8 @@ export const userSignOut = async () => { if (error) { throw error; } + + sessionStorage.clear(); return res; }; diff --git a/src/lib/apis/index.ts b/src/lib/apis/index.ts index 99b95b062..66572f20c 100644 --- a/src/lib/apis/index.ts +++ b/src/lib/apis/index.ts @@ -1587,12 +1587,13 @@ export interface ModelConfig { } export interface ModelMeta { + toolIds: never[]; description?: string; capabilities?: object; profile_image_url?: string; } -export interface ModelParams {} +export interface ModelParams { } export type GlobalModelConfig = ModelConfig[]; diff --git a/src/lib/components/AddServerModal.svelte b/src/lib/components/AddServerModal.svelte index 1c9ce46e2..fa3cab44c 100644 --- a/src/lib/components/AddServerModal.svelte +++ b/src/lib/components/AddServerModal.svelte @@ -3,6 +3,7 @@ import { getContext, onMount } from 'svelte'; const i18n = getContext('i18n'); + import { settings } from '$lib/stores'; import Modal from '$lib/components/common/Modal.svelte'; import Plus from '$lib/components/icons/Plus.svelte'; import Minus from '$lib/components/icons/Minus.svelte'; @@ -153,15 +154,16 @@
-
+

{#if edit} {$i18n.t('Edit Connection')} {:else} {$i18n.t('Add Connection')} {/if} -

+