diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index 675176ee54..02b8192522 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -3462,6 +3462,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"} ), } 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}", diff --git a/src/lib/components/chat/Messages/RateComment.svelte b/src/lib/components/chat/Messages/RateComment.svelte index d64c41c006..9a309ec4de 100644 --- a/src/lib/components/chat/Messages/RateComment.svelte +++ b/src/lib/components/chat/Messages/RateComment.svelte @@ -258,7 +258,7 @@ class="flex cursor-pointer items-center justify-between hover:bg-gray-50 dark:hover:bg-gray-850 w-full px-3 py-2 rounded-xl transition" >
-
{$i18n.t('Leave a review for {{modelName}}', { modelName: message.model })}
+
{$i18n.t('Leave a public review for {{modelName}}', { modelName: message.model })}
{$i18n.t('Help the community discover great models')}
diff --git a/src/lib/components/chat/ModelSelector/ModelItemMenu.svelte b/src/lib/components/chat/ModelSelector/ModelItemMenu.svelte index 39eba528af..a6a08e6e80 100644 --- a/src/lib/components/chat/ModelSelector/ModelItemMenu.svelte +++ b/src/lib/components/chat/ModelSelector/ModelItemMenu.svelte @@ -96,6 +96,8 @@ {#if $config?.features.enable_community_sharing} +
+ + diff --git a/src/lib/components/common/Tags.svelte b/src/lib/components/common/Tags.svelte index d42ac606b5..3bdaca41cc 100644 --- a/src/lib/components/common/Tags.svelte +++ b/src/lib/components/common/Tags.svelte @@ -1,5 +1,4 @@ - + diff --git a/src/lib/components/common/Tags/TagInput.svelte b/src/lib/components/common/Tags/TagInput.svelte index 20b4b61736..9388b4cbe9 100644 --- a/src/lib/components/common/Tags/TagInput.svelte +++ b/src/lib/components/common/Tags/TagInput.svelte @@ -1,95 +1,72 @@ -
- {#if showTagInput} -
- { - if (event.key === 'Enter') { - addTagHandler(); - } - }} - /> - {#if suggestionTags.length > 0} - - {#each suggestionTags as tag} - - {/if} - - -
- {/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 8b1a4e6f66..c17d87edf6 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,23 +10,14 @@ {#if tag} - - - + {/if} diff --git a/src/lib/components/workspace/Models/ModelEditor.svelte b/src/lib/components/workspace/Models/ModelEditor.svelte index 52734acb20..18e26963a4 100644 --- a/src/lib/components/workspace/Models/ModelEditor.svelte +++ b/src/lib/components/workspace/Models/ModelEditor.svelte @@ -724,19 +724,19 @@ {/if} -
+
-
+
{#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 @@ />
-
+
{#if selectedHistoryEntry} {selectedHistoryEntry.id.slice(0, 7)} @@ -509,9 +509,9 @@
-
{selectedHistoryEntry?.snapshot
+						
{selectedHistoryEntry?.snapshot
 								?.content || content}
@@ -623,10 +623,10 @@