diff --git a/application/single_app/config.py b/application/single_app/config.py
index 47dc23320..6cd524ec4 100644
--- a/application/single_app/config.py
+++ b/application/single_app/config.py
@@ -97,7 +97,7 @@
EXECUTOR_TYPE = 'thread'
EXECUTOR_MAX_WORKERS = 30
SESSION_TYPE = 'filesystem'
-VERSION = "0.250.065"
+VERSION = "0.250.066"
IS_DEVELOPMENT = is_development_env_enabled()
SESSION_COOKIE_SAMESITE = os.getenv('SESSION_COOKIE_SAMESITE', 'Lax')
diff --git a/application/single_app/functions_model_capabilities.py b/application/single_app/functions_model_capabilities.py
new file mode 100644
index 000000000..aecb13dab
--- /dev/null
+++ b/application/single_app/functions_model_capabilities.py
@@ -0,0 +1,53 @@
+# functions_model_capabilities.py
+
+import re
+from collections.abc import Mapping
+
+
+MODEL_IDENTIFIER_SEPARATOR_PATTERN = re.compile(r"[\s_.]+")
+GPT_VISION_MODEL_PATTERN = re.compile(r"(?:^|-)gpt-(?:[5-9]|\d{2,})(?:-|$)")
+O_SERIES_MODEL_PATTERN = re.compile(r"(?:^|-)o\d+(?:-|$)")
+MODEL_IDENTIFIER_FIELDS = (
+ "modelName",
+ "displayName",
+ "deploymentName",
+ "deployment",
+ "name",
+)
+
+
+def _normalize_model_identifier(value):
+ return MODEL_IDENTIFIER_SEPARATOR_PATTERN.sub(
+ "-",
+ str(value or "").strip().lower(),
+ )
+
+
+def is_vision_capable_model_name(*model_names):
+ """Return whether any supplied identifier names a supported vision model."""
+ for model_name in model_names:
+ normalized_name = _normalize_model_identifier(model_name)
+ if (
+ "vision" in normalized_name
+ or "gpt-4o" in normalized_name
+ or "gpt-4-1" in normalized_name
+ or "gpt-4-5" in normalized_name
+ or GPT_VISION_MODEL_PATTERN.search(normalized_name)
+ or O_SERIES_MODEL_PATTERN.search(normalized_name)
+ ):
+ return True
+
+ return False
+
+
+def is_vision_capable_model(model):
+ """Return whether a model record or identifier names a supported vision model."""
+ if isinstance(model, str):
+ return is_vision_capable_model_name(model)
+
+ if isinstance(model, Mapping):
+ model_names = [model.get(field_name) for field_name in MODEL_IDENTIFIER_FIELDS]
+ else:
+ model_names = [getattr(model, field_name, None) for field_name in MODEL_IDENTIFIER_FIELDS]
+
+ return is_vision_capable_model_name(*model_names)
\ No newline at end of file
diff --git a/application/single_app/route_frontend_admin_settings.py b/application/single_app/route_frontend_admin_settings.py
index 878c2abd8..366a34c32 100644
--- a/application/single_app/route_frontend_admin_settings.py
+++ b/application/single_app/route_frontend_admin_settings.py
@@ -27,6 +27,7 @@
from functions_notifications import broadcast_system_notification
from functions_logging import *
from functions_document_actions import normalize_document_action_capabilities
+from functions_model_capabilities import is_vision_capable_model
from functions_terms_of_use import (
TERMS_OF_USE_DEFAULT_REDIRECT,
TERMS_OF_USE_MAX_BUTTON_TEXT_LENGTH,
@@ -596,7 +597,8 @@ def admin_settings():
chunk_size_cap=get_chunk_size_cap(settings),
chunk_size_effective=get_chunk_size_config(settings),
audio_runtime_capabilities=audio_runtime_capabilities,
- source_review_runtime_capabilities=source_review_runtime_capabilities
+ source_review_runtime_capabilities=source_review_runtime_capabilities,
+ is_vision_capable_model=is_vision_capable_model,
# You don't need to pass deployments separately if they are added to settings['..._model']['all']
# gpt_deployments=gpt_deployments,
# embedding_deployments=embedding_deployments,
diff --git a/application/single_app/static/js/admin/admin_settings.js b/application/single_app/static/js/admin/admin_settings.js
index b5e996ac6..ad47f3a16 100644
--- a/application/single_app/static/js/admin/admin_settings.js
+++ b/application/single_app/static/js/admin/admin_settings.js
@@ -8364,18 +8364,22 @@ const visionToggle = document.getElementById('enable_multimodal_vision');
const visionModelDiv = document.getElementById('multimodal_vision_model_settings');
const visionSelect = document.getElementById('multimodal_vision_model');
-function isVisionCapableModelName(modelName) {
- const modelNameLower = (modelName || '').toLowerCase();
- return (
- modelNameLower.includes('vision') ||
- modelNameLower.includes('gpt-4o') ||
- modelNameLower.includes('gpt-4.1') ||
- modelNameLower.includes('gpt-4.5') ||
- modelNameLower.includes('gpt-5') ||
- /^o\d+/.test(modelNameLower) ||
- modelNameLower.includes('o1-') ||
- modelNameLower.includes('o3-')
- );
+function isVisionCapableModelName(...modelNames) {
+ return modelNames.some(modelName => {
+ const normalizedName = String(modelName || '')
+ .trim()
+ .toLowerCase()
+ .replace(/[\s_.]+/g, '-');
+
+ return (
+ normalizedName.includes('vision') ||
+ normalizedName.includes('gpt-4o') ||
+ normalizedName.includes('gpt-4-1') ||
+ normalizedName.includes('gpt-4-5') ||
+ /(?:^|-)gpt-(?:[5-9]|\d{2,})(?:-|$)/.test(normalizedName) ||
+ /(?:^|-)o\d+(?:-|$)/.test(normalizedName)
+ );
+ });
}
function getSelectedVisionModelOption() {
@@ -8401,10 +8405,18 @@ function populateVisionModels() {
.filter(ep => ep && ep.enabled)
.forEach(ep => {
(ep.models || [])
- .filter(m => m && m.enabled && isVisionCapableModelName(m.modelName || m.displayName))
+ .filter(m => m && m.enabled && isVisionCapableModelName(
+ m.modelName,
+ m.displayName,
+ m.deploymentName,
+ m.deployment,
+ m.name
+ ))
.forEach(m => {
const value = m.deploymentName;
- const label = `${m.displayName || m.deploymentName} (${m.modelName})`;
+ const label = m.modelName
+ ? `${m.displayName || m.deploymentName} (${m.modelName})`
+ : (m.displayName || m.deploymentName);
const opt = new Option(label, value);
opt.dataset.endpointId = ep.id || '';
opt.dataset.modelId = m.id || '';
diff --git a/application/single_app/templates/admin_settings.html b/application/single_app/templates/admin_settings.html
index 74d0fa978..8af4d70d8 100644
--- a/application/single_app/templates/admin_settings.html
+++ b/application/single_app/templates/admin_settings.html
@@ -8213,19 +8213,7 @@
{# Iterate all enabled endpoints and models, filter to vision-capable #}
{% for endpoint in settings.model_endpoints if endpoint.enabled %}
{% for m in endpoint.models if m.enabled %}
- {% set model_lower = (m.modelName or m.displayName or '').lower() %}
- {% set is_vision =
- 'vision' in model_lower or
- 'gpt-4o' in model_lower or
- 'gpt-4.1' in model_lower or
- 'gpt-4.5' in model_lower or
- '-5' in model_lower or
- model_lower.startswith('o1') or
- model_lower.startswith('o3') or
- 'o1-' in model_lower or
- 'o3-' in model_lower
- %}
- {% if is_vision %}
+ {% if is_vision_capable_model is defined and is_vision_capable_model(m) %}
{% set option_value = m.deploymentName %}
{% endif %}
{% endfor %}
@@ -8242,19 +8230,7 @@
{# Legacy APIM-based GPT configuration #}
{% for d in (settings.azure_apim_gpt_deployment or '').split(',') if d %}
{% set d_trim = d.strip() %}
- {% set model_lower = d_trim.lower() %}
- {% set is_vision =
- 'vision' in model_lower or
- 'gpt-4o' in model_lower or
- 'gpt-4.1' in model_lower or
- 'gpt-4.5' in model_lower or
- '-5' in model_lower or
- model_lower.startswith('o1') or
- model_lower.startswith('o3') or
- 'o1-' in model_lower or
- 'o3-' in model_lower
- %}
- {% if is_vision %}
+ {% if is_vision_capable_model is defined and is_vision_capable_model(d_trim) %}