From ef5374a34e0d76ef787593b192c15755be8d52f9 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Thu, 28 Aug 2025 14:46:47 +0400 Subject: [PATCH 001/154] typo --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 349b984e19..3a5663bb58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 🛂 **Granular Chat Interaction Permissions**: Added fine-grained permission controls for individual chat actions including "Continue Response", "Regenerate Response", "Rate Response", and "Delete Messages". Administrators can now configure these permissions per user group or set system defaults via environment variables, providing enhanced security and governance by preventing potential system prompt leakage through response continuation and enabling precise control over user interactions with AI responses. - 🧠 **Custom Reasoning Tags Configuration**: Added configurable reasoning tag detection for AI model responses, allowing administrators and users to customize how the system identifies and processes reasoning content. Users can now define custom reasoning tag pairs, use default tags like "think" and "reasoning", or disable reasoning detection entirely through the Advanced Parameters interface, providing enhanced control over AI thought process visibility. -- 📱 **Pull-to-Refresh SupportA**: Added pull-to-refresh functionality allowing user to easily refresh the interface by pulling down on the navbar area. This resolves timeout issues that occurred when temporarily switching away from the app during long AI response generations, eliminating the need to close and relaunch the PWA. +- 📱 **Pull-to-Refresh Support**: Added pull-to-refresh functionality allowing user to easily refresh the interface by pulling down on the navbar area. This resolves timeout issues that occurred when temporarily switching away from the app during long AI response generations, eliminating the need to close and relaunch the PWA. - 📁 **Configurable File Upload Processing Mode**: Added "process_in_background" query parameter to the file upload API endpoint, allowing clients to choose between asynchronous (default) and synchronous file processing. Setting "process_in_background=false" forces the upload request to wait until extraction and embedding complete, returning immediately usable files and simplifying integration for backend API consumers that prefer blocking calls over polling workflows. - 🔐 **Azure Document Intelligence DefaultAzureCredential Support**: Added support for authenticating with Azure Document Intelligence using DefaultAzureCredential in addition to API key authentication, enabling seamless integration with Azure Entra ID and managed identity authentication for enterprise Azure environments. - 🔐 **Authentication Bootstrapping Enhancements**: Added "ENABLE_INITIAL_ADMIN_SIGNUP" environment variable and "?form=true" URL parameter to enable initial admin user creation and forced login form display in SSO-only deployments. This resolves bootstrap issues where administrators couldn't create the first user when login forms were disabled, allowing proper initialization of SSO-configured deployments without requiring temporary configuration changes. From 0bca4e230ef276bec46889e3be036242ad11086f Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Thu, 28 Aug 2025 15:08:13 +0400 Subject: [PATCH 002/154] refac: rename tools to external tools for clarity --- src/lib/components/admin/Settings.svelte | 2 +- src/lib/components/chat/SettingsModal.svelte | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/components/admin/Settings.svelte b/src/lib/components/admin/Settings.svelte index d6a9e8a925..765a1d3ec9 100644 --- a/src/lib/components/admin/Settings.svelte +++ b/src/lib/components/admin/Settings.svelte @@ -204,7 +204,7 @@ /> -
{$i18n.t('Tools')}
+
{$i18n.t('External Tools')}
{/if} {:else if tabId === 'personalization'} From 898826dc22d90c4180ee466f378b6c3acefce72a Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Thu, 28 Aug 2025 19:16:50 +0400 Subject: [PATCH 003/154] Update dependabot.yml --- .github/dependabot.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ed93957ea4..1c83fd305b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -12,12 +12,6 @@ updates: interval: monthly target-branch: 'dev' - - package-ecosystem: npm - directory: '/' - schedule: - interval: monthly - target-branch: 'dev' - - package-ecosystem: 'github-actions' directory: '/' schedule: From be373e9fd42ac73b0302bdb487e16dbeae178b4e Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Thu, 28 Aug 2025 19:42:28 +0400 Subject: [PATCH 004/154] refac: dockerfile --- Dockerfile | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9c982e69e2..88afd66c38 100644 --- a/Dockerfile +++ b/Dockerfile @@ -123,7 +123,6 @@ RUN apt-get update && \ COPY --chown=$UID:$GID ./backend/requirements.txt ./requirements.txt RUN pip3 install --no-cache-dir uv && \ - if [ "$USE_SLIM" != "true" ]; then \ if [ "$USE_CUDA" = "true" ]; then \ # If you use CUDA the whisper and embedding model will be downloaded on first use pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/$USE_CUDA_DOCKER_VER --no-cache-dir && \ @@ -134,17 +133,17 @@ RUN pip3 install --no-cache-dir uv && \ else \ pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu --no-cache-dir && \ uv pip install --system -r requirements.txt --no-cache-dir && \ + if [ "$USE_SLIM" != "true" ]; then \ python -c "import os; from sentence_transformers import SentenceTransformer; SentenceTransformer(os.environ['RAG_EMBEDDING_MODEL'], device='cpu')" && \ python -c "import os; from faster_whisper import WhisperModel; WhisperModel(os.environ['WHISPER_MODEL'], device='cpu', compute_type='int8', download_root=os.environ['WHISPER_MODEL_DIR'])"; \ python -c "import os; import tiktoken; tiktoken.get_encoding(os.environ['TIKTOKEN_ENCODING_NAME'])"; \ fi; \ - else \ - uv pip install --system -r requirements.txt --no-cache-dir; \ fi; \ - mkdir -p /app/backend/data && chown -R $UID:$GID /app/backend/data/ + mkdir -p /app/backend/data && chown -R $UID:$GID /app/backend/data/ && \ + rm -rf /var/lib/apt/lists/*; # Install Ollama if requested -RUN if [ "$USE_OLLAMA" = "true" ] && [ "$USE_SLIM" != "true" ]; then \ +RUN if [ "$USE_OLLAMA" = "true" ]; then \ date +%s > /tmp/ollama_build_hash && \ echo "Cache broken at timestamp: `cat /tmp/ollama_build_hash`" && \ curl -fsSL https://ollama.com/install.sh | sh && \ From 0ebe4f8f8490451ac8e85a4846f010854d9b54e5 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Thu, 28 Aug 2025 20:19:47 +0400 Subject: [PATCH 005/154] refac: conditional USE_PERMISSION_HARDENING --- Dockerfile | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 88afd66c38..ad393338d8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,6 +4,7 @@ ARG USE_CUDA=false ARG USE_OLLAMA=false ARG USE_SLIM=false +ARG USE_PERMISSION_HARDENING=false # Tested with cu117 for CUDA 11 and cu121 for CUDA 12 (default) ARG USE_CUDA_VER=cu128 # any sentence transformer model; models to use can be found at https://huggingface.co/models?library=sentence-transformers @@ -25,6 +26,9 @@ ARG GID=0 FROM --platform=$BUILDPLATFORM node:22-alpine3.20 AS build ARG BUILD_HASH +# Set Node.js options (heap limit Allocation failed - JavaScript heap out of memory) +# ENV NODE_OPTIONS="--max-old-space-size=4096" + WORKDIR /app # to store git revision in build @@ -45,6 +49,7 @@ ARG USE_CUDA ARG USE_OLLAMA ARG USE_CUDA_VER ARG USE_SLIM +ARG USE_PERMISSION_HARDENING ARG USE_EMBEDDING_MODEL ARG USE_RERANKING_MODEL ARG UID @@ -169,11 +174,13 @@ HEALTHCHECK CMD curl --silent --fail http://localhost:${PORT:-8080}/health | jq # Minimal, atomic permission hardening for OpenShift (arbitrary UID): # - Group 0 owns /app and /root # - Directories are group-writable and have SGID so new files inherit GID 0 -RUN set -eux; \ +RUN if [ "$USE_PERMISSION_HARDENING" = "true" ]; then \ + set -eux; \ chgrp -R 0 /app /root || true; \ chmod -R g+rwX /app /root || true; \ find /app -type d -exec chmod g+s {} + || true; \ - find /root -type d -exec chmod g+s {} + || true + find /root -type d -exec chmod g+s {} + || true; \ + fi USER $UID:$GID From 9d80cc3b2d39e5e986b53ba19d66379643e80645 Mon Sep 17 00:00:00 2001 From: Thomas Cooper Date: Thu, 28 Aug 2025 14:47:13 -0400 Subject: [PATCH 006/154] PKCE requires no secret, with no secret the login button does not ever show --- backend/open_webui/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index 1fe031cdad..3cc522c26d 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -660,7 +660,7 @@ def load_oauth_providers(): if ( OAUTH_CLIENT_ID.value - and OAUTH_CLIENT_SECRET.value + and (OAUTH_CLIENT_SECRET.value or OAUTH_CODE_CHALLENGE_METHOD.value) and OPENID_PROVIDER_URL.value ): From d735b036fe4b3f05cf4eaf62249c277818fa8be5 Mon Sep 17 00:00:00 2001 From: Athanasios Oikonomou Date: Thu, 28 Aug 2025 22:19:25 +0300 Subject: [PATCH 007/154] fix: handle unicode filenames in external document loader MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Files with special characters in their names (e.g., ü.pdf) caused issues since HTTP headers only allow Latin-1 characters. This change URL-encodes `X-Filename` before adding it to request headers, preventing failures when uploading or processing such files. Fixes: #17000 --- backend/open_webui/retrieval/loaders/external_document.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/open_webui/retrieval/loaders/external_document.py b/backend/open_webui/retrieval/loaders/external_document.py index c0ccd72432..1be2ca3f24 100644 --- a/backend/open_webui/retrieval/loaders/external_document.py +++ b/backend/open_webui/retrieval/loaders/external_document.py @@ -1,6 +1,7 @@ import requests import logging, os from typing import Iterator, List, Union +from urllib.parse import quote from langchain_core.document_loaders import BaseLoader from langchain_core.documents import Document @@ -37,7 +38,7 @@ class ExternalDocumentLoader(BaseLoader): headers["Authorization"] = f"Bearer {self.api_key}" try: - headers["X-Filename"] = os.path.basename(self.file_path) + headers["X-Filename"] = quote(os.path.basename(self.file_path)) except: pass From 3111d1bf610a96eef849bf0ac73352aa812c3f9f Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Fri, 29 Aug 2025 02:07:31 +0400 Subject: [PATCH 008/154] refac --- src/lib/components/chat/Chat.svelte | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte index 86d86a9ae4..284ddd7ad1 100644 --- a/src/lib/components/chat/Chat.svelte +++ b/src/lib/components/chat/Chat.svelte @@ -1396,10 +1396,10 @@ const submitPrompt = async (userPrompt, { _raw = false } = {}) => { console.log('submitPrompt', userPrompt, $chatId); - const messages = createMessagesList(history, history.currentId); const _selectedModels = selectedModels.map((modelId) => $models.map((m) => m.id).includes(modelId) ? modelId : '' ); + if (JSON.stringify(selectedModels) !== JSON.stringify(_selectedModels)) { selectedModels = _selectedModels; } @@ -1413,15 +1413,6 @@ return; } - if (messages.length != 0 && messages.at(-1).done != true) { - // Response not done - return; - } - if (messages.length != 0 && messages.at(-1).error && !messages.at(-1).content) { - // Error in response - toast.error($i18n.t(`Oops! There was an error in the previous response.`)); - return; - } if ( files.length > 0 && files.filter((file) => file.type !== 'image' && file.status === 'uploading').length > 0 @@ -1431,6 +1422,7 @@ ); return; } + if ( ($config?.file?.max_count ?? null) !== null && files.length + chatFiles.length > $config?.file?.max_count @@ -1443,9 +1435,25 @@ return; } + if (history?.currentId) { + const lastMessage = history.messages[history.currentId]; + if (lastMessage.done != true) { + // Response not done + return; + } + + if (lastMessage.error && !lastMessage.content) { + // Error in response + toast.error($i18n.t(`Oops! There was an error in the previous response.`)); + return; + } + } + messageInput?.setText(''); prompt = ''; + const messages = createMessagesList(history, history.currentId); + // Reset chat input textarea if (!($settings?.richTextInput ?? true)) { const chatInputElement = document.getElementById('chat-input'); From 1ca5ad47b12d780c5ccfd555eacbdcf97a9f794f Mon Sep 17 00:00:00 2001 From: joaoback <156559121+joaoback@users.noreply.github.com> Date: Thu, 28 Aug 2025 19:49:23 -0300 Subject: [PATCH 009/154] Update translation.json (pt-BR) Some minor translations improvements --- src/lib/i18n/locales/pt-BR/translation.json | 36 ++++++++++----------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/lib/i18n/locales/pt-BR/translation.json b/src/lib/i18n/locales/pt-BR/translation.json index 61417f27b0..f216814eb1 100644 --- a/src/lib/i18n/locales/pt-BR/translation.json +++ b/src/lib/i18n/locales/pt-BR/translation.json @@ -78,15 +78,15 @@ "Allow Chat Delete": "Permitir Exclusão de Chats", "Allow Chat Deletion": "Permitir Exclusão de Chats", "Allow Chat Edit": "Permitir Edição de Chats", - "Allow Chat Export": "Permitir exportação de chat", - "Allow Chat Params": "Permitir parâmetros de chat", - "Allow Chat Share": "Permitir compartilhamento de chat", - "Allow Chat System Prompt": "Permitir prompt do sistema de chat", - "Allow Chat Valves": "Permitir válvulas de chat", - "Allow Continue Response": "Permitir resposta contínua", - "Allow Delete Messages": "Permitir exclusão de mensagens", + "Allow Chat Export": "Permitir Exportação de Chat", + "Allow Chat Params": "Permitir Parâmetros de Chat", + "Allow Chat Share": "Permitir Compartilhamento de Chat", + "Allow Chat System Prompt": "Permitir Prompt do Sistema no Chat", + "Allow Chat Valves": "Permitir Configurações de Chat", + "Allow Continue Response": "Permitir Resposta Contínua", + "Allow Delete Messages": "Permitir Exclusão de Mensagens", "Allow File Upload": "Permitir Envio de arquivos", - "Allow Multiple Models in Chat": "Permitir vários modelos no chat", + "Allow Multiple Models in Chat": "Permitir Vários Modelos no Chat", "Allow non-local voices": "Permitir vozes não locais", "Allow Rate Response": "Permitir Avaliar Resposta", "Allow Regenerate Response": "Permitir Regenerar Resposta", @@ -123,7 +123,7 @@ "API Key Endpoint Restrictions": "Restrições de endpoint de chave de API", "API keys": "Chaves API", "API Version": "Versão da API", - "API Version is required": "", + "API Version is required": "Versão da API é obrigatória", "Application DN": "DN da Aplicação", "Application DN Password": "Senha da aplicação DN", "applies to all users with the \"user\" role": "Aplicar para todos com permissão de \"usuário\"", @@ -485,7 +485,7 @@ "Embedding Model Engine": "Motor do Modelo de Embedding", "Embedding model set to \"{{embedding_model}}\"": "Modelo de embedding definido para \"{{embedding_model}}\"", "Enable API Key": "Habilitar chave de API", - "Enable autocomplete generation for chat messages": "Habilitar geração de preenchimento automático para mensagens de bate-papo", + "Enable autocomplete generation for chat messages": "Habilitar geração de preenchimento automático para mensagens do chat", "Enable Code Execution": "Habilitar execução de código", "Enable Code Interpreter": "Habilitar intérprete de código", "Enable Community Sharing": "Ativar Compartilhamento com a Comunidade", @@ -715,7 +715,7 @@ "Folder updated successfully": "Pasta atualizada com sucesso", "Follow up": "Acompanhamento", "Follow Up Generation": "Geração de Acompanhamento", - "Follow Up Generation Prompt": "Prompt de Geração de Acompanhamento", + "Follow Up Generation Prompt": "Prompt para Geração dos Acompanhamentos", "Follow-Up Auto-Generation": "Geração automática de acompanhamento", "Followed instructions perfectly": "Seguiu as instruções perfeitamente", "Force OCR": "Forçar OCR", @@ -1124,7 +1124,7 @@ "Pipelines": "Pipelines", "Pipelines are a plugin system with arbitrary code execution —": "Pipelines é um sistema de plugins com execução arbitrária de código —", "Pipelines Not Detected": "Pipelines Não Detectados", - "Pipelines Valves": "Válvulas de Pipelines", + "Pipelines Valves": "Configurações de Pipelines", "Plain text (.md)": "Texto simples (.md)", "Plain text (.txt)": "Texto simples (.txt)", "Playground": "Playground", @@ -1175,7 +1175,7 @@ "Read Aloud": "Ler em Voz Alta", "Reason": "Razão", "Reasoning Effort": "Esforço de raciocínio", - "Reasoning Tags": "", + "Reasoning Tags": "Tags de raciocínio", "Record": "Registro", "Record voice": "Gravar voz", "Redirecting you to Open WebUI Community": "Redirecionando você para a Comunidade OpenWebUI", @@ -1239,7 +1239,7 @@ "Search": "Pesquisar", "Search a model": "Pesquisar um modelo", "Search all emojis": "Pesquisar todos os emojis", - "Search Base": "Pesquisar base", + "Search Base": "Pesquisar Base", "Search Chats": "Pesquisar Chats", "Search Collection": "Pesquisar Coleção", "Search Filters": "Pesquisar Filtros", @@ -1252,7 +1252,7 @@ "Search In Models": "Pesquisar em modelos", "Search Knowledge": "Pesquisar Conhecimento", "Search Models": "Pesquisar Modelos", - "Search Notes": "Pesquisaar Notas", + "Search Notes": "Pesquisar Notas", "Search options": "Opções de pesquisa", "Search Prompts": "Prompts de Pesquisa", "Search Result Count": "Contagem de Resultados da Pesquisa", @@ -1572,9 +1572,9 @@ "Using the default arena model with all models. Click the plus button to add custom models.": "Usando a arena de modelos padrão para todos os modelos. Clique no botão mais para adicionar modelos personalizados.", "Valid time units:": "Unidades de tempo válidas:", "Validate certificate": "Validar certificado", - "Valves": "Válvulas", - "Valves updated": "Válvulas atualizadas", - "Valves updated successfully": "Válvulas atualizadas com sucesso", + "Valves": "Configurações", + "Valves updated": "Configurações atualizadas", + "Valves updated successfully": "Configurações atualizadas com sucesso", "variable": "variável", "Verify Connection": "Verificar conexão", "Verify SSL Certificate": "Verificar certificado SSL", From 1d1a83b75455b2c0a75f7faad5fea8b4c4a29522 Mon Sep 17 00:00:00 2001 From: Shirasawa <764798966@qq.com> Date: Fri, 29 Aug 2025 03:49:15 +0000 Subject: [PATCH 010/154] i18n: improve zh-CN translation --- src/lib/i18n/locales/zh-CN/translation.json | 12 ++++++------ src/lib/i18n/locales/zh-TW/translation.json | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/lib/i18n/locales/zh-CN/translation.json b/src/lib/i18n/locales/zh-CN/translation.json index 4dfcf8fe2a..a1bb51fa3e 100644 --- a/src/lib/i18n/locales/zh-CN/translation.json +++ b/src/lib/i18n/locales/zh-CN/translation.json @@ -11,7 +11,7 @@ "{{ models }}": "{{ models }}", "{{COUNT}} Available Tools": "{{COUNT}} 个可用工具", "{{COUNT}} characters": "{{COUNT}} 个字符", - "{{COUNT}} extracted lines": "", + "{{COUNT}} extracted lines": "已提取 {{COUNT}} 行", "{{COUNT}} hidden lines": "{{COUNT}} 行被隐藏", "{{COUNT}} Replies": "{{COUNT}} 条回复", "{{COUNT}} words": "{{COUNT}} 个词", @@ -494,9 +494,9 @@ "Enable Message Rating": "启用回复评价", "Enable Mirostat sampling for controlling perplexity.": "启用 Mirostat 采样以控制困惑度", "Enable New Sign Ups": "允许新用户注册", - "Enable, disable, or customize the reasoning tags used by the model. \"Enabled\" uses default tags, \"Disabled\" turns off reasoning tags, and \"Custom\" lets you specify your own start and end tags.": "", + "Enable, disable, or customize the reasoning tags used by the model. \"Enabled\" uses default tags, \"Disabled\" turns off reasoning tags, and \"Custom\" lets you specify your own start and end tags.": "启用、禁用或自定义模型的推理过程标签。“启用”将使用默认的推理过程标签,“禁用”则表示不识别推理过程标签,“自定义”允许您指定模型推理过程的起始和结束标签。", "Enabled": "已启用", - "End Tag": "", + "End Tag": "结束标签", "Endpoint URL": "端点 URL", "Enforce Temporary Chat": "强制临时对话", "Enhance": "润色", @@ -725,7 +725,7 @@ "Format Lines": "行内容格式化", "Format the lines in the output. Defaults to False. If set to True, the lines will be formatted to detect inline math and styles.": "对输出中的文本行进行格式处理。默认为 False。设置为 True 时,将会格式化这些文本行,以检测并识别行内数学公式和样式。", "Format your variables using brackets like this:": "使用括号格式化您的变量,如下所示:", - "Formatting may be inconsistent from source.": "", + "Formatting may be inconsistent from source.": "格式可能会与原始文件不完全一致。", "Forwards system user session credentials to authenticate": "转发系统用户 session 凭证以进行身份验证", "Full Context Mode": "完整上下文模式", "Function": "函数", @@ -1175,7 +1175,7 @@ "Read Aloud": "朗读", "Reason": "原因", "Reasoning Effort": "推理努力 (Reasoning Effort)", - "Reasoning Tags": "", + "Reasoning Tags": "推理过程标签", "Record": "录制", "Record voice": "录音", "Redirecting you to Open WebUI Community": "正在将您重定向到 Open WebUI 社区", @@ -1376,7 +1376,7 @@ "Speech-to-Text": "语音转文本", "Speech-to-Text Engine": "语音转文本引擎", "Start of the channel": "频道起点", - "Start Tag": "", + "Start Tag": "起始标签", "STDOUT/STDERR": "标准输出/标准错误", "Stop": "停止", "Stop Generating": "停止生成", diff --git a/src/lib/i18n/locales/zh-TW/translation.json b/src/lib/i18n/locales/zh-TW/translation.json index 096b44a82d..2ecf6b7953 100644 --- a/src/lib/i18n/locales/zh-TW/translation.json +++ b/src/lib/i18n/locales/zh-TW/translation.json @@ -11,7 +11,7 @@ "{{ models }}": "{{ models }}", "{{COUNT}} Available Tools": "{{COUNT}} 個可用工具", "{{COUNT}} characters": "{{COUNT}} 個字元", - "{{COUNT}} extracted lines": "", + "{{COUNT}} extracted lines": "已擷取 {{COUNT}} 行", "{{COUNT}} hidden lines": "已隱藏 {{COUNT}} 行", "{{COUNT}} Replies": "{{COUNT}} 回覆", "{{COUNT}} words": "{{COUNT}} 個詞", @@ -494,9 +494,9 @@ "Enable Message Rating": "啟用訊息評分", "Enable Mirostat sampling for controlling perplexity.": "啟用 Mirostat 取樣以控制 perplexity。", "Enable New Sign Ups": "允許新使用者註冊", - "Enable, disable, or customize the reasoning tags used by the model. \"Enabled\" uses default tags, \"Disabled\" turns off reasoning tags, and \"Custom\" lets you specify your own start and end tags.": "", + "Enable, disable, or customize the reasoning tags used by the model. \"Enabled\" uses default tags, \"Disabled\" turns off reasoning tags, and \"Custom\" lets you specify your own start and end tags.": "啟用、停用或自訂模型的推理標籤。「啟用」將使用預設的推理標籤,「停用」則不使用推理標籤,「自訂」允許您指定模型推理過程的起始與結束標籤。", "Enabled": "已啟用", - "End Tag": "", + "End Tag": "結束標籤", "Endpoint URL": "端點 URL", "Enforce Temporary Chat": "強制使用臨時對話", "Enhance": "增強", @@ -725,7 +725,7 @@ "Format Lines": "行內容格式化", "Format the lines in the output. Defaults to False. If set to True, the lines will be formatted to detect inline math and styles.": "對輸出中的文字行進行格式處理。預設為 False。設定為 True 時,將會格式化這些文字行,以偵測並識別行內數學公式和樣式。", "Format your variables using brackets like this:": "使用方括號格式化您的變數,如下所示:", - "Formatting may be inconsistent from source.": "", + "Formatting may be inconsistent from source.": "可能與原始格式不完全一致。", "Forwards system user session credentials to authenticate": "轉發系統使用者 session 憑證以進行驗證", "Full Context Mode": "完整上下文模式", "Function": "函式", @@ -1175,7 +1175,7 @@ "Read Aloud": "大聲朗讀", "Reason": "原因", "Reasoning Effort": "推理程度", - "Reasoning Tags": "", + "Reasoning Tags": "推理標籤", "Record": "錄製", "Record voice": "錄音", "Redirecting you to Open WebUI Community": "正在將您重導向至 Open WebUI 社群", @@ -1376,7 +1376,7 @@ "Speech-to-Text": "語音轉文字 (STT) ", "Speech-to-Text Engine": "語音轉文字 (STT) 引擎", "Start of the channel": "頻道起點", - "Start Tag": "", + "Start Tag": "起始標籤", "STDOUT/STDERR": "STDOUT/STDERR", "Stop": "停止", "Stop Generating": "停止生成", From 32a303f392689c75f13d6f519c259cf7dc73b0d6 Mon Sep 17 00:00:00 2001 From: Aleix Dorca Date: Fri, 29 Aug 2025 11:59:09 +0200 Subject: [PATCH 011/154] Update catalan translation.json --- src/lib/i18n/locales/ca-ES/translation.json | 22 ++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/lib/i18n/locales/ca-ES/translation.json b/src/lib/i18n/locales/ca-ES/translation.json index 2a73c826b6..d83fd1c8c4 100644 --- a/src/lib/i18n/locales/ca-ES/translation.json +++ b/src/lib/i18n/locales/ca-ES/translation.json @@ -11,7 +11,7 @@ "{{ models }}": "{{ models }}", "{{COUNT}} Available Tools": "{{COUNT}} eines disponibles", "{{COUNT}} characters": "{{COUNT}} caràcters", - "{{COUNT}} extracted lines": "", + "{{COUNT}} extracted lines": "{{COUNT}} línies extretes", "{{COUNT}} hidden lines": "{{COUNT}} línies ocultes", "{{COUNT}} Replies": "{{COUNT}} respostes", "{{COUNT}} words": "{{COUNT}} paraules", @@ -83,13 +83,13 @@ "Allow Chat Share": "Permetre compartir el xat", "Allow Chat System Prompt": "Permet la indicació de sistema al xat", "Allow Chat Valves": "Permetre Valves al xat", - "Allow Continue Response": "", - "Allow Delete Messages": "", + "Allow Continue Response": "Permetre continuar la resposta", + "Allow Delete Messages": "Permetre eliminar missatges", "Allow File Upload": "Permetre la pujada d'arxius", "Allow Multiple Models in Chat": "Permetre múltiple models al xat", "Allow non-local voices": "Permetre veus no locals", - "Allow Rate Response": "", - "Allow Regenerate Response": "", + "Allow Rate Response": "Permetre valorar les respostes", + "Allow Regenerate Response": "Permetre regenerar respostes", "Allow Speech to Text": "Permetre Parla a Text", "Allow Temporary Chat": "Permetre el xat temporal", "Allow Text to Speech": "Permetre Text a Parla", @@ -430,7 +430,7 @@ "Docling Server URL required.": "La URL del servidor Docling és necessària", "Document": "Document", "Document Intelligence": "Document Intelligence", - "Document Intelligence endpoint required.": "", + "Document Intelligence endpoint required.": "Es necessita un punt de connexió de Document Intelligence", "Documentation": "Documentació", "Documents": "Documents", "does not make any external connections, and your data stays securely on your locally hosted server.": "no realitza connexions externes, i les teves dades romanen segures al teu servidor allotjat localment.", @@ -494,9 +494,9 @@ "Enable Message Rating": "Permetre la qualificació de missatges", "Enable Mirostat sampling for controlling perplexity.": "Permetre el mostreig de Mirostat per controlar la perplexitat", "Enable New Sign Ups": "Permetre nous registres", - "Enable, disable, or customize the reasoning tags used by the model. \"Enabled\" uses default tags, \"Disabled\" turns off reasoning tags, and \"Custom\" lets you specify your own start and end tags.": "", + "Enable, disable, or customize the reasoning tags used by the model. \"Enabled\" uses default tags, \"Disabled\" turns off reasoning tags, and \"Custom\" lets you specify your own start and end tags.": "Activar, desactivar o personalitzar les etiquetes de raonament que utilitza el model. \"Activat\" utilitza etiquetes predeterminades, \"Desactivat\" desactiva les etiquetes de raonament i \"Personalitzat\" permet especificar les etiquetes d'inici i finalització.", "Enabled": "Habilitat", - "End Tag": "", + "End Tag": "Etiqueta de finalització", "Endpoint URL": "URL de connexió", "Enforce Temporary Chat": "Forçar els xats temporals", "Enhance": "Millorar", @@ -725,7 +725,7 @@ "Format Lines": "Formatar les línies", "Format the lines in the output. Defaults to False. If set to True, the lines will be formatted to detect inline math and styles.": "Formata les línies a la sortida. Per defecte, és Fals. Si es defineix com a Cert, les línies es formataran per detectar matemàtiques i estils en línia.", "Format your variables using brackets like this:": "Formata les teves variables utilitzant claudàtors així:", - "Formatting may be inconsistent from source.": "", + "Formatting may be inconsistent from source.": "La formatació pot ser inconsistent amb l'origen", "Forwards system user session credentials to authenticate": "Envia les credencials de l'usuari del sistema per autenticar", "Full Context Mode": "Mode de context complert", "Function": "Funció", @@ -1175,7 +1175,7 @@ "Read Aloud": "Llegir en veu alta", "Reason": "Raó", "Reasoning Effort": "Esforç de raonament", - "Reasoning Tags": "", + "Reasoning Tags": "Etiqueta de raonament", "Record": "Enregistrar", "Record voice": "Enregistrar la veu", "Redirecting you to Open WebUI Community": "Redirigint-te a la comunitat OpenWebUI", @@ -1376,7 +1376,7 @@ "Speech-to-Text": "Àudio-a-Text", "Speech-to-Text Engine": "Motor de veu a text", "Start of the channel": "Inici del canal", - "Start Tag": "", + "Start Tag": "Etiqueta d'inici", "STDOUT/STDERR": "STDOUT/STDERR", "Stop": "Atura", "Stop Generating": "Atura la generació", From 292cb62d4af2ed34e11c866c8d45e1a25ef2057d Mon Sep 17 00:00:00 2001 From: _00_ <131402327+rgaricano@users.noreply.github.com> Date: Sat, 30 Aug 2025 01:48:31 +0200 Subject: [PATCH 012/154] FIX: Hybrid Search lexical-semantic tags FIX Error in Hybrid Search lexical-semantic terms places I was reviewing and I noticed that the lexical-semantic terms are inverted. BM25 weight=1 --> lexical BM25 weight=0 --> semantic --- src/lib/components/admin/Settings/Documents.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/components/admin/Settings/Documents.svelte b/src/lib/components/admin/Settings/Documents.svelte index d3a244fa45..087a9bb950 100644 --- a/src/lib/components/admin/Settings/Documents.svelte +++ b/src/lib/components/admin/Settings/Documents.svelte @@ -1104,10 +1104,10 @@
- {$i18n.t('lexical')} + {$i18n.t('semantic')}
- {$i18n.t('semantic')} + {$i18n.t('lexical')}
From 647e38f701ff93bb40ed71ba445a8ba903518306 Mon Sep 17 00:00:00 2001 From: _00_ <131402327+rgaricano@users.noreply.github.com> Date: Sat, 30 Aug 2025 10:45:35 +0200 Subject: [PATCH 013/154] Revert bypass hybrid search when BM25_weight=0 Revert PR https://github.com/open-webui/open-webui/commit/74b1c801 --- backend/open_webui/retrieval/utils.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/backend/open_webui/retrieval/utils.py b/backend/open_webui/retrieval/utils.py index 100c92c6c0..856527083c 100644 --- a/backend/open_webui/retrieval/utils.py +++ b/backend/open_webui/retrieval/utils.py @@ -128,8 +128,6 @@ def query_doc_with_hybrid_search( log.warning(f"query_doc_with_hybrid_search:no_docs {collection_name}") return {"documents": [], "metadatas": [], "distances": []} - # BM_25 required only if weight is greater than 0 - if hybrid_bm25_weight > 0: log.debug(f"query_doc_with_hybrid_search:doc {collection_name}") bm25_retriever = BM25Retriever.from_texts( texts=collection_result.documents[0], @@ -343,8 +341,7 @@ def query_collection_with_hybrid_search( # Fetch collection data once per collection sequentially # Avoid fetching the same data multiple times later collection_results = {} - # Only retrieve entire collection if bm_25 calculation is required - if hybrid_bm25_weight > 0: + for collection_name in collection_names: try: log.debug( @@ -356,9 +353,7 @@ def query_collection_with_hybrid_search( except Exception as e: log.exception(f"Failed to fetch collection {collection_name}: {e}") collection_results[collection_name] = None - else: - for collection_name in collection_names: - collection_results[collection_name] = [] + log.info( f"Starting hybrid search for {len(queries)} queries in {len(collection_names)} collections..." ) From 562710fe33a71425cd6e14c132056f8bbf757da8 Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 30 Aug 2025 20:05:53 -0400 Subject: [PATCH 014/154] join the url instead of concatenating a string in case the user adds a slash to the end of their configured url. --- backend/open_webui/routers/audio.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/backend/open_webui/routers/audio.py b/backend/open_webui/routers/audio.py index cc5711569d..e1b08fa619 100644 --- a/backend/open_webui/routers/audio.py +++ b/backend/open_webui/routers/audio.py @@ -4,7 +4,7 @@ import logging import os import uuid from functools import lru_cache -from pathlib import Path + from pydub import AudioSegment from pydub.silence import split_on_silence from concurrent.futures import ThreadPoolExecutor @@ -15,7 +15,7 @@ import aiohttp import aiofiles import requests import mimetypes -from urllib.parse import quote +from urllib.parse import urljoin from fastapi import ( Depends, @@ -308,6 +308,7 @@ def load_speech_pipeline(request): @router.post("/speech") async def speech(request: Request, user=Depends(get_verified_user)): body = await request.body() + tts_model = request.app.state.config.TTS_MODEL name = hashlib.sha256( body + str(request.app.state.config.TTS_ENGINE).encode("utf-8") @@ -337,8 +338,9 @@ async def speech(request: Request, user=Depends(get_verified_user)): async with aiohttp.ClientSession( timeout=timeout, trust_env=True ) as session: + r = await session.post( - url=f"{request.app.state.config.TTS_OPENAI_API_BASE_URL}/audio/speech", + url=urljoin(request.app.state.config.TTS_OPENAI_API_BASE_URL, "/audio/speech"), json=payload, headers={ "Content-Type": "application/json", @@ -466,8 +468,7 @@ async def speech(request: Request, user=Depends(get_verified_user)): timeout=timeout, trust_env=True ) as session: async with session.post( - (base_url or f"https://{region}.tts.speech.microsoft.com") - + "/cognitiveservices/v1", + urljoin(base_url or f"https://{region}.tts.speech.microsoft.com", "/cognitiveservices/v1"), headers={ "Ocp-Apim-Subscription-Key": request.app.state.config.TTS_API_KEY, "Content-Type": "application/ssml+xml", From c62f30e22c50efa9846fe1fdd6ed1e11e8deebb7 Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 30 Aug 2025 20:12:46 -0400 Subject: [PATCH 015/154] remove whitespace --- backend/open_webui/routers/audio.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/open_webui/routers/audio.py b/backend/open_webui/routers/audio.py index e1b08fa619..2f6703e1c9 100644 --- a/backend/open_webui/routers/audio.py +++ b/backend/open_webui/routers/audio.py @@ -4,7 +4,6 @@ import logging import os import uuid from functools import lru_cache - from pydub import AudioSegment from pydub.silence import split_on_silence from concurrent.futures import ThreadPoolExecutor @@ -338,7 +337,6 @@ async def speech(request: Request, user=Depends(get_verified_user)): async with aiohttp.ClientSession( timeout=timeout, trust_env=True ) as session: - r = await session.post( url=urljoin(request.app.state.config.TTS_OPENAI_API_BASE_URL, "/audio/speech"), json=payload, From 08b958cfc9607d1b76777b67ba1d92924127d07c Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 30 Aug 2025 20:29:16 -0400 Subject: [PATCH 016/154] re-add used var --- backend/open_webui/routers/audio.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/open_webui/routers/audio.py b/backend/open_webui/routers/audio.py index 2f6703e1c9..b94f5dc60a 100644 --- a/backend/open_webui/routers/audio.py +++ b/backend/open_webui/routers/audio.py @@ -3,6 +3,7 @@ import json import logging import os import uuid + from functools import lru_cache from pydub import AudioSegment from pydub.silence import split_on_silence @@ -14,7 +15,7 @@ import aiohttp import aiofiles import requests import mimetypes -from urllib.parse import urljoin +from urllib.parse import urljoin, quote from fastapi import ( Depends, From 20b6902b9f0cc42dc0d81b654287ea873a7be724 Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 30 Aug 2025 20:30:34 -0400 Subject: [PATCH 017/154] whitespace --- backend/open_webui/routers/audio.py | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/open_webui/routers/audio.py b/backend/open_webui/routers/audio.py index b94f5dc60a..7ee6fce3df 100644 --- a/backend/open_webui/routers/audio.py +++ b/backend/open_webui/routers/audio.py @@ -3,7 +3,6 @@ import json import logging import os import uuid - from functools import lru_cache from pydub import AudioSegment from pydub.silence import split_on_silence From 39ae9167ee3b79e7f68fe4563c02570cb9e77f29 Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 30 Aug 2025 20:31:13 -0400 Subject: [PATCH 018/154] removed test code. --- backend/open_webui/routers/audio.py | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/open_webui/routers/audio.py b/backend/open_webui/routers/audio.py index 7ee6fce3df..f71be198af 100644 --- a/backend/open_webui/routers/audio.py +++ b/backend/open_webui/routers/audio.py @@ -307,7 +307,6 @@ def load_speech_pipeline(request): @router.post("/speech") async def speech(request: Request, user=Depends(get_verified_user)): body = await request.body() - tts_model = request.app.state.config.TTS_MODEL name = hashlib.sha256( body + str(request.app.state.config.TTS_ENGINE).encode("utf-8") From 48afd424a37414364da19ccfa5f31789137e7ff2 Mon Sep 17 00:00:00 2001 From: Hadad Date: Sun, 31 Aug 2025 14:33:32 +0700 Subject: [PATCH 019/154] fix: Resolve admin account creation on Hugging Face Spaces. Signed-off-by: Hadad --- backend/start.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/start.sh b/backend/start.sh index 9e106760c8..c32498aa45 100755 --- a/backend/start.sh +++ b/backend/start.sh @@ -53,12 +53,12 @@ if [ -n "$SPACE_ID" ]; then WEBUI_SECRET_KEY="$WEBUI_SECRET_KEY" uvicorn open_webui.main:app --host "$HOST" --port "$PORT" --forwarded-allow-ips '*' & webui_pid=$! echo "Waiting for webui to start..." - while ! curl -s http://localhost:8080/health > /dev/null; do + while ! curl -s "http://localhost:${PORT}/health" > /dev/null; do sleep 1 done echo "Creating admin user..." curl \ - -X POST "http://localhost:8080/api/v1/auths/signup" \ + -X POST "http://localhost:${PORT}/api/v1/auths/signup" \ -H "accept: application/json" \ -H "Content-Type: application/json" \ -d "{ \"email\": \"${ADMIN_USER_EMAIL}\", \"password\": \"${ADMIN_USER_PASSWORD}\", \"name\": \"Admin\" }" From 61f530ff4bb6ce3429fe28edc0d987d8a407391d Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sun, 31 Aug 2025 23:06:58 +0400 Subject: [PATCH 020/154] refac: styling --- src/lib/components/common/Banner.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/components/common/Banner.svelte b/src/lib/components/common/Banner.svelte index a79b8b42c3..d135cb605a 100644 --- a/src/lib/components/common/Banner.svelte +++ b/src/lib/components/common/Banner.svelte @@ -46,7 +46,7 @@ {#if !dismissed} {#if mounted}
From b0f6f24ca80fb8cadea2fd58392cb375977edf19 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sun, 31 Aug 2025 23:42:34 +0400 Subject: [PATCH 021/154] refac --- backend/open_webui/utils/oauth.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/backend/open_webui/utils/oauth.py b/backend/open_webui/utils/oauth.py index 5ac189d48d..9385897f4c 100644 --- a/backend/open_webui/utils/oauth.py +++ b/backend/open_webui/utils/oauth.py @@ -553,6 +553,15 @@ class OAuthManager: ) if ENABLE_OAUTH_SIGNUP.value: + oauth_access_token = token.get("access_token") + response.set_cookie( + key="oauth_access_token", + value=oauth_access_token, + httponly=True, + samesite=WEBUI_AUTH_COOKIE_SAME_SITE, + secure=WEBUI_AUTH_COOKIE_SECURE, + ) + oauth_id_token = token.get("id_token") response.set_cookie( key="oauth_id_token", From e0ab5adb9794c264d2ae537aea74f37c449a6ab8 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sun, 31 Aug 2025 23:52:50 +0400 Subject: [PATCH 022/154] refac --- backend/open_webui/utils/middleware.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index a298ebeb31..91a125aafb 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -1297,7 +1297,13 @@ async def process_chat_response( response_data = response if "error" in response_data: - error = response_data["error"].get("detail", response_data["error"]) + error = response_data.get("error") + + if isinstance(error, dict): + error = error.get("detail", error) + else: + error = str(error) + Chats.upsert_message_to_chat_by_id_and_message_id( metadata["chat_id"], metadata["message_id"], From c2b4976c82d335ed524bd80dc914b5e2f5bfbd9e Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sun, 31 Aug 2025 23:58:18 +0400 Subject: [PATCH 023/154] enh: PGVECTOR_CREATE_EXTENSION env var --- .../retrieval/vector/dbs/pgvector.py | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/backend/open_webui/retrieval/vector/dbs/pgvector.py b/backend/open_webui/retrieval/vector/dbs/pgvector.py index d978f0c824..06c1698cdd 100644 --- a/backend/open_webui/retrieval/vector/dbs/pgvector.py +++ b/backend/open_webui/retrieval/vector/dbs/pgvector.py @@ -37,6 +37,7 @@ from open_webui.retrieval.vector.main import ( from open_webui.config import ( PGVECTOR_DB_URL, PGVECTOR_INITIALIZE_MAX_VECTOR_LENGTH, + PGVECTOR_CREATE_EXTENSION, PGVECTOR_PGCRYPTO, PGVECTOR_PGCRYPTO_KEY, PGVECTOR_POOL_SIZE, @@ -112,18 +113,19 @@ class PgvectorClient(VectorDBBase): try: # Ensure the pgvector extension is available # Use a conditional check to avoid permission issues on Azure PostgreSQL - self.session.execute( - text( - """ - DO $$ - BEGIN - IF NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'vector') THEN - CREATE EXTENSION IF NOT EXISTS vector; - END IF; - END $$; - """ + if PGVECTOR_CREATE_EXTENSION: + self.session.execute( + text( + """ + DO $$ + BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'vector') THEN + CREATE EXTENSION IF NOT EXISTS vector; + END IF; + END $$; + """ + ) ) - ) if PGVECTOR_PGCRYPTO: # Ensure the pgcrypto extension is available for encryption From b45219c8b15b48d5ee3d42983e1107bbcefbab01 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 1 Sep 2025 00:04:26 +0400 Subject: [PATCH 024/154] refac --- backend/open_webui/config.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index 3cc522c26d..c69ef76c0b 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -1998,6 +1998,9 @@ PGVECTOR_INITIALIZE_MAX_VECTOR_LENGTH = int( os.environ.get("PGVECTOR_INITIALIZE_MAX_VECTOR_LENGTH", "1536") ) +PGVECTOR_CREATE_EXTENSION = ( + os.getenv("PGVECTOR_CREATE_EXTENSION", "true").lower() == "true" +) PGVECTOR_PGCRYPTO = os.getenv("PGVECTOR_PGCRYPTO", "false").lower() == "true" PGVECTOR_PGCRYPTO_KEY = os.getenv("PGVECTOR_PGCRYPTO_KEY", None) if PGVECTOR_PGCRYPTO and not PGVECTOR_PGCRYPTO_KEY: From 50371975417fbba81e3e32cfe544316cc4b20df6 Mon Sep 17 00:00:00 2001 From: Shirasawa <764798966@qq.com> Date: Mon, 1 Sep 2025 04:49:06 +0800 Subject: [PATCH 025/154] fix: fix event binding for composition end in MessageInput --- src/lib/components/chat/MessageInput.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/components/chat/MessageInput.svelte b/src/lib/components/chat/MessageInput.svelte index adea345ed0..e38f8e2c79 100644 --- a/src/lib/components/chat/MessageInput.svelte +++ b/src/lib/components/chat/MessageInput.svelte @@ -1407,7 +1407,7 @@ command = getCommand(); }} on:compositionstart={() => (isComposing = true)} - oncompositionend={(e) => { + on:compositionend={(e) => { compositionEndedAt = e.timeStamp; isComposing = false; }} From ac0243e8b78aff68d3d8ed39e1f8123a817ea4d8 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 1 Sep 2025 00:57:13 +0400 Subject: [PATCH 026/154] refac --- backend/open_webui/retrieval/utils.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/backend/open_webui/retrieval/utils.py b/backend/open_webui/retrieval/utils.py index 856527083c..8d4efd3f72 100644 --- a/backend/open_webui/retrieval/utils.py +++ b/backend/open_webui/retrieval/utils.py @@ -341,19 +341,18 @@ def query_collection_with_hybrid_search( # Fetch collection data once per collection sequentially # Avoid fetching the same data multiple times later collection_results = {} + for collection_name in collection_names: + try: + log.debug( + f"query_collection_with_hybrid_search:VECTOR_DB_CLIENT.get:collection {collection_name}" + ) + collection_results[collection_name] = VECTOR_DB_CLIENT.get( + collection_name=collection_name + ) + except Exception as e: + log.exception(f"Failed to fetch collection {collection_name}: {e}") + collection_results[collection_name] = None - for collection_name in collection_names: - try: - log.debug( - f"query_collection_with_hybrid_search:VECTOR_DB_CLIENT.get:collection {collection_name}" - ) - collection_results[collection_name] = VECTOR_DB_CLIENT.get( - collection_name=collection_name - ) - except Exception as e: - log.exception(f"Failed to fetch collection {collection_name}: {e}") - collection_results[collection_name] = None - log.info( f"Starting hybrid search for {len(queries)} queries in {len(collection_names)} collections..." ) From 487979859a6ffcfd60468f523822cdf838fbef5b Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 1 Sep 2025 01:22:50 +0400 Subject: [PATCH 027/154] fix: web/youtube attachements --- backend/open_webui/retrieval/utils.py | 7 +++++-- src/lib/components/chat/Chat.svelte | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/backend/open_webui/retrieval/utils.py b/backend/open_webui/retrieval/utils.py index 8d4efd3f72..4ef6dbce3b 100644 --- a/backend/open_webui/retrieval/utils.py +++ b/backend/open_webui/retrieval/utils.py @@ -487,9 +487,12 @@ def get_sources_from_items( if item.get("type") == "text": # Raw Text - # Used during temporary chat file uploads + # Used during temporary chat file uploads or web page & youtube attachements - if item.get("file"): + if item.get("collection_name"): + # If item has a collection name, use it + collection_names.append(item.get("collection_name")) + elif item.get("file"): # if item has file data, use it query_result = { "documents": [ diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte index 284ddd7ad1..8ad7da577e 100644 --- a/src/lib/components/chat/Chat.svelte +++ b/src/lib/components/chat/Chat.svelte @@ -671,7 +671,7 @@ console.log(url); const fileItem = { - type: 'doc', + type: 'text', name: url, collection_name: '', status: 'uploading', @@ -704,7 +704,7 @@ console.log(url); const fileItem = { - type: 'doc', + type: 'text', name: url, collection_name: '', status: 'uploading', From 77b65ccbfbf3971ca71d3c7a70d77168e8f007dd Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 1 Sep 2025 01:52:10 +0400 Subject: [PATCH 028/154] refac/enh: forward headers to tool server --- backend/open_webui/utils/tools.py | 31 ++++++++++++------- src/lib/apis/openai/index.ts | 1 + src/lib/components/AddServerModal.svelte | 10 ++++++ .../components/notes/NoteEditor/Chat.svelte | 2 +- src/lib/components/playground/Chat.svelte | 2 +- 5 files changed, 32 insertions(+), 14 deletions(-) diff --git a/backend/open_webui/utils/tools.py b/backend/open_webui/utils/tools.py index e68124bd5c..9007f59326 100644 --- a/backend/open_webui/utils/tools.py +++ b/backend/open_webui/utils/tools.py @@ -119,18 +119,26 @@ async def get_tools( function_name = spec["name"] auth_type = tool_server_connection.get("auth_type", "bearer") - token = None + headers = {} if auth_type == "bearer": - token = tool_server_connection.get("key", "") + headers["Authorization"] = ( + f"Bearer {tool_server_connection.get("key", "")}" + ) elif auth_type == "session": - token = request.state.token.credentials + headers["Authorization"] = ( + f"Bearer {request.state.token.credentials}" + ) + elif auth_type == "request_headers": + headers.update(dict(request.headers)) - def make_tool_function(function_name, token, tool_server_data): + headers["Content-Type"] = "application/json" + + def make_tool_function(function_name, tool_server_data, headers): async def tool_function(**kwargs): return await execute_tool_server( - token=token, url=tool_server_data["url"], + headers=headers, name=function_name, params=kwargs, server_data=tool_server_data, @@ -139,7 +147,7 @@ async def get_tools( return tool_function tool_function = make_tool_function( - function_name, token, tool_server_data + function_name, tool_server_data, headers ) callable = get_async_tool_function_and_apply_extra_params( @@ -610,7 +618,11 @@ async def get_tool_servers_data( async def execute_tool_server( - token: str, url: str, name: str, params: Dict[str, Any], server_data: Dict[str, Any] + url: str, + headers: Dict[str, str], + name: str, + params: Dict[str, Any], + server_data: Dict[str, Any], ) -> Any: error = None try: @@ -671,11 +683,6 @@ async def execute_tool_server( f"Request body expected for operation '{name}' but none found." ) - headers = {"Content-Type": "application/json"} - - if token: - headers["Authorization"] = f"Bearer {token}" - async with aiohttp.ClientSession( trust_env=True, timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT) ) as session: diff --git a/src/lib/apis/openai/index.ts b/src/lib/apis/openai/index.ts index c0cbe7b6d3..276fad145d 100644 --- a/src/lib/apis/openai/index.ts +++ b/src/lib/apis/openai/index.ts @@ -372,6 +372,7 @@ export const generateOpenAIChatCompletion = async ( Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, + credentials: 'include', body: JSON.stringify(body) }) .then(async (res) => { diff --git a/src/lib/components/AddServerModal.svelte b/src/lib/components/AddServerModal.svelte index 4d3cbabce5..6fad62bc15 100644 --- a/src/lib/components/AddServerModal.svelte +++ b/src/lib/components/AddServerModal.svelte @@ -285,6 +285,10 @@ > + + {#if !direct} + + {/if}
@@ -301,6 +305,12 @@ > {$i18n.t('Forwards system user session credentials to authenticate')}
+ {:else if auth_type === 'request_headers'} +
+ {$i18n.t('Forwards system user headers to authenticate')} +
{/if} diff --git a/src/lib/components/notes/NoteEditor/Chat.svelte b/src/lib/components/notes/NoteEditor/Chat.svelte index d0c050ef5d..9a509a3dc9 100644 --- a/src/lib/components/notes/NoteEditor/Chat.svelte +++ b/src/lib/components/notes/NoteEditor/Chat.svelte @@ -43,7 +43,7 @@ } from '$lib/constants'; import { WEBUI_NAME, config, user, models, settings } from '$lib/stores'; - import { chatCompletion, generateOpenAIChatCompletion } from '$lib/apis/openai'; + import { chatCompletion } from '$lib/apis/openai'; import { splitStream } from '$lib/utils'; diff --git a/src/lib/components/playground/Chat.svelte b/src/lib/components/playground/Chat.svelte index b060220268..933c9b34b8 100644 --- a/src/lib/components/playground/Chat.svelte +++ b/src/lib/components/playground/Chat.svelte @@ -12,7 +12,7 @@ } from '$lib/constants'; import { WEBUI_NAME, config, user, models, settings } from '$lib/stores'; - import { chatCompletion, generateOpenAIChatCompletion } from '$lib/apis/openai'; + import { chatCompletion } from '$lib/apis/openai'; import { splitStream } from '$lib/utils'; import Collapsible from '../common/Collapsible.svelte'; From ed5d95f4344251aa4bf6a2bcc1f2f3783a1b6d5a Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 1 Sep 2025 02:41:29 +0400 Subject: [PATCH 029/154] refac --- backend/open_webui/config.py | 2 +- backend/open_webui/utils/tools.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index c69ef76c0b..d62b219668 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -313,7 +313,7 @@ JWT_EXPIRES_IN = PersistentConfig( #################################### ENABLE_OAUTH_PERSISTENT_CONFIG = ( - os.environ.get("ENABLE_OAUTH_PERSISTENT_CONFIG", "True").lower() == "true" + os.environ.get("ENABLE_OAUTH_PERSISTENT_CONFIG", "False").lower() == "true" ) ENABLE_OAUTH_SIGNUP = PersistentConfig( diff --git a/backend/open_webui/utils/tools.py b/backend/open_webui/utils/tools.py index 9007f59326..d3ea432019 100644 --- a/backend/open_webui/utils/tools.py +++ b/backend/open_webui/utils/tools.py @@ -123,7 +123,7 @@ async def get_tools( if auth_type == "bearer": headers["Authorization"] = ( - f"Bearer {tool_server_connection.get("key", "")}" + f"Bearer {tool_server_connection.get('key', '')}" ) elif auth_type == "session": headers["Authorization"] = ( From 0e0c1c4ca422cdeaf0eb99e9a444b6b7fe74f496 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Sep 2025 05:53:53 +0000 Subject: [PATCH 030/154] build(deps): bump pdfjs-dist from 5.3.93 to 5.4.149 Bumps [pdfjs-dist](https://github.com/mozilla/pdf.js) from 5.3.93 to 5.4.149. - [Release notes](https://github.com/mozilla/pdf.js/releases) - [Commits](https://github.com/mozilla/pdf.js/compare/v5.3.93...v5.4.149) --- updated-dependencies: - dependency-name: pdfjs-dist dependency-version: 5.4.149 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package-lock.json | 96 +++++++++++++++++++++++------------------------ package.json | 2 +- 2 files changed, 49 insertions(+), 49 deletions(-) diff --git a/package-lock.json b/package-lock.json index f5960587a1..9081ba5357 100644 --- a/package-lock.json +++ b/package-lock.json @@ -69,7 +69,7 @@ "mermaid": "^11.6.0", "paneforge": "^0.0.6", "panzoom": "^9.4.3", - "pdfjs-dist": "^5.3.93", + "pdfjs-dist": "^5.4.149", "prosemirror-collab": "^1.3.1", "prosemirror-commands": "^1.6.0", "prosemirror-example-setup": "^1.2.3", @@ -2238,9 +2238,9 @@ "integrity": "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==" }, "node_modules/@napi-rs/canvas": { - "version": "0.1.73", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.73.tgz", - "integrity": "sha512-9iwPZrNlCK4rG+vWyDvyvGeYjck9MoP0NVQP6N60gqJNFA1GsN0imG05pzNsqfCvFxUxgiTYlR8ff0HC1HXJiw==", + "version": "0.1.78", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.78.tgz", + "integrity": "sha512-YaBHJvT+T1DoP16puvWM6w46Lq3VhwKIJ8th5m1iEJyGh7mibk5dT7flBvMQ1EH1LYmMzXJ+OUhu+8wQ9I6u7g==", "license": "MIT", "optional": true, "workspaces": [ @@ -2250,22 +2250,22 @@ "node": ">= 10" }, "optionalDependencies": { - "@napi-rs/canvas-android-arm64": "0.1.73", - "@napi-rs/canvas-darwin-arm64": "0.1.73", - "@napi-rs/canvas-darwin-x64": "0.1.73", - "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.73", - "@napi-rs/canvas-linux-arm64-gnu": "0.1.73", - "@napi-rs/canvas-linux-arm64-musl": "0.1.73", - "@napi-rs/canvas-linux-riscv64-gnu": "0.1.73", - "@napi-rs/canvas-linux-x64-gnu": "0.1.73", - "@napi-rs/canvas-linux-x64-musl": "0.1.73", - "@napi-rs/canvas-win32-x64-msvc": "0.1.73" + "@napi-rs/canvas-android-arm64": "0.1.78", + "@napi-rs/canvas-darwin-arm64": "0.1.78", + "@napi-rs/canvas-darwin-x64": "0.1.78", + "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.78", + "@napi-rs/canvas-linux-arm64-gnu": "0.1.78", + "@napi-rs/canvas-linux-arm64-musl": "0.1.78", + "@napi-rs/canvas-linux-riscv64-gnu": "0.1.78", + "@napi-rs/canvas-linux-x64-gnu": "0.1.78", + "@napi-rs/canvas-linux-x64-musl": "0.1.78", + "@napi-rs/canvas-win32-x64-msvc": "0.1.78" } }, "node_modules/@napi-rs/canvas-android-arm64": { - "version": "0.1.73", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.73.tgz", - "integrity": "sha512-s8dMhfYIHVv7gz8BXg3Nb6cFi950Y0xH5R/sotNZzUVvU9EVqHfkqiGJ4UIqu+15UhqguT6mI3Bv1mhpRkmMQw==", + "version": "0.1.78", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.78.tgz", + "integrity": "sha512-N1ikxztjrRmh8xxlG5kYm1RuNr8ZW1EINEDQsLhhuy7t0pWI/e7SH91uFVLZKCMDyjel1tyWV93b5fdCAi7ggw==", "cpu": [ "arm64" ], @@ -2279,9 +2279,9 @@ } }, "node_modules/@napi-rs/canvas-darwin-arm64": { - "version": "0.1.73", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.73.tgz", - "integrity": "sha512-bLPCq8Yyq1vMdVdIpQAqmgf6VGUknk8e7NdSZXJJFOA9gxkJ1RGcHOwoXo7h0gzhHxSorg71hIxyxtwXpq10Rw==", + "version": "0.1.78", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.78.tgz", + "integrity": "sha512-FA3aCU3G5yGc74BSmnLJTObnZRV+HW+JBTrsU+0WVVaNyVKlb5nMvYAQuieQlRVemsAA2ek2c6nYtHh6u6bwFw==", "cpu": [ "arm64" ], @@ -2295,9 +2295,9 @@ } }, "node_modules/@napi-rs/canvas-darwin-x64": { - "version": "0.1.73", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.73.tgz", - "integrity": "sha512-GR1CcehDjdNYXN3bj8PIXcXfYLUUOQANjQpM+KNnmpRo7ojsuqPjT7ZVH+6zoG/aqRJWhiSo+ChQMRazZlRU9g==", + "version": "0.1.78", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.78.tgz", + "integrity": "sha512-xVij69o9t/frixCDEoyWoVDKgE3ksLGdmE2nvBWVGmoLu94MWUlv2y4Qzf5oozBmydG5Dcm4pRHFBM7YWa1i6g==", "cpu": [ "x64" ], @@ -2311,9 +2311,9 @@ } }, "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": { - "version": "0.1.73", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.73.tgz", - "integrity": "sha512-cM7F0kBJVFio0+U2iKSW4fWSfYQ8CPg4/DRZodSum/GcIyfB8+UPJSRM1BvvlcWinKLfX1zUYOwonZX9IFRRcw==", + "version": "0.1.78", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.78.tgz", + "integrity": "sha512-aSEXrLcIpBtXpOSnLhTg4jPsjJEnK7Je9KqUdAWjc7T8O4iYlxWxrXFIF8rV8J79h5jNdScgZpAUWYnEcutR3g==", "cpu": [ "arm" ], @@ -2327,9 +2327,9 @@ } }, "node_modules/@napi-rs/canvas-linux-arm64-gnu": { - "version": "0.1.73", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.73.tgz", - "integrity": "sha512-PMWNrMON9uz9klz1B8ZY/RXepQSC5dxxHQTowfw93Tb3fLtWO5oNX2k9utw7OM4ypT9BUZUWJnDQ5bfuXc/EUQ==", + "version": "0.1.78", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.78.tgz", + "integrity": "sha512-dlEPRX1hLGKaY3UtGa1dtkA1uGgFITn2mDnfI6YsLlYyLJQNqHx87D1YTACI4zFCUuLr/EzQDzuX+vnp9YveVg==", "cpu": [ "arm64" ], @@ -2343,9 +2343,9 @@ } }, "node_modules/@napi-rs/canvas-linux-arm64-musl": { - "version": "0.1.73", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.73.tgz", - "integrity": "sha512-lX0z2bNmnk1PGZ+0a9OZwI2lPPvWjRYzPqvEitXX7lspyLFrOzh2kcQiLL7bhyODN23QvfriqwYqp5GreSzVvA==", + "version": "0.1.78", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.78.tgz", + "integrity": "sha512-TsCfjOPZtm5Q/NO1EZHR5pwDPSPjPEttvnv44GL32Zn1uvudssjTLbvaG1jHq81Qxm16GTXEiYLmx4jOLZQYlg==", "cpu": [ "arm64" ], @@ -2359,9 +2359,9 @@ } }, "node_modules/@napi-rs/canvas-linux-riscv64-gnu": { - "version": "0.1.73", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.73.tgz", - "integrity": "sha512-QDQgMElwxAoADsSR3UYvdTTQk5XOyD9J5kq15Z8XpGwpZOZsSE0zZ/X1JaOtS2x+HEZL6z1S6MF/1uhZFZb5ig==", + "version": "0.1.78", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.78.tgz", + "integrity": "sha512-+cpTTb0GDshEow/5Fy8TpNyzaPsYb3clQIjgWRmzRcuteLU+CHEU/vpYvAcSo7JxHYPJd8fjSr+qqh+nI5AtmA==", "cpu": [ "riscv64" ], @@ -2375,9 +2375,9 @@ } }, "node_modules/@napi-rs/canvas-linux-x64-gnu": { - "version": "0.1.73", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.73.tgz", - "integrity": "sha512-wbzLJrTalQrpyrU1YRrO6w6pdr5vcebbJa+Aut5QfTaW9eEmMb1WFG6l1V+cCa5LdHmRr8bsvl0nJDU/IYDsmw==", + "version": "0.1.78", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.78.tgz", + "integrity": "sha512-wxRcvKfvYBgtrO0Uy8OmwvjlnTcHpY45LLwkwVNIWHPqHAsyoTyG/JBSfJ0p5tWRzMOPDCDqdhpIO4LOgXjeyg==", "cpu": [ "x64" ], @@ -2391,9 +2391,9 @@ } }, "node_modules/@napi-rs/canvas-linux-x64-musl": { - "version": "0.1.73", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.73.tgz", - "integrity": "sha512-xbfhYrUufoTAKvsEx2ZUN4jvACabIF0h1F5Ik1Rk4e/kQq6c+Dwa5QF0bGrfLhceLpzHT0pCMGMDeQKQrcUIyA==", + "version": "0.1.78", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.78.tgz", + "integrity": "sha512-vQFOGwC9QDP0kXlhb2LU1QRw/humXgcbVp8mXlyBqzc/a0eijlLF9wzyarHC1EywpymtS63TAj8PHZnhTYN6hg==", "cpu": [ "x64" ], @@ -2407,9 +2407,9 @@ } }, "node_modules/@napi-rs/canvas-win32-x64-msvc": { - "version": "0.1.73", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.73.tgz", - "integrity": "sha512-YQmHXBufFBdWqhx+ympeTPkMfs3RNxaOgWm59vyjpsub7Us07BwCcmu1N5kildhO8Fm0syoI2kHnzGkJBLSvsg==", + "version": "0.1.78", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.78.tgz", + "integrity": "sha512-/eKlTZBtGUgpRKalzOzRr6h7KVSuziESWXgBcBnXggZmimwIJWPJlEcbrx5Tcwj8rPuZiANXQOG9pPgy9Q4LTQ==", "cpu": [ "x64" ], @@ -10252,15 +10252,15 @@ } }, "node_modules/pdfjs-dist": { - "version": "5.3.93", - "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.3.93.tgz", - "integrity": "sha512-w3fQKVL1oGn8FRyx5JUG5tnbblggDqyx2XzA5brsJ5hSuS+I0NdnJANhmeWKLjotdbPQucLBug5t0MeWr0AAdg==", + "version": "5.4.149", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.4.149.tgz", + "integrity": "sha512-Xe8/1FMJEQPUVSti25AlDpwpUm2QAVmNOpFP0SIahaPIOKBKICaefbzogLdwey3XGGoaP4Lb9wqiw2e9Jqp0LA==", "license": "Apache-2.0", "engines": { "node": ">=20.16.0 || >=22.3.0" }, "optionalDependencies": { - "@napi-rs/canvas": "^0.1.71" + "@napi-rs/canvas": "^0.1.77" } }, "node_modules/pend": { diff --git a/package.json b/package.json index d4f736d598..f1a391db5d 100644 --- a/package.json +++ b/package.json @@ -113,7 +113,7 @@ "mermaid": "^11.6.0", "paneforge": "^0.0.6", "panzoom": "^9.4.3", - "pdfjs-dist": "^5.3.93", + "pdfjs-dist": "^5.4.149", "prosemirror-collab": "^1.3.1", "prosemirror-commands": "^1.6.0", "prosemirror-example-setup": "^1.2.3", From bcc0cbf895e59d06e254e77ce381ba3d9bd8ceb8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Sep 2025 05:59:19 +0000 Subject: [PATCH 031/154] build(deps): bump argon2-cffi from 23.1.0 to 25.1.0 in /backend Bumps [argon2-cffi](https://github.com/hynek/argon2-cffi) from 23.1.0 to 25.1.0. - [Release notes](https://github.com/hynek/argon2-cffi/releases) - [Changelog](https://github.com/hynek/argon2-cffi/blob/main/CHANGELOG.md) - [Commits](https://github.com/hynek/argon2-cffi/compare/23.1.0...25.1.0) --- updated-dependencies: - dependency-name: argon2-cffi dependency-version: 25.1.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- backend/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/requirements.txt b/backend/requirements.txt index 03eeba2a1e..01a74ecb7e 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -29,7 +29,7 @@ pymongo redis boto3==1.40.5 -argon2-cffi==23.1.0 +argon2-cffi==25.1.0 APScheduler==3.10.4 pycrdt==0.12.25 From 3e69f10af7788d908f7759bcb7dbfa67ef8a7996 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Sep 2025 05:59:27 +0000 Subject: [PATCH 032/154] build(deps): bump youtube-transcript-api from 1.1.0 to 1.2.2 in /backend Bumps [youtube-transcript-api](https://github.com/jdepoix/youtube-transcript-api) from 1.1.0 to 1.2.2. - [Release notes](https://github.com/jdepoix/youtube-transcript-api/releases) - [Commits](https://github.com/jdepoix/youtube-transcript-api/compare/v1.1.0...v1.2.2) --- updated-dependencies: - dependency-name: youtube-transcript-api dependency-version: 1.2.2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- backend/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/requirements.txt b/backend/requirements.txt index 03eeba2a1e..714054221e 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -102,7 +102,7 @@ PyJWT[crypto]==2.10.1 authlib==1.6.1 black==25.1.0 -youtube-transcript-api==1.1.0 +youtube-transcript-api==1.2.2 pytube==15.0.0 pydub From 94dae76d0f0ca9cc3a8d8f3d7e867ea278de2a97 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Sep 2025 06:01:03 +0000 Subject: [PATCH 033/154] build(deps): bump pyodide from 0.27.7 to 0.28.2 Bumps [pyodide](https://github.com/pyodide/pyodide) from 0.27.7 to 0.28.2. - [Release notes](https://github.com/pyodide/pyodide/releases) - [Commits](https://github.com/pyodide/pyodide/compare/0.27.7...0.28.2) --- updated-dependencies: - dependency-name: pyodide dependency-version: 0.28.2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index f5960587a1..9fa5962e1d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -82,7 +82,7 @@ "prosemirror-state": "^1.4.3", "prosemirror-tables": "^1.7.1", "prosemirror-view": "^1.34.3", - "pyodide": "^0.27.3", + "pyodide": "^0.28.2", "socket.io-client": "^4.2.0", "sortablejs": "^1.15.6", "svelte-sonner": "^0.3.19", @@ -10905,9 +10905,9 @@ } }, "node_modules/pyodide": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/pyodide/-/pyodide-0.27.7.tgz", - "integrity": "sha512-RUSVJlhQdfWfgO9hVHCiXoG+nVZQRS5D9FzgpLJ/VcgGBLSAKoPL8kTiOikxbHQm1kRISeWUBdulEgO26qpSRA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/pyodide/-/pyodide-0.28.2.tgz", + "integrity": "sha512-2BrZHrALvhYZfIuTGDHOvyiirHNLziHfBiBb1tpBFzLgAvDBb2ACxNPFFROCOzLnqapORmgArDYY8mJmMWH1Eg==", "license": "MPL-2.0", "dependencies": { "ws": "^8.5.0" diff --git a/package.json b/package.json index d4f736d598..3b3ed32d4b 100644 --- a/package.json +++ b/package.json @@ -126,7 +126,7 @@ "prosemirror-state": "^1.4.3", "prosemirror-tables": "^1.7.1", "prosemirror-view": "^1.34.3", - "pyodide": "^0.27.3", + "pyodide": "^0.28.2", "socket.io-client": "^4.2.0", "sortablejs": "^1.15.6", "svelte-sonner": "^0.3.19", From 4cd550d1d01b4183b683b3dec45ab417477abc2d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Sep 2025 06:02:31 +0000 Subject: [PATCH 034/154] build(deps): update pytest requirement in /backend Updates the requirements on [pytest](https://github.com/pytest-dev/pytest) to permit the latest version. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/8.3.5...8.4.1) --- updated-dependencies: - dependency-name: pytest dependency-version: 8.4.1 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- backend/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/requirements.txt b/backend/requirements.txt index 03eeba2a1e..fa7320920a 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -115,7 +115,7 @@ google-auth-oauthlib ## Tests docker~=7.1.0 -pytest~=8.3.5 +pytest~=8.4.1 pytest-docker~=3.1.1 googleapis-common-protos==1.63.2 From 201eaa965567188ccfc55aede2df822f7d25380f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Sep 2025 06:02:48 +0000 Subject: [PATCH 035/154] build(deps): bump @tiptap/extension-highlight from 3.0.7 to 3.3.0 Bumps [@tiptap/extension-highlight](https://github.com/ueberdosis/tiptap/tree/HEAD/packages/extension-highlight) from 3.0.7 to 3.3.0. - [Release notes](https://github.com/ueberdosis/tiptap/releases) - [Changelog](https://github.com/ueberdosis/tiptap/blob/develop/packages/extension-highlight/CHANGELOG.md) - [Commits](https://github.com/ueberdosis/tiptap/commits/v3.3.0/packages/extension-highlight) --- updated-dependencies: - dependency-name: "@tiptap/extension-highlight" dependency-version: 3.3.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package-lock.json | 10 +++++----- package.json | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index f5960587a1..ca187a29d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "@tiptap/extension-drag-handle": "^3.0.7", "@tiptap/extension-file-handler": "^3.0.7", "@tiptap/extension-floating-menu": "^2.26.1", - "@tiptap/extension-highlight": "^3.0.7", + "@tiptap/extension-highlight": "^3.3.0", "@tiptap/extension-image": "^3.0.7", "@tiptap/extension-link": "^3.0.7", "@tiptap/extension-list": "^3.0.7", @@ -3516,16 +3516,16 @@ } }, "node_modules/@tiptap/extension-highlight": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@tiptap/extension-highlight/-/extension-highlight-3.0.7.tgz", - "integrity": "sha512-3oIRuXAg7l9+VPIMwHycXcqtZ7XJcC5vnLhPAQXIesYun6L9EoXmQox0225z8jpPG70N8zfl+YSd4qjsTMPaAg==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-highlight/-/extension-highlight-3.3.0.tgz", + "integrity": "sha512-G+mHVXkoQ4uG97JRFN56qL42iJVKbSeWgDGssmnjNZN/W4Nsc40LuNryNbQUOM9CJbEMIT5NGAwvc/RG0OpGGQ==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.0.7" + "@tiptap/core": "^3.3.0" } }, "node_modules/@tiptap/extension-horizontal-rule": { diff --git a/package.json b/package.json index d4f736d598..e325784f08 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "@tiptap/extension-drag-handle": "^3.0.7", "@tiptap/extension-file-handler": "^3.0.7", "@tiptap/extension-floating-menu": "^2.26.1", - "@tiptap/extension-highlight": "^3.0.7", + "@tiptap/extension-highlight": "^3.3.0", "@tiptap/extension-image": "^3.0.7", "@tiptap/extension-link": "^3.0.7", "@tiptap/extension-list": "^3.0.7", From dbe36e841a0363fa9264c5e40d4bc9efddc561c0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Sep 2025 06:02:57 +0000 Subject: [PATCH 036/154] build(deps): bump actions/download-artifact from 4 to 5 Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 5. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/docker-build.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/docker-build.yaml b/.github/workflows/docker-build.yaml index e597ff8055..a9b930ee8c 100644 --- a/.github/workflows/docker-build.yaml +++ b/.github/workflows/docker-build.yaml @@ -535,7 +535,7 @@ jobs: IMAGE_NAME: '${{ github.repository }}' - name: Download digests - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: pattern: digests-main-* path: /tmp/digests @@ -589,7 +589,7 @@ jobs: IMAGE_NAME: '${{ github.repository }}' - name: Download digests - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: pattern: digests-cuda-* path: /tmp/digests @@ -645,7 +645,7 @@ jobs: IMAGE_NAME: '${{ github.repository }}' - name: Download digests - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: pattern: digests-cuda126-* path: /tmp/digests @@ -701,7 +701,7 @@ jobs: IMAGE_NAME: '${{ github.repository }}' - name: Download digests - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: pattern: digests-ollama-* path: /tmp/digests @@ -757,7 +757,7 @@ jobs: IMAGE_NAME: '${{ github.repository }}' - name: Download digests - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: pattern: digests-slim-* path: /tmp/digests From 4dd9484b48aa28b43918ce33326944a6c5e2314d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Sep 2025 06:03:14 +0000 Subject: [PATCH 037/154] build(deps): bump actions/checkout from 4 to 5 Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build-release.yml | 2 +- .github/workflows/deploy-to-hf-spaces.yml | 2 +- .github/workflows/docker-build.yaml | 10 +++++----- .github/workflows/format-backend.yaml | 2 +- .github/workflows/format-build-frontend.yaml | 4 ++-- .github/workflows/release-pypi.yml | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 443d904199..7d5e30e23e 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -11,7 +11,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Check for changes in package.json run: | diff --git a/.github/workflows/deploy-to-hf-spaces.yml b/.github/workflows/deploy-to-hf-spaces.yml index 7fc66acf5c..a30046af89 100644 --- a/.github/workflows/deploy-to-hf-spaces.yml +++ b/.github/workflows/deploy-to-hf-spaces.yml @@ -27,7 +27,7 @@ jobs: HF_TOKEN: ${{ secrets.HF_TOKEN }} steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: lfs: true diff --git a/.github/workflows/docker-build.yaml b/.github/workflows/docker-build.yaml index e597ff8055..f5eb544f1d 100644 --- a/.github/workflows/docker-build.yaml +++ b/.github/workflows/docker-build.yaml @@ -43,7 +43,7 @@ jobs: echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -142,7 +142,7 @@ jobs: echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -244,7 +244,7 @@ jobs: echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -347,7 +347,7 @@ jobs: echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -449,7 +449,7 @@ jobs: echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up QEMU uses: docker/setup-qemu-action@v3 diff --git a/.github/workflows/format-backend.yaml b/.github/workflows/format-backend.yaml index 1bcdd92c1d..56074a84f4 100644 --- a/.github/workflows/format-backend.yaml +++ b/.github/workflows/format-backend.yaml @@ -30,7 +30,7 @@ jobs: - 3.12.x steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Python uses: actions/setup-python@v5 diff --git a/.github/workflows/format-build-frontend.yaml b/.github/workflows/format-build-frontend.yaml index 15dc53cc63..df961ca3f5 100644 --- a/.github/workflows/format-build-frontend.yaml +++ b/.github/workflows/format-build-frontend.yaml @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup Node.js uses: actions/setup-node@v4 @@ -51,7 +51,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup Node.js uses: actions/setup-node@v4 diff --git a/.github/workflows/release-pypi.yml b/.github/workflows/release-pypi.yml index fd1adab3a9..c4ae97422d 100644 --- a/.github/workflows/release-pypi.yml +++ b/.github/workflows/release-pypi.yml @@ -16,7 +16,7 @@ jobs: id-token: write steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: Install Git From 9f5df72d7cb40d345937d0eeaee017a170812e29 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Sep 2025 06:16:43 +0000 Subject: [PATCH 038/154] build(deps): bump dompurify from 3.2.5 to 3.2.6 Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.2.5 to 3.2.6. - [Release notes](https://github.com/cure53/DOMPurify/releases) - [Commits](https://github.com/cure53/DOMPurify/compare/3.2.5...3.2.6) --- updated-dependencies: - dependency-name: dompurify dependency-version: 3.2.6 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index f5960587a1..f7b1ae1701 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,7 +46,7 @@ "codemirror-lang-hcl": "^0.1.0", "crc-32": "^1.2.2", "dayjs": "^1.11.10", - "dompurify": "^3.2.5", + "dompurify": "^3.2.6", "eventsource-parser": "^1.1.2", "file-saver": "^2.0.5", "focus-trap": "^7.6.4", @@ -6767,9 +6767,9 @@ } }, "node_modules/dompurify": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.5.tgz", - "integrity": "sha512-mLPd29uoRe9HpvwP2TxClGQBzGXeEC/we/q+bFlmPPmj2p2Ugl3r6ATu/UU1v77DXNcehiBg9zsr1dREyA/dJQ==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz", + "integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" diff --git a/package.json b/package.json index d4f736d598..28d345be2e 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,7 @@ "codemirror-lang-hcl": "^0.1.0", "crc-32": "^1.2.2", "dayjs": "^1.11.10", - "dompurify": "^3.2.5", + "dompurify": "^3.2.6", "eventsource-parser": "^1.1.2", "file-saver": "^2.0.5", "focus-trap": "^7.6.4", From 4b97884fce7152e78b22098bf6cb378088bb5aab Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 1 Sep 2025 11:46:52 +0400 Subject: [PATCH 039/154] refac --- backend/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/requirements.txt b/backend/requirements.txt index 03eeba2a1e..7d002d094b 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -65,8 +65,8 @@ transformers sentence-transformers==4.1.0 accelerate colbert-ai==0.2.21 -pyarrow==20.0.0 -einops==0.8.1 +pyarrow==20.0.0 # fix: pin pyarrow version to 20 for rpi compatibility #15897 +einops==0.8.1 ftfy==6.2.3 From cef4028c1c68b57cf9ad56e89ce1df252b41de43 Mon Sep 17 00:00:00 2001 From: _00_ <131402327+rgaricano@users.noreply.github.com> Date: Mon, 1 Sep 2025 09:48:05 +0200 Subject: [PATCH 040/154] UPD: i18n Translation es-ES v.0.6.27 ### Update of i18n Translation es-ES v.0.6.27 Added new strings --- src/lib/i18n/locales/es-ES/translation.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/lib/i18n/locales/es-ES/translation.json b/src/lib/i18n/locales/es-ES/translation.json index b90265dda4..454be0d79e 100644 --- a/src/lib/i18n/locales/es-ES/translation.json +++ b/src/lib/i18n/locales/es-ES/translation.json @@ -11,7 +11,7 @@ "{{ models }}": "{{ models }}", "{{COUNT}} Available Tools": "{{COUNT}} herramientas disponibles", "{{COUNT}} characters": "{{COUNT}} caracteres", - "{{COUNT}} extracted lines": "", + "{{COUNT}} extracted lines": "{{COUNT}} líneas extraidas", "{{COUNT}} hidden lines": "{{COUNT}} líneas ocultas", "{{COUNT}} Replies": "{{COUNT}} Respuestas", "{{COUNT}} words": "{{COUNT}} palabras", @@ -83,13 +83,13 @@ "Allow Chat Share": "Permitir Compartir Chat", "Allow Chat System Prompt": "Permitir Indicador del Sistema en Chat", "Allow Chat Valves": "Permitir Válvulas en Chat", - "Allow Continue Response": "", - "Allow Delete Messages": "", + "Allow Continue Response": "Permitir Continuar Respuesta", + "Allow Delete Messages": "Permitir Borrar Mensajes", "Allow File Upload": "Permitir Subida de Archivos", "Allow Multiple Models in Chat": "Permitir Chat con Múltiples Modelos", "Allow non-local voices": "Permitir voces no locales", - "Allow Rate Response": "", - "Allow Regenerate Response": "", + "Allow Rate Response": "Permitir Calificar Respuesta", + "Allow Regenerate Response": "Permitir Regenerar Respuesta", "Allow Speech to Text": "Permitir Transcribir Voz a Texto", "Allow Temporary Chat": "Permitir Chat Temporal", "Allow Text to Speech": "Permitir Leer Texto", @@ -430,7 +430,7 @@ "Docling Server URL required.": "Docling URL del servidor necesaria.", "Document": "Documento", "Document Intelligence": "Azure Doc Intelligence", - "Document Intelligence endpoint required.": "", + "Document Intelligence endpoint required.": "Endpoint Azure Doc Intelligence requerido", "Documentation": "Documentación", "Documents": "Documentos", "does not make any external connections, and your data stays securely on your locally hosted server.": "no se realiza ninguna conexión externa y tus datos permanecen seguros alojados localmente en tu servidor.", @@ -496,7 +496,7 @@ "Enable New Sign Ups": "Habilitar Registros de Nuevos Usuarios", "Enable, disable, or customize the reasoning tags used by the model. \"Enabled\" uses default tags, \"Disabled\" turns off reasoning tags, and \"Custom\" lets you specify your own start and end tags.": "", "Enabled": "Habilitado", - "End Tag": "", + "End Tag": "Etiqueta de Fin", "Endpoint URL": "Endpoint URL", "Enforce Temporary Chat": "Forzar el uso de Chat Temporal", "Enhance": "Mejorar", @@ -725,7 +725,7 @@ "Format Lines": "Formatear Líneas", "Format the lines in the output. Defaults to False. If set to True, the lines will be formatted to detect inline math and styles.": "Formatear las lineas en la salida. Por defecto, False. Si la opción es True, las líneas se formatearán detectando estilos e 'inline math'", "Format your variables using brackets like this:": "Formatea tus variables usando corchetes así:", - "Formatting may be inconsistent from source.": "", + "Formatting may be inconsistent from source.": "El formato puede ser inconsistente con el original", "Forwards system user session credentials to authenticate": "Reenvío de las credenciales de la sesión del usuario del sistema para autenticación", "Full Context Mode": "Modo Contexto Completo", "Function": "Función", @@ -1175,7 +1175,7 @@ "Read Aloud": "Leer en voz alta", "Reason": "Razonamiento", "Reasoning Effort": "Esfuerzo del Razonamiento", - "Reasoning Tags": "", + "Reasoning Tags": "Eriquetas de Razonamiento", "Record": "Grabar", "Record voice": "Grabar voz", "Redirecting you to Open WebUI Community": "Redireccionando a la Comunidad Open-WebUI", @@ -1376,7 +1376,7 @@ "Speech-to-Text": "Voz a Texto", "Speech-to-Text Engine": "Motor Voz a Texto(STT)", "Start of the channel": "Inicio del canal", - "Start Tag": "", + "Start Tag": "Etiqueta de Inicio", "STDOUT/STDERR": "STDOUT/STDERR", "Stop": "Detener", "Stop Generating": "Detener la Generación", From f56889c5c7f0cf1a501c05d35dfa614e4f8b6958 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 1 Sep 2025 14:14:20 +0400 Subject: [PATCH 041/154] fix: fillter exception handling --- backend/open_webui/main.py | 19 +++++++++++++------ backend/open_webui/utils/middleware.py | 6 +++--- src/lib/components/chat/Chat.svelte | 7 +++++++ 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index d24bd5dcf1..2245be5bb7 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -1519,7 +1519,7 @@ async def chat_completion( try: event_emitter = get_event_emitter(metadata) await event_emitter( - {"type": "task-cancelled"}, + {"type": "chat:tasks:cancel"}, ) except Exception as e: pass @@ -1535,14 +1535,21 @@ async def chat_completion( "error": {"content": str(e)}, }, ) + + event_emitter = get_event_emitter(metadata) + await event_emitter( + { + "type": "chat:message:error", + "data": {"error": {"content": str(e)}}, + } + ) + await event_emitter( + {"type": "chat:tasks:cancel"}, + ) + except: pass - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=str(e), - ) - if ( metadata.get("session_id") and metadata.get("chat_id") diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index 91a125aafb..c2378db885 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -885,7 +885,7 @@ async def process_chat_payload(request, form_data, user, metadata, model): extra_params=extra_params, ) except Exception as e: - raise Exception(f"Error: {e}") + raise Exception(f"{e}") features = form_data.pop("features", None) if features: @@ -1316,7 +1316,7 @@ async def process_chat_response( { "type": "chat:message:error", "data": {"error": {"content": error}}, - }, + } ) if "selected_model_id" in response_data: @@ -2624,7 +2624,7 @@ async def process_chat_response( await background_tasks_handler() except asyncio.CancelledError: log.warning("Task was cancelled!") - await event_emitter({"type": "task-cancelled"}) + await event_emitter({"type": "chat:tasks:cancel"}) if not ENABLE_REALTIME_CHAT_SAVE: # Save message in the database diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte index 8ad7da577e..f258207593 100644 --- a/src/lib/components/chat/Chat.svelte +++ b/src/lib/components/chat/Chat.svelte @@ -318,6 +318,13 @@ } } else if (type === 'chat:completion') { chatCompletionEventHandler(data, message, event.chat_id); + } else if (type === 'chat:tasks:cancel') { + taskIds = null; + const responseMessage = history.messages[history.currentId]; + // Set all response messages to done + for (const messageId of history.messages[responseMessage.parentId].childrenIds) { + history.messages[messageId].done = true; + } } else if (type === 'chat:message:delta' || type === 'message') { message.content += data.content; } else if (type === 'chat:message' || type === 'replace') { From 85153afda8848f9591e04c877eb5989a0d50c9cd Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 1 Sep 2025 14:21:17 +0400 Subject: [PATCH 042/154] refac --- backend/open_webui/retrieval/utils.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/open_webui/retrieval/utils.py b/backend/open_webui/retrieval/utils.py index 4ef6dbce3b..eb88ad580a 100644 --- a/backend/open_webui/retrieval/utils.py +++ b/backend/open_webui/retrieval/utils.py @@ -128,12 +128,12 @@ def query_doc_with_hybrid_search( log.warning(f"query_doc_with_hybrid_search:no_docs {collection_name}") return {"documents": [], "metadatas": [], "distances": []} - log.debug(f"query_doc_with_hybrid_search:doc {collection_name}") - bm25_retriever = BM25Retriever.from_texts( - texts=collection_result.documents[0], - metadatas=collection_result.metadatas[0], - ) - bm25_retriever.k = k + log.debug(f"query_doc_with_hybrid_search:doc {collection_name}") + bm25_retriever = BM25Retriever.from_texts( + texts=collection_result.documents[0], + metadatas=collection_result.metadatas[0], + ) + bm25_retriever.k = k vector_search_retriever = VectorSearchRetriever( collection_name=collection_name, From 609a6a3721e33993a0f1e925cd9ff6243d3be718 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 1 Sep 2025 14:22:02 +0400 Subject: [PATCH 043/154] refac --- backend/open_webui/retrieval/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/open_webui/retrieval/utils.py b/backend/open_webui/retrieval/utils.py index eb88ad580a..87ea090daf 100644 --- a/backend/open_webui/retrieval/utils.py +++ b/backend/open_webui/retrieval/utils.py @@ -129,6 +129,7 @@ def query_doc_with_hybrid_search( return {"documents": [], "metadatas": [], "distances": []} log.debug(f"query_doc_with_hybrid_search:doc {collection_name}") + bm25_retriever = BM25Retriever.from_texts( texts=collection_result.documents[0], metadatas=collection_result.metadatas[0], From 4f2e426fc701a80cc76e8dc1b377f8b4ac0cc6b1 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 1 Sep 2025 14:27:20 +0400 Subject: [PATCH 044/154] refac --- backend/open_webui/retrieval/utils.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/backend/open_webui/retrieval/utils.py b/backend/open_webui/retrieval/utils.py index 87ea090daf..dead8458cb 100644 --- a/backend/open_webui/retrieval/utils.py +++ b/backend/open_webui/retrieval/utils.py @@ -499,9 +499,7 @@ def get_sources_from_items( "documents": [ [item.get("file", {}).get("data", {}).get("content")] ], - "metadatas": [ - [item.get("file", {}).get("data", {}).get("meta", {})] - ], + "metadatas": [[item.get("file", {}).get("meta", {})]], } else: # Fallback to item content From e6daad2ab9a7ae5ad5697444275f81dacbb064af Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 1 Sep 2025 22:45:06 +0400 Subject: [PATCH 045/154] chore: bump mermaid --- package-lock.json | 30 +++++++++++++++--------------- package.json | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4e731101b6..7a9dbea76d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -66,7 +66,7 @@ "leaflet": "^1.9.4", "lowlight": "^3.3.0", "marked": "^9.1.0", - "mermaid": "^11.6.0", + "mermaid": "^11.10.1", "paneforge": "^0.0.6", "panzoom": "^9.4.3", "pdfjs-dist": "^5.4.149", @@ -2224,9 +2224,9 @@ } }, "node_modules/@mermaid-js/parser": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.4.0.tgz", - "integrity": "sha512-wla8XOWvQAwuqy+gxiZqY+c7FokraOTHRWMsbB4AgRx9Sy7zKslNyejy7E+a77qHfey5GXw/ik3IXv/NHMJgaA==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.6.2.tgz", + "integrity": "sha512-+PO02uGF6L6Cs0Bw8RpGhikVvMWEysfAyl27qTlroUB8jSWr1lL0Sf6zi78ZxlSnmgSY2AMMKVgghnN9jTtwkQ==", "license": "MIT", "dependencies": { "langium": "3.3.1" @@ -9577,14 +9577,14 @@ } }, "node_modules/mermaid": { - "version": "11.6.0", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.6.0.tgz", - "integrity": "sha512-PE8hGUy1LDlWIHWBP05SFdqUHGmRcCcK4IzpOKPE35eOw+G9zZgcnMpyunJVUEOgb//KBORPjysKndw8bFLuRg==", + "version": "11.10.1", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.10.1.tgz", + "integrity": "sha512-0PdeADVWURz7VMAX0+MiMcgfxFKY4aweSGsjgFihe3XlMKNqmai/cugMrqTd3WNHM93V+K+AZL6Wu6tB5HmxRw==", "license": "MIT", "dependencies": { "@braintree/sanitize-url": "^7.0.4", "@iconify/utils": "^2.1.33", - "@mermaid-js/parser": "^0.4.0", + "@mermaid-js/parser": "^0.6.2", "@types/d3": "^7.4.3", "cytoscape": "^3.29.3", "cytoscape-cose-bilkent": "^4.1.0", @@ -9593,11 +9593,11 @@ "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.11", "dayjs": "^1.11.13", - "dompurify": "^3.2.4", - "katex": "^0.16.9", + "dompurify": "^3.2.5", + "katex": "^0.16.22", "khroma": "^2.1.0", "lodash-es": "^4.17.21", - "marked": "^15.0.7", + "marked": "^16.0.0", "roughjs": "^4.6.6", "stylis": "^4.3.6", "ts-dedent": "^2.2.0", @@ -9605,15 +9605,15 @@ } }, "node_modules/mermaid/node_modules/marked": { - "version": "15.0.8", - "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.8.tgz", - "integrity": "sha512-rli4l2LyZqpQuRve5C0rkn6pj3hT8EWPC+zkAxFTAJLxRbENfTAhEQq9itrmf1Y81QtAX5D/MYlGlIomNgj9lA==", + "version": "16.2.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-16.2.1.tgz", + "integrity": "sha512-r3UrXED9lMlHF97jJByry90cwrZBBvZmjG1L68oYfuPMW+uDTnuMbyJDymCWwbTE+f+3LhpNDKfpR3a3saFyjA==", "license": "MIT", "bin": { "marked": "bin/marked.js" }, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/mermaid/node_modules/uuid": { diff --git a/package.json b/package.json index 1be82e605b..ba031b04d9 100644 --- a/package.json +++ b/package.json @@ -110,7 +110,7 @@ "leaflet": "^1.9.4", "lowlight": "^3.3.0", "marked": "^9.1.0", - "mermaid": "^11.6.0", + "mermaid": "^11.10.1", "paneforge": "^0.0.6", "panzoom": "^9.4.3", "pdfjs-dist": "^5.4.149", From e830b4959ecd4b2795e29e53026984a58a7696a9 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 1 Sep 2025 22:49:49 +0400 Subject: [PATCH 046/154] enh: llama cpp timing stats --- backend/open_webui/utils/middleware.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index c2378db885..fa5634edbd 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -1964,6 +1964,10 @@ async def process_chat_response( } ) usage = data.get("usage", {}) + usage.update( + data.get("timing", {}) + ) # llama.cpp + if usage: await event_emitter( { From 513cab94b107679e3ca4af4f1d2160496758cd9d Mon Sep 17 00:00:00 2001 From: Chanwoo An Date: Tue, 2 Sep 2025 20:53:04 +0900 Subject: [PATCH 047/154] fix: prevent double-save race by awaiting API calls and adding isSaving guard --- .../workspace/Knowledge/KnowledgeBase.svelte | 60 +++++++++++-------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/src/lib/components/workspace/Knowledge/KnowledgeBase.svelte b/src/lib/components/workspace/Knowledge/KnowledgeBase.svelte index 5608ff0b74..179f9cfdfa 100644 --- a/src/lib/components/workspace/Knowledge/KnowledgeBase.svelte +++ b/src/lib/components/workspace/Knowledge/KnowledgeBase.svelte @@ -115,6 +115,7 @@ let debounceTimeout = null; let mediaQuery; let dragged = false; + let isSaving = false; const createFileFromText = (name, content) => { const blob = new Blob([content], { type: 'text/plain' }); @@ -434,27 +435,34 @@ }; const updateFileContentHandler = async () => { - const fileId = selectedFile.id; - const content = selectedFileContent; - - // Clear the cache for this file since we're updating it - fileContentCache.delete(fileId); - - const res = updateFileDataContentById(localStorage.token, fileId, content).catch((e) => { - toast.error(`${e}`); - }); - - const updatedKnowledge = await updateFileFromKnowledgeById( - localStorage.token, - id, - fileId - ).catch((e) => { - toast.error(`${e}`); - }); - - if (res && updatedKnowledge) { - knowledge = updatedKnowledge; - toast.success($i18n.t('File content updated successfully.')); + if (isSaving) { + console.log('Save operation already in progress, skipping...'); + return; + } + isSaving = true; + try { + const fileId = selectedFile.id; + const content = selectedFileContent; + // Clear the cache for this file since we're updating it + fileContentCache.delete(fileId); + const res = await updateFileDataContentById(localStorage.token, fileId, content).catch( + (e) => { + toast.error(`${e}`); + } + ); + const updatedKnowledge = await updateFileFromKnowledgeById( + localStorage.token, + id, + fileId + ).catch((e) => { + toast.error(`${e}`); + }); + if (res && updatedKnowledge) { + knowledge = updatedKnowledge; + toast.success($i18n.t('File content updated successfully.')); + } + } finally { + isSaving = false; } }; @@ -779,12 +787,13 @@
@@ -836,12 +845,13 @@
From 182408a52ed1f12f1f292a67cb04bd358217cdb5 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Tue, 2 Sep 2025 17:03:59 +0400 Subject: [PATCH 048/154] refac --- src/lib/components/notes/NoteEditor.svelte | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/components/notes/NoteEditor.svelte b/src/lib/components/notes/NoteEditor.svelte index c580b14e9a..4cd1e6b681 100644 --- a/src/lib/components/notes/NoteEditor.svelte +++ b/src/lib/components/notes/NoteEditor.svelte @@ -978,7 +978,6 @@ Provide the enhanced notes in markdown format. Use markdown syntax for headings, disabled={(note?.user_id !== $user?.id && $user?.role !== 'admin') || titleGenerating} required - on:input={changeDebounceHandler} on:focus={() => { titleInputFocused = true; }} From bc6afc905706afce70a235ba4ada78c7468ec360 Mon Sep 17 00:00:00 2001 From: Andreas Fuerer Date: Tue, 2 Sep 2025 15:37:15 +0200 Subject: [PATCH 049/154] feature: Azure OpenAI image generation support The image generation API used on Azure OpenAI requires to specify the API version by appending an `api-version` query parameter to the endpoint URL. Added the environment variable `IMAGES_OPENAI_API_VERSION` with configuration functionality in the administration UI. --- backend/open_webui/config.py | 6 +++ backend/open_webui/main.py | 2 + backend/open_webui/routers/images.py | 12 ++++- .../components/admin/Settings/Images.svelte | 48 ++++++++++++++----- 4 files changed, 54 insertions(+), 14 deletions(-) diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index d62b219668..069faab439 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -3100,6 +3100,12 @@ IMAGES_OPENAI_API_BASE_URL = PersistentConfig( "image_generation.openai.api_base_url", os.getenv("IMAGES_OPENAI_API_BASE_URL", OPENAI_API_BASE_URL), ) +IMAGES_OPENAI_API_VERSION = PersistentConfig( + "IMAGES_OPENAI_API_VERSION", + "image_generation.openai.api_version", + os.getenv("IMAGES_OPENAI_API_VERSION", ""), +) + IMAGES_OPENAI_API_KEY = PersistentConfig( "IMAGES_OPENAI_API_KEY", "image_generation.openai.api_key", diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index 2245be5bb7..e8c0e267fc 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -157,6 +157,7 @@ from open_webui.config import ( IMAGE_SIZE, IMAGE_STEPS, IMAGES_OPENAI_API_BASE_URL, + IMAGES_OPENAI_API_VERSION, IMAGES_OPENAI_API_KEY, IMAGES_GEMINI_API_BASE_URL, IMAGES_GEMINI_API_KEY, @@ -1019,6 +1020,7 @@ app.state.config.ENABLE_IMAGE_GENERATION = ENABLE_IMAGE_GENERATION app.state.config.ENABLE_IMAGE_PROMPT_GENERATION = ENABLE_IMAGE_PROMPT_GENERATION app.state.config.IMAGES_OPENAI_API_BASE_URL = IMAGES_OPENAI_API_BASE_URL +app.state.config.IMAGES_OPENAI_API_VERSION = IMAGES_OPENAI_API_VERSION app.state.config.IMAGES_OPENAI_API_KEY = IMAGES_OPENAI_API_KEY app.state.config.IMAGES_GEMINI_API_BASE_URL = IMAGES_GEMINI_API_BASE_URL diff --git a/backend/open_webui/routers/images.py b/backend/open_webui/routers/images.py index 9311cb6e2c..f10a9e1690 100644 --- a/backend/open_webui/routers/images.py +++ b/backend/open_webui/routers/images.py @@ -48,6 +48,7 @@ async def get_config(request: Request, user=Depends(get_admin_user)): "prompt_generation": request.app.state.config.ENABLE_IMAGE_PROMPT_GENERATION, "openai": { "OPENAI_API_BASE_URL": request.app.state.config.IMAGES_OPENAI_API_BASE_URL, + "OPENAI_API_VERSION": request.app.state.config.IMAGES_OPENAI_API_VERSION, "OPENAI_API_KEY": request.app.state.config.IMAGES_OPENAI_API_KEY, }, "automatic1111": { @@ -72,6 +73,7 @@ async def get_config(request: Request, user=Depends(get_admin_user)): class OpenAIConfigForm(BaseModel): OPENAI_API_BASE_URL: str + OPENAI_API_VERSION: str OPENAI_API_KEY: str @@ -119,6 +121,9 @@ async def update_config( request.app.state.config.IMAGES_OPENAI_API_BASE_URL = ( form_data.openai.OPENAI_API_BASE_URL ) + request.app.state.config.IMAGES_OPENAI_API_VERSION = ( + form_data.openai.OPENAI_API_VERSION + ) request.app.state.config.IMAGES_OPENAI_API_KEY = form_data.openai.OPENAI_API_KEY request.app.state.config.IMAGES_GEMINI_API_BASE_URL = ( @@ -165,6 +170,7 @@ async def update_config( "prompt_generation": request.app.state.config.ENABLE_IMAGE_PROMPT_GENERATION, "openai": { "OPENAI_API_BASE_URL": request.app.state.config.IMAGES_OPENAI_API_BASE_URL, + "OPENAI_API_VERSION": request.app.state.config.IMAGES_OPENAI_API_VERSION, "OPENAI_API_KEY": request.app.state.config.IMAGES_OPENAI_API_KEY, }, "automatic1111": { @@ -543,11 +549,15 @@ async def image_generations( else {"response_format": "b64_json"} ), } + + api_version_query_param = "" + if (request.app.state.config.IMAGES_OPENAI_API_VERSION): + api_version_query_param = f"?api-version={request.app.state.config.IMAGES_OPENAI_API_VERSION}" # Use asyncio.to_thread for the requests.post call r = await asyncio.to_thread( requests.post, - url=f"{request.app.state.config.IMAGES_OPENAI_API_BASE_URL}/images/generations", + url=f"{request.app.state.config.IMAGES_OPENAI_API_BASE_URL}/images/generations{api_version_query_param}", json=data, headers=headers, ) diff --git a/src/lib/components/admin/Settings/Images.svelte b/src/lib/components/admin/Settings/Images.svelte index 100ec7ad22..37a4092231 100644 --- a/src/lib/components/admin/Settings/Images.svelte +++ b/src/lib/components/admin/Settings/Images.svelte @@ -599,20 +599,42 @@ {/if} {:else if config?.engine === 'openai'}
-
{$i18n.t('OpenAI API Config')}
+
{$i18n.t('OpenAI API Config')}
+
+
+ +
+
+
-
- - - +
+
{$i18n.t('API Key')}
+
+
+ +
+
+
+ +
+
{$i18n.t('API Version')}
+
+
+ +
{:else if config?.engine === 'gemini'} From df0d29c81c83d598aa868275e4362f30678c73a2 Mon Sep 17 00:00:00 2001 From: Andreas Fuerer Date: Tue, 2 Sep 2025 19:15:32 +0200 Subject: [PATCH 050/154] style: fix formatting issues --- backend/open_webui/routers/images.py | 8 +++++--- src/lib/components/admin/Settings/Images.svelte | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/backend/open_webui/routers/images.py b/backend/open_webui/routers/images.py index f10a9e1690..802a3e9924 100644 --- a/backend/open_webui/routers/images.py +++ b/backend/open_webui/routers/images.py @@ -549,10 +549,12 @@ async def image_generations( else {"response_format": "b64_json"} ), } - + api_version_query_param = "" - if (request.app.state.config.IMAGES_OPENAI_API_VERSION): - api_version_query_param = f"?api-version={request.app.state.config.IMAGES_OPENAI_API_VERSION}" + if request.app.state.config.IMAGES_OPENAI_API_VERSION: + api_version_query_param = ( + f"?api-version={request.app.state.config.IMAGES_OPENAI_API_VERSION}" + ) # Use asyncio.to_thread for the requests.post call r = await asyncio.to_thread( diff --git a/src/lib/components/admin/Settings/Images.svelte b/src/lib/components/admin/Settings/Images.svelte index 37a4092231..0c19e0ba71 100644 --- a/src/lib/components/admin/Settings/Images.svelte +++ b/src/lib/components/admin/Settings/Images.svelte @@ -624,7 +624,7 @@
- +
{$i18n.t('API Version')}
From 22c4ef4fb096498066b73befe993ae3a82f7a8e7 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Tue, 2 Sep 2025 21:32:07 +0400 Subject: [PATCH 051/154] enh: delete_file query param --- backend/open_webui/routers/knowledge.py | 26 +++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/backend/open_webui/routers/knowledge.py b/backend/open_webui/routers/knowledge.py index e9ba9c39ad..10af496579 100644 --- a/backend/open_webui/routers/knowledge.py +++ b/backend/open_webui/routers/knowledge.py @@ -1,6 +1,6 @@ from typing import List, Optional from pydantic import BaseModel -from fastapi import APIRouter, Depends, HTTPException, status, Request +from fastapi import APIRouter, Depends, HTTPException, status, Request, Query import logging from open_webui.models.knowledge import ( @@ -492,6 +492,7 @@ def update_file_from_knowledge_by_id( def remove_file_from_knowledge_by_id( id: str, form_data: KnowledgeFileIdForm, + delete_file: bool = Query(True), user=Depends(get_verified_user), ): knowledge = Knowledges.get_knowledge_by_id(id=id) @@ -528,18 +529,19 @@ def remove_file_from_knowledge_by_id( log.debug(e) pass - try: - # Remove the file's collection from vector database - file_collection = f"file-{form_data.file_id}" - if VECTOR_DB_CLIENT.has_collection(collection_name=file_collection): - VECTOR_DB_CLIENT.delete_collection(collection_name=file_collection) - except Exception as e: - log.debug("This was most likely caused by bypassing embedding processing") - log.debug(e) - pass + if delete_file: + try: + # Remove the file's collection from vector database + file_collection = f"file-{form_data.file_id}" + if VECTOR_DB_CLIENT.has_collection(collection_name=file_collection): + VECTOR_DB_CLIENT.delete_collection(collection_name=file_collection) + except Exception as e: + log.debug("This was most likely caused by bypassing embedding processing") + log.debug(e) + pass - # Delete file from database - Files.delete_file_by_id(form_data.file_id) + # Delete file from database + Files.delete_file_by_id(form_data.file_id) if knowledge: data = knowledge.data or {} From a980af3ca46d05e6959aacdc324a032bbfcb972b Mon Sep 17 00:00:00 2001 From: viruz Date: Tue, 2 Sep 2025 10:30:19 -0300 Subject: [PATCH 052/154] fix: update default name to use i18n translation --- .../Knowledge/KnowledgeBase/AddTextContentModal.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/components/workspace/Knowledge/KnowledgeBase/AddTextContentModal.svelte b/src/lib/components/workspace/Knowledge/KnowledgeBase/AddTextContentModal.svelte index 2e288c6d7e..83844e2c76 100644 --- a/src/lib/components/workspace/Knowledge/KnowledgeBase/AddTextContentModal.svelte +++ b/src/lib/components/workspace/Knowledge/KnowledgeBase/AddTextContentModal.svelte @@ -14,7 +14,7 @@ import VoiceRecording from '$lib/components/chat/MessageInput/VoiceRecording.svelte'; export let show = false; - let name = 'Untitled'; + let name = $i18n.t('Untitled'); let content = ''; let voiceInput = false; From 3ccbb4693809f12380daa5c1e7e180f9afe45310 Mon Sep 17 00:00:00 2001 From: Sihyeon Jang Date: Wed, 3 Sep 2025 05:17:41 +0900 Subject: [PATCH 053/154] perf: fix cache key generation for model list caching - Replace Request object with user.id in cache key for get_all_models - Request objects are new instances per HTTP request, preventing cache hits - Cache keys now use user.id ensuring proper cache functionality - Affects both Ollama and OpenAI model list endpoints Signed-off-by: Sihyeon Jang --- backend/open_webui/routers/ollama.py | 2 +- backend/open_webui/routers/openai.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/open_webui/routers/ollama.py b/backend/open_webui/routers/ollama.py index 1a6b75c555..4c5cdce8ca 100644 --- a/backend/open_webui/routers/ollama.py +++ b/backend/open_webui/routers/ollama.py @@ -340,7 +340,7 @@ def merge_ollama_models_lists(model_lists): return list(merged_models.values()) -@cached(ttl=MODELS_CACHE_TTL) +@cached(ttl=MODELS_CACHE_TTL, key=lambda _, user: f"ollama_all_models_{user.id}" if user else "ollama_all_models") async def get_all_models(request: Request, user: UserModel = None): log.info("get_all_models()") if request.app.state.config.ENABLE_OLLAMA_API: diff --git a/backend/open_webui/routers/openai.py b/backend/open_webui/routers/openai.py index 7ba0c5f68a..a94791bdf5 100644 --- a/backend/open_webui/routers/openai.py +++ b/backend/open_webui/routers/openai.py @@ -401,7 +401,7 @@ async def get_filtered_models(models, user): return filtered_models -@cached(ttl=MODELS_CACHE_TTL) +@cached(ttl=MODELS_CACHE_TTL, key=lambda _, user: f"openai_all_models_{user.id}" if user else "openai_all_models") async def get_all_models(request: Request, user: UserModel) -> dict[str, list]: log.info("get_all_models()") From c45201a8a2a31a4faaa61cd038a701e83d8dbe0c Mon Sep 17 00:00:00 2001 From: Sihyeon Jang Date: Wed, 3 Sep 2025 05:25:26 +0900 Subject: [PATCH 054/154] perf: fix N+1 query issue in get_prompts method - Replace individual user queries with batch fetching - Use single query to fetch all required users at once - Implement O(1) user lookup with dictionary mapping - Reduce query count from 1+N to 1+1 pattern Signed-off-by: Sihyeon Jang --- backend/open_webui/models/prompts.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/backend/open_webui/models/prompts.py b/backend/open_webui/models/prompts.py index 8ef4cd2bec..706f0096ca 100644 --- a/backend/open_webui/models/prompts.py +++ b/backend/open_webui/models/prompts.py @@ -103,10 +103,16 @@ class PromptsTable: def get_prompts(self) -> list[PromptUserResponse]: with get_db() as db: - prompts = [] + all_prompts = db.query(Prompt).order_by(Prompt.timestamp.desc()).all() - for prompt in db.query(Prompt).order_by(Prompt.timestamp.desc()).all(): - user = Users.get_user_by_id(prompt.user_id) + user_ids = list(set(prompt.user_id for prompt in all_prompts)) + + users = Users.get_users_by_user_ids(user_ids) if user_ids else [] + users_dict = {user.id: user for user in users} + + prompts = [] + for prompt in all_prompts: + user = users_dict.get(prompt.user_id) prompts.append( PromptUserResponse.model_validate( { From f588655f7f4e72f861b96f3278e31f59b6330a66 Mon Sep 17 00:00:00 2001 From: Sihyeon Jang Date: Wed, 3 Sep 2025 05:29:47 +0900 Subject: [PATCH 055/154] perf: fix N+1 query issue in get_knowledge_bases method - Replace individual user queries with batch fetching - Use single query to fetch all required users at once - Implement O(1) user lookup with dictionary mapping - Reduce query count from 1+N to 1+1 pattern Signed-off-by: Sihyeon Jang --- backend/open_webui/models/knowledge.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/backend/open_webui/models/knowledge.py b/backend/open_webui/models/knowledge.py index bed3d5542e..29db82e408 100644 --- a/backend/open_webui/models/knowledge.py +++ b/backend/open_webui/models/knowledge.py @@ -128,11 +128,16 @@ class KnowledgeTable: def get_knowledge_bases(self) -> list[KnowledgeUserModel]: with get_db() as db: + all_knowledge = db.query(Knowledge).order_by(Knowledge.updated_at.desc()).all() + + user_ids = list(set(knowledge.user_id for knowledge in all_knowledge)) + + users = Users.get_users_by_user_ids(user_ids) if user_ids else [] + users_dict = {user.id: user for user in users} + knowledge_bases = [] - for knowledge in ( - db.query(Knowledge).order_by(Knowledge.updated_at.desc()).all() - ): - user = Users.get_user_by_id(knowledge.user_id) + for knowledge in all_knowledge: + user = users_dict.get(knowledge.user_id) knowledge_bases.append( KnowledgeUserModel.model_validate( { From c0b3db38a5cf0c5225f84d14045ea05db8e9524b Mon Sep 17 00:00:00 2001 From: Sihyeon Jang Date: Wed, 3 Sep 2025 05:33:41 +0900 Subject: [PATCH 056/154] perf: fix N+1 query issue in get_models method - Replace individual user queries with batch fetching - Use single query to fetch all required users at once - Implement O(1) user lookup with dictionary mapping - Reduce query count from 1+N to 1+1 pattern for models with base_model_id Signed-off-by: Sihyeon Jang --- backend/open_webui/models/models.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/backend/open_webui/models/models.py b/backend/open_webui/models/models.py index 1a29b86eae..e450e65ed9 100755 --- a/backend/open_webui/models/models.py +++ b/backend/open_webui/models/models.py @@ -175,9 +175,16 @@ class ModelsTable: def get_models(self) -> list[ModelUserResponse]: with get_db() as db: + all_models = db.query(Model).filter(Model.base_model_id != None).all() + + user_ids = list(set(model.user_id for model in all_models)) + + users = Users.get_users_by_user_ids(user_ids) if user_ids else [] + users_dict = {user.id: user for user in users} + models = [] - for model in db.query(Model).filter(Model.base_model_id != None).all(): - user = Users.get_user_by_id(model.user_id) + for model in all_models: + user = users_dict.get(model.user_id) models.append( ModelUserResponse.model_validate( { From 03d1d2a88b12043086d489090bb9c14594f7de68 Mon Sep 17 00:00:00 2001 From: Sihyeon Jang Date: Wed, 3 Sep 2025 05:35:35 +0900 Subject: [PATCH 057/154] perf: fix N+1 query issue in get_tools method - Replace individual user queries with batch fetching - Use single query to fetch all required users at once - Implement O(1) user lookup with dictionary mapping - Reduce query count from 1+N to 1+1 pattern for tools listing Signed-off-by: Sihyeon Jang --- backend/open_webui/models/tools.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/backend/open_webui/models/tools.py b/backend/open_webui/models/tools.py index 7f1409a900..29fc064c4b 100644 --- a/backend/open_webui/models/tools.py +++ b/backend/open_webui/models/tools.py @@ -144,9 +144,16 @@ class ToolsTable: def get_tools(self) -> list[ToolUserModel]: with get_db() as db: + all_tools = db.query(Tool).order_by(Tool.updated_at.desc()).all() + + user_ids = list(set(tool.user_id for tool in all_tools)) + + users = Users.get_users_by_user_ids(user_ids) if user_ids else [] + users_dict = {user.id: user for user in users} + tools = [] - for tool in db.query(Tool).order_by(Tool.updated_at.desc()).all(): - user = Users.get_user_by_id(tool.user_id) + for tool in all_tools: + user = users_dict.get(tool.user_id) tools.append( ToolUserModel.model_validate( { From 0503fbd2e347f34353f4cde218eee70ff67928a4 Mon Sep 17 00:00:00 2001 From: Sihyeon Jang Date: Wed, 3 Sep 2025 05:49:53 +0900 Subject: [PATCH 058/154] perf: fix N+1 query issue in tools access control checking - Pre-fetch user group IDs once per request in get_tools endpoint - Pass user_group_ids to has_access to avoid repeated group queries - Optimize access control validation from 1+N to 1+1 query pattern - Reduce database load when checking multiple tools access permissions Signed-off-by: Sihyeon Jang --- backend/open_webui/routers/tools.py | 4 +++- backend/open_webui/utils/access_control.py | 9 ++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/backend/open_webui/routers/tools.py b/backend/open_webui/routers/tools.py index c017233765..5f82e7f1bd 100644 --- a/backend/open_webui/routers/tools.py +++ b/backend/open_webui/routers/tools.py @@ -4,6 +4,7 @@ from typing import Optional import time import re import aiohttp +from open_webui.models.groups import Groups from pydantic import BaseModel, HttpUrl from fastapi import APIRouter, Depends, HTTPException, Request, status @@ -71,11 +72,12 @@ async def get_tools(request: Request, user=Depends(get_verified_user)): # Admin can see all tools return tools else: + user_group_ids = {group.id for group in Groups.get_groups_by_member_id(user.id)} tools = [ tool for tool in tools if tool.user_id == user.id - or has_access(user.id, "read", tool.access_control) + or has_access(user.id, "read", tool.access_control, user_group_ids) ] return tools diff --git a/backend/open_webui/utils/access_control.py b/backend/open_webui/utils/access_control.py index c36d861ad6..1529773c44 100644 --- a/backend/open_webui/utils/access_control.py +++ b/backend/open_webui/utils/access_control.py @@ -1,4 +1,4 @@ -from typing import Optional, Union, List, Dict, Any +from typing import Optional, Set, Union, List, Dict, Any from open_webui.models.users import Users, UserModel from open_webui.models.groups import Groups @@ -109,12 +109,15 @@ def has_access( user_id: str, type: str = "write", access_control: Optional[dict] = None, + user_group_ids: Optional[Set[str]] = None, ) -> bool: if access_control is None: return type == "read" - user_groups = Groups.get_groups_by_member_id(user_id) - user_group_ids = [group.id for group in user_groups] + if user_group_ids is None: + user_groups = Groups.get_groups_by_member_id(user_id) + user_group_ids = {group.id for group in user_groups} + permission_access = access_control.get(type, {}) permitted_group_ids = permission_access.get("group_ids", []) permitted_user_ids = permission_access.get("user_ids", []) From eff06538a65cb9685dcdf00c536e13f4996d83c2 Mon Sep 17 00:00:00 2001 From: Sihyeon Jang Date: Wed, 3 Sep 2025 05:56:48 +0900 Subject: [PATCH 059/154] perf: fix N+1 query issues in user group access control validation - Pre-fetch user group IDs in get_*_by_user_id methods across models layer - Pass user_group_ids to has_access to avoid repeated group queries - Reduce query count from 1+N to 1+1 pattern for access control validation - Apply consistent optimization across knowledge, models, notes, prompts, and tools Signed-off-by: Sihyeon Jang --- backend/open_webui/models/knowledge.py | 4 +++- backend/open_webui/models/models.py | 4 +++- backend/open_webui/models/notes.py | 4 +++- backend/open_webui/models/prompts.py | 4 +++- backend/open_webui/models/tools.py | 3 ++- 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/backend/open_webui/models/knowledge.py b/backend/open_webui/models/knowledge.py index bed3d5542e..c0f68cf34d 100644 --- a/backend/open_webui/models/knowledge.py +++ b/backend/open_webui/models/knowledge.py @@ -8,6 +8,7 @@ from open_webui.internal.db import Base, get_db from open_webui.env import SRC_LOG_LEVELS from open_webui.models.files import FileMetadataResponse +from open_webui.models.groups import Groups from open_webui.models.users import Users, UserResponse @@ -147,11 +148,12 @@ class KnowledgeTable: self, user_id: str, permission: str = "write" ) -> list[KnowledgeUserModel]: knowledge_bases = self.get_knowledge_bases() + user_group_ids = {group.id for group in Groups.get_groups_by_member_id(user_id)} return [ knowledge_base for knowledge_base in knowledge_bases if knowledge_base.user_id == user_id - or has_access(user_id, permission, knowledge_base.access_control) + or has_access(user_id, permission, knowledge_base.access_control, user_group_ids) ] def get_knowledge_by_id(self, id: str) -> Optional[KnowledgeModel]: diff --git a/backend/open_webui/models/models.py b/backend/open_webui/models/models.py index 1a29b86eae..defc02572d 100755 --- a/backend/open_webui/models/models.py +++ b/backend/open_webui/models/models.py @@ -5,6 +5,7 @@ from typing import Optional from open_webui.internal.db import Base, JSONField, get_db from open_webui.env import SRC_LOG_LEVELS +from open_webui.models.groups import Groups from open_webui.models.users import Users, UserResponse @@ -199,11 +200,12 @@ class ModelsTable: self, user_id: str, permission: str = "write" ) -> list[ModelUserResponse]: models = self.get_models() + user_group_ids = {group.id for group in Groups.get_groups_by_member_id(user_id)} return [ model for model in models if model.user_id == user_id - or has_access(user_id, permission, model.access_control) + or has_access(user_id, permission, model.access_control, user_group_ids) ] def get_model_by_id(self, id: str) -> Optional[ModelModel]: diff --git a/backend/open_webui/models/notes.py b/backend/open_webui/models/notes.py index ce3b9f2e20..c720ff80a4 100644 --- a/backend/open_webui/models/notes.py +++ b/backend/open_webui/models/notes.py @@ -4,6 +4,7 @@ import uuid from typing import Optional from open_webui.internal.db import Base, get_db +from open_webui.models.groups import Groups from open_webui.utils.access_control import has_access from open_webui.models.users import Users, UserResponse @@ -105,11 +106,12 @@ class NoteTable: self, user_id: str, permission: str = "write" ) -> list[NoteModel]: notes = self.get_notes() + user_group_ids = {group.id for group in Groups.get_groups_by_member_id(user_id)} return [ note for note in notes if note.user_id == user_id - or has_access(user_id, permission, note.access_control) + or has_access(user_id, permission, note.access_control, user_group_ids) ] def get_note_by_id(self, id: str) -> Optional[NoteModel]: diff --git a/backend/open_webui/models/prompts.py b/backend/open_webui/models/prompts.py index 8ef4cd2bec..ef85c047a0 100644 --- a/backend/open_webui/models/prompts.py +++ b/backend/open_webui/models/prompts.py @@ -2,6 +2,7 @@ import time from typing import Optional from open_webui.internal.db import Base, get_db +from open_webui.models.groups import Groups from open_webui.models.users import Users, UserResponse from pydantic import BaseModel, ConfigDict @@ -122,12 +123,13 @@ class PromptsTable: self, user_id: str, permission: str = "write" ) -> list[PromptUserResponse]: prompts = self.get_prompts() + user_group_ids = {group.id for group in Groups.get_groups_by_member_id(user_id)} return [ prompt for prompt in prompts if prompt.user_id == user_id - or has_access(user_id, permission, prompt.access_control) + or has_access(user_id, permission, prompt.access_control, user_group_ids) ] def update_prompt_by_command( diff --git a/backend/open_webui/models/tools.py b/backend/open_webui/models/tools.py index 7f1409a900..fa0fb77893 100644 --- a/backend/open_webui/models/tools.py +++ b/backend/open_webui/models/tools.py @@ -161,12 +161,13 @@ class ToolsTable: self, user_id: str, permission: str = "write" ) -> list[ToolUserModel]: tools = self.get_tools() + user_group_ids = {group.id for group in Groups.get_groups_by_member_id(user_id)} return [ tool for tool in tools if tool.user_id == user_id - or has_access(user_id, permission, tool.access_control) + or has_access(user_id, permission, tool.access_control, user_group_ids) ] def get_tool_valves_by_id(self, id: str) -> Optional[dict]: From 2d627966161e7bfa652fbce9024b3f69510ed133 Mon Sep 17 00:00:00 2001 From: Gary Meng Date: Wed, 3 Sep 2025 13:20:43 +0400 Subject: [PATCH 060/154] Allow user get /api/config with auth header --- backend/open_webui/main.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index d24bd5dcf1..91798b555e 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -1642,8 +1642,18 @@ async def list_tasks_by_chat_id_endpoint( @app.get("/api/config") async def get_app_config(request: Request): user = None - if "token" in request.cookies: + token = None + + auth_header = request.headers.get("Authorization") + if auth_header: + cred = get_http_authorization_cred(auth_header) + if cred: + token = cred.credentials + + if not token and "token" in request.cookies: token = request.cookies.get("token") + + if token: try: data = decode_token(token) except Exception as e: From 4ca936f0bf9813bee11ec8aea41d7e34fb6b16a9 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Wed, 3 Sep 2025 13:38:07 +0400 Subject: [PATCH 061/154] refac --- .../workspace/Knowledge/KnowledgeBase.svelte | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/lib/components/workspace/Knowledge/KnowledgeBase.svelte b/src/lib/components/workspace/Knowledge/KnowledgeBase.svelte index 179f9cfdfa..ebbd9d4eba 100644 --- a/src/lib/components/workspace/Knowledge/KnowledgeBase.svelte +++ b/src/lib/components/workspace/Knowledge/KnowledgeBase.svelte @@ -793,7 +793,12 @@ updateFileContentHandler(); }} > - {isSaving ? $i18n.t('Running...') : $i18n.t('Save')} + {$i18n.t('Save')} + {#if isSaving} +
+ +
+ {/if}
@@ -851,7 +856,12 @@ updateFileContentHandler(); }} > - {isSaving ? $i18n.t('Running...') : $i18n.t('Save')} + {$i18n.t('Save')} + {#if isSaving} +
+ +
+ {/if} From 37bf0087e5b8a324009c9d06b304027df351ea6b Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Wed, 3 Sep 2025 13:57:14 +0400 Subject: [PATCH 062/154] refac: tool message format --- backend/open_webui/utils/middleware.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index fa5634edbd..d68936d02a 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -1501,7 +1501,7 @@ async def process_chat_response( tool_result_files = result.get("files", None) break - if tool_result: + if tool_result is not None: tool_calls_display_content = f'{tool_calls_display_content}
\nTool Executed\n
\n' else: tool_calls_display_content = f'{tool_calls_display_content}
\nExecuting...\n
\n' @@ -1616,7 +1616,7 @@ async def process_chat_response( { "role": "tool", "tool_call_id": result["tool_call_id"], - "content": result["content"], + "content": result.get("content", "") or "", } ) temp_blocks = [] @@ -2341,7 +2341,7 @@ async def process_chat_response( results.append( { "tool_call_id": tool_call_id, - "content": tool_result, + "content": tool_result or "", **( {"files": tool_result_files} if tool_result_files From bbe116795860a81a647d9567e0d9cb1950650095 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Wed, 3 Sep 2025 15:46:42 +0400 Subject: [PATCH 063/154] refac/fix: pyodide import issue --- src/lib/components/chat/Messages/CodeBlock.svelte | 2 +- src/routes/+layout.svelte | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/components/chat/Messages/CodeBlock.svelte b/src/lib/components/chat/Messages/CodeBlock.svelte index e3109a25e7..635e7a085e 100644 --- a/src/lib/components/chat/Messages/CodeBlock.svelte +++ b/src/lib/components/chat/Messages/CodeBlock.svelte @@ -222,7 +222,7 @@ code.includes('pandas') ? 'pandas' : null, code.includes('sklearn') ? 'scikit-learn' : null, code.includes('scipy') ? 'scipy' : null, - code.includes('re') ? 'regex' : null, + /\bimport\s+re\b|\bfrom\s+re\b/.test(code) ? 'regex' : null, code.includes('seaborn') ? 'seaborn' : null, code.includes('sympy') ? 'sympy' : null, code.includes('tiktoken') ? 'tiktoken' : null, diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 67054cb3ad..88229786da 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -126,7 +126,7 @@ code.includes('matplotlib') ? 'matplotlib' : null, code.includes('sklearn') ? 'scikit-learn' : null, code.includes('scipy') ? 'scipy' : null, - code.includes('re') ? 'regex' : null, + /\bimport\s+re\b|\bfrom\s+re\b/.test(code) ? 'regex' : null, code.includes('seaborn') ? 'seaborn' : null, code.includes('sympy') ? 'sympy' : null, code.includes('tiktoken') ? 'tiktoken' : null, From 66bf28cd85b06b498c960ab3b31294b381e3a25a Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Wed, 3 Sep 2025 15:48:07 +0400 Subject: [PATCH 064/154] refac --- .../components/chat/Messages/CodeBlock.svelte | 24 +++++++++---------- src/routes/+layout.svelte | 23 +++++++++--------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/src/lib/components/chat/Messages/CodeBlock.svelte b/src/lib/components/chat/Messages/CodeBlock.svelte index 635e7a085e..e954dc7232 100644 --- a/src/lib/components/chat/Messages/CodeBlock.svelte +++ b/src/lib/components/chat/Messages/CodeBlock.svelte @@ -216,19 +216,19 @@ const executePythonAsWorker = async (code) => { let packages = [ - code.includes('requests') ? 'requests' : null, - code.includes('bs4') ? 'beautifulsoup4' : null, - code.includes('numpy') ? 'numpy' : null, - code.includes('pandas') ? 'pandas' : null, - code.includes('sklearn') ? 'scikit-learn' : null, - code.includes('scipy') ? 'scipy' : null, + /\bimport\s+requests\b|\bfrom\s+requests\b/.test(code) ? 'requests' : null, + /\bimport\s+bs4\b|\bfrom\s+bs4\b/.test(code) ? 'beautifulsoup4' : null, + /\bimport\s+numpy\b|\bfrom\s+numpy\b/.test(code) ? 'numpy' : null, + /\bimport\s+pandas\b|\bfrom\s+pandas\b/.test(code) ? 'pandas' : null, + /\bimport\s+matplotlib\b|\bfrom\s+matplotlib\b/.test(code) ? 'matplotlib' : null, + /\bimport\s+seaborn\b|\bfrom\s+seaborn\b/.test(code) ? 'seaborn' : null, + /\bimport\s+sklearn\b|\bfrom\s+sklearn\b/.test(code) ? 'scikit-learn' : null, + /\bimport\s+scipy\b|\bfrom\s+scipy\b/.test(code) ? 'scipy' : null, /\bimport\s+re\b|\bfrom\s+re\b/.test(code) ? 'regex' : null, - code.includes('seaborn') ? 'seaborn' : null, - code.includes('sympy') ? 'sympy' : null, - code.includes('tiktoken') ? 'tiktoken' : null, - code.includes('matplotlib') ? 'matplotlib' : null, - code.includes('pytz') ? 'pytz' : null, - code.includes('openai') ? 'openai' : null + /\bimport\s+seaborn\b|\bfrom\s+seaborn\b/.test(code) ? 'seaborn' : null, + /\bimport\s+sympy\b|\bfrom\s+sympy\b/.test(code) ? 'sympy' : null, + /\bimport\s+tiktoken\b|\bfrom\s+tiktoken\b/.test(code) ? 'tiktoken' : null, + /\bimport\s+pytz\b|\bfrom\s+pytz\b/.test(code) ? 'pytz' : null ].filter(Boolean); console.log(packages); diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 88229786da..d0453dc5ed 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -119,18 +119,19 @@ let executing = true; let packages = [ - code.includes('requests') ? 'requests' : null, - code.includes('bs4') ? 'beautifulsoup4' : null, - code.includes('numpy') ? 'numpy' : null, - code.includes('pandas') ? 'pandas' : null, - code.includes('matplotlib') ? 'matplotlib' : null, - code.includes('sklearn') ? 'scikit-learn' : null, - code.includes('scipy') ? 'scipy' : null, + /\bimport\s+requests\b|\bfrom\s+requests\b/.test(code) ? 'requests' : null, + /\bimport\s+bs4\b|\bfrom\s+bs4\b/.test(code) ? 'beautifulsoup4' : null, + /\bimport\s+numpy\b|\bfrom\s+numpy\b/.test(code) ? 'numpy' : null, + /\bimport\s+pandas\b|\bfrom\s+pandas\b/.test(code) ? 'pandas' : null, + /\bimport\s+matplotlib\b|\bfrom\s+matplotlib\b/.test(code) ? 'matplotlib' : null, + /\bimport\s+seaborn\b|\bfrom\s+seaborn\b/.test(code) ? 'seaborn' : null, + /\bimport\s+sklearn\b|\bfrom\s+sklearn\b/.test(code) ? 'scikit-learn' : null, + /\bimport\s+scipy\b|\bfrom\s+scipy\b/.test(code) ? 'scipy' : null, /\bimport\s+re\b|\bfrom\s+re\b/.test(code) ? 'regex' : null, - code.includes('seaborn') ? 'seaborn' : null, - code.includes('sympy') ? 'sympy' : null, - code.includes('tiktoken') ? 'tiktoken' : null, - code.includes('pytz') ? 'pytz' : null + /\bimport\s+seaborn\b|\bfrom\s+seaborn\b/.test(code) ? 'seaborn' : null, + /\bimport\s+sympy\b|\bfrom\s+sympy\b/.test(code) ? 'sympy' : null, + /\bimport\s+tiktoken\b|\bfrom\s+tiktoken\b/.test(code) ? 'tiktoken' : null, + /\bimport\s+pytz\b|\bfrom\s+pytz\b/.test(code) ? 'pytz' : null ].filter(Boolean); const pyodideWorker = new PyodideWorker(); From 926954f93b1c9c12cbfb3d039e682403aa23bb5e Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Wed, 3 Sep 2025 18:37:25 +0400 Subject: [PATCH 065/154] refac: styling --- src/app.css | 8 ++++++++ src/lib/components/chat/Messages/CodeBlock.svelte | 12 ++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/app.css b/src/app.css index 7d465210ba..c48914febf 100644 --- a/src/app.css +++ b/src/app.css @@ -282,6 +282,14 @@ input[type='number'] { outline: none; } +.cm-gutters { + @apply !bg-white dark:!bg-black !border-none; +} + +.cm-editor { + @apply bg-white dark:bg-black; +} + .tippy-box[data-theme~='dark'] { @apply rounded-lg bg-gray-950 text-xs border border-gray-900 shadow-xl; } diff --git a/src/lib/components/chat/Messages/CodeBlock.svelte b/src/lib/components/chat/Messages/CodeBlock.svelte index e954dc7232..6697d3e199 100644 --- a/src/lib/components/chat/Messages/CodeBlock.svelte +++ b/src/lib/components/chat/Messages/CodeBlock.svelte @@ -437,7 +437,7 @@ >
@@ -512,7 +512,7 @@ ? '' : 'rounded-b-lg'} overflow-hidden" > -
+
{#if !collapsed} {#if edit} From 7a166152d9d989db6c38c1ff521e18575a915e9b Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Wed, 3 Sep 2025 18:40:25 +0400 Subject: [PATCH 066/154] refac: styling --- src/lib/components/common/Banner.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/components/common/Banner.svelte b/src/lib/components/common/Banner.svelte index d135cb605a..a64cc857ff 100644 --- a/src/lib/components/common/Banner.svelte +++ b/src/lib/components/common/Banner.svelte @@ -46,7 +46,7 @@ {#if !dismissed} {#if mounted}
From 51fc7925014ab79f2fb50c2a273d8f29e1184dd3 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Wed, 3 Sep 2025 18:47:50 +0400 Subject: [PATCH 067/154] refac: styling --- src/lib/components/layout/Sidebar.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/components/layout/Sidebar.svelte b/src/lib/components/layout/Sidebar.svelte index b3f20aedb1..ed3eadf4c0 100644 --- a/src/lib/components/layout/Sidebar.svelte +++ b/src/lib/components/layout/Sidebar.svelte @@ -513,7 +513,7 @@ {#if !$mobile && !$showSidebar} +
+ {/if} + {#if azure}
From 71b6a942fe6a9a01baf4ff525e6bf785b850f3f8 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Wed, 3 Sep 2025 20:53:50 +0400 Subject: [PATCH 075/154] refac: styling --- src/lib/components/common/Tags.svelte | 2 +- src/lib/components/layout/Navbar/Menu.svelte | 36 +++++++++---------- .../components/layout/Sidebar/ChatMenu.svelte | 18 +++++----- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/lib/components/common/Tags.svelte b/src/lib/components/common/Tags.svelte index 6c34b72dee..9ed1a321c7 100644 --- a/src/lib/components/common/Tags.svelte +++ b/src/lib/components/common/Tags.svelte @@ -9,7 +9,7 @@ export let tags = []; -
    +
      { diff --git a/src/lib/components/layout/Navbar/Menu.svelte b/src/lib/components/layout/Navbar/Menu.svelte index a174fe3bbd..344a3f73f4 100644 --- a/src/lib/components/layout/Navbar/Menu.svelte +++ b/src/lib/components/layout/Navbar/Menu.svelte @@ -281,7 +281,7 @@ transition={flyAndScale} >
      @@ -1797,7 +1797,7 @@ > @@ -1816,7 +1816,7 @@ > @@ -1842,7 +1842,7 @@ > From e5ea595425c9d46a431c58bba8cb33f05f252649 Mon Sep 17 00:00:00 2001 From: Shirasawa <764798966@qq.com> Date: Thu, 4 Sep 2025 16:52:19 +0800 Subject: [PATCH 081/154] fix: fix error when stopping non-existent task --- backend/open_webui/tasks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/open_webui/tasks.py b/backend/open_webui/tasks.py index 714c532fca..a15e8ac146 100644 --- a/backend/open_webui/tasks.py +++ b/backend/open_webui/tasks.py @@ -153,9 +153,9 @@ async def stop_task(redis, task_id: str): # Optionally check if task_id still in Redis a few moments later for feedback? return {"status": True, "message": f"Stop signal sent for {task_id}"} - task = tasks.pop(task_id) + task = tasks.pop(task_id, None) if not task: - raise ValueError(f"Task with ID {task_id} not found.") + return {"status": False, "message": f"Task with ID {task_id} not found."} task.cancel() # Request task cancellation try: From c0a47169fa059154d5f5a9ea6b94f9a66d82f255 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Thu, 4 Sep 2025 22:10:46 +0400 Subject: [PATCH 082/154] refac: emoji picker case sensitivity --- src/lib/components/common/EmojiPicker.svelte | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lib/components/common/EmojiPicker.svelte b/src/lib/components/common/EmojiPicker.svelte index 23f1a465bd..cc761039b8 100644 --- a/src/lib/components/common/EmojiPicker.svelte +++ b/src/lib/components/common/EmojiPicker.svelte @@ -30,16 +30,18 @@ $: { if (search) { emojis = Object.keys(emojiShortCodes).reduce((acc, key) => { - if (key.includes(search)) { + if (key.includes(search.toLowerCase())) { acc[key] = emojiShortCodes[key]; } else { if (Array.isArray(emojiShortCodes[key])) { - const filtered = emojiShortCodes[key].filter((emoji) => emoji.includes(search)); + const filtered = emojiShortCodes[key].filter((emoji) => + emoji.includes(search.toLowerCase()) + ); if (filtered.length) { acc[key] = filtered; } } else { - if (emojiShortCodes[key].includes(search)) { + if (emojiShortCodes[key].includes(search.toLowerCase())) { acc[key] = emojiShortCodes[key]; } } From 49e045ea3d2eeadb35cffc9ffe5945b3346603fe Mon Sep 17 00:00:00 2001 From: Shirasawa <764798966@qq.com> Date: Fri, 5 Sep 2025 17:55:04 +0800 Subject: [PATCH 083/154] feat: dynamically load pdfjs --- src/lib/components/chat/MessageInput.svelte | 6 +---- src/lib/utils/index.ts | 28 ++++++++++++++------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/lib/components/chat/MessageInput.svelte b/src/lib/components/chat/MessageInput.svelte index d489b8278d..a50c2f1c11 100644 --- a/src/lib/components/chat/MessageInput.svelte +++ b/src/lib/components/chat/MessageInput.svelte @@ -1,8 +1,4 @@ + + + +{#if statusHistory} +
      + {#if showHistory} +
      + {#if statusHistory.length > 1} +
      + +
      + {#each statusHistory as status, idx} + {#if idx !== statusHistory.length - 1} +
      +
      + + + +
      + +
      + {/if} + {/each} +
      + {/if} +
      + {/if} + + {#if statusHistory.length > 0} + {@const status = statusHistory.at(-1)} + + {/if} +
      +{/if} diff --git a/src/lib/components/chat/Messages/ResponseMessage/StatusHistory/StatusItem.svelte b/src/lib/components/chat/Messages/ResponseMessage/StatusHistory/StatusItem.svelte new file mode 100644 index 0000000000..7f325f7f6e --- /dev/null +++ b/src/lib/components/chat/Messages/ResponseMessage/StatusHistory/StatusItem.svelte @@ -0,0 +1,101 @@ + + +{#if !status?.hidden} +
      + {#if status?.action === 'web_search' && (status?.urls || status?.items)} + +
      +
      + + + + {#if status?.description.includes('{{count}}')} + {$i18n.t(status?.description, { + count: (status?.urls || status?.items).length + })} + {:else if status?.description === 'No search query generated'} + {$i18n.t('No search query generated')} + {:else if status?.description === 'Generating search query'} + {$i18n.t('Generating search query')} + {:else} + {status?.description} + {/if} +
      +
      +
      + {:else if status?.action === 'knowledge_search'} +
      +
      + {$i18n.t(`Searching Knowledge for "{{searchQuery}}"`, { + searchQuery: status.query + })} +
      +
      + {:else if status?.action === 'web_search_queries_generated' && status?.queries} +
      +
      + {$i18n.t(`Searching`)} +
      + +
      + {#each status.queries as query, idx (query)} +
      +
      + +
      + + + {query} + +
      + {/each} +
      +
      + {:else} +
      +
      + + {#if status?.description.includes('{{searchQuery}}')} + {$i18n.t(status?.description, { + searchQuery: status?.query + })} + {:else if status?.description === 'No search query generated'} + {$i18n.t('No search query generated')} + {:else if status?.description === 'Generating search query'} + {$i18n.t('Generating search query')} + {:else if status?.description === 'Searching the web'} + {$i18n.t('Searching the web')} + {:else} + {status?.description} + {/if} +
      +
      + {/if} +
      +{/if} diff --git a/src/lib/components/chat/Messages/ResponseMessage/WebSearchResults.svelte b/src/lib/components/chat/Messages/ResponseMessage/WebSearchResults.svelte index 0e81116c5f..31360f7132 100644 --- a/src/lib/components/chat/Messages/ResponseMessage/WebSearchResults.svelte +++ b/src/lib/components/chat/Messages/ResponseMessage/WebSearchResults.svelte @@ -8,20 +8,13 @@ let state = false; - -
      + +
      - - {#if state} - - {:else} - - {/if}
      +
      {#if status?.query} diff --git a/src/lib/components/common/Collapsible.svelte b/src/lib/components/common/Collapsible.svelte index 56e214ae34..b092a49826 100644 --- a/src/lib/components/common/Collapsible.svelte +++ b/src/lib/components/common/Collapsible.svelte @@ -169,7 +169,10 @@
      { + on:click={(e) => { + e.stopPropagation(); + }} + on:pointerup={(e) => { if (!disabled) { open = !open; } diff --git a/src/lib/i18n/locales/ar-BH/translation.json b/src/lib/i18n/locales/ar-BH/translation.json index 659bdba40a..0087fc96c5 100644 --- a/src/lib/i18n/locales/ar-BH/translation.json +++ b/src/lib/i18n/locales/ar-BH/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "", "Searching \"{{searchQuery}}\"": "", "Searching Knowledge for \"{{searchQuery}}\"": "", - "Searching the web...": "", + "Searching the web": "", "Searxng Query URL": "عنوان URL لاستعلام Searxng", "See readme.md for instructions": "readme.md للحصول على التعليمات", "See what's new": "ما الجديد", diff --git a/src/lib/i18n/locales/ar/translation.json b/src/lib/i18n/locales/ar/translation.json index 68724db173..30893078a8 100644 --- a/src/lib/i18n/locales/ar/translation.json +++ b/src/lib/i18n/locales/ar/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "تم البحث في {{count}} مواقع", "Searching \"{{searchQuery}}\"": "جارٍ البحث عن \"{{searchQuery}}\"", "Searching Knowledge for \"{{searchQuery}}\"": "جارٍ البحث في المعرفة عن \"{{searchQuery}}\"", - "Searching the web...": "", + "Searching the web": "", "Searxng Query URL": "عنوان URL لاستعلام Searxng", "See readme.md for instructions": "readme.md للحصول على التعليمات", "See what's new": "ما الجديد", diff --git a/src/lib/i18n/locales/bg-BG/translation.json b/src/lib/i18n/locales/bg-BG/translation.json index 012f5fdf66..69dd424f33 100644 --- a/src/lib/i18n/locales/bg-BG/translation.json +++ b/src/lib/i18n/locales/bg-BG/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "Претърсени {{count}} сайт", "Searching \"{{searchQuery}}\"": "Търсене на \"{{searchQuery}}\"", "Searching Knowledge for \"{{searchQuery}}\"": "Търсене в знанията за \"{{searchQuery}}\"", - "Searching the web...": "Търсене в интернет...", + "Searching the web": "Търсене в интернет...", "Searxng Query URL": "URL адрес на заявка за търсене в Searxng", "See readme.md for instructions": "Вижте readme.md за инструкции", "See what's new": "Виж какво е новото", diff --git a/src/lib/i18n/locales/bn-BD/translation.json b/src/lib/i18n/locales/bn-BD/translation.json index cf6b2498a0..3bca969fb7 100644 --- a/src/lib/i18n/locales/bn-BD/translation.json +++ b/src/lib/i18n/locales/bn-BD/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "", "Searching \"{{searchQuery}}\"": "", "Searching Knowledge for \"{{searchQuery}}\"": "", - "Searching the web...": "", + "Searching the web": "", "Searxng Query URL": "Searxng ক্যোয়ারী URL", "See readme.md for instructions": "নির্দেশিকার জন্য readme.md দেখুন", "See what's new": "নতুন কী আছে দেখুন", diff --git a/src/lib/i18n/locales/bo-TB/translation.json b/src/lib/i18n/locales/bo-TB/translation.json index 5a6adeca78..3472696b31 100644 --- a/src/lib/i18n/locales/bo-TB/translation.json +++ b/src/lib/i18n/locales/bo-TB/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "དྲ་ཚིགས་ {{count}} འཚོལ་བཤེར་བྱས།", "Searching \"{{searchQuery}}\"": "\"{{searchQuery}}\" འཚོལ་བཞིན་པ།", "Searching Knowledge for \"{{searchQuery}}\"": "\"{{searchQuery}}\" ཆེད་དུ་ཤེས་བྱ་འཚོལ་བཞིན་པ།", - "Searching the web...": "", + "Searching the web": "", "Searxng Query URL": "Searxng Query URL", "See readme.md for instructions": "ལམ་སྟོན་ཆེད་དུ་ readme.md ལ་ལྟ་བ།", "See what's new": "གསར་པ་ཅི་ཡོད་ལྟ་བ།", diff --git a/src/lib/i18n/locales/ca-ES/translation.json b/src/lib/i18n/locales/ca-ES/translation.json index d83fd1c8c4..d8a38d5470 100644 --- a/src/lib/i18n/locales/ca-ES/translation.json +++ b/src/lib/i18n/locales/ca-ES/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "S'han cercat {{count}} pàgines", "Searching \"{{searchQuery}}\"": "Cercant \"{{searchQuery}}\"", "Searching Knowledge for \"{{searchQuery}}\"": "Cercant \"{{searchQuery}}\" al coneixement", - "Searching the web...": "Cercant la web...", + "Searching the web": "Cercant la web...", "Searxng Query URL": "URL de consulta de Searxng", "See readme.md for instructions": "Consulta l'arxiu readme.md per obtenir instruccions", "See what's new": "Veure què hi ha de nou", diff --git a/src/lib/i18n/locales/ceb-PH/translation.json b/src/lib/i18n/locales/ceb-PH/translation.json index 862d589352..90fae95c6b 100644 --- a/src/lib/i18n/locales/ceb-PH/translation.json +++ b/src/lib/i18n/locales/ceb-PH/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "", "Searching \"{{searchQuery}}\"": "", "Searching Knowledge for \"{{searchQuery}}\"": "", - "Searching the web...": "", + "Searching the web": "", "Searxng Query URL": "", "See readme.md for instructions": "Tan-awa ang readme.md alang sa mga panudlo", "See what's new": "Tan-awa unsay bag-o", diff --git a/src/lib/i18n/locales/cs-CZ/translation.json b/src/lib/i18n/locales/cs-CZ/translation.json index d027fe8d84..cc8cbc046d 100644 --- a/src/lib/i18n/locales/cs-CZ/translation.json +++ b/src/lib/i18n/locales/cs-CZ/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "Prohledáno {{count}} stránek", "Searching \"{{searchQuery}}\"": "Hledám \"{{searchQuery}}\"", "Searching Knowledge for \"{{searchQuery}}\"": "Hledám ve znalostech \"{{searchQuery}}\"", - "Searching the web...": "Hledám na webu...", + "Searching the web": "Hledám na webu...", "Searxng Query URL": "URL dotazu pro Searxng", "See readme.md for instructions": "Pokyny naleznete v souboru readme.md.", "See what's new": "Podívejte se, co je nového", diff --git a/src/lib/i18n/locales/da-DK/translation.json b/src/lib/i18n/locales/da-DK/translation.json index 63b962204f..d65435642e 100644 --- a/src/lib/i18n/locales/da-DK/translation.json +++ b/src/lib/i18n/locales/da-DK/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "Søgte {{count}} sider", "Searching \"{{searchQuery}}\"": "Søger efter \"{{searchQuery}}\"", "Searching Knowledge for \"{{searchQuery}}\"": "Søger i viden efter \"{{searchQuery}}\"", - "Searching the web...": "Søger på internettet...", + "Searching the web": "Søger på internettet...", "Searxng Query URL": "Searxng forespørgsels-URL", "See readme.md for instructions": "Se readme.md for instruktioner", "See what's new": "Se, hvad der er nyt", diff --git a/src/lib/i18n/locales/de-DE/translation.json b/src/lib/i18n/locales/de-DE/translation.json index 5955d71c13..6579f802a1 100644 --- a/src/lib/i18n/locales/de-DE/translation.json +++ b/src/lib/i18n/locales/de-DE/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "{{count}} Websites durchsucht", "Searching \"{{searchQuery}}\"": "Suche nach \"{{searchQuery}}\"", "Searching Knowledge for \"{{searchQuery}}\"": "Suche im Wissen nach \"{{searchQuery}}\"", - "Searching the web...": "Durchsuche das Web...", + "Searching the web": "Durchsuche das Web...", "Searxng Query URL": "Searxng-Abfrage-URL", "See readme.md for instructions": "Anleitung in readme.md anzeigen", "See what's new": "Entdecken Sie die Neuigkeiten", diff --git a/src/lib/i18n/locales/dg-DG/translation.json b/src/lib/i18n/locales/dg-DG/translation.json index a891994023..26727016ee 100644 --- a/src/lib/i18n/locales/dg-DG/translation.json +++ b/src/lib/i18n/locales/dg-DG/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "", "Searching \"{{searchQuery}}\"": "", "Searching Knowledge for \"{{searchQuery}}\"": "", - "Searching the web...": "", + "Searching the web": "", "Searxng Query URL": "", "See readme.md for instructions": "See readme.md for instructions wow", "See what's new": "See what's new so amaze", diff --git a/src/lib/i18n/locales/el-GR/translation.json b/src/lib/i18n/locales/el-GR/translation.json index c09f33df3c..070f6e6e7a 100644 --- a/src/lib/i18n/locales/el-GR/translation.json +++ b/src/lib/i18n/locales/el-GR/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "", "Searching \"{{searchQuery}}\"": "Αναζήτηση \"{{searchQuery}}\"", "Searching Knowledge for \"{{searchQuery}}\"": "Αναζήτηση Γνώσης για \"{{searchQuery}}\"", - "Searching the web...": "", + "Searching the web": "", "Searxng Query URL": "URL Ερώτησης Searxng", "See readme.md for instructions": "Δείτε readme.md για οδηγίες", "See what's new": "Δείτε τι νέο υπάρχει", diff --git a/src/lib/i18n/locales/en-GB/translation.json b/src/lib/i18n/locales/en-GB/translation.json index d063e9ef3d..47a9f17c1c 100644 --- a/src/lib/i18n/locales/en-GB/translation.json +++ b/src/lib/i18n/locales/en-GB/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "", "Searching \"{{searchQuery}}\"": "", "Searching Knowledge for \"{{searchQuery}}\"": "", - "Searching the web...": "", + "Searching the web": "", "Searxng Query URL": "", "See readme.md for instructions": "", "See what's new": "", diff --git a/src/lib/i18n/locales/en-US/translation.json b/src/lib/i18n/locales/en-US/translation.json index bcf47bb956..9f172b7a3c 100644 --- a/src/lib/i18n/locales/en-US/translation.json +++ b/src/lib/i18n/locales/en-US/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "", "Searching \"{{searchQuery}}\"": "", "Searching Knowledge for \"{{searchQuery}}\"": "", - "Searching the web...": "", + "Searching the web": "", "Searxng Query URL": "", "See readme.md for instructions": "", "See what's new": "", diff --git a/src/lib/i18n/locales/es-ES/translation.json b/src/lib/i18n/locales/es-ES/translation.json index 454be0d79e..a4a23abe3b 100644 --- a/src/lib/i18n/locales/es-ES/translation.json +++ b/src/lib/i18n/locales/es-ES/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "{{count}} sitios buscados", "Searching \"{{searchQuery}}\"": "Buscando \"{{searchQuery}}\"", "Searching Knowledge for \"{{searchQuery}}\"": "Buscando \"{{searchQuery}}\" en Conocimiento", - "Searching the web...": "Buscando en la web...", + "Searching the web": "Buscando en la web...", "Searxng Query URL": "Searxng URL de Consulta", "See readme.md for instructions": "Ver readme.md para instrucciones", "See what's new": "Ver las novedades", diff --git a/src/lib/i18n/locales/et-EE/translation.json b/src/lib/i18n/locales/et-EE/translation.json index 792e553ad5..b1fbe21ffe 100644 --- a/src/lib/i18n/locales/et-EE/translation.json +++ b/src/lib/i18n/locales/et-EE/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "Otsiti {{count}} saidilt", "Searching \"{{searchQuery}}\"": "Otsimine: \"{{searchQuery}}\"", "Searching Knowledge for \"{{searchQuery}}\"": "Teadmistest otsimine: \"{{searchQuery}}\"", - "Searching the web...": "", + "Searching the web": "", "Searxng Query URL": "Searxng päringu URL", "See readme.md for instructions": "Juhiste saamiseks vaadake readme.md", "See what's new": "Vaata, mis on uut", diff --git a/src/lib/i18n/locales/eu-ES/translation.json b/src/lib/i18n/locales/eu-ES/translation.json index 5d78777fec..68eedaa525 100644 --- a/src/lib/i18n/locales/eu-ES/translation.json +++ b/src/lib/i18n/locales/eu-ES/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "", "Searching \"{{searchQuery}}\"": "\"{{searchQuery}}\" bilatzen", "Searching Knowledge for \"{{searchQuery}}\"": "\"{{searchQuery}}\"rentzako ezagutza bilatzen", - "Searching the web...": "", + "Searching the web": "", "Searxng Query URL": "Searxng kontsulta URLa", "See readme.md for instructions": "Ikusi readme.md argibideetarako", "See what's new": "Ikusi berritasunak", diff --git a/src/lib/i18n/locales/fa-IR/translation.json b/src/lib/i18n/locales/fa-IR/translation.json index d74570c6fc..c8bb3072bc 100644 --- a/src/lib/i18n/locales/fa-IR/translation.json +++ b/src/lib/i18n/locales/fa-IR/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "جستجوی {{count}} سایت", "Searching \"{{searchQuery}}\"": "جستجوی «{{searchQuery}}»", "Searching Knowledge for \"{{searchQuery}}\"": "جستجوی دانش برای «{{searchQuery}}»", - "Searching the web...": "", + "Searching the web": "", "Searxng Query URL": "نشانی وب جستجوی Searxng", "See readme.md for instructions": "برای مشاهده دستورالعمل\u200cها به readme.md مراجعه کنید", "See what's new": "ببینید موارد جدید چه بوده", diff --git a/src/lib/i18n/locales/fi-FI/translation.json b/src/lib/i18n/locales/fi-FI/translation.json index 1b4a7d21ea..91a0f28a35 100644 --- a/src/lib/i18n/locales/fi-FI/translation.json +++ b/src/lib/i18n/locales/fi-FI/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "Etsitty {{count}} sivulta", "Searching \"{{searchQuery}}\"": "Haetaan \"{{searchQuery}}\"", "Searching Knowledge for \"{{searchQuery}}\"": "Haetaan tietämystä \"{{searchQuery}}\"", - "Searching the web...": "Haetaan verkosta...", + "Searching the web": "Haetaan verkosta...", "Searxng Query URL": "Searxng-kyselyn verkko-osoite", "See readme.md for instructions": "Katso ohjeet readme.md-tiedostosta", "See what's new": "Katso, mitä uutta", diff --git a/src/lib/i18n/locales/fr-CA/translation.json b/src/lib/i18n/locales/fr-CA/translation.json index 093200f411..0a9393e0da 100644 --- a/src/lib/i18n/locales/fr-CA/translation.json +++ b/src/lib/i18n/locales/fr-CA/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "{{count}} sites recherchés", "Searching \"{{searchQuery}}\"": "Recherche de « {{searchQuery}} »", "Searching Knowledge for \"{{searchQuery}}\"": "Recherche des connaissances pour « {{searchQuery}} »", - "Searching the web...": "Recherche sur internet...", + "Searching the web": "Recherche sur internet...", "Searxng Query URL": "URL de recherche Searxng", "See readme.md for instructions": "Voir le fichier readme.md pour les instructions", "See what's new": "Découvrez les nouvelles fonctionnalités", diff --git a/src/lib/i18n/locales/fr-FR/translation.json b/src/lib/i18n/locales/fr-FR/translation.json index 2d6ce819d9..db15fe1f32 100644 --- a/src/lib/i18n/locales/fr-FR/translation.json +++ b/src/lib/i18n/locales/fr-FR/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "{{count}} sites recherchés", "Searching \"{{searchQuery}}\"": "Recherche de « {{searchQuery}} »", "Searching Knowledge for \"{{searchQuery}}\"": "Recherche des connaissances pour « {{searchQuery}} »", - "Searching the web...": "Recherche sur internet...", + "Searching the web": "Recherche sur internet...", "Searxng Query URL": "URL de recherche Searxng", "See readme.md for instructions": "Voir le fichier readme.md pour les instructions", "See what's new": "Découvrez les nouvelles fonctionnalités", diff --git a/src/lib/i18n/locales/gl-ES/translation.json b/src/lib/i18n/locales/gl-ES/translation.json index 45a2ec2ce6..3f3950d1a1 100644 --- a/src/lib/i18n/locales/gl-ES/translation.json +++ b/src/lib/i18n/locales/gl-ES/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "Buscadas {{count}} sitios", "Searching \"{{searchQuery}}\"": "Buscando \"{{searchQuery}}\"", "Searching Knowledge for \"{{searchQuery}}\"": "Buscando coñecemento para \"{{searchQuery}}\"", - "Searching the web...": "", + "Searching the web": "", "Searxng Query URL": "Searxng URL de consulta", "See readme.md for instructions": "Vea o readme.md para instruccions", "See what's new": "Ver as novedades", diff --git a/src/lib/i18n/locales/he-IL/translation.json b/src/lib/i18n/locales/he-IL/translation.json index c35cfaefab..e08670d061 100644 --- a/src/lib/i18n/locales/he-IL/translation.json +++ b/src/lib/i18n/locales/he-IL/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "", "Searching \"{{searchQuery}}\"": "", "Searching Knowledge for \"{{searchQuery}}\"": "", - "Searching the web...": "", + "Searching the web": "", "Searxng Query URL": "כתובת URL של שאילתת Searxng", "See readme.md for instructions": "ראה את readme.md להוראות", "See what's new": "ראה מה חדש", diff --git a/src/lib/i18n/locales/hi-IN/translation.json b/src/lib/i18n/locales/hi-IN/translation.json index c1533096c1..224c379e3b 100644 --- a/src/lib/i18n/locales/hi-IN/translation.json +++ b/src/lib/i18n/locales/hi-IN/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "", "Searching \"{{searchQuery}}\"": "", "Searching Knowledge for \"{{searchQuery}}\"": "", - "Searching the web...": "", + "Searching the web": "", "Searxng Query URL": "Searxng क्वेरी URL", "See readme.md for instructions": "निर्देशों के लिए readme.md देखें", "See what's new": "देखें, क्या नया है", diff --git a/src/lib/i18n/locales/hr-HR/translation.json b/src/lib/i18n/locales/hr-HR/translation.json index 9eb5e405f4..d0585d85ae 100644 --- a/src/lib/i18n/locales/hr-HR/translation.json +++ b/src/lib/i18n/locales/hr-HR/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "", "Searching \"{{searchQuery}}\"": "", "Searching Knowledge for \"{{searchQuery}}\"": "", - "Searching the web...": "", + "Searching the web": "", "Searxng Query URL": "Searxng URL upita", "See readme.md for instructions": "Pogledajte readme.md za upute", "See what's new": "Pogledajte što je novo", diff --git a/src/lib/i18n/locales/hu-HU/translation.json b/src/lib/i18n/locales/hu-HU/translation.json index 016f3bbbe7..90dea5b83f 100644 --- a/src/lib/i18n/locales/hu-HU/translation.json +++ b/src/lib/i18n/locales/hu-HU/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "{{count}} oldal keresése megtörtént", "Searching \"{{searchQuery}}\"": "Keresés: \"{{searchQuery}}\"", "Searching Knowledge for \"{{searchQuery}}\"": "Tudásbázis keresése: \"{{searchQuery}}\"", - "Searching the web...": "", + "Searching the web": "", "Searxng Query URL": "Searxng lekérdezési URL", "See readme.md for instructions": "Lásd a readme.md fájlt az útmutatásért", "See what's new": "Újdonságok megtekintése", diff --git a/src/lib/i18n/locales/id-ID/translation.json b/src/lib/i18n/locales/id-ID/translation.json index 0b106d19e0..25bdbaee13 100644 --- a/src/lib/i18n/locales/id-ID/translation.json +++ b/src/lib/i18n/locales/id-ID/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "", "Searching \"{{searchQuery}}\"": "Mencari \"{{searchQuery}}\"", "Searching Knowledge for \"{{searchQuery}}\"": "", - "Searching the web...": "", + "Searching the web": "", "Searxng Query URL": "URL Kueri Pencarian Searxng", "See readme.md for instructions": "Lihat readme.md untuk instruksi", "See what's new": "Lihat apa yang baru", diff --git a/src/lib/i18n/locales/ie-GA/translation.json b/src/lib/i18n/locales/ie-GA/translation.json index bfa21e96c2..b617131ceb 100644 --- a/src/lib/i18n/locales/ie-GA/translation.json +++ b/src/lib/i18n/locales/ie-GA/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "Cuardaíodh {{count}} suíomh", "Searching \"{{searchQuery}}\"": "Ag cuardach \"{{searchQuery}}\"", "Searching Knowledge for \"{{searchQuery}}\"": "Cuardach Eolas do \"{{searchQuery}}\"", - "Searching the web...": "Ag cuardach an ghréasáin...", + "Searching the web": "Ag cuardach an ghréasáin...", "Searxng Query URL": "URL ceisteanna cuardaigh", "See readme.md for instructions": "Féach readme.md le haghaidh treoracha", "See what's new": "Féach cad atá nua", diff --git a/src/lib/i18n/locales/it-IT/translation.json b/src/lib/i18n/locales/it-IT/translation.json index 891ff74fe6..5a1d172b1d 100644 --- a/src/lib/i18n/locales/it-IT/translation.json +++ b/src/lib/i18n/locales/it-IT/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "Cercati {{count}} siti", "Searching \"{{searchQuery}}\"": "Cercando \"{{searchQuery}}\"", "Searching Knowledge for \"{{searchQuery}}\"": "Cercando conoscenza per \"{{searchQuery}}\"", - "Searching the web...": "Cercando nel web...", + "Searching the web": "Cercando nel web...", "Searxng Query URL": "Searxng Query URL", "See readme.md for instructions": "Vedi readme.md per le istruzioni", "See what's new": "Guarda le novità", diff --git a/src/lib/i18n/locales/ja-JP/translation.json b/src/lib/i18n/locales/ja-JP/translation.json index 82235e95fc..d856cfe387 100644 --- a/src/lib/i18n/locales/ja-JP/translation.json +++ b/src/lib/i18n/locales/ja-JP/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "{{count}} サイトを検索しました", "Searching \"{{searchQuery}}\"": "「{{searchQuery}}」を検索中...", "Searching Knowledge for \"{{searchQuery}}\"": "「{{searchQuery}}」のナレッジを検索中...", - "Searching the web...": "ウェブを検索中...", + "Searching the web": "ウェブを検索中...", "Searxng Query URL": "Searxng クエリ URL", "See readme.md for instructions": "手順については readme.md を参照してください", "See what's new": "新機能を見る", diff --git a/src/lib/i18n/locales/ka-GE/translation.json b/src/lib/i18n/locales/ka-GE/translation.json index f90d197991..f8449b4d6e 100644 --- a/src/lib/i18n/locales/ka-GE/translation.json +++ b/src/lib/i18n/locales/ka-GE/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "მოძებნილია {{count}} საიტზე", "Searching \"{{searchQuery}}\"": "მიმდინარეობს ძებნა \"{{searchQuery}}\"", "Searching Knowledge for \"{{searchQuery}}\"": "", - "Searching the web...": "", + "Searching the web": "", "Searxng Query URL": "Searxng Query URL", "See readme.md for instructions": "ინსტრუქციებისთვის იხილეთ readme.md", "See what's new": "ნახეთ, რა არის ახალი", diff --git a/src/lib/i18n/locales/kab-DZ/translation.json b/src/lib/i18n/locales/kab-DZ/translation.json index 6657f8811b..154078c895 100644 --- a/src/lib/i18n/locales/kab-DZ/translation.json +++ b/src/lib/i18n/locales/kab-DZ/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "Inuda deg {{count}} n yismal web", "Searching \"{{searchQuery}}\"": "Anadi ɣef \"{{searchQuery}}\"", "Searching Knowledge for \"{{searchQuery}}\"": "Anadi n tmessunin ɣef \"{{searchQuery}}\"", - "Searching the web...": "Anadi deg web…", + "Searching the web": "Anadi deg web…", "Searxng Query URL": "URL n unadi Searxng", "See readme.md for instructions": "Ẓer taɣuṛi i lewṣaya", "See what's new": "Wali d acu i yellan d amaynut", diff --git a/src/lib/i18n/locales/ko-KR/translation.json b/src/lib/i18n/locales/ko-KR/translation.json index ce61d0af9c..9e24750c85 100644 --- a/src/lib/i18n/locales/ko-KR/translation.json +++ b/src/lib/i18n/locales/ko-KR/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "{{count}}개 사이트 검색됨", "Searching \"{{searchQuery}}\"": "\"{{searchQuery}}\" 검색 중", "Searching Knowledge for \"{{searchQuery}}\"": "\"{{searchQuery}}\"에 대한 지식 기반 검색 중", - "Searching the web...": "웹에서 검색 중...", + "Searching the web": "웹에서 검색 중...", "Searxng Query URL": "Searxng 쿼리 URL", "See readme.md for instructions": "설명은 readme.md를 참조하세요.", "See what's new": "새로운 기능 보기", diff --git a/src/lib/i18n/locales/lt-LT/translation.json b/src/lib/i18n/locales/lt-LT/translation.json index 9ca884f652..343d5d7203 100644 --- a/src/lib/i18n/locales/lt-LT/translation.json +++ b/src/lib/i18n/locales/lt-LT/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "", "Searching \"{{searchQuery}}\"": "Ieškoma \"{{searchQuery}}\"", "Searching Knowledge for \"{{searchQuery}}\"": "", - "Searching the web...": "", + "Searching the web": "", "Searxng Query URL": "Searxng užklausos URL", "See readme.md for instructions": "Žiūrėti readme.md papildomoms instrukcijoms", "See what's new": "Žiūrėti naujoves", diff --git a/src/lib/i18n/locales/ms-MY/translation.json b/src/lib/i18n/locales/ms-MY/translation.json index bddd127f28..622cf681ae 100644 --- a/src/lib/i18n/locales/ms-MY/translation.json +++ b/src/lib/i18n/locales/ms-MY/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "", "Searching \"{{searchQuery}}\"": "encari \"{{ searchQuery }}\"", "Searching Knowledge for \"{{searchQuery}}\"": "", - "Searching the web...": "", + "Searching the web": "", "Searxng Query URL": "URL Pertanyaan Searxng", "See readme.md for instructions": "Lihat readme.md untuk arahan", "See what's new": "Lihat apa yang terbaru", diff --git a/src/lib/i18n/locales/nb-NO/translation.json b/src/lib/i18n/locales/nb-NO/translation.json index 891673d510..6b0594558e 100644 --- a/src/lib/i18n/locales/nb-NO/translation.json +++ b/src/lib/i18n/locales/nb-NO/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "Søkte på {{count}} nettsider", "Searching \"{{searchQuery}}\"": "Søker etter \"{{searchQuery}}\"", "Searching Knowledge for \"{{searchQuery}}\"": "Søker etter kunnskap for \"{{searchQuery}}\"", - "Searching the web...": "", + "Searching the web": "", "Searxng Query URL": "Searxng forespørsels-URL", "See readme.md for instructions": "Se readme.md for å få instruksjoner", "See what's new": "Se hva som er nytt", diff --git a/src/lib/i18n/locales/nl-NL/translation.json b/src/lib/i18n/locales/nl-NL/translation.json index 5c323a0ade..a2b17497a9 100644 --- a/src/lib/i18n/locales/nl-NL/translation.json +++ b/src/lib/i18n/locales/nl-NL/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "Zocht op {{count}} sites", "Searching \"{{searchQuery}}\"": "\"{{searchQuery}}\" aan het zoeken.", "Searching Knowledge for \"{{searchQuery}}\"": "Zoek kennis bij \"{{searchQuery}}\"", - "Searching the web...": "", + "Searching the web": "", "Searxng Query URL": "Searxng Query URL", "See readme.md for instructions": "Zie readme.md voor instructies", "See what's new": "Zie wat er nieuw is", diff --git a/src/lib/i18n/locales/pa-IN/translation.json b/src/lib/i18n/locales/pa-IN/translation.json index c2a158195c..13e320892b 100644 --- a/src/lib/i18n/locales/pa-IN/translation.json +++ b/src/lib/i18n/locales/pa-IN/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "", "Searching \"{{searchQuery}}\"": "", "Searching Knowledge for \"{{searchQuery}}\"": "", - "Searching the web...": "", + "Searching the web": "", "Searxng Query URL": "Searxng Query URL", "See readme.md for instructions": "ਹਦਾਇਤਾਂ ਲਈ readme.md ਵੇਖੋ", "See what's new": "ਨਵਾਂ ਕੀ ਹੈ ਵੇਖੋ", diff --git a/src/lib/i18n/locales/pl-PL/translation.json b/src/lib/i18n/locales/pl-PL/translation.json index ab88c58cb2..4822919795 100644 --- a/src/lib/i18n/locales/pl-PL/translation.json +++ b/src/lib/i18n/locales/pl-PL/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "Przeszukano {{count}} stron", "Searching \"{{searchQuery}}\"": "Wyszukiwanie \"{{searchQuery}}\"", "Searching Knowledge for \"{{searchQuery}}\"": "Przeszukiwanie wiedzy dla \"{{searchQuery}}\"", - "Searching the web...": "Przeszukuję sieć Web...", + "Searching the web": "Przeszukuję sieć Web...", "Searxng Query URL": "Adres URL zapytania Searxng", "See readme.md for instructions": "Sprawdź readme.md dla instrukcji", "See what's new": "Sprawdź nowości", diff --git a/src/lib/i18n/locales/pt-BR/translation.json b/src/lib/i18n/locales/pt-BR/translation.json index f216814eb1..6453b7ebcd 100644 --- a/src/lib/i18n/locales/pt-BR/translation.json +++ b/src/lib/i18n/locales/pt-BR/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "{{count}} sites pesquisados", "Searching \"{{searchQuery}}\"": "Pesquisando \"{{searchQuery}}\"", "Searching Knowledge for \"{{searchQuery}}\"": "Buscando conhecimento para \"{{searchQuery}}\"", - "Searching the web...": "Pesquisando na Internet...", + "Searching the web": "Pesquisando na Internet...", "Searxng Query URL": "URL da Consulta Searxng", "See readme.md for instructions": "Veja readme.md para instruções", "See what's new": "Veja o que há de novo", diff --git a/src/lib/i18n/locales/pt-PT/translation.json b/src/lib/i18n/locales/pt-PT/translation.json index 058b580035..f1c0df3721 100644 --- a/src/lib/i18n/locales/pt-PT/translation.json +++ b/src/lib/i18n/locales/pt-PT/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "", "Searching \"{{searchQuery}}\"": "", "Searching Knowledge for \"{{searchQuery}}\"": "", - "Searching the web...": "", + "Searching the web": "", "Searxng Query URL": "URL de consulta Searxng", "See readme.md for instructions": "Consulte readme.md para obter instruções", "See what's new": "Veja o que há de novo", diff --git a/src/lib/i18n/locales/ro-RO/translation.json b/src/lib/i18n/locales/ro-RO/translation.json index 7c5e927db3..9e66001058 100644 --- a/src/lib/i18n/locales/ro-RO/translation.json +++ b/src/lib/i18n/locales/ro-RO/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "", "Searching \"{{searchQuery}}\"": "Căutare \"{{searchQuery}}\"", "Searching Knowledge for \"{{searchQuery}}\"": "Căutare cunoștințe pentru \"{{searchQuery}}\"", - "Searching the web...": "", + "Searching the web": "", "Searxng Query URL": "URL Interogare Searxng", "See readme.md for instructions": "Consultați readme.md pentru instrucțiuni", "See what's new": "Vezi ce e nou", diff --git a/src/lib/i18n/locales/ru-RU/translation.json b/src/lib/i18n/locales/ru-RU/translation.json index be10181aec..92639ac759 100644 --- a/src/lib/i18n/locales/ru-RU/translation.json +++ b/src/lib/i18n/locales/ru-RU/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "Поиск по {{count}} сайтам", "Searching \"{{searchQuery}}\"": "Поиск по запросу \"{{searchQuery}}\"", "Searching Knowledge for \"{{searchQuery}}\"": "Поиск знания для \"{{searchQuery}}\"", - "Searching the web...": "Поиск в интернете...", + "Searching the web": "Поиск в интернете...", "Searxng Query URL": "URL-адрес запроса Searxng", "See readme.md for instructions": "Смотрите readme.md для инструкций", "See what's new": "Посмотреть, что нового", diff --git a/src/lib/i18n/locales/sk-SK/translation.json b/src/lib/i18n/locales/sk-SK/translation.json index 92ccc3f2c5..81c56d3cea 100644 --- a/src/lib/i18n/locales/sk-SK/translation.json +++ b/src/lib/i18n/locales/sk-SK/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "", "Searching \"{{searchQuery}}\"": "Hľadanie \"{{searchQuery}}\"", "Searching Knowledge for \"{{searchQuery}}\"": "Vyhľadávanie znalostí pre \"{{searchQuery}}\"", - "Searching the web...": "", + "Searching the web": "", "Searxng Query URL": "Adresa URL dotazu Searxng", "See readme.md for instructions": "Pozrite si {{readme.md}} pre pokyny.", "See what's new": "Pozrite sa, čo je nové", diff --git a/src/lib/i18n/locales/sr-RS/translation.json b/src/lib/i18n/locales/sr-RS/translation.json index 4f5cae32d7..27aecc40bc 100644 --- a/src/lib/i18n/locales/sr-RS/translation.json +++ b/src/lib/i18n/locales/sr-RS/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "", "Searching \"{{searchQuery}}\"": "", "Searching Knowledge for \"{{searchQuery}}\"": "", - "Searching the web...": "", + "Searching the web": "", "Searxng Query URL": "УРЛ адреса Сеарxнг упита", "See readme.md for instructions": "Погледај readme.md за упутства", "See what's new": "Погледај шта је ново", diff --git a/src/lib/i18n/locales/sv-SE/translation.json b/src/lib/i18n/locales/sv-SE/translation.json index a6743b1102..23eed43f69 100644 --- a/src/lib/i18n/locales/sv-SE/translation.json +++ b/src/lib/i18n/locales/sv-SE/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "Sökte på {{count}} webbplatser", "Searching \"{{searchQuery}}\"": "Söker \"{{searchQuery}}\"", "Searching Knowledge for \"{{searchQuery}}\"": "Söker kunskap efter \"{{searchQuery}}\"", - "Searching the web...": "Söker på webben...", + "Searching the web": "Söker på webben...", "Searxng Query URL": "Searxng Query URL", "See readme.md for instructions": "Se readme.md för instruktioner", "See what's new": "Se vad som är nytt", diff --git a/src/lib/i18n/locales/th-TH/translation.json b/src/lib/i18n/locales/th-TH/translation.json index d9b03389ae..ddcb1073d3 100644 --- a/src/lib/i18n/locales/th-TH/translation.json +++ b/src/lib/i18n/locales/th-TH/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "", "Searching \"{{searchQuery}}\"": "กำลังค้นหา \"{{searchQuery}}\"", "Searching Knowledge for \"{{searchQuery}}\"": "", - "Searching the web...": "", + "Searching the web": "", "Searxng Query URL": "URL คำค้นหา", "See readme.md for instructions": "ดู readme.md สำหรับคำแนะนำ", "See what's new": "ดูสิ่งที่ใหม่", diff --git a/src/lib/i18n/locales/tk-TM/translation.json b/src/lib/i18n/locales/tk-TM/translation.json index 039c301198..227e302006 100644 --- a/src/lib/i18n/locales/tk-TM/translation.json +++ b/src/lib/i18n/locales/tk-TM/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "", "Searching \"{{searchQuery}}\"": "", "Searching Knowledge for \"{{searchQuery}}\"": "", - "Searching the web...": "", + "Searching the web": "", "Searxng Query URL": "", "See readme.md for instructions": "", "See what's new": "", diff --git a/src/lib/i18n/locales/tr-TR/translation.json b/src/lib/i18n/locales/tr-TR/translation.json index be131eee27..1bb5e22e21 100644 --- a/src/lib/i18n/locales/tr-TR/translation.json +++ b/src/lib/i18n/locales/tr-TR/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "{{count}} site arandı", "Searching \"{{searchQuery}}\"": "\"{{searchQuery}}\" aranıyor", "Searching Knowledge for \"{{searchQuery}}\"": "\"{{searchQuery}}\" için Bilgi aranıyor", - "Searching the web...": "İnternette aranıyor...", + "Searching the web": "İnternette aranıyor...", "Searxng Query URL": "Searxng Sorgu URL'si", "See readme.md for instructions": "Yönergeler için readme.md dosyasına bakın", "See what's new": "Yeniliklere göz atın", diff --git a/src/lib/i18n/locales/ug-CN/translation.json b/src/lib/i18n/locales/ug-CN/translation.json index 3dd5aeb111..dfc13ef660 100644 --- a/src/lib/i18n/locales/ug-CN/translation.json +++ b/src/lib/i18n/locales/ug-CN/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "{{count}} تور بېكەت ئىزدەندى", "Searching \"{{searchQuery}}\"": "\"{{searchQuery}}\" ئىزدەۋاتىدۇ", "Searching Knowledge for \"{{searchQuery}}\"": "\"{{searchQuery}}\" ئۈچۈن بىلىم ئىزدەۋاتىدۇ", - "Searching the web...": "تور ئىزدەۋاتىدۇ...", + "Searching the web": "تور ئىزدەۋاتىدۇ...", "Searxng Query URL": "Searxng ئىزدەش URL", "See readme.md for instructions": "قوللانما ئۈچۈن readme.md غا قاراڭ", "See what's new": "يېڭىلىقلارنى كۆرۈش", diff --git a/src/lib/i18n/locales/uk-UA/translation.json b/src/lib/i18n/locales/uk-UA/translation.json index 4ddad9df64..50b142e660 100644 --- a/src/lib/i18n/locales/uk-UA/translation.json +++ b/src/lib/i18n/locales/uk-UA/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "Шукалося {{count}} сайтів", "Searching \"{{searchQuery}}\"": "Шукаю \"{{searchQuery}}\"", "Searching Knowledge for \"{{searchQuery}}\"": "Пошук знань для \"{{searchQuery}}\"", - "Searching the web...": "", + "Searching the web": "", "Searxng Query URL": "Searxng Query URL", "See readme.md for instructions": "Див. readme.md для інструкцій", "See what's new": "Подивіться, що нового", diff --git a/src/lib/i18n/locales/ur-PK/translation.json b/src/lib/i18n/locales/ur-PK/translation.json index 9168f15573..daa09e7f5f 100644 --- a/src/lib/i18n/locales/ur-PK/translation.json +++ b/src/lib/i18n/locales/ur-PK/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "", "Searching \"{{searchQuery}}\"": "\"{{searchQuery}}\" تلاش کر رہے ہیں", "Searching Knowledge for \"{{searchQuery}}\"": "\"{{searchQuery}}\" کے لیے علم کی تلاش", - "Searching the web...": "", + "Searching the web": "", "Searxng Query URL": "تلاش کا سوال URL", "See readme.md for instructions": "ہدایات کے لیے readme.md دیکھیں", "See what's new": "نیا کیا ہے دیکھیں", diff --git a/src/lib/i18n/locales/uz-Cyrl-UZ/translation.json b/src/lib/i18n/locales/uz-Cyrl-UZ/translation.json index 93bac0e7bd..1c4360b801 100644 --- a/src/lib/i18n/locales/uz-Cyrl-UZ/translation.json +++ b/src/lib/i18n/locales/uz-Cyrl-UZ/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "{{count}} та сайт қидирилди", "Searching \"{{searchQuery}}\"": "“{{searchQuery}}” қидирилмоқда", "Searching Knowledge for \"{{searchQuery}}\"": "“{{searchQuery}}” бўйича маълумотлар қидирилмоқда", - "Searching the web...": "Интернетда қидирилмоқда...", + "Searching the web": "Интернетда қидирилмоқда...", "Searxng Query URL": "Searxng сўрови УРЛ", "See readme.md for instructions": "Кўрсатмалар учун readme.md га қаранг", "See what's new": "Нима янгиликлар борлигини кўринг", diff --git a/src/lib/i18n/locales/uz-Latn-Uz/translation.json b/src/lib/i18n/locales/uz-Latn-Uz/translation.json index 16a16603e4..66d0fb39aa 100644 --- a/src/lib/i18n/locales/uz-Latn-Uz/translation.json +++ b/src/lib/i18n/locales/uz-Latn-Uz/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "{{count}} ta sayt qidirildi", "Searching \"{{searchQuery}}\"": "“{{searchQuery}}” qidirilmoqda", "Searching Knowledge for \"{{searchQuery}}\"": "“{{searchQuery}}” boʻyicha maʼlumotlar qidirilmoqda", - "Searching the web...": "Internetda qidirilmoqda...", + "Searching the web": "Internetda qidirilmoqda...", "Searxng Query URL": "Searchxng so'rovi URL", "See readme.md for instructions": "Ko'rsatmalar uchun readme.md ga qarang", "See what's new": "Nima yangiliklar borligini ko'ring", diff --git a/src/lib/i18n/locales/vi-VN/translation.json b/src/lib/i18n/locales/vi-VN/translation.json index 34a3218a11..880751215b 100644 --- a/src/lib/i18n/locales/vi-VN/translation.json +++ b/src/lib/i18n/locales/vi-VN/translation.json @@ -1263,7 +1263,7 @@ "Searched {{count}} sites": "Đã tìm kiếm {{count}} trang web", "Searching \"{{searchQuery}}\"": "Đang tìm \"{{searchQuery}}\"", "Searching Knowledge for \"{{searchQuery}}\"": "Đang tìm kiếm Kiến thức cho \"{{searchQuery}}\"", - "Searching the web...": "", + "Searching the web": "", "Searxng Query URL": "URL truy vấn Searxng", "See readme.md for instructions": "Xem readme.md để biết hướng dẫn", "See what's new": "Xem những cập nhật mới", diff --git a/src/lib/i18n/locales/zh-CN/translation.json b/src/lib/i18n/locales/zh-CN/translation.json index e4a5e82478..63fa58322b 100644 --- a/src/lib/i18n/locales/zh-CN/translation.json +++ b/src/lib/i18n/locales/zh-CN/translation.json @@ -1267,7 +1267,7 @@ "Searched {{count}} sites": "已搜索 {{count}} 个网站", "Searching \"{{searchQuery}}\"": "搜索 \"{{searchQuery}}\" 中", "Searching Knowledge for \"{{searchQuery}}\"": "检索有关 \"{{searchQuery}}\" 的知识中", - "Searching the web...": "正在搜索网络...", + "Searching the web": "正在搜索网络...", "Searxng Query URL": "Searxng 查询 URL", "See readme.md for instructions": "查看 readme.md 以获取说明", "See what's new": "查阅最新更新内容", diff --git a/src/lib/i18n/locales/zh-TW/translation.json b/src/lib/i18n/locales/zh-TW/translation.json index dfc0eb3d2b..867d97ffcc 100644 --- a/src/lib/i18n/locales/zh-TW/translation.json +++ b/src/lib/i18n/locales/zh-TW/translation.json @@ -1267,7 +1267,7 @@ "Searched {{count}} sites": "搜尋到 {{count}} 個網站", "Searching \"{{searchQuery}}\"": "正在搜尋「{{searchQuery}}」", "Searching Knowledge for \"{{searchQuery}}\"": "正在搜尋知識庫中的「{{searchQuery}}」", - "Searching the web...": "正在搜尋網路...", + "Searching the web": "正在搜尋網路...", "Searxng Query URL": "Searxng 查詢 URL", "See readme.md for instructions": "檢視 readme.md 以取得說明", "See what's new": "檢視新功能", From f2525ebc447c008cf7269ef20ce04fa456f302c4 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sun, 7 Sep 2025 04:25:52 +0400 Subject: [PATCH 103/154] refac --- .../ResponseMessage/StatusHistory.svelte | 57 ++++++++++--------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/src/lib/components/chat/Messages/ResponseMessage/StatusHistory.svelte b/src/lib/components/chat/Messages/ResponseMessage/StatusHistory.svelte index 6a33dd7bf7..bfdac3a817 100644 --- a/src/lib/components/chat/Messages/ResponseMessage/StatusHistory.svelte +++ b/src/lib/components/chat/Messages/ResponseMessage/StatusHistory.svelte @@ -19,35 +19,36 @@ --> -{#if statusHistory} -
      - {#if showHistory} -
      - {#if statusHistory.length > 1} -
      +{#if statusHistory && statusHistory.length > 0} + {@const status = statusHistory.at(-1)} -
      - {#each statusHistory as status, idx} - {#if idx !== statusHistory.length - 1} -
      -
      - - - + {#if status?.hidden !== true} +
      + {#if showHistory} +
      + {#if statusHistory.length > 1} +
      + +
      + {#each statusHistory as status, idx} + {#if idx !== statusHistory.length - 1} +
      +
      + + + +
      +
      - -
      - {/if} - {/each} -
      - {/if} -
      - {/if} + {/if} + {/each} +
      + {/if} +
      + {/if} - {#if statusHistory.length > 0} - {@const status = statusHistory.at(-1)}
      - {/if} -
      +
      + {/if} {/if} From 7f523de408ede4075349d8de71ae0214b7e1a62e Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sun, 7 Sep 2025 04:27:42 +0400 Subject: [PATCH 104/154] refac --- .../chat/Messages/ResponseMessage/WebSearchResults.svelte | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/lib/components/chat/Messages/ResponseMessage/WebSearchResults.svelte b/src/lib/components/chat/Messages/ResponseMessage/WebSearchResults.svelte index 31360f7132..bcd35f7586 100644 --- a/src/lib/components/chat/Messages/ResponseMessage/WebSearchResults.svelte +++ b/src/lib/components/chat/Messages/ResponseMessage/WebSearchResults.svelte @@ -8,9 +8,14 @@ let state = false; - +
      + {#if state} + + {:else} + + {/if}
      Date: Sun, 7 Sep 2025 05:06:03 +0400 Subject: [PATCH 105/154] refac --- backend/open_webui/utils/middleware.py | 35 ++++++++++---- .../chat/Messages/ResponseMessage.svelte | 7 ++- .../ResponseMessage/StatusHistory.svelte | 35 +++++++------- .../StatusHistory/StatusItem.svelte | 48 ++++++++++++++++++- 4 files changed, 92 insertions(+), 33 deletions(-) diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index d17b03b882..7320d77816 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -663,16 +663,16 @@ async def chat_completion_files_handler( if len(queries) == 0: queries = [get_last_user_message(body["messages"])] - # await __event_emitter__( - # { - # "type": "status", - # "data": { - # "action": "queries_generated", - # "queries": queries, - # "done": True, - # }, - # } - # ) + await __event_emitter__( + { + "type": "status", + "data": { + "action": "queries_generated", + "queries": queries, + "done": False, + }, + } + ) try: # Offload get_sources_from_items to a separate thread @@ -710,6 +710,21 @@ async def chat_completion_files_handler( log.debug(f"rag_contexts:sources: {sources}") + sources_count = 0 + for source in sources: + sources_count += len(source.get("document", [])) + + await __event_emitter__( + { + "type": "status", + "data": { + "action": "sources_retrieved", + "count": sources_count, + "done": True, + }, + } + ) + return body, {"sources": sources} diff --git a/src/lib/components/chat/Messages/ResponseMessage.svelte b/src/lib/components/chat/Messages/ResponseMessage.svelte index 271a49e940..35aaf111e2 100644 --- a/src/lib/components/chat/Messages/ResponseMessage.svelte +++ b/src/lib/components/chat/Messages/ResponseMessage.svelte @@ -643,7 +643,10 @@
      - + {#if message?.files && message.files?.filter((f) => f.type === 'image').length > 0}
      @@ -729,7 +732,7 @@
      {:else}
      - {#if message.content === '' && !message.error && (message?.statusHistory ?? [...(message?.status ? [message?.status] : [])]).length === 0} + {#if message.content === '' && !message.error && ((message?.statusHistory ?? [...(message?.status ? [message?.status] : [])]).length === 0 || (message?.statusHistory?.at(-1)?.hidden ?? false))} {:else if message.content && message.error !== true} diff --git a/src/lib/components/chat/Messages/ResponseMessage/StatusHistory.svelte b/src/lib/components/chat/Messages/ResponseMessage/StatusHistory.svelte index bfdac3a817..ed32d16fdd 100644 --- a/src/lib/components/chat/Messages/ResponseMessage/StatusHistory.svelte +++ b/src/lib/components/chat/Messages/ResponseMessage/StatusHistory.svelte @@ -2,36 +2,33 @@ import { getContext } from 'svelte'; const i18n = getContext('i18n'); - import Collapsible from '$lib/components/common/Collapsible.svelte'; import StatusItem from './StatusHistory/StatusItem.svelte'; export let statusHistory = []; + export let showHistory = true; - let showHistory = false; + let history = []; + let status = null; + + $: if (history && history.length > 0) { + status = history.at(-1); + } + + $: if (JSON.stringify(statusHistory) !== JSON.stringify(history)) { + history = statusHistory; + } - - -{#if statusHistory && statusHistory.length > 0} - {@const status = statusHistory.at(-1)} - +{#if history && history.length > 0} {#if status?.hidden !== true}
      {#if showHistory}
      - {#if statusHistory.length > 1} + {#if history.length > 1}
      - {#each statusHistory as status, idx} - {#if idx !== statusHistory.length - 1} + {#each history as status, idx} + {#if idx !== history.length - 1}
      @@ -55,7 +52,7 @@ showHistory = !showHistory; }} > -
      +
      {#if status?.done === false} diff --git a/src/lib/components/chat/Messages/ResponseMessage/StatusHistory/StatusItem.svelte b/src/lib/components/chat/Messages/ResponseMessage/StatusHistory/StatusItem.svelte index 7f325f7f6e..f4728a2bf0 100644 --- a/src/lib/components/chat/Messages/ResponseMessage/StatusHistory/StatusItem.svelte +++ b/src/lib/components/chat/Messages/ResponseMessage/StatusHistory/StatusItem.svelte @@ -66,13 +66,57 @@
      - + {query}
      {/each}
      + {:else if status?.action === 'queries_generated' && status?.queries} +
      +
      + {$i18n.t(`Querying`)} +
      + +
      + {#each status.queries as query, idx (query)} +
      +
      + +
      + + + {query} + +
      + {/each} +
      +
      + {:else if status?.action === 'sources_retrieved' && status?.count !== undefined} +
      +
      + {#if status.count === 0} + {$i18n.t('No sources found')} + {:else if status.count === 1} + {$i18n.t('Retrieved 1 source')} + {:else} + {$i18n.t('Retrieved {{count}} sources', { + count: status.count + })} + {/if} +
      +
      {:else}
      - {#if status?.description.includes('{{searchQuery}}')} + {#if status?.description?.includes('{{searchQuery}}')} {$i18n.t(status?.description, { searchQuery: status?.query })} From cd5e2be27b613314aadda6107089331783987985 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sun, 7 Sep 2025 05:09:14 +0400 Subject: [PATCH 106/154] refac --- .../components/chat/Messages/ResponseMessage.svelte | 2 +- .../chat/Messages/ResponseMessage/StatusHistory.svelte | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/lib/components/chat/Messages/ResponseMessage.svelte b/src/lib/components/chat/Messages/ResponseMessage.svelte index 35aaf111e2..0dfe4381c7 100644 --- a/src/lib/components/chat/Messages/ResponseMessage.svelte +++ b/src/lib/components/chat/Messages/ResponseMessage.svelte @@ -645,7 +645,7 @@
      {#if message?.files && message.files?.filter((f) => f.type === 'image').length > 0} diff --git a/src/lib/components/chat/Messages/ResponseMessage/StatusHistory.svelte b/src/lib/components/chat/Messages/ResponseMessage/StatusHistory.svelte index ed32d16fdd..b31e5ee601 100644 --- a/src/lib/components/chat/Messages/ResponseMessage/StatusHistory.svelte +++ b/src/lib/components/chat/Messages/ResponseMessage/StatusHistory.svelte @@ -4,7 +4,15 @@ import StatusItem from './StatusHistory/StatusItem.svelte'; export let statusHistory = []; - export let showHistory = true; + export let expand = false; + + let showHistory = true; + + $: if (expand) { + showHistory = true; + } else { + showHistory = false; + } let history = []; let status = null; From 6dc0df247347aede2762fe2065cf30275fd137ae Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sun, 7 Sep 2025 05:17:38 +0400 Subject: [PATCH 107/154] refac --- backend/open_webui/utils/middleware.py | 23 ++++++++++++++++--- .../chat/Messages/ResponseMessage.svelte | 12 ++++++---- .../workspace/Models/Capabilities.svelte | 5 ++++ .../workspace/Models/ModelEditor.svelte | 1 + 4 files changed, 33 insertions(+), 8 deletions(-) diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index 7320d77816..463f52d0af 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -710,9 +710,26 @@ async def chat_completion_files_handler( log.debug(f"rag_contexts:sources: {sources}") - sources_count = 0 - for source in sources: - sources_count += len(source.get("document", [])) + unique_ids = set() + + for source in sources or []: + if not source or len(source.keys()) == 0: + continue + + documents = source.get("document") or [] + metadatas = source.get("metadata") or [] + src_info = source.get("source") or {} + + for index, _ in enumerate(documents): + metadata = metadatas[index] if index < len(metadatas) else None + _id = ( + (metadata or {}).get("source") + or (src_info or {}).get("id") + or "N/A" + ) + unique_ids.add(_id) + + sources_count = len(unique_ids) await __event_emitter__( { diff --git a/src/lib/components/chat/Messages/ResponseMessage.svelte b/src/lib/components/chat/Messages/ResponseMessage.svelte index 0dfe4381c7..9db76bfa85 100644 --- a/src/lib/components/chat/Messages/ResponseMessage.svelte +++ b/src/lib/components/chat/Messages/ResponseMessage.svelte @@ -643,10 +643,12 @@
      - + {#if model?.info?.meta?.capabilities?.status_updates ?? true} + + {/if} {#if message?.files && message.files?.filter((f) => f.type === 'image').length > 0}
      @@ -732,7 +734,7 @@
      {:else}
      - {#if message.content === '' && !message.error && ((message?.statusHistory ?? [...(message?.status ? [message?.status] : [])]).length === 0 || (message?.statusHistory?.at(-1)?.hidden ?? false))} + {#if message.content === '' && !message.error && ((model?.info?.meta?.capabilities?.status_updates ?? true) ? (message?.statusHistory ?? [...(message?.status ? [message?.status] : [])]).length === 0 || (message?.statusHistory?.at(-1)?.hidden ?? false) : true)} {:else if message.content && message.error !== true} diff --git a/src/lib/components/workspace/Models/Capabilities.svelte b/src/lib/components/workspace/Models/Capabilities.svelte index d0b45384a6..2d10802df8 100644 --- a/src/lib/components/workspace/Models/Capabilities.svelte +++ b/src/lib/components/workspace/Models/Capabilities.svelte @@ -36,6 +36,10 @@ citations: { label: $i18n.t('Citations'), description: $i18n.t('Displays citations in the response') + }, + status_updates: { + label: $i18n.t('Status Updates'), + description: $i18n.t('Displays status updates (e.g., web search progress) in the response') } }; @@ -47,6 +51,7 @@ code_interpreter?: boolean; usage?: boolean; citations?: boolean; + status_updates?: boolean; } = {}; diff --git a/src/lib/components/workspace/Models/ModelEditor.svelte b/src/lib/components/workspace/Models/ModelEditor.svelte index 0791b80bbb..fc9c167e9b 100644 --- a/src/lib/components/workspace/Models/ModelEditor.svelte +++ b/src/lib/components/workspace/Models/ModelEditor.svelte @@ -86,6 +86,7 @@ image_generation: true, code_interpreter: true, citations: true, + status_updates: true, usage: undefined }; From e023a98f11fc52feb21e4065ec707cc98e50c7d3 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sun, 7 Sep 2025 19:24:32 +0400 Subject: [PATCH 108/154] refac: submit suggestion prompt by default --- src/lib/components/chat/Chat.svelte | 7 +++- src/lib/components/chat/MessageInput.svelte | 38 ++++++++++++------- .../components/chat/Settings/Interface.svelte | 21 ++++++++++ 3 files changed, 51 insertions(+), 15 deletions(-) diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte index ded370b4c2..e0b9a0a3c0 100644 --- a/src/lib/components/chat/Chat.svelte +++ b/src/lib/components/chat/Chat.svelte @@ -202,7 +202,12 @@ if (type === 'prompt') { // Handle prompt selection - messageInput?.setText(data); + messageInput?.setText(data, async () => { + if (!($settings?.insertSuggestionPrompt ?? false)) { + await tick(); + submitPrompt(prompt); + } + }); } }; diff --git a/src/lib/components/chat/MessageInput.svelte b/src/lib/components/chat/MessageInput.svelte index 6109767929..dbac52ad59 100644 --- a/src/lib/components/chat/MessageInput.svelte +++ b/src/lib/components/chat/MessageInput.svelte @@ -101,6 +101,7 @@ export let codeInterpreterEnabled = false; let showInputVariablesModal = false; + let inputVariablesModalCallback = (variableValues) => {}; let inputVariables = {}; let inputVariableValues = {}; @@ -122,11 +123,24 @@ codeInterpreterEnabled }); - const inputVariableHandler = async (text: string) => { + const inputVariableHandler = async (text: string): Promise => { inputVariables = extractInputVariables(text); - if (Object.keys(inputVariables).length > 0) { - showInputVariablesModal = true; + + // No variables? return the original text immediately. + if (Object.keys(inputVariables).length === 0) { + return text; } + + // Show modal and wait for the user's input. + showInputVariablesModal = true; + return await new Promise((resolve) => { + inputVariablesModalCallback = (variableValues) => { + inputVariableValues = { ...inputVariableValues, ...variableValues }; + replaceVariables(inputVariableValues); + showInputVariablesModal = false; + resolve(text); + }; + }); }; const textVariableHandler = async (text: string) => { @@ -244,7 +258,6 @@ text = text.replaceAll('{{CURRENT_WEEKDAY}}', weekday); } - inputVariableHandler(text); return text; }; @@ -280,7 +293,7 @@ } }; - export const setText = async (text?: string) => { + export const setText = async (text?: string, cb?: (text: string) => void) => { const chatInput = document.getElementById('chat-input'); if (chatInput) { @@ -296,6 +309,10 @@ chatInput.focus(); chatInput.dispatchEvent(new Event('input')); } + + text = await inputVariableHandler(text); + await tick(); + if (cb) await cb(text); } }; @@ -758,11 +775,7 @@ } ]; }; - reader.readAsDataURL( - file['type'] === 'image/heic' - ? await convertHeicToJpeg(file) - : file - ); + reader.readAsDataURL(file['type'] === 'image/heic' ? await convertHeicToJpeg(file) : file); } else { uploadFileHandler(file); } @@ -868,10 +881,7 @@ { - inputVariableValues = { ...inputVariableValues, ...variableValues }; - replaceVariables(inputVariableValues); - }} + onSave={inputVariablesModalCallback} /> {#if loaded} diff --git a/src/lib/components/chat/Settings/Interface.svelte b/src/lib/components/chat/Settings/Interface.svelte index 9c6526c1d2..c383b831bb 100644 --- a/src/lib/components/chat/Settings/Interface.svelte +++ b/src/lib/components/chat/Settings/Interface.svelte @@ -49,6 +49,7 @@ let largeTextAsFile = false; + let insertSuggestionPrompt = false; let keepFollowUpPrompts = false; let insertFollowUpPrompt = false; @@ -200,6 +201,7 @@ insertPromptAsRichText = $settings?.insertPromptAsRichText ?? false; promptAutocomplete = $settings?.promptAutocomplete ?? false; + insertSuggestionPrompt = $settings?.insertSuggestionPrompt ?? false; keepFollowUpPrompts = $settings?.keepFollowUpPrompts ?? false; insertFollowUpPrompt = $settings?.insertFollowUpPrompt ?? false; @@ -697,6 +699,25 @@
      +
      +
      +
      + {$i18n.t('Insert Suggestion Prompt to Input')} +
      + +
      + { + saveSettings({ insertSuggestionPrompt }); + }} + /> +
      +
      +
      +
      From a28ca305193d13724a00784ebe6b4e82cad3281f Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 8 Sep 2025 02:35:51 +0400 Subject: [PATCH 109/154] refac/fix: source citation --- src/lib/utils/index.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts index 0bf594a560..ea635bf286 100644 --- a/src/lib/utils/index.ts +++ b/src/lib/utils/index.ts @@ -70,7 +70,10 @@ export const replaceTokens = (content, sourceIds, char, user) => { if (Array.isArray(sourceIds)) { sourceIds.forEach((sourceId, idx) => { const regex = new RegExp(`\\[${idx + 1}\\]`, 'g'); - segment = segment.replace(regex, ``); + segment = segment.replace( + regex, + `` + ); }); } From 91755309cebc5d732c79e265fdf8ce25451822fe Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 8 Sep 2025 14:18:25 +0400 Subject: [PATCH 110/154] refac --- backend/open_webui/env.py | 4 ++++ backend/open_webui/routers/auths.py | 8 +++++++- backend/open_webui/utils/auth.py | 6 ++++++ backend/open_webui/utils/oauth.py | 16 +++++++++++++--- 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/backend/open_webui/env.py b/backend/open_webui/env.py index f0b26ae25c..f72d827afc 100644 --- a/backend/open_webui/env.py +++ b/backend/open_webui/env.py @@ -465,6 +465,10 @@ ENABLE_COMPRESSION_MIDDLEWARE = ( os.environ.get("ENABLE_COMPRESSION_MIDDLEWARE", "True").lower() == "true" ) +ENABLE_OAUTH_SESSION_TOKENS_COOKIES = ( + os.environ.get("ENABLE_OAUTH_SESSION_TOKENS_COOKIES", "True").lower() == "true" +) + #################################### # SCIM Configuration diff --git a/backend/open_webui/routers/auths.py b/backend/open_webui/routers/auths.py index b8670edeaa..665660a954 100644 --- a/backend/open_webui/routers/auths.py +++ b/backend/open_webui/routers/auths.py @@ -28,6 +28,7 @@ from open_webui.env import ( WEBUI_AUTH_TRUSTED_GROUPS_HEADER, WEBUI_AUTH_COOKIE_SAME_SITE, WEBUI_AUTH_COOKIE_SECURE, + ENABLE_OAUTH_SESSION_TOKENS_COOKIES, WEBUI_AUTH_SIGNOUT_REDIRECT_URL, ENABLE_INITIAL_ADMIN_SIGNUP, SRC_LOG_LEVELS, @@ -678,6 +679,7 @@ async def signout(request: Request, response: Response): response.delete_cookie("oui-session") if ENABLE_OAUTH_SIGNUP.value: + # TODO: update this to use oauth_session_tokens in User Object oauth_id_token = request.cookies.get("oauth_id_token") if oauth_id_token and OPENID_PROVIDER_URL.value: try: @@ -687,7 +689,11 @@ async def signout(request: Request, response: Response): openid_data = await resp.json() logout_url = openid_data.get("end_session_endpoint") if logout_url: - response.delete_cookie("oauth_id_token") + + if ENABLE_OAUTH_SESSION_TOKENS_COOKIES: + response.delete_cookie("oauth_id_token") + response.delete_cookie("oauth_access_token") + response.delete_cookie("oauth_refresh_token") return JSONResponse( status_code=200, diff --git a/backend/open_webui/utils/auth.py b/backend/open_webui/utils/auth.py index 228dd3e30a..33b377ad03 100644 --- a/backend/open_webui/utils/auth.py +++ b/backend/open_webui/utils/auth.py @@ -285,8 +285,14 @@ def get_current_user( # Delete the token cookie response.delete_cookie("token") # Delete OAuth token if present + if request.cookies.get("oauth_id_token"): response.delete_cookie("oauth_id_token") + if request.cookies.get("oauth_access_token"): + response.delete_cookie("oauth_access_token") + if request.cookies.get("oauth_refresh_token"): + response.delete_cookie("oauth_refresh_token") + raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="User mismatch. Please sign in again.", diff --git a/backend/open_webui/utils/oauth.py b/backend/open_webui/utils/oauth.py index 9dfdad50a1..9763b35463 100644 --- a/backend/open_webui/utils/oauth.py +++ b/backend/open_webui/utils/oauth.py @@ -626,6 +626,15 @@ class OAuthManager: ) if ENABLE_OAUTH_SIGNUP.value: + oauth_id_token = token.get("id_token") + response.set_cookie( + key="oauth_id_token", + value=oauth_id_token, + httponly=True, + samesite=WEBUI_AUTH_COOKIE_SAME_SITE, + secure=WEBUI_AUTH_COOKIE_SECURE, + ) + oauth_access_token = token.get("access_token") response.set_cookie( key="oauth_access_token", @@ -635,12 +644,13 @@ class OAuthManager: secure=WEBUI_AUTH_COOKIE_SECURE, ) - oauth_id_token = token.get("id_token") + oauth_refresh_token = token.get("refresh_token") response.set_cookie( - key="oauth_id_token", - value=oauth_id_token, + key="oauth_refresh_token", + value=oauth_refresh_token, httponly=True, samesite=WEBUI_AUTH_COOKIE_SAME_SITE, secure=WEBUI_AUTH_COOKIE_SECURE, ) + return response From 6d38ac41b6375c70659cab1c8551032bba94efcc Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 8 Sep 2025 14:36:00 +0400 Subject: [PATCH 111/154] refac --- backend/open_webui/routers/auths.py | 11 ++--- backend/open_webui/utils/oauth.py | 68 ++++++++++++++--------------- 2 files changed, 40 insertions(+), 39 deletions(-) diff --git a/backend/open_webui/routers/auths.py b/backend/open_webui/routers/auths.py index 665660a954..524edf373d 100644 --- a/backend/open_webui/routers/auths.py +++ b/backend/open_webui/routers/auths.py @@ -681,15 +681,16 @@ async def signout(request: Request, response: Response): if ENABLE_OAUTH_SIGNUP.value: # TODO: update this to use oauth_session_tokens in User Object oauth_id_token = request.cookies.get("oauth_id_token") + if oauth_id_token and OPENID_PROVIDER_URL.value: try: async with ClientSession(trust_env=True) as session: - async with session.get(OPENID_PROVIDER_URL.value) as resp: - if resp.status == 200: - openid_data = await resp.json() + async with session.get(OPENID_PROVIDER_URL.value) as r: + if r.status == 200: + openid_data = await r.json() logout_url = openid_data.get("end_session_endpoint") - if logout_url: + if logout_url: if ENABLE_OAUTH_SESSION_TOKENS_COOKIES: response.delete_cookie("oauth_id_token") response.delete_cookie("oauth_access_token") @@ -710,7 +711,7 @@ async def signout(request: Request, response: Response): ) else: raise HTTPException( - status_code=resp.status, + status_code=r.status, detail="Failed to fetch OpenID configuration", ) except Exception as e: diff --git a/backend/open_webui/utils/oauth.py b/backend/open_webui/utils/oauth.py index 9763b35463..4411f40e3b 100644 --- a/backend/open_webui/utils/oauth.py +++ b/backend/open_webui/utils/oauth.py @@ -49,6 +49,7 @@ from open_webui.env import ( WEBUI_NAME, WEBUI_AUTH_COOKIE_SAME_SITE, WEBUI_AUTH_COOKIE_SECURE, + ENABLE_OAUTH_SESSION_TOKENS_COOKIES, ) from open_webui.utils.misc import parse_duration from open_webui.utils.auth import get_password_hash, create_token @@ -410,6 +411,8 @@ class OAuthManager: except Exception as e: log.warning(f"OAuth callback error: {e}") raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED) + + # Try to get userinfo from the token first, some providers include it there user_data: UserInfo = token.get("userinfo") if ( (not user_data) @@ -421,18 +424,19 @@ class OAuthManager: log.warning(f"OAuth callback failed, user data is missing: {token}") raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED) + # Extract the "sub" claim, using custom claim if configured if auth_manager_config.OAUTH_SUB_CLAIM: sub = user_data.get(auth_manager_config.OAUTH_SUB_CLAIM) else: # Fallback to the default sub claim if not configured sub = user_data.get(OAUTH_PROVIDERS[provider].get("sub_claim", "sub")) - if not sub: log.warning(f"OAuth callback failed, sub is missing: {user_data}") raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED) provider_sub = f"{provider}@{sub}" + # Email extraction email_claim = auth_manager_config.OAUTH_EMAIL_CLAIM email = user_data.get(email_claim, "") # We currently mandate that email addresses are provided @@ -480,6 +484,8 @@ class OAuthManager: log.warning(f"OAuth callback failed, email is missing: {user_data}") raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED) email = email.lower() + + # If allowed domains are configured, check if the email domain is in the list if ( "*" not in auth_manager_config.OAUTH_ALLOWED_DOMAINS and email.split("@")[-1] @@ -492,7 +498,6 @@ class OAuthManager: # Check if the user exists user = Users.get_user_by_oauth_sub(provider_sub) - if not user: # If the user does not exist, check if merging is enabled if auth_manager_config.OAUTH_MERGE_ACCOUNTS_BY_EMAIL: @@ -506,7 +511,6 @@ class OAuthManager: determined_role = self.get_user_role(user, user_data) if user.role != determined_role: Users.update_user_role_by_id(user.id, determined_role) - # Update profile picture if enabled and different from current if auth_manager_config.OAUTH_UPDATE_PICTURE_ON_LOGIN: picture_claim = auth_manager_config.OAUTH_PICTURE_CLAIM @@ -523,8 +527,7 @@ class OAuthManager: user.id, processed_picture_url ) log.debug(f"Updated profile picture for user {user.email}") - - if not user: + else: # If the user does not exist, check if signups are enabled if auth_manager_config.ENABLE_OAUTH_SIGNUP: # Check if an existing user with the same email already exists @@ -543,7 +546,6 @@ class OAuthManager: ) else: picture_url = "/user.png" - username_claim = auth_manager_config.OAUTH_USERNAME_CLAIM name = user_data.get(username_claim) @@ -551,8 +553,6 @@ class OAuthManager: log.warning("Username claim is missing, using email as name") name = email - role = self.get_user_role(None, user_data) - user = Auths.insert_new_auth( email=email, password=get_password_hash( @@ -560,7 +560,7 @@ class OAuthManager: ), # Random password, not used name=name, profile_image_url=picture_url, - role=role, + role=self.get_user_role(None, user_data), oauth_sub=provider_sub, ) @@ -585,7 +585,6 @@ class OAuthManager: data={"id": user.id}, expires_delta=parse_duration(auth_manager_config.JWT_EXPIRES_IN), ) - if ( auth_manager_config.ENABLE_OAUTH_GROUP_MANAGEMENT and user.role != "admin" @@ -626,31 +625,32 @@ class OAuthManager: ) if ENABLE_OAUTH_SIGNUP.value: - oauth_id_token = token.get("id_token") - response.set_cookie( - key="oauth_id_token", - value=oauth_id_token, - httponly=True, - samesite=WEBUI_AUTH_COOKIE_SAME_SITE, - secure=WEBUI_AUTH_COOKIE_SECURE, - ) + if ENABLE_OAUTH_SESSION_TOKENS_COOKIES: + oauth_id_token = token.get("id_token") + response.set_cookie( + key="oauth_id_token", + value=oauth_id_token, + httponly=True, + samesite=WEBUI_AUTH_COOKIE_SAME_SITE, + secure=WEBUI_AUTH_COOKIE_SECURE, + ) - oauth_access_token = token.get("access_token") - response.set_cookie( - key="oauth_access_token", - value=oauth_access_token, - httponly=True, - samesite=WEBUI_AUTH_COOKIE_SAME_SITE, - secure=WEBUI_AUTH_COOKIE_SECURE, - ) + oauth_access_token = token.get("access_token") + response.set_cookie( + key="oauth_access_token", + value=oauth_access_token, + httponly=True, + samesite=WEBUI_AUTH_COOKIE_SAME_SITE, + secure=WEBUI_AUTH_COOKIE_SECURE, + ) - oauth_refresh_token = token.get("refresh_token") - response.set_cookie( - key="oauth_refresh_token", - value=oauth_refresh_token, - httponly=True, - samesite=WEBUI_AUTH_COOKIE_SAME_SITE, - secure=WEBUI_AUTH_COOKIE_SECURE, - ) + oauth_refresh_token = token.get("refresh_token") + response.set_cookie( + key="oauth_refresh_token", + value=oauth_refresh_token, + httponly=True, + samesite=WEBUI_AUTH_COOKIE_SAME_SITE, + secure=WEBUI_AUTH_COOKIE_SECURE, + ) return response From 217f4daef09b36d3d4cc4681e11d3ebd9984a1a5 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 8 Sep 2025 18:05:43 +0400 Subject: [PATCH 112/154] feat: server-side OAuth token management system Co-Authored-By: Classic298 <27028174+Classic298@users.noreply.github.com> --- backend/open_webui/env.py | 13 +- backend/open_webui/main.py | 1 + .../38d63c18f30f_add_oauth_session_table.py | 52 ++++ backend/open_webui/models/oauth_sessions.py | 247 ++++++++++++++++++ backend/open_webui/routers/auths.py | 32 +-- backend/open_webui/utils/auth.py | 96 +++---- backend/open_webui/utils/middleware.py | 9 + backend/open_webui/utils/oauth.py | 247 ++++++++++++++++-- backend/open_webui/utils/tools.py | 15 ++ src/lib/components/AddServerModal.svelte | 7 + 10 files changed, 627 insertions(+), 92 deletions(-) create mode 100644 backend/open_webui/migrations/versions/38d63c18f30f_add_oauth_session_table.py create mode 100644 backend/open_webui/models/oauth_sessions.py diff --git a/backend/open_webui/env.py b/backend/open_webui/env.py index f72d827afc..b4fdc97d82 100644 --- a/backend/open_webui/env.py +++ b/backend/open_webui/env.py @@ -465,8 +465,17 @@ ENABLE_COMPRESSION_MIDDLEWARE = ( os.environ.get("ENABLE_COMPRESSION_MIDDLEWARE", "True").lower() == "true" ) -ENABLE_OAUTH_SESSION_TOKENS_COOKIES = ( - os.environ.get("ENABLE_OAUTH_SESSION_TOKENS_COOKIES", "True").lower() == "true" +#################################### +# OAUTH Configuration +#################################### + + +ENABLE_OAUTH_ID_TOKEN_COOKIE = ( + os.environ.get("ENABLE_OAUTH_ID_TOKEN_COOKIE", "True").lower() == "true" +) + +OAUTH_SESSION_TOKEN_ENCRYPTION_KEY = os.environ.get( + "OAUTH_SESSION_TOKEN_ENCRYPTION_KEY", WEBUI_SECRET_KEY ) diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index 7decfcd83b..de7dcae086 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -592,6 +592,7 @@ app = FastAPI( ) oauth_manager = OAuthManager(app) +app.state.oauth_manager = oauth_manager app.state.instance_id = None app.state.config = AppConfig( diff --git a/backend/open_webui/migrations/versions/38d63c18f30f_add_oauth_session_table.py b/backend/open_webui/migrations/versions/38d63c18f30f_add_oauth_session_table.py new file mode 100644 index 0000000000..8ead6db6d4 --- /dev/null +++ b/backend/open_webui/migrations/versions/38d63c18f30f_add_oauth_session_table.py @@ -0,0 +1,52 @@ +"""Add oauth_session table + +Revision ID: 38d63c18f30f +Revises: 3af16a1c9fb6 +Create Date: 2025-09-08 14:19:59.583921 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = "38d63c18f30f" +down_revision: Union[str, None] = "3af16a1c9fb6" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # Create oauth_session table + op.create_table( + "oauth_session", + sa.Column("id", sa.Text(), nullable=False), + sa.Column("user_id", sa.Text(), nullable=False), + sa.Column("provider", sa.Text(), nullable=False), + sa.Column("token", sa.Text(), nullable=False), + sa.Column("expires_at", sa.BigInteger(), nullable=False), + sa.Column("created_at", sa.BigInteger(), nullable=False), + sa.Column("updated_at", sa.BigInteger(), nullable=False), + sa.PrimaryKeyConstraint("id"), + sa.ForeignKeyConstraint(["user_id"], ["user.id"], ondelete="CASCADE"), + ) + + # Create indexes for better performance + op.create_index("idx_oauth_session_user_id", "oauth_session", ["user_id"]) + op.create_index("idx_oauth_session_expires_at", "oauth_session", ["expires_at"]) + op.create_index( + "idx_oauth_session_user_provider", "oauth_session", ["user_id", "provider"] + ) + + +def downgrade() -> None: + # Drop indexes first + op.drop_index("idx_oauth_session_user_provider", table_name="oauth_session") + op.drop_index("idx_oauth_session_expires_at", table_name="oauth_session") + op.drop_index("idx_oauth_session_user_id", table_name="oauth_session") + + # Drop the table + op.drop_table("oauth_session") diff --git a/backend/open_webui/models/oauth_sessions.py b/backend/open_webui/models/oauth_sessions.py new file mode 100644 index 0000000000..b0b5aa29a6 --- /dev/null +++ b/backend/open_webui/models/oauth_sessions.py @@ -0,0 +1,247 @@ +import time +import logging +import uuid +from typing import Optional, List +import base64 +import hashlib +import json + +from cryptography.fernet import Fernet + +from open_webui.internal.db import Base, get_db +from open_webui.env import SRC_LOG_LEVELS, OAUTH_SESSION_TOKEN_ENCRYPTION_KEY + +from pydantic import BaseModel, ConfigDict +from sqlalchemy import BigInteger, Column, String, Text, Index + +log = logging.getLogger(__name__) +log.setLevel(SRC_LOG_LEVELS["MODELS"]) + +#################### +# DB MODEL +#################### + + +class OAuthSession(Base): + __tablename__ = "oauth_session" + + id = Column(Text, primary_key=True) + user_id = Column(Text, nullable=False) + provider = Column(Text, nullable=False) + token = Column( + Text, nullable=False + ) # JSON with access_token, id_token, refresh_token + expires_at = Column(BigInteger, nullable=False) + created_at = Column(BigInteger, nullable=False) + updated_at = Column(BigInteger, nullable=False) + + # Add indexes for better performance + __table_args__ = ( + Index("idx_oauth_session_user_id", "user_id"), + Index("idx_oauth_session_expires_at", "expires_at"), + Index("idx_oauth_session_user_provider", "user_id", "provider"), + ) + + +class OAuthSessionModel(BaseModel): + id: str + user_id: str + provider: str + token: dict + expires_at: int # timestamp in epoch + created_at: int # timestamp in epoch + updated_at: int # timestamp in epoch + + model_config = ConfigDict(from_attributes=True) + + +#################### +# Forms +#################### + + +class OAuthSessionResponse(BaseModel): + id: str + user_id: str + provider: str + expires_at: int + + +class OAuthSessionTable: + def __init__(self): + self.encryption_key = OAUTH_SESSION_TOKEN_ENCRYPTION_KEY + if not self.encryption_key: + raise Exception("OAUTH_SESSION_TOKEN_ENCRYPTION_KEY is not set") + + # check if encryption key is in the right format for Fernet (32 url-safe base64-encoded bytes) + if len(self.encryption_key) != 44: + key_bytes = hashlib.sha256(self.encryption_key.encode()).digest() + self.encryption_key = base64.urlsafe_b64encode(key_bytes) + else: + self.encryption_key = self.encryption_key.encode() + + try: + self.fernet = Fernet(self.encryption_key) + except Exception as e: + log.error(f"Error initializing Fernet with provided key: {e}") + raise + + def _encrypt_token(self, token) -> str: + """Encrypt OAuth tokens for storage""" + try: + token_json = json.dumps(token) + encrypted = self.fernet.encrypt(token_json.encode()).decode() + return encrypted + except Exception as e: + log.error(f"Error encrypting tokens: {e}") + raise + + def _decrypt_token(self, token: str): + """Decrypt OAuth tokens from storage""" + try: + decrypted = self.fernet.decrypt(token.encode()).decode() + return json.loads(decrypted) + except Exception as e: + log.error(f"Error decrypting tokens: {e}") + raise + + def create_session( + self, + user_id: str, + provider: str, + token: dict, + ) -> Optional[OAuthSessionModel]: + """Create a new OAuth session""" + try: + with get_db() as db: + current_time = int(time.time()) + id = str(uuid.uuid4()) + + result = OAuthSession( + **{ + "id": id, + "user_id": user_id, + "provider": provider, + "token": self._encrypt_token(token), + "expires_at": token.get("expires_at"), + "created_at": current_time, + "updated_at": current_time, + } + ) + + db.add(result) + db.commit() + db.refresh(result) + + if result: + result.token = token # Return decrypted token + return OAuthSessionModel.model_validate(result) + else: + return None + except Exception as e: + log.error(f"Error creating OAuth session: {e}") + return None + + def get_session_by_id(self, session_id: str) -> Optional[OAuthSessionModel]: + """Get OAuth session by ID""" + try: + with get_db() as db: + session = db.query(OAuthSession).filter_by(id=session_id).first() + if session: + session.token = self._decrypt_token(session.token) + return OAuthSessionModel.model_validate(session) + + return None + except Exception as e: + log.error(f"Error getting OAuth session by ID: {e}") + return None + + def get_session_by_id_and_user_id( + self, session_id: str, user_id: str + ) -> Optional[OAuthSessionModel]: + """Get OAuth session by ID and user ID""" + try: + with get_db() as db: + session = ( + db.query(OAuthSession) + .filter_by(id=session_id, user_id=user_id) + .first() + ) + if session: + session.token = self._decrypt_token(session.token) + return OAuthSessionModel.model_validate(session) + ) + return None + except Exception as e: + log.error(f"Error getting OAuth session by ID: {e}") + return None + + def get_sessions_by_user_id(self, user_id: str) -> List[OAuthSessionModel]: + """Get all OAuth sessions for a user""" + try: + with get_db() as db: + sessions = db.query(OAuthSession).filter_by(user_id=user_id).all() + + + results = [] + for session in sessions: + session.token = self._decrypt_token(session.token) + results.append(OAuthSessionModel.model_validate(session)) + + return results + + except Exception as e: + log.error(f"Error getting OAuth sessions by user ID: {e}") + return [] + + def update_session_by_id( + self, session_id: str, token: dict + ) -> Optional[OAuthSessionModel]: + """Update OAuth session tokens""" + try: + with get_db() as db: + current_time = int(time.time()) + + db.query(OAuthSession).filter_by(id=session_id).update( + { + "token": self._encrypt_token(token), + "expires_at": token.get("expires_at"), + "updated_at": current_time, + } + ) + db.commit() + session = db.query(OAuthSession).filter_by(id=session_id).first() + + if session: + session.token = self._decrypt_token(session.token) + return OAuthSessionModel.model_validate(session) + + return None + except Exception as e: + log.error(f"Error updating OAuth session tokens: {e}") + return None + + def delete_session_by_id(self, session_id: str) -> bool: + """Delete an OAuth session""" + try: + with get_db() as db: + result = db.query(OAuthSession).filter_by(id=session_id).delete() + db.commit() + return result > 0 + except Exception as e: + log.error(f"Error deleting OAuth session: {e}") + return False + + def delete_sessions_by_user_id(self, user_id: str) -> bool: + """Delete all OAuth sessions for a user""" + try: + with get_db() as db: + result = db.query(OAuthSession).filter_by(user_id=user_id).delete() + db.commit() + return True + except Exception as e: + log.error(f"Error deleting OAuth sessions by user ID: {e}") + return False + + +OAuthSessions = OAuthSessionTable() diff --git a/backend/open_webui/routers/auths.py b/backend/open_webui/routers/auths.py index 524edf373d..d044b4a168 100644 --- a/backend/open_webui/routers/auths.py +++ b/backend/open_webui/routers/auths.py @@ -19,6 +19,7 @@ from open_webui.models.auths import ( ) from open_webui.models.users import Users, UpdateProfileForm from open_webui.models.groups import Groups +from open_webui.models.oauth_sessions import OAuthSessions from open_webui.constants import ERROR_MESSAGES, WEBHOOK_MESSAGES from open_webui.env import ( @@ -28,7 +29,6 @@ from open_webui.env import ( WEBUI_AUTH_TRUSTED_GROUPS_HEADER, WEBUI_AUTH_COOKIE_SAME_SITE, WEBUI_AUTH_COOKIE_SECURE, - ENABLE_OAUTH_SESSION_TOKENS_COOKIES, WEBUI_AUTH_SIGNOUT_REDIRECT_URL, ENABLE_INITIAL_ADMIN_SIGNUP, SRC_LOG_LEVELS, @@ -678,24 +678,27 @@ async def signout(request: Request, response: Response): response.delete_cookie("token") response.delete_cookie("oui-session") - if ENABLE_OAUTH_SIGNUP.value: - # TODO: update this to use oauth_session_tokens in User Object - oauth_id_token = request.cookies.get("oauth_id_token") + oauth_session_id = request.cookies.get("oauth_session_id") + if oauth_session_id: + response.delete_cookie("oauth_session_id") - if oauth_id_token and OPENID_PROVIDER_URL.value: + session = OAuthSessions.get_session_by_id(oauth_session_id) + oauth_server_metadata_url = ( + request.app.state.oauth_manager.get_server_metadata_url(session.provider) + if session + else None + ) or OPENID_PROVIDER_URL.value + + if session and oauth_server_metadata_url: + oauth_id_token = session.token.get("id_token") try: async with ClientSession(trust_env=True) as session: - async with session.get(OPENID_PROVIDER_URL.value) as r: + async with session.get(oauth_server_metadata_url) as r: if r.status == 200: openid_data = await r.json() logout_url = openid_data.get("end_session_endpoint") if logout_url: - if ENABLE_OAUTH_SESSION_TOKENS_COOKIES: - response.delete_cookie("oauth_id_token") - response.delete_cookie("oauth_access_token") - response.delete_cookie("oauth_refresh_token") - return JSONResponse( status_code=200, content={ @@ -710,15 +713,14 @@ async def signout(request: Request, response: Response): headers=response.headers, ) else: - raise HTTPException( - status_code=r.status, - detail="Failed to fetch OpenID configuration", - ) + raise Exception("Failed to fetch OpenID configuration") + except Exception as e: log.error(f"OpenID signout error: {str(e)}") raise HTTPException( status_code=500, detail="Failed to sign out from the OpenID provider.", + headers=response.headers, ) if WEBUI_AUTH_SIGNOUT_REDIRECT_URL: diff --git a/backend/open_webui/utils/auth.py b/backend/open_webui/utils/auth.py index 33b377ad03..19994bafbd 100644 --- a/backend/open_webui/utils/auth.py +++ b/backend/open_webui/utils/auth.py @@ -261,61 +261,63 @@ def get_current_user( return user # auth by jwt token - try: - data = decode_token(token) - except Exception as e: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Invalid token", - ) - if data is not None and "id" in data: - user = Users.get_user_by_id(data["id"]) - if user is None: + try: + try: + data = decode_token(token) + except Exception as e: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, - detail=ERROR_MESSAGES.INVALID_TOKEN, + detail="Invalid token", ) - else: - if WEBUI_AUTH_TRUSTED_EMAIL_HEADER: - trusted_email = request.headers.get( - WEBUI_AUTH_TRUSTED_EMAIL_HEADER, "" - ).lower() - if trusted_email and user.email != trusted_email: - # Delete the token cookie - response.delete_cookie("token") - # Delete OAuth token if present - if request.cookies.get("oauth_id_token"): - response.delete_cookie("oauth_id_token") - if request.cookies.get("oauth_access_token"): - response.delete_cookie("oauth_access_token") - if request.cookies.get("oauth_refresh_token"): - response.delete_cookie("oauth_refresh_token") + if data is not None and "id" in data: + user = Users.get_user_by_id(data["id"]) + if user is None: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=ERROR_MESSAGES.INVALID_TOKEN, + ) + else: + if WEBUI_AUTH_TRUSTED_EMAIL_HEADER: + trusted_email = request.headers.get( + WEBUI_AUTH_TRUSTED_EMAIL_HEADER, "" + ).lower() + if trusted_email and user.email != trusted_email: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="User mismatch. Please sign in again.", + ) - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="User mismatch. Please sign in again.", + # Add user info to current span + current_span = trace.get_current_span() + if current_span: + current_span.set_attribute("client.user.id", user.id) + current_span.set_attribute("client.user.email", user.email) + current_span.set_attribute("client.user.role", user.role) + current_span.set_attribute("client.auth.type", "jwt") + + # Refresh the user's last active timestamp asynchronously + # to prevent blocking the request + if background_tasks: + background_tasks.add_task( + Users.update_user_last_active_by_id, user.id ) + return user + else: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=ERROR_MESSAGES.UNAUTHORIZED, + ) + except Exception as e: + # Delete the token cookie + if request.cookies.get("token"): + response.delete_cookie("token") + # Delete OAuth session if present + if request.cookies.get("oauth_session_id"): + response.delete_cookie("oauth_session_id") - # Add user info to current span - current_span = trace.get_current_span() - if current_span: - current_span.set_attribute("client.user.id", user.id) - current_span.set_attribute("client.user.email", user.email) - current_span.set_attribute("client.user.role", user.role) - current_span.set_attribute("client.auth.type", "jwt") - - # Refresh the user's last active timestamp asynchronously - # to prevent blocking the request - if background_tasks: - background_tasks.add_task(Users.update_user_last_active_by_id, user.id) - return user - else: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail=ERROR_MESSAGES.UNAUTHORIZED, - ) + raise e def get_current_user_by_api_key(api_key: str): diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index 463f52d0af..27b0f11290 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -815,6 +815,14 @@ async def process_chat_payload(request, form_data, user, metadata, model): event_emitter = get_event_emitter(metadata) event_call = get_event_call(metadata) + oauth_token = None + try: + oauth_token = await request.app.state.oauth_manager.get_oauth_token( + user.id, request.cookies.get("oauth_session_id", None) + ) + except Exception as e: + log.error(f"Error getting OAuth token: {e}") + extra_params = { "__event_emitter__": event_emitter, "__event_call__": event_call, @@ -822,6 +830,7 @@ async def process_chat_payload(request, form_data, user, metadata, model): "__metadata__": metadata, "__request__": request, "__model__": model, + "__oauth_token__": oauth_token, } # Initialize events to store additional event to be sent to the client diff --git a/backend/open_webui/utils/oauth.py b/backend/open_webui/utils/oauth.py index 4411f40e3b..55ee3eee54 100644 --- a/backend/open_webui/utils/oauth.py +++ b/backend/open_webui/utils/oauth.py @@ -4,9 +4,11 @@ import mimetypes import sys import uuid import json +from datetime import datetime, timedelta import re import fnmatch +import time import aiohttp from authlib.integrations.starlette_client import OAuth @@ -17,8 +19,12 @@ from fastapi import ( ) from starlette.responses import RedirectResponse + from open_webui.models.auths import Auths +from open_webui.models.oauth_sessions import OAuthSessions from open_webui.models.users import Users + + from open_webui.models.groups import Groups, GroupModel, GroupUpdateForm, GroupForm from open_webui.config import ( DEFAULT_USER_ROLE, @@ -49,7 +55,7 @@ from open_webui.env import ( WEBUI_NAME, WEBUI_AUTH_COOKIE_SAME_SITE, WEBUI_AUTH_COOKIE_SECURE, - ENABLE_OAUTH_SESSION_TOKENS_COOKIES, + ENABLE_OAUTH_ID_TOKEN_COOKIE, ) from open_webui.utils.misc import parse_duration from open_webui.utils.auth import get_password_hash, create_token @@ -131,11 +137,187 @@ class OAuthManager: def __init__(self, app): self.oauth = OAuth() self.app = app + + self._clients = {} for _, provider_config in OAUTH_PROVIDERS.items(): provider_config["register"](self.oauth) def get_client(self, provider_name): - return self.oauth.create_client(provider_name) + if provider_name not in self._clients: + self._clients[provider_name] = self.oauth.create_client(provider_name) + return self._clients[provider_name] + + def get_server_metadata_url(self, provider_name): + if provider_name in self._clients: + client = self._clients[provider_name] + return ( + client.server_metadata_url + if hasattr(client, "server_metadata_url") + else None + ) + return None + + def get_oauth_token( + self, user_id: str, session_id: str, force_refresh: bool = False + ): + """ + Get a valid OAuth token for the user, automatically refreshing if needed. + + Args: + user_id: The user ID + provider: Optional provider name. If None, gets the most recent session. + force_refresh: Force token refresh even if current token appears valid + + Returns: + dict: OAuth token data with access_token, or None if no valid token available + """ + try: + # Get the OAuth session + session = OAuthSessions.get_session_by_id_and_user_id(session_id, user_id) + if not session: + log.warning( + f"No OAuth session found for user {user_id}, session {session_id}" + ) + return None + + if force_refresh or datetime.now() + timedelta( + minutes=5 + ) >= datetime.fromtimestamp(session.expires_at): + log.debug( + f"Token refresh needed for user {user_id}, provider {session.provider}" + ) + refreshed_token = self._refresh_token(session) + if refreshed_token: + return refreshed_token + else: + log.warning( + f"Token refresh failed for user {user_id}, provider {session.provider}" + ) + return None + return session.token + + except Exception as e: + log.error(f"Error getting OAuth token for user {user_id}: {e}") + return None + + async def _refresh_token(self, session) -> dict: + """ + Refresh an OAuth token if needed, with concurrency protection. + + Args: + session: The OAuth session object + + Returns: + dict: Refreshed token data, or None if refresh failed + """ + try: + # Perform the actual refresh + refreshed_token = await self._perform_token_refresh(session) + + if refreshed_token: + # Update the session with new token data + session = OAuthSessions.update_session_by_id( + session.id, refreshed_token + ) + log.info(f"Successfully refreshed token for session {session.id}") + return session.token + else: + log.error(f"Failed to refresh token for session {session.id}") + return None + + except Exception as e: + log.error(f"Error refreshing token for session {session.id}: {e}") + return None + + async def _perform_token_refresh(self, session) -> dict: + """ + Perform the actual OAuth token refresh. + + Args: + session: The OAuth session object + + Returns: + dict: New token data, or None if refresh failed + """ + provider = session.provider + token_data = session.token + + if not token_data.get("refresh_token"): + log.warning(f"No refresh token available for session {session.id}") + return None + + try: + client = self.get_client(provider) + if not client: + log.error(f"No OAuth client found for provider {provider}") + return None + + token_endpoint = None + async with aiohttp.ClientSession(trust_env=True) as session_http: + async with session_http.get(client.gserver_metadata_url) as r: + if r.status == 200: + openid_data = await r.json() + token_endpoint = openid_data.get("token_endpoint") + else: + log.error( + f"Failed to fetch OpenID configuration for provider {provider}" + ) + if not token_endpoint: + log.error(f"No token endpoint found for provider {provider}") + return None + + # Prepare refresh request + refresh_data = { + "grant_type": "refresh_token", + "refresh_token": token_data["refresh_token"], + "client_id": client.client_id, + } + # Add client_secret if available (some providers require it) + if hasattr(client, "client_secret") and client.client_secret: + refresh_data["client_secret"] = client.client_secret + + # Make refresh request + async with aiohttp.ClientSession(trust_env=True) as session_http: + async with session_http.post( + token_endpoint, + data=refresh_data, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + ssl=AIOHTTP_CLIENT_SESSION_SSL, + ) as r: + if r.status == 200: + new_token_data = await r.json() + + # Merge with existing token data (preserve refresh_token if not provided) + if "refresh_token" not in new_token_data: + new_token_data["refresh_token"] = token_data[ + "refresh_token" + ] + + # Add timestamp for tracking + new_token_data["issued_at"] = datetime.now().timestamp() + + # Calculate expires_at if we have expires_in + if ( + "expires_in" in new_token_data + and "expires_at" not in new_token_data + ): + new_token_data["expires_at"] = ( + datetime.now().timestamp() + + new_token_data["expires_in"] + ) + + log.debug(f"Token refresh successful for provider {provider}") + return new_token_data + else: + error_text = await r.text() + log.error( + f"Token refresh failed for provider {provider}: {r.status} - {error_text}" + ) + return None + + except Exception as e: + log.error(f"Exception during token refresh for provider {provider}: {e}") + return None def get_user_role(self, user, user_data): user_count = Users.get_num_users() @@ -624,33 +806,42 @@ class OAuthManager: secure=WEBUI_AUTH_COOKIE_SECURE, ) - if ENABLE_OAUTH_SIGNUP.value: - if ENABLE_OAUTH_SESSION_TOKENS_COOKIES: - oauth_id_token = token.get("id_token") - response.set_cookie( - key="oauth_id_token", - value=oauth_id_token, - httponly=True, - samesite=WEBUI_AUTH_COOKIE_SAME_SITE, - secure=WEBUI_AUTH_COOKIE_SECURE, - ) + # Legacy cookies for compatibility with older frontend versions + if ENABLE_OAUTH_ID_TOKEN_COOKIE: + response.set_cookie( + key="oauth_id_token", + value=token.get("id_token"), + httponly=True, + samesite=WEBUI_AUTH_COOKIE_SAME_SITE, + secure=WEBUI_AUTH_COOKIE_SECURE, + ) - oauth_access_token = token.get("access_token") - response.set_cookie( - key="oauth_access_token", - value=oauth_access_token, - httponly=True, - samesite=WEBUI_AUTH_COOKIE_SAME_SITE, - secure=WEBUI_AUTH_COOKIE_SECURE, - ) + try: + # Add timestamp for tracking + token["issued_at"] = datetime.now().timestamp() - oauth_refresh_token = token.get("refresh_token") - response.set_cookie( - key="oauth_refresh_token", - value=oauth_refresh_token, - httponly=True, - samesite=WEBUI_AUTH_COOKIE_SAME_SITE, - secure=WEBUI_AUTH_COOKIE_SECURE, - ) + # Calculate expires_at if we have expires_in + if "expires_in" in token and "expires_at" not in token: + token["expires_at"] = datetime.now().timestamp() + token["expires_in"] + + session_id = await OAuthSessions.create_session( + user_id=user.id, + provider=provider, + token=token, + ) + + response.set_cookie( + key="oauth_session_id", + value=session_id, + httponly=True, + samesite=WEBUI_AUTH_COOKIE_SAME_SITE, + secure=WEBUI_AUTH_COOKIE_SECURE, + ) + + log.info( + f"Stored OAuth session server-side for user {user.id}, provider {provider}" + ) + except Exception as e: + log.error(f"Failed to store OAuth session server-side: {e}") return response diff --git a/backend/open_webui/utils/tools.py b/backend/open_webui/utils/tools.py index d3ea432019..f0e889d023 100644 --- a/backend/open_webui/utils/tools.py +++ b/backend/open_webui/utils/tools.py @@ -129,6 +129,21 @@ async def get_tools( headers["Authorization"] = ( f"Bearer {request.state.token.credentials}" ) + elif auth_type == "oauth": + oauth_token = None + try: + oauth_token = ( + await request.app.state.oauth_manager.get_oauth_token( + user.id, + request.cookies.get("oauth_session_id", None), + ) + ) + except Exception as e: + log.error(f"Error getting OAuth token: {e}") + + headers["Authorization"] = ( + f"Bearer {oauth_token.get('access_token', '')}" + ) elif auth_type == "request_headers": headers.update(dict(request.headers)) diff --git a/src/lib/components/AddServerModal.svelte b/src/lib/components/AddServerModal.svelte index 6fad62bc15..8951696c74 100644 --- a/src/lib/components/AddServerModal.svelte +++ b/src/lib/components/AddServerModal.svelte @@ -287,6 +287,7 @@ {#if !direct} + {/if} @@ -305,6 +306,12 @@ > {$i18n.t('Forwards system user session credentials to authenticate')}
      + {:else if auth_type === 'oauth'} +
      + {$i18n.t('Forwards user OAuth access token to authenticate')} +
      {:else if auth_type === 'request_headers'}
      Date: Mon, 8 Sep 2025 18:09:01 +0400 Subject: [PATCH 113/154] refac --- backend/open_webui/models/oauth_sessions.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/backend/open_webui/models/oauth_sessions.py b/backend/open_webui/models/oauth_sessions.py index b0b5aa29a6..9fd5335ce5 100644 --- a/backend/open_webui/models/oauth_sessions.py +++ b/backend/open_webui/models/oauth_sessions.py @@ -150,7 +150,7 @@ class OAuthSessionTable: if session: session.token = self._decrypt_token(session.token) return OAuthSessionModel.model_validate(session) - + return None except Exception as e: log.error(f"Error getting OAuth session by ID: {e}") @@ -170,7 +170,7 @@ class OAuthSessionTable: if session: session.token = self._decrypt_token(session.token) return OAuthSessionModel.model_validate(session) - ) + return None except Exception as e: log.error(f"Error getting OAuth session by ID: {e}") @@ -182,14 +182,13 @@ class OAuthSessionTable: with get_db() as db: sessions = db.query(OAuthSession).filter_by(user_id=user_id).all() - results = [] for session in sessions: session.token = self._decrypt_token(session.token) results.append(OAuthSessionModel.model_validate(session)) return results - + except Exception as e: log.error(f"Error getting OAuth sessions by user ID: {e}") return [] @@ -215,7 +214,7 @@ class OAuthSessionTable: if session: session.token = self._decrypt_token(session.token) return OAuthSessionModel.model_validate(session) - + return None except Exception as e: log.error(f"Error updating OAuth session tokens: {e}") From fc11e4384fe98fac659e10596f67c23483578867 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 8 Sep 2025 18:17:11 +0400 Subject: [PATCH 114/154] refac --- backend/open_webui/routers/auths.py | 1 + backend/open_webui/utils/auth.py | 4 ++++ backend/open_webui/utils/oauth.py | 4 ++-- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/backend/open_webui/routers/auths.py b/backend/open_webui/routers/auths.py index d044b4a168..e3271250c1 100644 --- a/backend/open_webui/routers/auths.py +++ b/backend/open_webui/routers/auths.py @@ -677,6 +677,7 @@ async def signup(request: Request, response: Response, form_data: SignupForm): async def signout(request: Request, response: Response): response.delete_cookie("token") response.delete_cookie("oui-session") + response.delete_cookie("oauth_id_token") oauth_session_id = request.cookies.get("oauth_session_id") if oauth_session_id: diff --git a/backend/open_webui/utils/auth.py b/backend/open_webui/utils/auth.py index 19994bafbd..f941ef9263 100644 --- a/backend/open_webui/utils/auth.py +++ b/backend/open_webui/utils/auth.py @@ -313,6 +313,10 @@ def get_current_user( # Delete the token cookie if request.cookies.get("token"): response.delete_cookie("token") + + if request.cookies.get("oauth_id_token"): + response.delete_cookie("oauth_id_token") + # Delete OAuth session if present if request.cookies.get("oauth_session_id"): response.delete_cookie("oauth_session_id") diff --git a/backend/open_webui/utils/oauth.py b/backend/open_webui/utils/oauth.py index 55ee3eee54..63250c2a54 100644 --- a/backend/open_webui/utils/oauth.py +++ b/backend/open_webui/utils/oauth.py @@ -824,7 +824,7 @@ class OAuthManager: if "expires_in" in token and "expires_at" not in token: token["expires_at"] = datetime.now().timestamp() + token["expires_in"] - session_id = await OAuthSessions.create_session( + session = OAuthSessions.create_session( user_id=user.id, provider=provider, token=token, @@ -832,7 +832,7 @@ class OAuthManager: response.set_cookie( key="oauth_session_id", - value=session_id, + value=session.id, httponly=True, samesite=WEBUI_AUTH_COOKIE_SAME_SITE, secure=WEBUI_AUTH_COOKIE_SECURE, From 35c1c48fd2aea41c89b701aaf16cbffff1e9327d Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 8 Sep 2025 18:18:04 +0400 Subject: [PATCH 115/154] refac --- src/lib/components/AddServerModal.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/components/AddServerModal.svelte b/src/lib/components/AddServerModal.svelte index 8951696c74..6863c8cbb8 100644 --- a/src/lib/components/AddServerModal.svelte +++ b/src/lib/components/AddServerModal.svelte @@ -310,7 +310,7 @@
      - {$i18n.t('Forwards user OAuth access token to authenticate')} + {$i18n.t('Forwards system user OAuth access token to authenticate')}
      {:else if auth_type === 'request_headers'}
      Date: Mon, 8 Sep 2025 18:23:44 +0400 Subject: [PATCH 116/154] refac --- backend/open_webui/utils/tools.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/backend/open_webui/utils/tools.py b/backend/open_webui/utils/tools.py index f0e889d023..9d64db7d78 100644 --- a/backend/open_webui/utils/tools.py +++ b/backend/open_webui/utils/tools.py @@ -119,6 +119,8 @@ async def get_tools( function_name = spec["name"] auth_type = tool_server_connection.get("auth_type", "bearer") + + cookies = {} headers = {} if auth_type == "bearer": @@ -126,10 +128,12 @@ async def get_tools( f"Bearer {tool_server_connection.get('key', '')}" ) elif auth_type == "session": + cookies = request.cookies headers["Authorization"] = ( f"Bearer {request.state.token.credentials}" ) elif auth_type == "oauth": + cookies = request.cookies oauth_token = None try: oauth_token = ( @@ -145,6 +149,7 @@ async def get_tools( f"Bearer {oauth_token.get('access_token', '')}" ) elif auth_type == "request_headers": + cookies = request.cookies headers.update(dict(request.headers)) headers["Content-Type"] = "application/json" @@ -154,6 +159,7 @@ async def get_tools( return await execute_tool_server( url=tool_server_data["url"], headers=headers, + cookies=cookies, name=function_name, params=kwargs, server_data=tool_server_data, @@ -635,6 +641,7 @@ async def get_tool_servers_data( async def execute_tool_server( url: str, headers: Dict[str, str], + cookies: Dict[str, str], name: str, params: Dict[str, Any], server_data: Dict[str, Any], From f71834720e623761d972d4d740e9bbd90a3a86c6 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 8 Sep 2025 18:35:09 +0400 Subject: [PATCH 117/154] refac --- backend/open_webui/utils/middleware.py | 2 +- backend/open_webui/utils/tools.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index 27b0f11290..406ee1b16f 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -817,7 +817,7 @@ async def process_chat_payload(request, form_data, user, metadata, model): oauth_token = None try: - oauth_token = await request.app.state.oauth_manager.get_oauth_token( + oauth_token = request.app.state.oauth_manager.get_oauth_token( user.id, request.cookies.get("oauth_session_id", None) ) except Exception as e: diff --git a/backend/open_webui/utils/tools.py b/backend/open_webui/utils/tools.py index 9d64db7d78..efdb7c85b1 100644 --- a/backend/open_webui/utils/tools.py +++ b/backend/open_webui/utils/tools.py @@ -137,7 +137,7 @@ async def get_tools( oauth_token = None try: oauth_token = ( - await request.app.state.oauth_manager.get_oauth_token( + request.app.state.oauth_manager.get_oauth_token( user.id, request.cookies.get("oauth_session_id", None), ) @@ -715,6 +715,7 @@ async def execute_tool_server( final_url, json=body_params, headers=headers, + cookies=cookies, ssl=AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL, ) as response: if response.status >= 400: @@ -731,6 +732,7 @@ async def execute_tool_server( async with request_method( final_url, headers=headers, + cookies=cookies, ssl=AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL, ) as response: if response.status >= 400: From b5bb6ae177dcdc4e8274d7e5ffa50bc8099fd466 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 8 Sep 2025 18:50:23 +0400 Subject: [PATCH 118/154] refac --- backend/open_webui/functions.py | 10 ++++++++++ backend/open_webui/main.py | 8 ++++++++ backend/open_webui/routers/users.py | 14 ++++++++++++++ backend/open_webui/utils/middleware.py | 13 ++++++++++++- backend/open_webui/utils/tools.py | 12 +----------- 5 files changed, 45 insertions(+), 12 deletions(-) diff --git a/backend/open_webui/functions.py b/backend/open_webui/functions.py index db367ccbd0..4122cbbe0d 100644 --- a/backend/open_webui/functions.py +++ b/backend/open_webui/functions.py @@ -219,6 +219,15 @@ async def generate_function_chat_completion( __task__ = metadata.get("task", None) __task_body__ = metadata.get("task_body", None) + oauth_token = None + try: + oauth_token = request.app.state.oauth_manager.get_oauth_token( + user.id, + request.cookies.get("oauth_session_id", None), + ) + except Exception as e: + log.error(f"Error getting OAuth token: {e}") + extra_params = { "__event_emitter__": __event_emitter__, "__event_call__": __event_call__, @@ -230,6 +239,7 @@ async def generate_function_chat_completion( "__files__": files, "__user__": user.model_dump() if isinstance(user, UserModel) else {}, "__metadata__": metadata, + "__oauth_token__": oauth_token, "__request__": request, } extra_params["__tools__"] = await get_tools( diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index de7dcae086..ea60900c9c 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -1408,6 +1408,14 @@ async def chat_completion( model_item = form_data.pop("model_item", {}) tasks = form_data.pop("background_tasks", None) + oauth_token = None + try: + oauth_token = request.app.state.oauth_manager.get_oauth_token( + user.id, request.cookies.get("oauth_session_id", None) + ) + except Exception as e: + log.error(f"Error getting OAuth token: {e}") + metadata = {} try: if not model_item.get("direct", False): diff --git a/backend/open_webui/routers/users.py b/backend/open_webui/routers/users.py index 4d2539a18e..5b331dce73 100644 --- a/backend/open_webui/routers/users.py +++ b/backend/open_webui/routers/users.py @@ -10,6 +10,8 @@ from pydantic import BaseModel from open_webui.models.auths import Auths +from open_webui.models.oauth_sessions import OAuthSessions + from open_webui.models.groups import Groups from open_webui.models.chats import Chats from open_webui.models.users import ( @@ -340,6 +342,18 @@ async def get_user_by_id(user_id: str, user=Depends(get_verified_user)): ) +@router.get("/{user_id}/oauth/sessions", response_model=Optional[dict]) +async def get_user_oauth_sessions_by_id(user_id: str, user=Depends(get_admin_user)): + sessions = OAuthSessions.get_sessions_by_user_id(user_id) + if sessions and len(sessions) > 0: + return sessions + else: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=ERROR_MESSAGES.USER_NOT_FOUND, + ) + + ############################ # GetUserProfileImageById ############################ diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index 406ee1b16f..ae2c96c6da 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -818,7 +818,8 @@ async def process_chat_payload(request, form_data, user, metadata, model): oauth_token = None try: oauth_token = request.app.state.oauth_manager.get_oauth_token( - user.id, request.cookies.get("oauth_session_id", None) + user.id, + request.cookies.get("oauth_session_id", None), ) except Exception as e: log.error(f"Error getting OAuth token: {e}") @@ -1493,11 +1494,21 @@ async def process_chat_response( ): return response + oauth_token = None + try: + oauth_token = request.app.state.oauth_manager.get_oauth_token( + user.id, + request.cookies.get("oauth_session_id", None), + ) + except Exception as e: + log.error(f"Error getting OAuth token: {e}") + extra_params = { "__event_emitter__": event_emitter, "__event_call__": event_caller, "__user__": user.model_dump() if isinstance(user, UserModel) else {}, "__metadata__": metadata, + "__oauth_token__": oauth_token, "__request__": request, "__model__": model, } diff --git a/backend/open_webui/utils/tools.py b/backend/open_webui/utils/tools.py index efdb7c85b1..f7e7f7acef 100644 --- a/backend/open_webui/utils/tools.py +++ b/backend/open_webui/utils/tools.py @@ -134,17 +134,7 @@ async def get_tools( ) elif auth_type == "oauth": cookies = request.cookies - oauth_token = None - try: - oauth_token = ( - request.app.state.oauth_manager.get_oauth_token( - user.id, - request.cookies.get("oauth_session_id", None), - ) - ) - except Exception as e: - log.error(f"Error getting OAuth token: {e}") - + oauth_token = extra_params.get("__oauth_token__", None) headers["Authorization"] = ( f"Bearer {oauth_token.get('access_token', '')}" ) From b786d1e3f3308ef4f0f95d7130ddbcaaca4fc927 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 8 Sep 2025 18:52:59 +0400 Subject: [PATCH 119/154] refac --- backend/open_webui/utils/oauth.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend/open_webui/utils/oauth.py b/backend/open_webui/utils/oauth.py index 63250c2a54..7eedc30c31 100644 --- a/backend/open_webui/utils/oauth.py +++ b/backend/open_webui/utils/oauth.py @@ -824,6 +824,12 @@ class OAuthManager: if "expires_in" in token and "expires_at" not in token: token["expires_at"] = datetime.now().timestamp() + token["expires_in"] + # Clean up any existing sessions for this user/provider first + sessions = OAuthSessions.get_sessions_by_user_id(user.id) + for session in sessions: + if session.provider == provider: + OAuthSessions.delete_session_by_id(session.id) + session = OAuthSessions.create_session( user_id=user.id, provider=provider, From 001dab0439972e9cbd7b88afcb6f7f6950a01bba Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 8 Sep 2025 18:55:57 +0400 Subject: [PATCH 120/154] refac: wording --- src/lib/components/AddConnectionModal.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/components/AddConnectionModal.svelte b/src/lib/components/AddConnectionModal.svelte index 21c900fba2..fd19c0b302 100644 --- a/src/lib/components/AddConnectionModal.svelte +++ b/src/lib/components/AddConnectionModal.svelte @@ -355,7 +355,7 @@ for="prefix-id-input" class={`mb-0.5 text-xs text-gray-500 ${($settings?.highContrastMode ?? false) ? 'text-gray-800 dark:text-gray-100' : ''}`} - >{$i18n.t('Provider')}{$i18n.t('Provider Type')}
      From 8a9f8627017bd0a74cbd647891552b26e56aabb7 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 8 Sep 2025 19:07:00 +0400 Subject: [PATCH 121/154] refac --- src/lib/components/AddConnectionModal.svelte | 130 ++++++++++++------ src/lib/components/AddServerModal.svelte | 7 - .../Connections/OllamaConnection.svelte | 1 + .../Connections/OpenAIConnection.svelte | 8 +- .../Settings/Connections/Connection.svelte | 6 - 5 files changed, 91 insertions(+), 61 deletions(-) diff --git a/src/lib/components/AddConnectionModal.svelte b/src/lib/components/AddConnectionModal.svelte index fd19c0b302..02784a7abb 100644 --- a/src/lib/components/AddConnectionModal.svelte +++ b/src/lib/components/AddConnectionModal.svelte @@ -31,6 +31,7 @@ let url = ''; let key = ''; + let auth_type = 'bearer'; let connectionType = 'external'; let azure = false; @@ -73,6 +74,7 @@ { url, key, + auth_type, config: { azure: azure, api_version: apiVersion @@ -140,6 +142,7 @@ const connection = { url, key, + auth_type, config: { enable: enable, tags: tags, @@ -157,6 +160,7 @@ url = ''; key = ''; + auth_type = 'bearer'; prefixId = ''; tags = []; modelIds = []; @@ -167,6 +171,8 @@ url = connection.url; key = connection.key; + auth_type = connection.auth_type ?? 'bearer'; + enable = connection.config?.enable ?? true; tags = connection.config?.tags ?? []; prefixId = connection.config?.prefix_id ?? ''; @@ -305,23 +311,63 @@
      -
      {$i18n.t('Auth')} - {$i18n.t('Key')} -
      -
      - +
      +
      + +
      + +
      + {#if auth_type === 'bearer'} + + {:else if auth_type === 'none'} +
      + {$i18n.t('No authentication')} +
      + {:else if auth_type === 'session'} +
      + {$i18n.t('Forwards system user session credentials to authenticate')} +
      + {:else if auth_type === 'oauth'} +
      + {$i18n.t('Forwards system user OAuth access token to authenticate')} +
      + {/if} +
      +
      +
      - {:else if auth_type === 'request_headers'} -
      - {$i18n.t('Forwards system user headers to authenticate')} -
      {/if}
      diff --git a/src/lib/components/admin/Settings/Connections/OllamaConnection.svelte b/src/lib/components/admin/Settings/Connections/OllamaConnection.svelte index 48976f0769..3e25965391 100644 --- a/src/lib/components/admin/Settings/Connections/OllamaConnection.svelte +++ b/src/lib/components/admin/Settings/Connections/OllamaConnection.svelte @@ -71,6 +71,7 @@ class="w-full text-sm bg-transparent outline-hidden" placeholder={$i18n.t('Enter URL (e.g. http://localhost:11434)')} bind:value={url} + readonly={true} /> diff --git a/src/lib/components/admin/Settings/Connections/OpenAIConnection.svelte b/src/lib/components/admin/Settings/Connections/OpenAIConnection.svelte index b82e47be76..a4c2ada059 100644 --- a/src/lib/components/admin/Settings/Connections/OpenAIConnection.svelte +++ b/src/lib/components/admin/Settings/Connections/OpenAIConnection.svelte @@ -69,6 +69,7 @@ placeholder={$i18n.t('API Base URL')} bind:value={url} autocomplete="off" + readonly={true} /> {#if pipeline} @@ -94,13 +95,6 @@
      {/if}
      - -
      diff --git a/src/lib/components/chat/Settings/Connections/Connection.svelte b/src/lib/components/chat/Settings/Connections/Connection.svelte index ea9089f2f1..c8a5ec152a 100644 --- a/src/lib/components/chat/Settings/Connections/Connection.svelte +++ b/src/lib/components/chat/Settings/Connections/Connection.svelte @@ -72,12 +72,6 @@ autocomplete="off" />
      - -
      From 4cea3a57be18869eb2b9dc2484b80a9cb0a68619 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 8 Sep 2025 19:09:26 +0400 Subject: [PATCH 122/154] refac --- backend/open_webui/utils/tools.py | 3 +++ src/lib/components/AddServerModal.svelte | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/backend/open_webui/utils/tools.py b/backend/open_webui/utils/tools.py index f7e7f7acef..74974e1461 100644 --- a/backend/open_webui/utils/tools.py +++ b/backend/open_webui/utils/tools.py @@ -127,6 +127,9 @@ async def get_tools( headers["Authorization"] = ( f"Bearer {tool_server_connection.get('key', '')}" ) + elif auth_type == "none": + # No authentication + pass elif auth_type == "session": cookies = request.cookies headers["Authorization"] = ( diff --git a/src/lib/components/AddServerModal.svelte b/src/lib/components/AddServerModal.svelte index fdd905aef0..fd70534d28 100644 --- a/src/lib/components/AddServerModal.svelte +++ b/src/lib/components/AddServerModal.svelte @@ -283,6 +283,8 @@ class={`w-full text-sm bg-transparent pr-5 ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`} bind:value={auth_type} > + + @@ -299,6 +301,12 @@ placeholder={$i18n.t('API Key')} required={false} /> + {:else if auth_type === 'none'} +
      + {$i18n.t('No authentication')} +
      {:else if auth_type === 'session'}
      Date: Mon, 8 Sep 2025 19:12:20 +0400 Subject: [PATCH 123/154] refac --- src/lib/apis/index.ts | 13 ++++++++++++- src/routes/+layout.svelte | 13 ++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/lib/apis/index.ts b/src/lib/apis/index.ts index 646514eee8..e36eeba12e 100644 --- a/src/lib/apis/index.ts +++ b/src/lib/apis/index.ts @@ -354,8 +354,19 @@ export const getToolServersData = async (servers: object[]) => { .filter((server) => server?.config?.enable) .map(async (server) => { let error = null; + + let toolServerToken = null; + const auth_type = server?.auth_type ?? 'bearer'; + if (auth_type === 'bearer') { + toolServerToken = server?.key; + } else if (auth_type === 'none') { + // No authentication + } else if (auth_type === 'session') { + toolServerToken = localStorage.token; + } + const data = await getToolServerData( - (server?.auth_type ?? 'bearer') === 'bearer' ? server?.key : localStorage.token, + toolServerToken, (server?.path ?? '').includes('://') ? server?.path : `${server?.url}${(server?.path ?? '').startsWith('/') ? '' : '/'}${server?.path}` diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index d0453dc5ed..d4205bb9ee 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -222,8 +222,19 @@ if (toolServer) { console.log(toolServer); + + let toolServerToken = null; + const auth_type = toolServer?.auth_type ?? 'bearer'; + if (auth_type === 'bearer') { + toolServerToken = toolServer?.key; + } else if (auth_type === 'none') { + // No authentication + } else if (auth_type === 'session') { + toolServerToken = localStorage.token; + } + const res = await executeToolServer( - (toolServer?.auth_type ?? 'bearer') === 'bearer' ? toolServer?.key : localStorage.token, + toolServerToken, toolServer.url, data?.name, data?.params, From 474df5e534ef85848f32d541366f955edab4cdf9 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 8 Sep 2025 19:18:55 +0400 Subject: [PATCH 124/154] refac --- src/lib/components/AddConnectionModal.svelte | 6 +++--- src/lib/components/admin/Settings/Connections.svelte | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/lib/components/AddConnectionModal.svelte b/src/lib/components/AddConnectionModal.svelte index 02784a7abb..ecf03ee2ce 100644 --- a/src/lib/components/AddConnectionModal.svelte +++ b/src/lib/components/AddConnectionModal.svelte @@ -74,8 +74,8 @@ { url, key, - auth_type, config: { + auth_type, azure: azure, api_version: apiVersion } @@ -142,13 +142,13 @@ const connection = { url, key, - auth_type, config: { enable: enable, tags: tags, prefix_id: prefixId, model_ids: modelIds, connection_type: connectionType, + auth_type, ...(!ollama && azure ? { azure: true, api_version: apiVersion } : {}) } }; @@ -171,7 +171,7 @@ url = connection.url; key = connection.key; - auth_type = connection.auth_type ?? 'bearer'; + auth_type = connection.config.auth_type ?? 'bearer'; enable = connection.config?.enable ?? true; tags = connection.config?.tags ?? []; diff --git a/src/lib/components/admin/Settings/Connections.svelte b/src/lib/components/admin/Settings/Connections.svelte index 0b9d2874b2..03897accc6 100644 --- a/src/lib/components/admin/Settings/Connections.svelte +++ b/src/lib/components/admin/Settings/Connections.svelte @@ -261,10 +261,10 @@
      {#each OPENAI_API_BASE_URLS as url, idx} { updateOpenAIHandler(); }} @@ -326,7 +326,7 @@
      {#each OLLAMA_BASE_URLS as url, idx} { From 2b2d123531eb3f42c0e940593832a64e2806240d Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 8 Sep 2025 19:42:50 +0400 Subject: [PATCH 125/154] refac: oauth auth type in openai connection --- backend/open_webui/routers/openai.py | 222 ++++++++++--------- backend/open_webui/utils/tools.py | 19 +- src/lib/components/AddConnectionModal.svelte | 2 - 3 files changed, 121 insertions(+), 122 deletions(-) diff --git a/backend/open_webui/routers/openai.py b/backend/open_webui/routers/openai.py index a94791bdf5..d7b5f75260 100644 --- a/backend/open_webui/routers/openai.py +++ b/backend/open_webui/routers/openai.py @@ -119,6 +119,74 @@ def openai_reasoning_model_handler(payload): return payload +def get_headers_and_cookies( + request: Request, + url, + key=None, + config=None, + metadata: Optional[dict] = None, + user: UserModel = None, +): + cookies = {} + headers = { + "Content-Type": "application/json", + **( + { + "HTTP-Referer": "https://openwebui.com/", + "X-Title": "Open WebUI", + } + if "openrouter.ai" in url + else {} + ), + **( + { + "X-OpenWebUI-User-Name": quote(user.name, safe=" "), + "X-OpenWebUI-User-Id": user.id, + "X-OpenWebUI-User-Email": user.email, + "X-OpenWebUI-User-Role": user.role, + **( + {"X-OpenWebUI-Chat-Id": metadata.get("chat_id")} + if metadata and metadata.get("chat_id") + else {} + ), + } + if ENABLE_FORWARD_USER_INFO_HEADERS + else {} + ), + } + + token = None + auth_type = config.get("auth_type") + + if auth_type == "bearer" or auth_type is None: + # Default to bearer if not specified + token = f"{key}" + elif auth_type == "none": + token = None + elif auth_type == "session": + cookies = request.cookies + token = request.state.token.credentials + elif auth_type == "oauth": + cookies = request.cookies + + oauth_token = None + try: + oauth_token = request.app.state.oauth_manager.get_oauth_token( + user.id, + request.cookies.get("oauth_session_id", None), + ) + except Exception as e: + log.error(f"Error getting OAuth token: {e}") + + if oauth_token: + token = f"{oauth_token.get('access_token', '')}" + + if token: + headers["Authorization"] = f"Bearer {token}" + + return headers, cookies + + ########################################## # # API routes @@ -210,34 +278,23 @@ async def speech(request: Request, user=Depends(get_verified_user)): return FileResponse(file_path) url = request.app.state.config.OPENAI_API_BASE_URLS[idx] + key = request.app.state.config.OPENAI_API_KEYS[idx] + api_config = request.app.state.config.OPENAI_API_CONFIGS.get( + str(idx), + request.app.state.config.OPENAI_API_CONFIGS.get(url, {}), # Legacy support + ) + + headers, cookies = get_headers_and_cookies( + request, url, key, api_config, user=user + ) r = None try: r = requests.post( url=f"{url}/audio/speech", data=body, - headers={ - "Content-Type": "application/json", - "Authorization": f"Bearer {request.app.state.config.OPENAI_API_KEYS[idx]}", - **( - { - "HTTP-Referer": "https://openwebui.com/", - "X-Title": "Open WebUI", - } - if "openrouter.ai" in url - else {} - ), - **( - { - "X-OpenWebUI-User-Name": quote(user.name, safe=" "), - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, - } - if ENABLE_FORWARD_USER_INFO_HEADERS - else {} - ), - }, + headers=headers, + cookies=cookies, stream=True, ) @@ -401,7 +458,10 @@ async def get_filtered_models(models, user): return filtered_models -@cached(ttl=MODELS_CACHE_TTL, key=lambda _, user: f"openai_all_models_{user.id}" if user else "openai_all_models") +@cached( + ttl=MODELS_CACHE_TTL, + key=lambda _, user: f"openai_all_models_{user.id}" if user else "openai_all_models", +) async def get_all_models(request: Request, user: UserModel) -> dict[str, list]: log.info("get_all_models()") @@ -489,19 +549,9 @@ async def get_models( timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST), ) as session: try: - headers = { - "Content-Type": "application/json", - **( - { - "X-OpenWebUI-User-Name": quote(user.name, safe=" "), - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, - } - if ENABLE_FORWARD_USER_INFO_HEADERS - else {} - ), - } + headers, cookies = get_headers_and_cookies( + request, url, key, api_config, user=user + ) if api_config.get("azure", False): models = { @@ -509,11 +559,10 @@ async def get_models( "object": "list", } else: - headers["Authorization"] = f"Bearer {key}" - async with session.get( f"{url}/models", headers=headers, + cookies=cookies, ssl=AIOHTTP_CLIENT_SESSION_SSL, ) as r: if r.status != 200: @@ -572,7 +621,9 @@ class ConnectionVerificationForm(BaseModel): @router.post("/verify") async def verify_connection( - form_data: ConnectionVerificationForm, user=Depends(get_admin_user) + request: Request, + form_data: ConnectionVerificationForm, + user=Depends(get_admin_user), ): url = form_data.url key = form_data.key @@ -584,19 +635,9 @@ async def verify_connection( timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST), ) as session: try: - headers = { - "Content-Type": "application/json", - **( - { - "X-OpenWebUI-User-Name": quote(user.name, safe=" "), - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, - } - if ENABLE_FORWARD_USER_INFO_HEADERS - else {} - ), - } + headers, cookies = get_headers_and_cookies( + request, url, key, api_config, user=user + ) if api_config.get("azure", False): headers["api-key"] = key @@ -605,6 +646,7 @@ async def verify_connection( async with session.get( url=f"{url}/openai/models?api-version={api_version}", headers=headers, + cookies=cookies, ssl=AIOHTTP_CLIENT_SESSION_SSL, ) as r: try: @@ -624,11 +666,10 @@ async def verify_connection( return response_data else: - headers["Authorization"] = f"Bearer {key}" - async with session.get( f"{url}/models", headers=headers, + cookies=cookies, ssl=AIOHTTP_CLIENT_SESSION_SSL, ) as r: try: @@ -836,32 +877,9 @@ async def generate_chat_completion( convert_logit_bias_input_to_json(payload["logit_bias"]) ) - headers = { - "Content-Type": "application/json", - **( - { - "HTTP-Referer": "https://openwebui.com/", - "X-Title": "Open WebUI", - } - if "openrouter.ai" in url - else {} - ), - **( - { - "X-OpenWebUI-User-Name": quote(user.name, safe=" "), - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, - **( - {"X-OpenWebUI-Chat-Id": metadata.get("chat_id")} - if metadata and metadata.get("chat_id") - else {} - ), - } - if ENABLE_FORWARD_USER_INFO_HEADERS - else {} - ), - } + headers, cookies = get_headers_and_cookies( + request, url, key, api_config, metadata, user=user + ) if api_config.get("azure", False): api_version = api_config.get("api_version", "2023-03-15-preview") @@ -871,7 +889,6 @@ async def generate_chat_completion( request_url = f"{request_url}/chat/completions?api-version={api_version}" else: request_url = f"{url}/chat/completions" - headers["Authorization"] = f"Bearer {key}" payload = json.dumps(payload) @@ -890,6 +907,7 @@ async def generate_chat_completion( url=request_url, data=payload, headers=headers, + cookies=cookies, ssl=AIOHTTP_CLIENT_SESSION_SSL, ) @@ -951,31 +969,27 @@ async def embeddings(request: Request, form_data: dict, user): models = request.app.state.OPENAI_MODELS if model_id in models: idx = models[model_id]["urlIdx"] + url = request.app.state.config.OPENAI_API_BASE_URLS[idx] key = request.app.state.config.OPENAI_API_KEYS[idx] + api_config = request.app.state.config.OPENAI_API_CONFIGS.get( + str(idx), + request.app.state.config.OPENAI_API_CONFIGS.get(url, {}), # Legacy support + ) + r = None session = None streaming = False + + headers, cookies = get_headers_and_cookies(request, url, key, api_config, user=user) try: session = aiohttp.ClientSession(trust_env=True) r = await session.request( method="POST", url=f"{url}/embeddings", data=body, - headers={ - "Authorization": f"Bearer {key}", - "Content-Type": "application/json", - **( - { - "X-OpenWebUI-User-Name": quote(user.name, safe=" "), - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, - } - if ENABLE_FORWARD_USER_INFO_HEADERS and user - else {} - ), - }, + headers=headers, + cookies=cookies, ) if "text/event-stream" in r.headers.get("Content-Type", ""): @@ -1037,19 +1051,9 @@ async def proxy(path: str, request: Request, user=Depends(get_verified_user)): streaming = False try: - headers = { - "Content-Type": "application/json", - **( - { - "X-OpenWebUI-User-Name": quote(user.name, safe=" "), - "X-OpenWebUI-User-Id": user.id, - "X-OpenWebUI-User-Email": user.email, - "X-OpenWebUI-User-Role": user.role, - } - if ENABLE_FORWARD_USER_INFO_HEADERS - else {} - ), - } + headers, cookies = get_headers_and_cookies( + request, url, key, api_config, user=user + ) if api_config.get("azure", False): api_version = api_config.get("api_version", "2023-03-15-preview") @@ -1062,7 +1066,6 @@ async def proxy(path: str, request: Request, user=Depends(get_verified_user)): request_url = f"{url}/{path}?api-version={api_version}" else: - headers["Authorization"] = f"Bearer {key}" request_url = f"{url}/{path}" session = aiohttp.ClientSession(trust_env=True) @@ -1071,6 +1074,7 @@ async def proxy(path: str, request: Request, user=Depends(get_verified_user)): url=request_url, data=body, headers=headers, + cookies=cookies, ssl=AIOHTTP_CLIENT_SESSION_SSL, ) diff --git a/backend/open_webui/utils/tools.py b/backend/open_webui/utils/tools.py index 74974e1461..fd441df9e1 100644 --- a/backend/open_webui/utils/tools.py +++ b/backend/open_webui/utils/tools.py @@ -138,12 +138,10 @@ async def get_tools( elif auth_type == "oauth": cookies = request.cookies oauth_token = extra_params.get("__oauth_token__", None) - headers["Authorization"] = ( - f"Bearer {oauth_token.get('access_token', '')}" - ) - elif auth_type == "request_headers": - cookies = request.cookies - headers.update(dict(request.headers)) + if oauth_token: + headers["Authorization"] = ( + f"Bearer {oauth_token.get('access_token', '')}" + ) headers["Content-Type"] = "application/json" @@ -564,9 +562,7 @@ async def get_tool_server_data(token: str, url: str) -> Dict[str, Any]: return data -async def get_tool_servers_data( - servers: List[Dict[str, Any]], session_token: Optional[str] = None -) -> List[Dict[str, Any]]: +async def get_tool_servers_data(servers: List[Dict[str, Any]]) -> List[Dict[str, Any]]: # Prepare list of enabled servers along with their original index server_entries = [] for idx, server in enumerate(servers): @@ -582,8 +578,9 @@ async def get_tool_servers_data( if auth_type == "bearer": token = server.get("key", "") - elif auth_type == "session": - token = session_token + elif auth_type == "none": + # No authentication + pass id = info.get("id") if not id: diff --git a/src/lib/components/AddConnectionModal.svelte b/src/lib/components/AddConnectionModal.svelte index ecf03ee2ce..900dc74d2b 100644 --- a/src/lib/components/AddConnectionModal.svelte +++ b/src/lib/components/AddConnectionModal.svelte @@ -443,8 +443,6 @@
      {/if} -
      -
      Date: Mon, 8 Sep 2025 19:53:44 +0400 Subject: [PATCH 126/154] refac --- backend/open_webui/routers/openai.py | 2 +- backend/open_webui/utils/tools.py | 2 +- src/lib/components/AddConnectionModal.svelte | 4 ++-- src/lib/components/AddServerModal.svelte | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/open_webui/routers/openai.py b/backend/open_webui/routers/openai.py index d7b5f75260..184f47038d 100644 --- a/backend/open_webui/routers/openai.py +++ b/backend/open_webui/routers/openai.py @@ -166,7 +166,7 @@ def get_headers_and_cookies( elif auth_type == "session": cookies = request.cookies token = request.state.token.credentials - elif auth_type == "oauth": + elif auth_type == "system_oauth": cookies = request.cookies oauth_token = None diff --git a/backend/open_webui/utils/tools.py b/backend/open_webui/utils/tools.py index fd441df9e1..0ef0cf47fb 100644 --- a/backend/open_webui/utils/tools.py +++ b/backend/open_webui/utils/tools.py @@ -135,7 +135,7 @@ async def get_tools( headers["Authorization"] = ( f"Bearer {request.state.token.credentials}" ) - elif auth_type == "oauth": + elif auth_type == "system_oauth": cookies = request.cookies oauth_token = extra_params.get("__oauth_token__", None) if oauth_token: diff --git a/src/lib/components/AddConnectionModal.svelte b/src/lib/components/AddConnectionModal.svelte index 900dc74d2b..fb4da3175f 100644 --- a/src/lib/components/AddConnectionModal.svelte +++ b/src/lib/components/AddConnectionModal.svelte @@ -330,7 +330,7 @@ {#if !ollama} {#if !direct} - + {/if} {/if} @@ -355,7 +355,7 @@ > {$i18n.t('Forwards system user session credentials to authenticate')}
      - {:else if auth_type === 'oauth'} + {:else if auth_type === 'system_oauth'}
      diff --git a/src/lib/components/AddServerModal.svelte b/src/lib/components/AddServerModal.svelte index fd70534d28..e35421a091 100644 --- a/src/lib/components/AddServerModal.svelte +++ b/src/lib/components/AddServerModal.svelte @@ -289,7 +289,7 @@ {#if !direct} - + {/if}
      From 41f9a8caff14e2c0c780b3cb23ffccaaa859ec83 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 8 Sep 2025 20:25:19 +0400 Subject: [PATCH 127/154] refac --- src/lib/components/AddServerModal.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/components/AddServerModal.svelte b/src/lib/components/AddServerModal.svelte index e35421a091..4a407b8279 100644 --- a/src/lib/components/AddServerModal.svelte +++ b/src/lib/components/AddServerModal.svelte @@ -313,7 +313,7 @@ > {$i18n.t('Forwards system user session credentials to authenticate')}
      - {:else if auth_type === 'oauth'} + {:else if auth_type === 'system_oauth'}
      From daa2a036f8743f58c158663d7b5ee32cce837b3a Mon Sep 17 00:00:00 2001 From: Antonio Pisano Date: Mon, 8 Sep 2025 18:51:33 +0200 Subject: [PATCH 128/154] Extend docling configuration options to include: * do_ocr * force_ocr * pdf_backend * table_mode * pipeline as per https://github.com/docling-project/docling-serve/blob/main/docs/usage.md See https://github.com/open-webui/open-webui/issues/17148 --- backend/open_webui/config.py | 30 +++++ backend/open_webui/main.py | 10 ++ backend/open_webui/retrieval/loaders/main.py | 18 ++- backend/open_webui/routers/retrieval.py | 46 +++++++- .../admin/Settings/Documents.svelte | 111 +++++++++++++++--- 5 files changed, 198 insertions(+), 17 deletions(-) diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index 1fe031cdad..692b22c742 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -2229,6 +2229,18 @@ DOCLING_SERVER_URL = PersistentConfig( os.getenv("DOCLING_SERVER_URL", "http://docling:5001"), ) +DOCLING_DO_OCR = PersistentConfig( + "DOCLING_DO_OCR", + "rag.docling_do_ocr", + os.getenv("DOCLING_DO_OCR", "True").lower() == "true", +) + +DOCLING_FORCE_OCR = PersistentConfig( + "DOCLING_FORCE_OCR", + "rag.docling_force_ocr", + os.getenv("DOCLING_FORCE_OCR", "False").lower() == "true", +) + DOCLING_OCR_ENGINE = PersistentConfig( "DOCLING_OCR_ENGINE", "rag.docling_ocr_engine", @@ -2241,6 +2253,24 @@ DOCLING_OCR_LANG = PersistentConfig( os.getenv("DOCLING_OCR_LANG", "eng,fra,deu,spa"), ) +DOCLING_PDF_BACKEND = PersistentConfig( + "DOCLING_PDF_BACKEND", + "rag.docling_pdf_backend", + os.getenv("DOCLING_PDF_BACKEND", "dlparse_v4"), +) + +DOCLING_TABLE_MODE = PersistentConfig( + "DOCLING_TABLE_MODE", + "rag.docling_table_mode", + os.getenv("DOCLING_TABLE_MODE", "accurate"), +) + +DOCLING_PIPELINE = PersistentConfig( + "DOCLING_PIPELINE", + "rag.docling_pipeline", + os.getenv("DOCLING_PIPELINE", "standard"), +) + DOCLING_DO_PICTURE_DESCRIPTION = PersistentConfig( "DOCLING_DO_PICTURE_DESCRIPTION", "rag.docling_do_picture_description", diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index d24bd5dcf1..547027bc9d 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -243,8 +243,13 @@ from open_webui.config import ( EXTERNAL_DOCUMENT_LOADER_API_KEY, TIKA_SERVER_URL, DOCLING_SERVER_URL, + DOCLING_DO_OCR, + DOCLING_FORCE_OCR, DOCLING_OCR_ENGINE, DOCLING_OCR_LANG, + DOCLING_PDF_BACKEND, + DOCLING_TABLE_MODE, + DOCLING_PIPELINE, DOCLING_DO_PICTURE_DESCRIPTION, DOCLING_PICTURE_DESCRIPTION_MODE, DOCLING_PICTURE_DESCRIPTION_LOCAL, @@ -810,8 +815,13 @@ app.state.config.EXTERNAL_DOCUMENT_LOADER_URL = EXTERNAL_DOCUMENT_LOADER_URL app.state.config.EXTERNAL_DOCUMENT_LOADER_API_KEY = EXTERNAL_DOCUMENT_LOADER_API_KEY app.state.config.TIKA_SERVER_URL = TIKA_SERVER_URL app.state.config.DOCLING_SERVER_URL = DOCLING_SERVER_URL +app.state.config.DOCLING_DO_OCR = DOCLING_DO_OCR +app.state.config.DOCLING_FORCE_OCR = DOCLING_FORCE_OCR app.state.config.DOCLING_OCR_ENGINE = DOCLING_OCR_ENGINE app.state.config.DOCLING_OCR_LANG = DOCLING_OCR_LANG +app.state.config.DOCLING_PDF_BACKEND = DOCLING_PDF_BACKEND +app.state.config.DOCLING_TABLE_MODE = DOCLING_TABLE_MODE +app.state.config.DOCLING_PIPELINE = DOCLING_PIPELINE app.state.config.DOCLING_DO_PICTURE_DESCRIPTION = DOCLING_DO_PICTURE_DESCRIPTION app.state.config.DOCLING_PICTURE_DESCRIPTION_MODE = DOCLING_PICTURE_DESCRIPTION_MODE app.state.config.DOCLING_PICTURE_DESCRIPTION_LOCAL = DOCLING_PICTURE_DESCRIPTION_LOCAL diff --git a/backend/open_webui/retrieval/loaders/main.py b/backend/open_webui/retrieval/loaders/main.py index 9b90dca041..a459274a09 100644 --- a/backend/open_webui/retrieval/loaders/main.py +++ b/backend/open_webui/retrieval/loaders/main.py @@ -148,7 +148,7 @@ class DoclingLoader: ) } - params = {"image_export_mode": "placeholder", "table_mode": "accurate"} + params = {"image_export_mode": "placeholder"} if self.params: if self.params.get("do_picture_description"): @@ -174,7 +174,11 @@ class DoclingLoader: self.params.get("picture_description_api", {}) ) - if self.params.get("ocr_engine") and self.params.get("ocr_lang"): + params["do_ocr"] = self.params.get("do_ocr") + + params["force_ocr"] = self.params.get("force_ocr") + + if self.params.get("do_ocr") and self.params.get("ocr_engine") and self.params.get("ocr_lang"): params["ocr_engine"] = self.params.get("ocr_engine") params["ocr_lang"] = [ lang.strip() @@ -182,6 +186,16 @@ class DoclingLoader: if lang.strip() ] + if self.params.get("pdf_backend"): + params["pdf_backend"] = self.params.get("pdf_backend") + + if self.params.get("table_mode"): + params["table_mode"] = self.params.get("table_mode") + + if self.params.get("pipeline"): + params["pipeline"] = self.params.get("pipeline") + + endpoint = f"{self.url}/v1/convert/file" r = requests.post(endpoint, files=files, data=params) diff --git a/backend/open_webui/routers/retrieval.py b/backend/open_webui/routers/retrieval.py index fdb7786258..d76944868b 100644 --- a/backend/open_webui/routers/retrieval.py +++ b/backend/open_webui/routers/retrieval.py @@ -426,8 +426,13 @@ async def get_rag_config(request: Request, user=Depends(get_admin_user)): "EXTERNAL_DOCUMENT_LOADER_API_KEY": request.app.state.config.EXTERNAL_DOCUMENT_LOADER_API_KEY, "TIKA_SERVER_URL": request.app.state.config.TIKA_SERVER_URL, "DOCLING_SERVER_URL": request.app.state.config.DOCLING_SERVER_URL, + "DOCLING_DO_OCR": request.app.state.config.DOCLING_DO_OCR, + "DOCLING_FORCE_OCR": request.app.state.config.DOCLING_FORCE_OCR, "DOCLING_OCR_ENGINE": request.app.state.config.DOCLING_OCR_ENGINE, "DOCLING_OCR_LANG": request.app.state.config.DOCLING_OCR_LANG, + "DOCLING_PDF_BACKEND": request.app.state.config.DOCLING_PDF_BACKEND, + "DOCLING_TABLE_MODE": request.app.state.config.DOCLING_TABLE_MODE, + "DOCLING_PIPELINE": request.app.state.config.DOCLING_PIPELINE, "DOCLING_DO_PICTURE_DESCRIPTION": request.app.state.config.DOCLING_DO_PICTURE_DESCRIPTION, "DOCLING_PICTURE_DESCRIPTION_MODE": request.app.state.config.DOCLING_PICTURE_DESCRIPTION_MODE, "DOCLING_PICTURE_DESCRIPTION_LOCAL": request.app.state.config.DOCLING_PICTURE_DESCRIPTION_LOCAL, @@ -596,8 +601,13 @@ class ConfigForm(BaseModel): TIKA_SERVER_URL: Optional[str] = None DOCLING_SERVER_URL: Optional[str] = None + DOCLING_DO_OCR: Optional[bool] = None + DOCLING_FORCE_OCR: Optional[bool] = None DOCLING_OCR_ENGINE: Optional[str] = None DOCLING_OCR_LANG: Optional[str] = None + DOCLING_PDF_BACKEND: Optional[str] = None + DOCLING_TABLE_MODE: Optional[str] = None + DOCLING_PIPELINE: Optional[str] = None DOCLING_DO_PICTURE_DESCRIPTION: Optional[bool] = None DOCLING_PICTURE_DESCRIPTION_MODE: Optional[str] = None DOCLING_PICTURE_DESCRIPTION_LOCAL: Optional[dict] = None @@ -767,6 +777,16 @@ async def update_rag_config( if form_data.DOCLING_SERVER_URL is not None else request.app.state.config.DOCLING_SERVER_URL ) + request.app.state.config.DOCLING_DO_OCR = ( + form_data.DOCLING_DO_OCR + if form_data.DOCLING_DO_OCR is not None + else request.app.state.config.DOCLING_DO_OCR + ) + request.app.state.config.DOCLING_FORCE_OCR = ( + form_data.DOCLING_FORCE_OCR + if form_data.DOCLING_FORCE_OCR is not None + else request.app.state.config.DOCLING_FORCE_OCR + ) request.app.state.config.DOCLING_OCR_ENGINE = ( form_data.DOCLING_OCR_ENGINE if form_data.DOCLING_OCR_ENGINE is not None @@ -777,7 +797,21 @@ async def update_rag_config( if form_data.DOCLING_OCR_LANG is not None else request.app.state.config.DOCLING_OCR_LANG ) - + request.app.state.config.DOCLING_PDF_BACKEND = ( + form_data.DOCLING_PDF_BACKEND + if form_data.DOCLING_PDF_BACKEND is not None + else request.app.state.config.DOCLING_PDF_BACKEND + ) + request.app.state.config.DOCLING_TABLE_MODE = ( + form_data.DOCLING_TABLE_MODE + if form_data.DOCLING_TABLE_MODE is not None + else request.app.state.config.DOCLING_TABLE_MODE + ) + request.app.state.config.DOCLING_PIPELINE = ( + form_data.DOCLING_PIPELINE + if form_data.DOCLING_PIPELINE is not None + else request.app.state.config.DOCLING_PIPELINE + ) request.app.state.config.DOCLING_DO_PICTURE_DESCRIPTION = ( form_data.DOCLING_DO_PICTURE_DESCRIPTION if form_data.DOCLING_DO_PICTURE_DESCRIPTION is not None @@ -1062,8 +1096,13 @@ async def update_rag_config( "EXTERNAL_DOCUMENT_LOADER_API_KEY": request.app.state.config.EXTERNAL_DOCUMENT_LOADER_API_KEY, "TIKA_SERVER_URL": request.app.state.config.TIKA_SERVER_URL, "DOCLING_SERVER_URL": request.app.state.config.DOCLING_SERVER_URL, + "DOCLING_DO_OCR": request.app.state.config.DOCLING_DO_OCR, + "DOCLING_FORCE_OCR": request.app.state.config.DOCLING_FORCE_OCR, "DOCLING_OCR_ENGINE": request.app.state.config.DOCLING_OCR_ENGINE, "DOCLING_OCR_LANG": request.app.state.config.DOCLING_OCR_LANG, + "DOCLING_PDF_BACKEND": request.app.state.config.DOCLING_PDF_BACKEND, + "DOCLING_TABLE_MODE": request.app.state.config.DOCLING_TABLE_MODE, + "DOCLING_PIPELINE": request.app.state.config.DOCLING_PIPELINE, "DOCLING_DO_PICTURE_DESCRIPTION": request.app.state.config.DOCLING_DO_PICTURE_DESCRIPTION, "DOCLING_PICTURE_DESCRIPTION_MODE": request.app.state.config.DOCLING_PICTURE_DESCRIPTION_MODE, "DOCLING_PICTURE_DESCRIPTION_LOCAL": request.app.state.config.DOCLING_PICTURE_DESCRIPTION_LOCAL, @@ -1453,8 +1492,13 @@ def process_file( TIKA_SERVER_URL=request.app.state.config.TIKA_SERVER_URL, DOCLING_SERVER_URL=request.app.state.config.DOCLING_SERVER_URL, DOCLING_PARAMS={ + "do_ocr": request.app.state.config.DOCLING_DO_OCR, + "force_ocr": request.app.state.config.DOCLING_FORCE_OCR, "ocr_engine": request.app.state.config.DOCLING_OCR_ENGINE, "ocr_lang": request.app.state.config.DOCLING_OCR_LANG, + "pdf_backend": request.app.state.config.DOCLING_PDF_BACKEND, + "table_mode": request.app.state.config.DOCLING_TABLE_MODE, + "pipeline": request.app.state.config.DOCLING_PIPELINE, "do_picture_description": request.app.state.config.DOCLING_DO_PICTURE_DESCRIPTION, "picture_description_mode": request.app.state.config.DOCLING_PICTURE_DESCRIPTION_MODE, "picture_description_local": request.app.state.config.DOCLING_PICTURE_DESCRIPTION_LOCAL, diff --git a/src/lib/components/admin/Settings/Documents.svelte b/src/lib/components/admin/Settings/Documents.svelte index d3a244fa45..b3e8aa1a44 100644 --- a/src/lib/components/admin/Settings/Documents.svelte +++ b/src/lib/components/admin/Settings/Documents.svelte @@ -152,7 +152,8 @@ return; } if ( - RAGConfig.CONTENT_EXTRACTION_ENGINE === 'docling' && + RAGConfig.CONTENT_EXTRACTION_ENGINE === 'docling' && + RAGConfig.DOCLING_DO_OCR && ((RAGConfig.DOCLING_OCR_ENGINE === '' && RAGConfig.DOCLING_OCR_LANG !== '') || (RAGConfig.DOCLING_OCR_ENGINE !== '' && RAGConfig.DOCLING_OCR_LANG === '')) ) { @@ -161,6 +162,16 @@ ); return; } + if ( + RAGConfig.CONTENT_EXTRACTION_ENGINE === 'docling' && + RAGConfig.DOCLING_DO_OCR === false && + RAGConfig.DOCLING_FORCE_OCR === true + ) { + toast.error( + $i18n.t('In order to force OCR, performing OCR must be enabled.') + ); + return; + } if ( RAGConfig.CONTENT_EXTRACTION_ENGINE === 'datalab_marker' && @@ -544,21 +555,93 @@ placeholder={$i18n.t('Enter Docling Server URL')} bind:value={RAGConfig.DOCLING_SERVER_URL} /> -
      -
      - - -
      +
      +
      +
      + {$i18n.t('Perform OCR')} +
      +
      + +
      +
      +
      + {#if RAGConfig.DOCLING_DO_OCR} +
      + + +
      + {/if} +
      +
      +
      + {$i18n.t('Force OCR')} +
      +
      + +
      +
      +
      +
      +
      + + {$i18n.t('PDF Backend')} + +
      +
      + +
      +
      +
      +
      + + {$i18n.t('Table Mode')} + +
      +
      + +
      +
      +
      +
      + + {$i18n.t('Pipeline')} + +
      +
      + +
      +
      +
      {$i18n.t('Describe Pictures in Documents')} From fcebc82ec2009a37233e71aad823978b4348b781 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 8 Sep 2025 20:54:18 +0400 Subject: [PATCH 129/154] refac: chat controls panel resize logic --- src/lib/components/chat/ChatControls.svelte | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/lib/components/chat/ChatControls.svelte b/src/lib/components/chat/ChatControls.svelte index 96160a7e13..cfd1fdc1c1 100644 --- a/src/lib/components/chat/ChatControls.svelte +++ b/src/lib/components/chat/ChatControls.svelte @@ -39,7 +39,11 @@ export const openPane = () => { if (parseInt(localStorage?.chatControlsSize)) { - pane.resize(parseInt(localStorage?.chatControlsSize)); + const container = document.getElementById('chat-container'); + let size = Math.floor( + (parseInt(localStorage?.chatControlsSize) / container.clientWidth) * 100 + ); + pane.resize(size); } else { pane.resize(minSize); } @@ -91,7 +95,7 @@ const resizeObserver = new ResizeObserver((entries) => { for (let entry of entries) { const width = entry.contentRect.width; - // calculate the percentage of 200px + // calculate the percentage of 350px const percentage = (350 / width) * 100; // set the minSize to the percentage, must be an integer minSize = Math.floor(percentage); @@ -99,6 +103,13 @@ if ($showControls) { if (pane && pane.isExpanded() && pane.getSize() < minSize) { pane.resize(minSize); + } else { + let size = Math.floor( + (parseInt(localStorage?.chatControlsSize) / container.clientWidth) * 100 + ); + if (size < minSize) { + pane.resize(minSize); + } } } } @@ -217,7 +228,11 @@ if (size < minSize) { localStorage.chatControlsSize = 0; } else { - localStorage.chatControlsSize = size; + // save the size in pixels to localStorage + const container = document.getElementById('chat-container'); + localStorage.chatControlsSize = Math.floor((size / 100) * container.clientWidth); + + console.log('saved size', localStorage.chatControlsSize); } } }} From 4a76bea80cdcd757807e72e5ccd527ca24816d82 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 8 Sep 2025 21:03:29 +0400 Subject: [PATCH 130/154] refac --- src/lib/components/chat/Messages/Markdown/Source.svelte | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/lib/components/chat/Messages/Markdown/Source.svelte b/src/lib/components/chat/Messages/Markdown/Source.svelte index 7215d19134..f87de43461 100644 --- a/src/lib/components/chat/Messages/Markdown/Source.svelte +++ b/src/lib/components/chat/Messages/Markdown/Source.svelte @@ -21,6 +21,10 @@ // Helper function to return only the domain from a URL function getDomain(url: string): string { const domain = url.replace('http://', '').replace('https://', '').split(/[/?#]/)[0]; + + if (domain.startsWith('www.')) { + return domain.slice(4); + } return domain; } @@ -44,7 +48,9 @@ }} > - {attributes.title ? formattedTitle(attributes.title) : ''} + {decodeURIComponent(attributes.title) + ? formattedTitle(decodeURIComponent(attributes.title)) + : ''} {/if} From c6a46195c06f9f954a8c6a2659250201a3b27f63 Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Mon, 8 Sep 2025 23:40:39 +0200 Subject: [PATCH 131/154] Changelog dev (#18) * Update CHANGELOG.md * Update CHANGELOG.md * Update CHANGELOG.md * Update CHANGELOG.md * Update CHANGELOG.md * Update CHANGELOG.md * Update CHANGELOG.md --- CHANGELOG.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 349b984e19..ec9154c6d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,60 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.6.27] - 2025-09-08 + +### Added + +- 🎨 Azure OpenAI image generation is now supported, with configurations for IMAGES_OPENAI_API_VERSION via environment variable and admin UI. [#17147](https://github.com/open-webui/open-webui/pull/17147), [#16274](https://github.com/open-webui/open-webui/discussions/16274), [Docs:#679](https://github.com/open-webui/docs/pull/679) +- 📁 Emoji folder icons were added, allowing users to personalize workspace organization with visual cues, including improved chevron display. [Commit](https://github.com/open-webui/open-webui/pull/17070/commits/1588f42fe777ad5d807e3f2fc8dbbc47a8db87c0), [Commit](https://github.com/open-webui/open-webui/pull/17070/commits/b70c0f36c0f5bbfc2a767429984d6fba1a7bb26c), [Commit](https://github.com/open-webui/open-webui/pull/17070/commits/11dea8795bfce42aa5d8d58ef316ded05173bd87), [Commit](https://github.com/open-webui/open-webui/pull/17070/commits/c0a47169fa059154d5f5a9ea6b94f9a66d82f255) +- 📁 The 'Search Collection' input field now dynamically displays the total number of files within the knowledge base. [Commit](https://github.com/open-webui/open-webui/pull/17070/commits/fbbe1117ae4c9c8fec6499d790eee275818eccc5) +- ☁️ A provider toggle in connection settings now allows users to manually specify Azure OpenAI deployments. [Commit](https://github.com/open-webui/open-webui/pull/17070/commits/5bdd334b74fbd154085f2d590f4afdba32469c8a) +- ⚡ Model list caching performance was optimized by fixing cache key generation to reduce redundant API calls. [#17158](https://github.com/open-webui/open-webui/pull/17158) +- ⚡ Comprehensive N+1 query performance is optimized by reducing database queries from 1+N to 1+1 patterns across major listing endpoints. [#17165](https://github.com/open-webui/open-webui/pull/17165), [#17160](https://github.com/open-webui/open-webui/pull/17160), [#17161](https://github.com/open-webui/open-webui/pull/17161), [#17162](https://github.com/open-webui/open-webui/pull/17162), [#17159](https://github.com/open-webui/open-webui/pull/17159), [#17166](https://github.com/open-webui/open-webui/pull/17166) +- ⚡ The PDF.js library is now dynamically loaded, significantly reducing initial page load size and improving responsiveness. [#17222](https://github.com/open-webui/open-webui/pull/17222) +- ⚡ The heic2any library is now dynamically loaded across various message input components, including channels, for faster page loads. [#17225](https://github.com/open-webui/open-webui/pull/17225), [#17229](https://github.com/open-webui/open-webui/pull/17229) +- 📚 The knowledge API now supports a "delete_file" query parameter, allowing configurable file deletion behavior. [Commit](https://github.com/open-webui/open-webui/pull/17070/commits/22c4ef4fb096498066b73befe993ae3a82f7a8e7) +- 📊 Llama.cpp timing statistics are now integrated into the usage field for comprehensive model performance metrics. [Commit](https://github.com/open-webui/open-webui/pull/17070/commits/e830b4959ecd4b2795e29e53026984a58a7696a9) +- 🗄️ The PGVECTOR_CREATE_EXTENSION environment variable now allows control over automatic pgvector extension creation. [Commit](https://github.com/open-webui/open-webui/pull/17070/commits/c2b4976c82d335ed524bd80dc914b5e2f5bfbd9e), [Commit](https://github.com/open-webui/open-webui/pull/17070/commits/b45219c8b15b48d5ee3d42983e1107bbcefbab01), [Docs:#672](https://github.com/open-webui/docs/pull/672) +- 🔧 External tool server authentication is enhanced with a "request_headers" type that forwards complete request headers. [Commit](https://github.com/open-webui/open-webui/pull/17070/commits/77b65ccbfbf3971ca71d3c7a70d77168e8f007dd) +- 🔒 Comprehensive server-side OAuth token management was implemented, securely storing encrypted tokens in a new database table and introducing an automatic refresh mechanism, enabling seamless and secure forwarding of valid user-specific OAuth tokens to downstream services, including OpenAI-compatible endpoints and external tool servers via the new "system_oauth" authentication type, resolving long-standing issues such as large token size limitations, stale/expired tokens, and reliable token propagation, and enhancing overall security by minimizing client-side token exposure, configurable via "ENABLE_OAUTH_ID_TOKEN_COOKIE" and "OAUTH_SESSION_TOKEN_ENCRYPTION_KEY" environment variables. [Docs:#683](https://github.com/open-webui/docs/pull/683), [#17210](https://github.com/open-webui/open-webui/pull/17210), [#8957](https://github.com/open-webui/open-webui/discussions/8957), [#11029](https://github.com/open-webui/open-webui/discussions/11029), [#17178](https://github.com/open-webui/open-webui/issues/17178), [#17183](https://github.com/open-webui/open-webui/issues/17183), [Commit](https://github.com/open-webui/open-webui/commit/217f4daef09b36d3d4cc4681e11d3ebd9984a1a5), [Commit](https://github.com/open-webui/open-webui/commit/fc11e4384fe98fac659e10596f67c23483578867), [Commit](https://github.com/open-webui/open-webui/commit/f11bdc6ab5dd5682bb3e27166e77581f5b8af3e0), [Commit](https://github.com/open-webui/open-webui/commit/f71834720e623761d972d4d740e9bbd90a3a86c6), [Commit](https://github.com/open-webui/open-webui/commit/b5bb6ae177dcdc4e8274d7e5ffa50bc8099fd466), [Commit](https://github.com/open-webui/open-webui/commit/b786d1e3f3308ef4f0f95d7130ddbcaaca4fc927), [Commit](https://github.com/open-webui/open-webui/commit/8a9f8627017bd0a74cbd647891552b26e56aabb7), [Commit](https://github.com/open-webui/open-webui/commit/30d1dc2c60e303756120fe1c5538968c4e6139f4), [Commit](https://github.com/open-webui/open-webui/commit/2b2d123531eb3f42c0e940593832a64e2806240d), [Commit](https://github.com/open-webui/open-webui/commit/6f6412dd16c63c2bb4df79a96b814bf69cb3f880) +- 🔒 Conditional Permission Hardening for OpenShift Deployments: Added a build argument to enable optional permission hardening for OpenShift and container environments. [Commit](https://github.com/open-webui/open-webui/pull/17070/commits/0ebe4f8f8490451ac8e85a4846f010854d9b54e5) +- 👥 Regex pattern support is added for OAuth blocked groups, allowing more flexible group filtering rules. [Commit](https://github.com/open-webui/open-webui/pull/17070/commits/df66e21472646648d008ebb22b0e8d5424d491df) +- 💬 Web search result display was enhanced to include titles and favicons, providing a clearer overview of search sources. [Commit](https://github.com/open-webui/open-webui/pull/17070/commits/33f04a771455e3fabf8f0e8ebb994ae7f41b8ed4), [Commit](https://github.com/open-webui/open-webui/pull/17070/commits/0a85dd4bca23022729eafdbc82c8c139fa365af2), [Commit](https://github.com/open-webui/open-webui/pull/17070/commits/16090bc2721fde492afa2c4af5927e2b668527e1), [#17197](https://github.com/open-webui/open-webui/pull/17197), [#14179](https://github.com/open-webui/open-webui/issues/14179), [Commit](https://github.com/open-webui/open-webui/pull/17070/commits/1cdb7aed1ee9bf81f2fd0404be52dcfa64f8ed4f), [Commit](https://github.com/open-webui/open-webui/pull/17070/commits/f2525ebc447c008cf7269ef20ce04fa456f302c4), [Commit](https://github.com/open-webui/open-webui/pull/17070/commits/7f523de408ede4075349d8de71ae0214b7e1a62e), [Commit](https://github.com/open-webui/open-webui/pull/17070/commits/3d37e4a42d344051ae715ab59bd7b5718e46c343), [Commit](https://github.com/open-webui/open-webui/pull/17070/commits/cd5e2be27b613314aadda6107089331783987985), [Commit](https://github.com/open-webui/open-webui/pull/17070/commits/6dc0df247347aede2762fe2065cf30275fd137ae) +- 💬 A new setting was added to control whether clicking a suggested prompt automatically sends the message or only inserts the text. [#17192](https://github.com/open-webui/open-webui/issues/17192), [Commit](https://github.com/open-webui/open-webui/commit/e023a98f11fc52feb21e4065ec707cc98e50c7d3) +- 🔄 Various improvements were implemented across the frontend and backend to enhance performance, stability, and security. +- 🌐 Translations for Portuguese (Brazil), Simplified Chinese, Catalan, and Spanish were enhanced and expanded. + +### Fixed + +- 🔍 Hybrid search functionality now correctly handles lexical-semantic weight labels and avoids errors when BM25 weight is zero. [#17049](https://github.com/open-webui/open-webui/pull/17049), [#17046](https://github.com/open-webui/open-webui/issues/17046) +- 🛑 Task stopping errors are prevented by gracefully handling multiple stop requests for the same task. [#17195](https://github.com/open-webui/open-webui/pull/17195) +- 🐍 Code execution package detection precision is improved in Pyodide to prevent unnecessary package inclusions. [Commit](https://github.com/open-webui/open-webui/pull/17070/commits/bbe116795860a81a647d9567e0d9cb1950650095) +- 🛠️ Tool message format API compliance is fixed by ensuring content fields in tool call responses contain valid string values instead of null. [Commit](https://github.com/open-webui/open-webui/pull/17070/commits/37bf0087e5b8a324009c9d06b304027df351ea6b) +- 📱 Mobile app config API authentication now supports Authorization header token verification with cookie fallback for iOS and Android requests. [#17175](https://github.com/open-webui/open-webui/pull/17175) +- 💾 Knowledge file save race conditions are prevented by serializing API calls and adding an "isSaving" guard. [#17137](https://github.com/open-webui/open-webui/pull/17137), [Commit](https://github.com/open-webui/open-webui/pull/17070/commits/4ca936f0bf9813bee11ec8aea41d7e34fb6b16a9) +- 🔐 The SSO login button visibility is restored for OIDC PKCE authentication without a client secret. [#17012](https://github.com/open-webui/open-webui/pull/17012) +- 🔊 Text-to-Speech (TTS) API requests now use proper URL joining methods, ensuring reliable functionality regardless of trailing slashes in the base URL. [#17061](https://github.com/open-webui/open-webui/pull/17061) +- 🛡️ Admin account creation on Hugging Face Spaces now correctly detects the configured port, resolving issues with custom port deployments. [#17064](https://github.com/open-webui/open-webui/pull/17064) +- 📁 Unicode filename support is improved for external document loaders by properly URL-encoding filenames in HTTP headers. [#17013](https://github.com/open-webui/open-webui/pull/17013), [#17000](https://github.com/open-webui/open-webui/issues/17000) +- 🔗 Web page and YouTube attachments are now correctly processed by setting their type as "text" and using collection names for accurate content retrieval. [Commit](https://github.com/open-webui/open-webui/pull/17070/commits/487979859a6ffcfd60468f523822cdf838fbef5b) +- ✍️ Message input composition event handling is fixed to properly manage text input for multilingual users using Input Method Editors (IME). [#17085](https://github.com/open-webui/open-webui/pull/17085) +- 💬 Follow-up tooltip duplication is removed, streamlining the user interface and preventing visual clutter. [#17186](https://github.com/open-webui/open-webui/pull/17186) +- 🎨 Chat button text display is corrected by preventing clipping of descending characters and removing unnecessary capitalization. [#17191](https://github.com/open-webui/open-webui/pull/17191) +- 🧠 RAG Loop/Error with Gemma 3.1 2B Instruct is fixed by correctly unwrapping unexpected single-item list responses from models. [Commit](https://github.com/open-webui/open-webui/pull/17070/commits/1bc9711afd2b72cd07c4e539a83783868733767c), [#17213](https://github.com/open-webui/open-webui/issues/17213) +- 🖼️ HEIC conversion failures are resolved, improving robustness of image handling. [#17225](https://github.com/open-webui/open-webui/pull/17225) +- 📦 The slim Docker image size regression has been fixed by refining the build process to correctly exclude components when USE_SLIM=true. [#16997](https://github.com/open-webui/open-webui/issues/16997), [Commit](https://github.com/open-webui/open-webui/commit/be373e9fd42ac73b0302bdb487e16dbeae178b4e), [Commit](https://github.com/open-webui/open-webui/commit/0ebe4f8f8490451ac8e85a4846f010854d9b54e5) +- 📁 Knowledge base update validation errors are resolved, ensuring seamless management via UI or API. [#17244](https://github.com/open-webui/open-webui/issues/17244), [Commit](https://github.com/open-webui/open-webui/commit/9aac1489080a5c9441e89b1a56de0d3a672bc5fb) +- 🔐 Resolved a security issue where a global web search setting overrode model-specific restrictions, ensuring model-level settings are now correctly prioritized. [#17151](https://github.com/open-webui/open-webui/issues/17151), [Commit](https://github.com/open-webui/open-webui/commit/9368d0ac751ec3072d5a96712b80a9b20a642ce6) +- 🔐 OAuth redirect reliability is improved by robustly preserving the intended redirect path using session storage. [#17235](https://github.com/open-webui/open-webui/issues/17235), [Commit](https://github.com/open-webui/open-webui/pull/17070/commits/4f2b821088367da18374027919594365c7a3f459), [#15575](https://github.com/open-webui/open-webui/pull/15575), [Commit](https://github.com/open-webui/open-webui/pull/17070/commits/d9f97c832c556fae4b116759da0177bf4fe619de) +- 🔐 Fixed a security vulnerability where knowledge base access within chat folders persisted after permissions were revoked. [#17182](https://github.com/open-webui/open-webui/issues/17182), [Commit](https://github.com/open-webui/open-webui/commit/40e40d1dddf9ca937e99af41c8ca038dbc93a7e6) +- 🔒 OIDC access denied errors are now displayed as user-friendly toast notifications instead of raw JSON. [#17208](https://github.com/open-webui/open-webui/issues/17208), [Commit](https://github.com/open-webui/open-webui/commit/3d6d050ad82d360adc42d6e9f42e8faf8d13c9f4) +- 💬 Chat exception handling is enhanced to prevent system instability during message generation and ensure graceful error recovery. [Commit](https://github.com/open-webui/open-webui/pull/17070/commits/f56889c5c7f0cf1a501c05d35dfa614e4f8b6958) + +### Changed + +- 🛠️ Renamed "Tools" to "External Tools" across the UI for clearer distinction between built-in and external functionalities. [Commit](https://github.com/open-webui/open-webui/pull/17070/commits/0bca4e230ef276bec468889e3be036242ad11086f) + ## [0.6.26] - 2025-08-28 ### Added From 218701e6174d6c04a4f6f58259e07312c983ec3b Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Mon, 8 Sep 2025 23:42:58 +0200 Subject: [PATCH 132/154] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec9154c6d1..12fcc8fb21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.6.27] - 2025-09-08 +## [0.6.27] - 2025-09-09 ### Added From d783708745d028ab39d7ed15ff2ba94aa7963cbf Mon Sep 17 00:00:00 2001 From: Shirasawa <764798966@qq.com> Date: Tue, 9 Sep 2025 15:42:58 +0800 Subject: [PATCH 133/154] feat: change default permission check for regenerate and delete actions --- src/lib/components/chat/Messages/ResponseMessage.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/components/chat/Messages/ResponseMessage.svelte b/src/lib/components/chat/Messages/ResponseMessage.svelte index 9db76bfa85..fd4b2ebb45 100644 --- a/src/lib/components/chat/Messages/ResponseMessage.svelte +++ b/src/lib/components/chat/Messages/ResponseMessage.svelte @@ -1275,7 +1275,7 @@ {/if} - {#if $user?.role === 'admin' || ($user?.permissions?.chat?.regenerate_response ?? false)} + {#if $user?.role === 'admin' || ($user?.permissions?.chat?.regenerate_response ?? true)} {#if $settings?.regenerateMenu ?? true} - {/each} + {@const urlCitations = citations.filter((c) => c?.source?.name?.startsWith('http'))} +
      + - {/each} -
      -
      -
      - - {citations.length - ($mobile ? 1 : 2)} - {$i18n.t('more')} -
      -
      -
      - {#if isCollapsibleOpen} - - {:else} - - {/if} -
      -
      -
      -
      - {#each citations.slice($mobile ? 1 : 2) as citation, idx} - - {/each} -
      -
      - - {/if} +
      {/if} diff --git a/src/lib/components/chat/Messages/CitationsModal.svelte b/src/lib/components/chat/Messages/Citations/CitationModal.svelte similarity index 100% rename from src/lib/components/chat/Messages/CitationsModal.svelte rename to src/lib/components/chat/Messages/Citations/CitationModal.svelte diff --git a/src/lib/components/chat/Messages/Citations/CitationsModal.svelte b/src/lib/components/chat/Messages/Citations/CitationsModal.svelte new file mode 100644 index 0000000000..1f53c8e186 --- /dev/null +++ b/src/lib/components/chat/Messages/Citations/CitationsModal.svelte @@ -0,0 +1,68 @@ + + + +
      +
      +
      + {$i18n.t('Citations')} +
      + +
      + +
      +
      + {#each citations as citation, idx} + + {/each} +
      +
      +
      +
      diff --git a/src/lib/components/chat/Messages/Markdown/Source.svelte b/src/lib/components/chat/Messages/Markdown/Source.svelte index f87de43461..b298337320 100644 --- a/src/lib/components/chat/Messages/Markdown/Source.svelte +++ b/src/lib/components/chat/Messages/Markdown/Source.svelte @@ -37,6 +37,14 @@ return title; } + const getDisplayTitle = (title: string) => { + if (!title) return 'N/A'; + if (title.length > 30) { + return title.slice(0, 15) + '...' + title.slice(-10); + } + return title; + }; + $: attributes = extractAttributes(token.text); @@ -48,9 +56,11 @@ }} > - {decodeURIComponent(attributes.title) - ? formattedTitle(decodeURIComponent(attributes.title)) - : ''} + {getDisplayTitle( + decodeURIComponent(attributes.title) + ? formattedTitle(decodeURIComponent(attributes.title)) + : '' + )} {/if} From 63ca0b8cba292f943a1578ff4d98cf0e7bdc7438 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Tue, 9 Sep 2025 17:36:18 +0400 Subject: [PATCH 144/154] refac --- .../components/chat/Messages/Citations.svelte | 15 ++++++++++++--- .../Messages/Citations/CitationsModal.svelte | 16 ++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/lib/components/chat/Messages/Citations.svelte b/src/lib/components/chat/Messages/Citations.svelte index a76b5def80..f234b52d4c 100644 --- a/src/lib/components/chat/Messages/Citations.svelte +++ b/src/lib/components/chat/Messages/Citations.svelte @@ -11,14 +11,16 @@ let showPercentage = false; let showRelevance = true; + let citationModal = null; let showCitationModal = false; let selectedCitation: any = null; let isCollapsibleOpen = false; export const showSourceModal = (sourceIdx) => { if (citations[sourceIdx]) { - selectedCitation = citations[sourceIdx]; - showCitationModal = true; + console.log('Showing citation modal for:', citations[sourceIdx]); + citationModal?.showCitation(citations[sourceIdx]); + // showCitationModal = true; } }; @@ -94,7 +96,14 @@ } - + {#if citations.length > 0} {@const urlCitations = citations.filter((c) => c?.source?.name?.startsWith('http'))} diff --git a/src/lib/components/chat/Messages/Citations/CitationsModal.svelte b/src/lib/components/chat/Messages/Citations/CitationsModal.svelte index 1f53c8e186..0e40827ac6 100644 --- a/src/lib/components/chat/Messages/Citations/CitationsModal.svelte +++ b/src/lib/components/chat/Messages/Citations/CitationsModal.svelte @@ -5,6 +5,7 @@ import Modal from '$lib/components/common/Modal.svelte'; import XMark from '$lib/components/icons/XMark.svelte'; + import CitationModal from './CitationModal.svelte'; export let id = ''; export let show = false; @@ -12,6 +13,14 @@ export let showPercentage = false; export let showRelevance = true; + let showCitationModal = false; + let selectedCitation: any = null; + + export const showCitation = (citation) => { + selectedCitation = citation; + showCitationModal = true; + }; + const decodeString = (str: string) => { try { return decodeURIComponent(str); @@ -21,6 +30,13 @@ }; + +
      From d0f338bb9954109eb19c7f211fb1d7a84aa142a2 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Tue, 9 Sep 2025 17:48:41 +0400 Subject: [PATCH 145/154] refac/enh: ability to export/sync function valves --- backend/open_webui/models/functions.py | 37 ++++++++++++++++++++----- backend/open_webui/routers/functions.py | 11 ++++---- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/backend/open_webui/models/functions.py b/backend/open_webui/models/functions.py index 7530573e79..2bb6d60889 100644 --- a/backend/open_webui/models/functions.py +++ b/backend/open_webui/models/functions.py @@ -54,6 +54,22 @@ class FunctionModel(BaseModel): model_config = ConfigDict(from_attributes=True) +class FunctionWithValvesModel(BaseModel): + id: str + user_id: str + name: str + type: str + content: str + meta: FunctionMeta + valves: Optional[dict] = None + is_active: bool = False + is_global: bool = False + updated_at: int # timestamp in epoch + created_at: int # timestamp in epoch + + model_config = ConfigDict(from_attributes=True) + + #################### # Forms #################### @@ -111,8 +127,8 @@ class FunctionsTable: return None def sync_functions( - self, user_id: str, functions: list[FunctionModel] - ) -> list[FunctionModel]: + self, user_id: str, functions: list[FunctionWithValvesModel] + ) -> list[FunctionWithValvesModel]: # Synchronize functions for a user by updating existing ones, inserting new ones, and removing those that are no longer present. try: with get_db() as db: @@ -166,17 +182,24 @@ class FunctionsTable: except Exception: return None - def get_functions(self, active_only=False) -> list[FunctionModel]: + def get_functions( + self, active_only=False, include_valves=False + ) -> list[FunctionModel | FunctionWithValvesModel]: with get_db() as db: if active_only: + functions = db.query(Function).filter_by(is_active=True).all() + + else: + functions = db.query(Function).all() + + if include_valves: return [ - FunctionModel.model_validate(function) - for function in db.query(Function).filter_by(is_active=True).all() + FunctionWithValvesModel.model_validate(function) + for function in functions ] else: return [ - FunctionModel.model_validate(function) - for function in db.query(Function).all() + FunctionModel.model_validate(function) for function in functions ] def get_functions_by_type( diff --git a/backend/open_webui/routers/functions.py b/backend/open_webui/routers/functions.py index b5beb96cf0..9ef6915709 100644 --- a/backend/open_webui/routers/functions.py +++ b/backend/open_webui/routers/functions.py @@ -10,6 +10,7 @@ from open_webui.models.functions import ( FunctionForm, FunctionModel, FunctionResponse, + FunctionWithValvesModel, Functions, ) from open_webui.utils.plugin import ( @@ -46,9 +47,9 @@ async def get_functions(user=Depends(get_verified_user)): ############################ -@router.get("/export", response_model=list[FunctionModel]) -async def get_functions(user=Depends(get_admin_user)): - return Functions.get_functions() +@router.get("/export", response_model=list[FunctionModel | FunctionWithValvesModel]) +async def get_functions(include_valves: bool = False, user=Depends(get_admin_user)): + return Functions.get_functions(include_valves=include_valves) ############################ @@ -132,10 +133,10 @@ async def load_function_from_url( class SyncFunctionsForm(BaseModel): - functions: list[FunctionModel] = [] + functions: list[FunctionWithValvesModel] = [] -@router.post("/sync", response_model=list[FunctionModel]) +@router.post("/sync", response_model=list[FunctionWithValvesModel]) async def sync_functions( request: Request, form_data: SyncFunctionsForm, user=Depends(get_admin_user) ): From 77779b30d46340b39e4be65e4c124746f917ec1a Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Tue, 9 Sep 2025 18:01:59 +0400 Subject: [PATCH 146/154] refac --- .../Messages/Citations/CitationModal.svelte | 141 ++++++++---------- 1 file changed, 63 insertions(+), 78 deletions(-) diff --git a/src/lib/components/chat/Messages/Citations/CitationModal.svelte b/src/lib/components/chat/Messages/Citations/CitationModal.svelte index 566f0c6e06..c6e460d964 100644 --- a/src/lib/components/chat/Messages/Citations/CitationModal.svelte +++ b/src/lib/components/chat/Messages/Citations/CitationModal.svelte @@ -61,8 +61,34 @@
      -
      - {$i18n.t('Citation')} +
      + {#if citation?.source?.name} + {@const document = mergedDocuments?.[0]} + {#if document?.metadata?.file_id || document.source?.url?.includes('http')} + + + {decodeString(citation?.source?.name)} + + + {:else} + {decodeString(citation?.source?.name)} + {/if} + {:else} + {$i18n.t('Citation')} + {/if}