diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index 1ac45f3e70..15412572b0 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -220,6 +220,9 @@ from open_webui.utils.chat import ( from open_webui.utils.chat import ( generate_chat_completion as chat_completion_handler, ) +from open_webui.utils.chat_variables import ( + normalize_chat_variables, +) from open_webui.utils.embeddings import generate_embeddings from open_webui.utils.logger import start_logger from open_webui.utils.middleware import ( @@ -1135,6 +1138,13 @@ async def chat_completion( message_ids = [{'model_id': model_id, 'message_id': form_data.pop('id', None)}] user_message = form_data.pop('user_message', None) or form_data.pop('parent_message', None) + chat_id = form_data.get('chat_id') or '' + chat_variables = form_data.pop('chat_variables', None) + if chat_variables is None: + existing_chat = await Chats.get_chat_by_id(chat_id) if chat_id else None + chat_variables = existing_chat.variables if existing_chat else {} + + chat_variables = normalize_chat_variables(chat_variables) # Drop tool_servers if caller lacks features.direct_tool_servers — # mirrors the storage-side strip in user/settings/update. @@ -1166,6 +1176,7 @@ async def chat_completion( 'files': form_data.get('files', None), 'features': form_data.get('features', {}), 'variables': form_data.get('variables', {}), + 'chat_variables': chat_variables, 'model': model, 'direct': model_item.get('direct', False), 'params': { @@ -1280,6 +1291,7 @@ async def chat_completion( 'tags': [], 'timestamp': int(time.time() * 1000), }, + variables=chat_variables, folder_id=metadata.get('folder_id'), ), ) @@ -1385,6 +1397,8 @@ async def chat_completion( updated['models'] = selected_chat_models await Chats.update_chat_by_id(chat_id, updated) + await Chats.update_chat_variables_by_id(chat_id, chat_variables) + # Save user message to DB if user_message and user_message.get('id'): await Chats.upsert_message_to_chat_by_id_and_message_id( diff --git a/backend/open_webui/routers/models.py b/backend/open_webui/routers/models.py index 517d3e76e3..8af1afbdce 100644 --- a/backend/open_webui/routers/models.py +++ b/backend/open_webui/routers/models.py @@ -40,6 +40,7 @@ from open_webui.models.models import ( from open_webui.utils.access_control import filter_allowed_access_grants, has_permission from open_webui.utils.access_control.files import has_access_to_file from open_webui.utils.auth import get_admin_user, get_verified_user +from open_webui.utils.chat_variables import get_chat_variables_schema from pydantic import BaseModel from sqlalchemy.ext.asyncio import AsyncSession @@ -48,6 +49,14 @@ log = logging.getLogger(__name__) router = APIRouter() +def add_chat_variables_schema(model_dict: dict) -> dict: + system = ((model_dict.get('params') or {}).get('system') if isinstance(model_dict.get('params'), dict) else None) + schema = get_chat_variables_schema(system) + if schema: + model_dict.setdefault('meta', {})['chat_variables_schema'] = schema + return model_dict + + def _safe_static_redirect_path(url: str) -> str | None: """ If url is a same-origin static asset path, return a normalized path safe for @@ -177,7 +186,7 @@ async def get_models( # Strip profile_image_url from meta — images are served via /model/profile/image. items = [] for model in result.items: - data = model.model_dump() + data = add_chat_variables_schema(model.model_dump()) if data.get('meta'): data['meta'].pop('profile_image_url', None) items.append( @@ -527,6 +536,7 @@ async def get_model_by_id(id: str, user=Depends(get_verified_user), db: AsyncSes db=db, ): model_dict = model.model_dump() + model_dict = add_chat_variables_schema(model_dict) # Strip params (system prompt and other admin-curated config) # for read-only callers — matches the params strip already # enforced on /api/models in utils/models.py. Owners, admins diff --git a/backend/open_webui/utils/chat_variables.py b/backend/open_webui/utils/chat_variables.py new file mode 100644 index 0000000000..f639ed845a --- /dev/null +++ b/backend/open_webui/utils/chat_variables.py @@ -0,0 +1,242 @@ +from __future__ import annotations + +import json +import re +from typing import Any + + +CHAT_VARIABLE_KEY_RE = re.compile(r'^[a-z][a-z0-9_]*$') +CHAT_VARIABLE_ANY_RE = re.compile(r'{{\s*chat\.variables\.([^\s|}]+)(?:\s*\|\s*([^}]*))?\s*}}') +MAX_VARIABLE_VALUE_LENGTH = 20_000 +MAX_VARIABLES_JSON_LENGTH = 100_000 + + +class ChatVariablesError(ValueError): + pass + + +def split_properties(value: str, delimiter: str) -> list[str]: + result: list[str] = [] + current = '' + depth = 0 + in_string = False + escape_next = False + + for char in value: + if escape_next: + current += char + escape_next = False + continue + + if char == '\\': + current += char + escape_next = True + continue + + if char == '"' and not escape_next: + in_string = not in_string + current += char + continue + + if not in_string: + if char in ('{', '['): + depth += 1 + elif char in ('}', ']'): + depth -= 1 + + if char == delimiter and depth == 0: + result.append(current.strip()) + current = '' + continue + + current += char + + if current.strip(): + result.append(current.strip()) + + return result + + +def parse_json_value(value: str) -> Any: + if value.startswith('"') and value.endswith('"'): + return value[1:-1] + + if re.match(r'^[\[{]', value): + try: + return json.loads(value) + except json.JSONDecodeError: + return value + + return value + + +def parse_variable_definition(definition: str) -> dict[str, Any]: + parts = split_properties(definition, ':') + if not parts: + return {'type': 'text'} + + first_part, *property_parts = parts + field_type = first_part[5:] if first_part.startswith('type=') else first_part + field_type = field_type.strip() or 'text' + properties: dict[str, Any] = {} + + for part in property_parts: + trimmed = part.strip() + if not trimmed: + continue + + equals_parts = split_properties(trimmed, '=') + if len(equals_parts) == 1: + properties[equals_parts[0].strip()] = True + continue + + property_name, *value_parts = equals_parts + properties[property_name.strip()] = parse_json_value('='.join(value_parts).strip()) + + return {'type': field_type, **properties} + + +def _safe_field(key: str, definition: dict[str, Any]) -> dict[str, Any]: + allowed_keys = { + 'default', + 'label', + 'max', + 'maxlength', + 'min', + 'minlength', + 'options', + 'placeholder', + 'required', + 'step', + 'type', + } + field = {'key': key} + for field_key in allowed_keys: + if field_key in definition: + field[field_key] = definition[field_key] + + field.setdefault('type', 'text') + if field.get('type') == 'select' and not isinstance(field.get('options'), list): + field['options'] = [] + field['required'] = bool(field.get('required', False)) + + return field + + +def get_chat_variables_schema(system_prompt: str | None) -> dict[str, list[dict[str, Any]]] | None: + if not system_prompt: + return None + + try: + fields_by_key = collect_chat_variable_fields(system_prompt) + except ChatVariablesError: + fields_by_key = {} + + if not fields_by_key: + return None + + return {'fields': list(fields_by_key.values())} + + +def collect_chat_variable_fields(system_prompt: str | None) -> dict[str, dict[str, Any]]: + fields_by_key: dict[str, dict[str, Any]] = {} + if not system_prompt: + return fields_by_key + + typed_fields_by_key: dict[str, dict[str, Any]] = {} + for match in CHAT_VARIABLE_ANY_RE.finditer(system_prompt): + key = match.group(1).strip() + definition = match.group(2) + if not CHAT_VARIABLE_KEY_RE.match(key): + raise ChatVariablesError(f'Invalid chat variable key: {key}') + + if definition is None or not definition.strip(): + fields_by_key.setdefault(key, _safe_field(key, {'type': 'text'})) + continue + + field = _safe_field(key, parse_variable_definition(definition.strip())) + + if field.get('type') == 'select' and not field.get('options'): + raise ChatVariablesError(f'Chat variable {key} select needs options.') + previous = typed_fields_by_key.get(key) + if previous and previous != field: + raise ChatVariablesError(f'Chat variable {key} has conflicting definitions.') + typed_fields_by_key[key] = field + fields_by_key[key] = field + + return fields_by_key + + +def normalize_chat_variables(variables: Any) -> dict[str, Any]: + if not isinstance(variables, dict): + return {} + return variables + + +def validate_chat_variables( + system_prompt: str | None, + variables: Any, + *, + required: bool = True, +) -> dict[str, Any]: + field_map = collect_chat_variable_fields(system_prompt) + variables = normalize_chat_variables(variables) + + try: + if len(json.dumps(variables)) > MAX_VARIABLES_JSON_LENGTH: + raise ChatVariablesError('Chat variables are too large.') + except TypeError: + raise ChatVariablesError('Chat variables must be JSON serializable.') + + validated: dict[str, Any] = {} + for key, field in field_map.items(): + has_value = key in variables and variables[key] not in (None, '') + value = variables.get(key) + + if not has_value: + if field.get('default') not in (None, ''): + value = field.get('default') + has_value = True + elif required and field.get('required'): + label = field.get('label') or key + raise ChatVariablesError(f'Missing required chat variable: {label}') + else: + value = '' + + if field.get('type') == 'select': + options = field.get('options') or [] + if has_value and value not in options: + label = field.get('label') or key + raise ChatVariablesError(f'Invalid value for chat variable: {label}') + + if isinstance(value, str): + value = value.replace('\r\n', '\n') + if len(value) > MAX_VARIABLE_VALUE_LENGTH: + label = field.get('label') or key + raise ChatVariablesError(f'Chat variable is too long: {label}') + + validated[key] = value + + return validated + + +def render_chat_variables( + system_prompt: str | None, + variables: Any, + *, + required: bool = True, +) -> str | None: + if not system_prompt: + return system_prompt + + try: + validated = validate_chat_variables(system_prompt, variables, required=required) + except ChatVariablesError: + validated = {} + + def replace(match: re.Match) -> str: + key = match.group(1).strip() + value = validated.get(key, '') + return '' if value is None else str(value) + + return CHAT_VARIABLE_ANY_RE.sub(replace, system_prompt) diff --git a/backend/open_webui/utils/models.py b/backend/open_webui/utils/models.py index 6b5de1daae..ddc59180fd 100644 --- a/backend/open_webui/utils/models.py +++ b/backend/open_webui/utils/models.py @@ -16,6 +16,7 @@ from open_webui.models.config import Config from open_webui.models.functions import Functions from open_webui.models.groups import Groups from open_webui.models.models import Models +from open_webui.utils.chat_variables import get_chat_variables_schema from open_webui.models.users import UserModel from open_webui.routers import ollama, openai from open_webui.socket.utils import RedisDict @@ -162,6 +163,9 @@ async def get_all_models(request, refresh: bool = False, user: UserModel = None) if custom_model.is_active: model['name'] = custom_model.name model['info'] = custom_model.model_dump() + schema = get_chat_variables_schema(custom_model.params.model_dump().get('system')) + if schema: + model['info'].setdefault('meta', {})['chat_variables_schema'] = schema action_ids = [] filter_ids = [] @@ -211,6 +215,9 @@ async def get_all_models(request, refresh: bool = False, user: UserModel = None) } info = custom_model.model_dump() + schema = get_chat_variables_schema(custom_model.params.model_dump().get('system')) + if schema: + info.setdefault('meta', {})['chat_variables_schema'] = schema if 'params' in info: # Remove params to avoid exposing sensitive info del info['params'] diff --git a/backend/open_webui/utils/payload.py b/backend/open_webui/utils/payload.py index d6242261db..d0618a31cd 100644 --- a/backend/open_webui/utils/payload.py +++ b/backend/open_webui/utils/payload.py @@ -7,6 +7,7 @@ from open_webui.utils.misc import ( deep_update, replace_system_message_content, ) +from open_webui.utils.chat_variables import render_chat_variables from open_webui.utils.task import prompt_template, prompt_variables_template @@ -18,6 +19,13 @@ async def resolve_system_prompt( if not system: return '' + if metadata: + system = render_chat_variables( + system, + metadata.get('chat_variables', {}), + required=False, + ) + # Metadata (WebUI Usage) if metadata: variables = metadata.get('variables', {}) diff --git a/src/lib/apis/chats/index.ts b/src/lib/apis/chats/index.ts index 64d2960dbf..0804a0f502 100644 --- a/src/lib/apis/chats/index.ts +++ b/src/lib/apis/chats/index.ts @@ -58,7 +58,12 @@ export const updateChatConfig = async (token: string, config: object) => { return res; }; -export const createNewChat = async (token: string, chat: object, folderId: string | null) => { +export const createNewChat = async ( + token: string, + chat: object, + folderId: string | null, + variables: object | null = null +) => { let error = null; const res = await fetch(`${WEBUI_API_BASE_URL}/chats/new`, { @@ -70,6 +75,7 @@ export const createNewChat = async (token: string, chat: object, folderId: strin }, body: JSON.stringify({ chat: chat, + ...(variables !== null ? { variables } : {}), folder_id: folderId ?? null }) }) @@ -1197,7 +1203,12 @@ export const getChatAccessGrants = async (token: string, id: string) => { return res; }; -export const updateChatById = async (token: string, id: string, chat: object) => { +export const updateChatById = async ( + token: string, + id: string, + chat: object, + variables: object | null = null +) => { let error = null; const res = await fetch(`${WEBUI_API_BASE_URL}/chats/${id}`, { @@ -1208,7 +1219,8 @@ export const updateChatById = async (token: string, id: string, chat: object) => ...(token && { authorization: `Bearer ${token}` }) }, body: JSON.stringify({ - chat: chat + chat: chat, + ...(variables !== null ? { variables } : {}) }) }) .then(async (res) => { diff --git a/src/lib/apis/index.ts b/src/lib/apis/index.ts index 70bd660e9a..4f55ab14be 100644 --- a/src/lib/apis/index.ts +++ b/src/lib/apis/index.ts @@ -163,7 +163,14 @@ export const getModels = async ( // Remove duplicates const modelsMap = {}; for (const model of models) { - modelsMap[model.id] = model; + const existing = modelsMap[model.id]; + modelsMap[model.id] = existing + ? { + ...existing, + ...model, + info: existing.info ?? model.info + } + : model; } models = Object.values(modelsMap); diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte index d47a91697a..38048e4cf5 100644 --- a/src/lib/components/chat/Chat.svelte +++ b/src/lib/components/chat/Chat.svelte @@ -111,12 +111,14 @@ import FilesOverlay from './MessageInput/FilesOverlay.svelte'; import NotificationToast from '../NotificationToast.svelte'; import Spinner from '../common/Spinner.svelte'; + import Modal from '../common/Modal.svelte'; import { isEmbedWindow } from '../common/FullHeightIframe.svelte'; import Tooltip from '../common/Tooltip.svelte'; import Sidebar from '../icons/Sidebar.svelte'; import Image from '../common/Image.svelte'; import XMark from '../icons/XMark.svelte'; import EmbeddedChatHistoryDropdown from './EmbeddedChatHistoryDropdown.svelte'; + import InputVariablesModal from './MessageInput/InputVariablesModal.svelte'; export let chatIdProp = ''; export let embedded = false; @@ -367,9 +369,112 @@ let chatFiles = []; let files = []; let params = {}; + let chatVariables = {}; + let showChatVariablesModal = false; let loadedChatIdProp = ''; let currentDraftKey = ''; + const mergeChatVariableSchemas = (modelIds = []) => { + const byKey: Record = {}; + const conflicts: any[] = []; + + for (const modelId of modelIds.filter(Boolean)) { + const fields = + $models.find((model) => model.id === modelId)?.info?.meta?.chat_variables_schema?.fields ?? + []; + for (const rawField of fields) { + const field = { + ...rawField, + type: rawField?.type ?? 'text', + required: Boolean(rawField?.required) + }; + if (!field?.key) continue; + const { required, ...shape } = field; + + const existing = byKey[field.key]; + if (!existing) { + byKey[field.key] = { + field, + modelIds: [modelId], + shape + }; + continue; + } + + if (!equal(existing.shape, shape)) { + conflicts.push({ + key: field.key, + modelIds: [...existing.modelIds, modelId] + }); + continue; + } + + existing.field = { + ...existing.field, + required: existing.field.required || field.required + }; + existing.modelIds.push(modelId); + } + } + + return { + fields: Object.values(byKey).map((item: any) => item.field), + conflicts + }; + }; + + const hasValue = (value) => value !== undefined && value !== null && value !== ''; + + const getChatVariablesForm = () => { + const { fields, conflicts } = mergeChatVariableSchemas(selectedModelIds); + const empty = + fields.length > 0 && + fields.every((field) => !hasValue(chatVariables?.[field.key]) && !hasValue(field.default)); + const missing = fields.some( + (field) => field.required && !hasValue(chatVariables?.[field.key]) && !hasValue(field.default) + ); + const variables = fields.reduce( + (acc, field) => { + const { key, ...inputField } = field; + acc[key] = { + ...inputField, + default: hasValue(chatVariables?.[key]) ? chatVariables[key] : inputField.default + }; + return acc; + }, + {} as Record + ); + + return { conflicts, empty, missing, variables }; + }; + + const saveChatVariables = async (values) => { + chatVariables = { ...chatVariables, ...values }; + + if ($chatId && !$temporaryChatEnabled && !$chatId.startsWith('local:')) { + const res = await updateChatById(localStorage.token, $chatId, {}, chatVariables).catch( + (err) => { + console.error('[chat variables save]', err); + toast.error($i18n.t('Failed to save chat variables')); + return null; + } + ); + if (res) chat = res; + } + }; + + $: chatVariablesForm = getChatVariablesForm(); + + let oldSelectedModelIds = ['']; + $: if (!equal(selectedModelIds, oldSelectedModelIds)) { + onSelectedModelIdsChange(); + } + + const onSelectedModelIdsChange = () => { + resetInput(); + oldSelectedModelIds = structuredClone(selectedModelIds); + }; + const mergeFiles = (current, incoming) => { const seen = new Set(); return [...(incoming ?? []), ...(current ?? [])].filter((file) => { @@ -514,6 +619,7 @@ currentId: null }; params = {}; + chatVariables = {}; chatFiles = []; files = []; selectedToolIds = []; @@ -585,16 +691,6 @@ initiateOAuthRedirect(nextTool); }; - let oldSelectedModelIds = ['']; - $: if (!equal(selectedModelIds, oldSelectedModelIds)) { - onSelectedModelIdsChange(); - } - - const onSelectedModelIdsChange = () => { - resetInput(); - oldSelectedModelIds = structuredClone(selectedModelIds); - }; - const resetInput = async () => { selectedToolIds = []; selectedSkillIds = []; @@ -1703,6 +1799,7 @@ chatFiles = []; params = {}; + chatVariables = {}; taskIds = null; chatTasks = []; @@ -1854,6 +1951,7 @@ noteChatDebug('getTagsById completed', { tagCount: tags?.length ?? 0 }); const chatContent = chat.chat; + chatVariables = chat?.variables ?? {}; if (chatContent) { noteChatDebug('chat payload found', { @@ -2552,6 +2650,15 @@ toast.error($i18n.t('Model not selected')); return; } + if (chatVariablesForm.conflicts.length > 0) { + showChatVariablesModal = true; + toast.error($i18n.t('Chat Variables have conflicting model definitions')); + return; + } + if (chatVariablesForm.missing || chatVariablesForm.empty) { + showChatVariablesModal = true; + return; + } if ( files.length > 0 && @@ -2968,6 +3075,8 @@ // Only send terminal_id if the model has terminal capability enabled const terminalEnabled = model.info?.meta?.capabilities?.terminal ?? true; + const useChatVariablesFallback = + !_chatId || $temporaryChatEnabled || _chatId.startsWith('local:'); const res = await generateOpenAIChatCompletion( localStorage.token, @@ -3002,6 +3111,7 @@ $user?.email ) }, + ...(useChatVariablesFallback ? { chat_variables: chatVariables } : {}), model_item: $models.find((m) => m.id === model.id), session_id: $socket?.id, @@ -3352,7 +3462,8 @@ tags: [], timestamp: Date.now() }, - $selectedFolder?.id + $selectedFolder?.id, + chatVariables ); _chatId = chat.id; @@ -3515,6 +3626,47 @@ +{#if chatVariablesForm.conflicts.length > 0} + +
+
+
{$i18n.t('Chat Variables')}
+ +
+ +
+
+ {$i18n.t('Selected models define incompatible Chat Variables.')} +
+
+ {#each chatVariablesForm.conflicts as conflict} +
+
{conflict.key}
+
+ {conflict.modelIds.join(', ')} +
+
+ {/each} +
+
+
+
+{:else} + +{/if} + { + showChatVariablesModal = true; + }} on:submit={async (e) => { clearDraft($chatId); if (e.detail || files.length > 0) { @@ -3895,6 +4051,9 @@ messageQueue={$chatRequestQueues[$chatId] ?? []} {chatTasks} onWebSearchToggle={handleWebSearchToggle} + on:chatVariables={() => { + showChatVariablesModal = true; + }} on:submit={async (e) => { clearDraft($chatId); if (e.detail || files.length > 0) { @@ -3930,6 +4089,9 @@ {onSelect} {onUpload} onWebSearchToggle={handleWebSearchToggle} + on:chatVariables={() => { + showChatVariablesModal = true; + }} onChange={(data) => { if (!$temporaryChatEnabled) { saveDraft(data); diff --git a/src/lib/components/chat/MessageInput.svelte b/src/lib/components/chat/MessageInput.svelte index 97e01a8034..86f089c9ff 100644 --- a/src/lib/components/chat/MessageInput.svelte +++ b/src/lib/components/chat/MessageInput.svelte @@ -132,6 +132,11 @@ let selectedModelIds = []; $: selectedModelIds = atSelectedModel !== undefined ? [atSelectedModel.id] : selectedModels; + $: hasChatVariables = selectedModelIds.some( + (modelId) => + ($models.find((model) => model.id === modelId)?.info?.meta?.chat_variables_schema?.fields + ?.length ?? 0) > 0 + ); export let history; export let taskIds = null; @@ -2236,6 +2241,22 @@ /> + {#if hasChatVariables} + + + + {/if} + {#if isActive && prompt === '' && files.length === 0}
diff --git a/src/lib/components/chat/Placeholder.svelte b/src/lib/components/chat/Placeholder.svelte index fcebb6ad7a..eb7a9ef596 100644 --- a/src/lib/components/chat/Placeholder.svelte +++ b/src/lib/components/chat/Placeholder.svelte @@ -237,6 +237,7 @@ {onChange} {onUpload} {onWebSearchToggle} + on:chatVariables on:submit={(e) => { dispatch('submit', e.detail); }} diff --git a/src/lib/components/workspace/Models/ModelEditor.svelte b/src/lib/components/workspace/Models/ModelEditor.svelte index 537abdb23b..de4e7e9af1 100644 --- a/src/lib/components/workspace/Models/ModelEditor.svelte +++ b/src/lib/components/workspace/Models/ModelEditor.svelte @@ -33,6 +33,7 @@ import TTSVoiceInput from './TTSVoiceInput.svelte'; import AccessControlModal from '../common/AccessControlModal.svelte'; import AccessButton from '$lib/components/common/AccessButton.svelte'; + import { extractInputVariables } from '$lib/utils'; const i18n = getContext('i18n'); @@ -113,6 +114,46 @@ export let suggestionTags: { name: string }[] = []; let voices: { id: string; name?: string }[] = []; + const chatVariableKeyRegex = /^[a-z][a-z0-9_]*$/; + const getChatVariablesPreview = (prompt: string) => { + const variables = extractInputVariables(prompt); + const warnings: string[] = []; + const seenDefinitions: Record = {}; + const typedRegex = /{{\s*chat\.variables\.([a-zA-Z0-9_.-]+)\s*\|\s*([^}]*)\s*}}/g; + + for (const match of prompt.matchAll(typedRegex)) { + const key = match[1]; + const definition = match[2].trim(); + if (seenDefinitions[key] && seenDefinitions[key] !== definition) { + warnings.push(`${key} has conflicting duplicate definitions`); + } + seenDefinitions[key] = definition; + } + + const fields = Object.entries(variables) + .filter(([name]) => name.startsWith('chat.variables.')) + .map(([name, field]) => ({ key: name.replace('chat.variables.', ''), ...(field as any) })); + + for (const field of fields) { + const key = field.key; + if (!chatVariableKeyRegex.test(key)) { + warnings.push(`${key} must be lowercase snake case`); + continue; + } + + if ( + field.type === 'select' && + (!Array.isArray(field.options) || field.options.length === 0) + ) { + warnings.push(`${key} select needs options=[...]`); + } + } + + return { fields, warnings }; + }; + + $: chatVariablesPreview = getChatVariablesPreview(system ?? ''); + const getBaseModelItems = (models: any[] = []) => { const currentModelId = (model as any)?.id; @@ -721,6 +762,44 @@ bind:value={system} />
+ {#if chatVariablesPreview.fields.length > 0 || chatVariablesPreview.warnings.length > 0} +
+
+
+ {$i18n.t('Detected Chat Variables')} +
+ {#if chatVariablesPreview.fields.length > 0} +
+ {chatVariablesPreview.fields.length} +
+ {/if} +
+ + {#if chatVariablesPreview.fields.length > 0} +
+ {#each chatVariablesPreview.fields as field} +
+ {field.key} + {field.type} + {#if field.required} + required + {/if} +
+ {/each} +
+ {/if} + + {#if chatVariablesPreview.warnings.length > 0} +
+ {#each chatVariablesPreview.warnings as warning} +
{warning}
+ {/each} +
+ {/if} +
+ {/if}