From 654172d757759a75ea77da844f2065b934ddb973 Mon Sep 17 00:00:00 2001 From: Thomas Rehn <271119+tremlin@users.noreply.github.com> Date: Mon, 10 Nov 2025 07:35:26 +0100 Subject: [PATCH 01/11] fix: redis clustermode instrumentation --- .../utils/telemetry/instrumentors.py | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/backend/open_webui/utils/telemetry/instrumentors.py b/backend/open_webui/utils/telemetry/instrumentors.py index dbc4ebb2cb..9e251e38ae 100644 --- a/backend/open_webui/utils/telemetry/instrumentors.py +++ b/backend/open_webui/utils/telemetry/instrumentors.py @@ -22,6 +22,7 @@ from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor from opentelemetry.instrumentation.aiohttp_client import AioHttpClientInstrumentor from opentelemetry.trace import Span, StatusCode from redis import Redis +from redis.cluster import RedisCluster from requests import PreparedRequest, Response from sqlalchemy import Engine from fastapi import status @@ -59,16 +60,28 @@ def response_hook(span: Span, request: PreparedRequest, response: Response): span.set_status(StatusCode.ERROR if response.status_code >= 400 else StatusCode.OK) -def redis_request_hook(span: Span, instance: Redis, args, kwargs): +def redis_request_hook(span: Span, instance: Union[Redis|RedisCluster], args, kwargs): """ Redis Request Hook """ + # In cluster mode, the instance can be of two types: + # - redis.asyncio.cluster.RedisCluster + # - redis.cluster.RedisCluster + # Instead of checking the type, we check if the instance has a nodes_manager attribute. try: - connection_kwargs: dict = instance.connection_pool.connection_kwargs - host = connection_kwargs.get("host") - port = connection_kwargs.get("port") - db = connection_kwargs.get("db") + db = "" + if hasattr(instance, 'nodes_manager'): + default_node = instance.nodes_manager.default_node + if not default_node: + return + host = default_node.host + port = default_node.port + else: + connection_kwargs: dict = instance.connection_pool.connection_kwargs + host = connection_kwargs.get("host") + port = connection_kwargs.get("port") + db = connection_kwargs.get("db") span.set_attributes( { SpanAttributes.DB_INSTANCE: f"{host}/{db}", From a9e0462e57688b0cb69c8f38f406527872a4b0cb Mon Sep 17 00:00:00 2001 From: Thomas Rehn <271119+tremlin@users.noreply.github.com> Date: Tue, 3 Feb 2026 21:09:02 +0100 Subject: [PATCH 02/11] feat: add keyboard shortcut for opening the model selector (#21130) --- src/lib/shortcuts.ts | 6 ++++++ src/routes/(app)/+layout.svelte | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/src/lib/shortcuts.ts b/src/lib/shortcuts.ts index 7e7ba45eee..e1973d4776 100644 --- a/src/lib/shortcuts.ts +++ b/src/lib/shortcuts.ts @@ -16,6 +16,7 @@ export enum Shortcut { NEW_CHAT = 'newChat', NEW_TEMPORARY_CHAT = 'newTemporaryChat', DELETE_CHAT = 'deleteChat', + OPEN_MODEL_SELECTOR = 'openModelSelector', //Global SEARCH = 'search', @@ -58,6 +59,11 @@ export const shortcuts: ShortcutRegistry = { keys: ['mod', 'shift', 'Backspace', 'Delete'], category: 'Chat' }, + [Shortcut.OPEN_MODEL_SELECTOR]: { + name: 'Open Model Selector', + keys: ['mod', 'shift', 'M'], + category: 'Chat' + }, //Global [Shortcut.SEARCH]: { diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index a43665a1b5..87c09440dd 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -230,6 +230,10 @@ event.preventDefault(); showSettings.set(false); showShortcuts.set(false); + } else if (isShortcutMatch(event, shortcuts[Shortcut.OPEN_MODEL_SELECTOR])) { + console.log('Shortcut triggered: OPEN_MODEL_SELECTOR'); + event.preventDefault(); + document.getElementById('model-selector-0-button')?.click(); } else if (isShortcutMatch(event, shortcuts[Shortcut.NEW_TEMPORARY_CHAT])) { console.log('Shortcut triggered: NEW_TEMPORARY_CHAT'); event.preventDefault(); From 0a6166619794eb6c3452039a09a7c0539045e602 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Tue, 3 Feb 2026 16:33:12 -0600 Subject: [PATCH 03/11] refac --- .../components/workspace/Prompts/PromptEditor.svelte | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib/components/workspace/Prompts/PromptEditor.svelte b/src/lib/components/workspace/Prompts/PromptEditor.svelte index 5f9a0da870..cad0b6936e 100644 --- a/src/lib/components/workspace/Prompts/PromptEditor.svelte +++ b/src/lib/components/workspace/Prompts/PromptEditor.svelte @@ -509,9 +509,9 @@
-
{selectedHistoryEntry?.snapshot
+						
{selectedHistoryEntry?.snapshot
 								?.content || content}
@@ -623,10 +623,10 @@
{#if selectedHistoryEntry} {selectedHistoryEntry.id.slice(0, 7)} From 4aacaeb9b80d1e5cd617f32a941fc8f16e953179 Mon Sep 17 00:00:00 2001 From: EntropyYue <164553692+EntropyYue@users.noreply.github.com> Date: Wed, 4 Feb 2026 06:52:38 +0800 Subject: [PATCH 05/11] fix: Shortcut i18n (#21138) --- src/lib/components/chat/ShortcutsModal.svelte | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/components/chat/ShortcutsModal.svelte b/src/lib/components/chat/ShortcutsModal.svelte index cc62258caa..4db85ec06d 100644 --- a/src/lib/components/chat/ShortcutsModal.svelte +++ b/src/lib/components/chat/ShortcutsModal.svelte @@ -73,6 +73,7 @@ + From ecf3fa2feb28e74ff6c17ca97d94581f316da56a Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Tue, 3 Feb 2026 23:36:15 -0600 Subject: [PATCH 06/11] refac --- backend/open_webui/config.py | 6 ++++++ backend/open_webui/routers/images.py | 12 +++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index b4f33afd47..e8338a65aa 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -3450,6 +3450,12 @@ IMAGE_GENERATION_MODEL = PersistentConfig( os.getenv("IMAGE_GENERATION_MODEL", ""), ) +# Regex pattern for models that support IMAGE_SIZE = "auto". +IMAGE_AUTO_SIZE_MODELS_REGEX_PATTERN = os.getenv("IMAGE_AUTO_SIZE_MODELS_REGEX_PATTERN", "^gpt-image") + +# Regex pattern for models that return URLs instead of base64 data. +IMAGE_URL_RESPONSE_MODELS_REGEX_PATTERN = os.getenv("IMAGE_URL_RESPONSE_MODELS_REGEX_PATTERN", "^gpt-image") + IMAGE_SIZE = PersistentConfig( "IMAGE_SIZE", "image_generation.size", os.getenv("IMAGE_SIZE", "512x512") ) diff --git a/backend/open_webui/routers/images.py b/backend/open_webui/routers/images.py index 7fdd84b3fa..05afa63bb6 100644 --- a/backend/open_webui/routers/images.py +++ b/backend/open_webui/routers/images.py @@ -14,7 +14,7 @@ import requests from fastapi import APIRouter, Depends, HTTPException, Request, UploadFile from fastapi.responses import FileResponse -from open_webui.config import CACHE_DIR +from open_webui.config import CACHE_DIR, IMAGE_AUTO_SIZE_MODELS_REGEX_PATTERN, IMAGE_URL_RESPONSE_MODELS_REGEX_PATTERN from open_webui.constants import ERROR_MESSAGES from open_webui.retrieval.web.utils import validate_url from open_webui.env import ENABLE_FORWARD_USER_INFO_HEADERS @@ -201,12 +201,12 @@ async def update_config( set_image_model(request, form_data.IMAGE_GENERATION_MODEL) if ( form_data.IMAGE_SIZE == "auto" - and not form_data.IMAGE_GENERATION_MODEL.startswith("gpt-image") + and not re.match(IMAGE_AUTO_SIZE_MODELS_REGEX_PATTERN, form_data.IMAGE_GENERATION_MODEL) ): raise HTTPException( status_code=400, detail=ERROR_MESSAGES.INCORRECT_FORMAT( - " (auto is only allowed with gpt-image models)." + f" (auto is only allowed with models matching {IMAGE_AUTO_SIZE_MODELS_REGEX_PATTERN})." ), ) @@ -610,9 +610,7 @@ async def image_generations( ), **( {} - if request.app.state.config.IMAGE_GENERATION_MODEL.startswith( - "gpt-image" - ) + if re.match(IMAGE_URL_RESPONSE_MODELS_REGEX_PATTERN, request.app.state.config.IMAGE_GENERATION_MODEL) else {"response_format": "b64_json"} ), **( @@ -949,7 +947,7 @@ async def image_edits( **({"size": size} if size else {}), **( {} - if request.app.state.config.IMAGE_EDIT_MODEL.startswith("gpt-image") + if re.match(IMAGE_URL_RESPONSE_MODELS_REGEX_PATTERN, request.app.state.config.IMAGE_EDIT_MODEL) else {"response_format": "b64_json"} ), } From 43c68468f79682b78a0f85f7c7742adc35ec1c29 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Tue, 3 Feb 2026 23:38:03 -0600 Subject: [PATCH 07/11] refac --- src/lib/components/common/Tags/TagInput.svelte | 1 + src/lib/components/common/Tags/TagItem.svelte | 1 + 2 files changed, 2 insertions(+) diff --git a/src/lib/components/common/Tags/TagInput.svelte b/src/lib/components/common/Tags/TagInput.svelte index 20b4b61736..9bd2e1757e 100644 --- a/src/lib/components/common/Tags/TagInput.svelte +++ b/src/lib/components/common/Tags/TagInput.svelte @@ -35,6 +35,7 @@ list="tagOptions" on:keydown={(event) => { if (event.key === 'Enter') { + event.preventDefault(); addTagHandler(); } }} diff --git a/src/lib/components/common/Tags/TagItem.svelte b/src/lib/components/common/Tags/TagItem.svelte index 8b1a4e6f66..cf26461027 100644 --- a/src/lib/components/common/Tags/TagItem.svelte +++ b/src/lib/components/common/Tags/TagItem.svelte @@ -13,6 +13,7 @@ {#if tag} - - {/if} - +{#if showInput} +
+ + + { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault(); + addTagHandler(); + } else if (event.key === 'Escape') { + tagName = ''; + showInput = false; + } + }} + on:blur={closeInput} + /> +
+{:else} +{/if} - {#if label && !showTagInput} - {label} - {/if} - +{#if suggestionTags.length > 0} + + {#each suggestionTags as tag} + +{/if} diff --git a/src/lib/components/common/Tags/TagItem.svelte b/src/lib/components/common/Tags/TagItem.svelte index cf26461027..01c9c85b6b 100644 --- a/src/lib/components/common/Tags/TagItem.svelte +++ b/src/lib/components/common/Tags/TagItem.svelte @@ -3,7 +3,6 @@ const i18n = getContext('i18n'); - import Tooltip from '../Tooltip.svelte'; import XMark from '$lib/components/icons/XMark.svelte'; export let tag; @@ -11,24 +10,14 @@ {#if tag} - - - + {/if} From 405dc26cc6e6049b52dcfeacde872c91ec9fcde4 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Wed, 4 Feb 2026 00:04:09 -0600 Subject: [PATCH 09/11] refac --- src/lib/components/common/Tags.svelte | 8 +++++--- src/lib/components/common/Tags/TagItem.svelte | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/lib/components/common/Tags.svelte b/src/lib/components/common/Tags.svelte index 54dd465d0b..3bdaca41cc 100644 --- a/src/lib/components/common/Tags.svelte +++ b/src/lib/components/common/Tags.svelte @@ -19,7 +19,7 @@ }; -
+
{ @@ -29,8 +29,10 @@ { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); diff --git a/src/lib/components/common/Tags/TagItem.svelte b/src/lib/components/common/Tags/TagItem.svelte index 01c9c85b6b..c17d87edf6 100644 --- a/src/lib/components/common/Tags/TagItem.svelte +++ b/src/lib/components/common/Tags/TagItem.svelte @@ -12,7 +12,7 @@ {#if tag}
-
+
-
+
{#if ($functions ?? []).filter((func) => func.type === 'filter').length > 0 || ($functions ?? []).filter((func) => func.type === 'action').length > 0} -
+
{#if ($functions ?? []).filter((func) => func.type === 'filter').length > 0} -
+
func.type === 'filter')} @@ -751,7 +751,7 @@ )} {#if toggleableFilters.length > 0} -
+
func.type === 'action').length > 0} -
+
func.type === 'action')} @@ -770,9 +770,9 @@ {/if} {/if} -
+
-
+
@@ -785,19 +785,19 @@ .map(([key, value]) => key)} {#if availableFeatures.length > 0} -
+
{/if} {/if} {#if capabilities.builtin_tools} -
+
{/if} -
+
{$i18n.t('TTS Voice')} @@ -811,7 +811,7 @@ />
-
+