From fdb839d2098d0d96c10583b6a1549b1140eb5819 Mon Sep 17 00:00:00 2001 From: Theinruj Toranavikrai Date: Wed, 13 May 2026 07:26:44 +0700 Subject: [PATCH] feat(chat): add temperature control, persist and ui --- omlx/admin/templates/chat.html | 147 +++++++++++++++++++++++++++++---- 1 file changed, 133 insertions(+), 14 deletions(-) diff --git a/omlx/admin/templates/chat.html b/omlx/admin/templates/chat.html index 785ea81f3..4d561a14b 100644 --- a/omlx/admin/templates/chat.html +++ b/omlx/admin/templates/chat.html @@ -843,13 +843,43 @@

{{ t('login.login.label_api_key') }}

- +
+
+ +
+
+ +
+
+ + +
+
+
+ + +
@@ -953,10 +983,16 @@

{{ t('chat.welcome_heading') }}

-
+ + + +
@@ -1279,6 +1315,7 @@

// Model State availableModels: [], currentModel: null, + modelTemperatureMap: {}, // Streaming State isStreaming: false, @@ -1334,6 +1371,13 @@

systemPrompt: localStorage.getItem('omlx_chat_system_prompt') || '', showSystemPromptModal: false, systemPromptDraft: '', + temperature: (() => { + const stored = Number.parseFloat(localStorage.getItem('omlx_chat_temperature')); + if (Number.isNaN(stored)) { + return 1.0; + } + return Math.min(2, Math.max(0, Math.round(stored * 10) / 10)); + })(), // MCP Tool Call Limits MAX_TOOL_DEPTH: 10, // Max recursive streamResponse calls for tool loops TOOL_TIMEOUT_MS: 30000, // Per-tool execution timeout (ms) @@ -1634,12 +1678,15 @@

async loadModels() { try { // Fetch models and server health in parallel - const [modelsResponse, healthResponse] = await Promise.all([ + const [modelsResponse, adminModelsResponse, healthResponse] = await Promise.all([ fetch('/v1/models', { headers: { 'Authorization': `Bearer ${this.getApiKey()}` } }), + fetch('/admin/api/models', { + credentials: 'same-origin' + }), fetch('/health') ]); @@ -1658,6 +1705,18 @@

const modelsData = await modelsResponse.json(); const allModels = modelsData.data || []; + if (adminModelsResponse.ok) { + const adminModelsData = await adminModelsResponse.json(); + this.modelTemperatureMap = Object.fromEntries( + (adminModelsData.models || []).map(model => [ + model.id, + model.settings?.temperature, + ]) + ); + } else { + this.modelTemperatureMap = {}; + } + // Fetch model types first (needed for filtering and VLM detection) await this.fetchModelTypes(); @@ -1676,9 +1735,9 @@

// Auto-select default model if set, otherwise first model if (!this.currentModel && this.availableModels.length > 0) { if (defaultModel && this.availableModels.some(m => m.id === defaultModel)) { - this.currentModel = defaultModel; + this.selectModel(defaultModel, { persistChat: false }); } else { - this.currentModel = this.availableModels[0].id; + this.selectModel(this.availableModels[0].id, { persistChat: false }); } } } catch (error) { @@ -1686,8 +1745,48 @@

} }, - selectModel(modelId) { + getModelConfiguredTemperature(modelId) { + return this.modelTemperatureMap[modelId]; + }, + + applyModelTemperature(modelId) { + const configuredTemperature = this.getModelConfiguredTemperature(modelId); + if (configuredTemperature == null) { + return false; + } + this.temperature = this.normalizeTemperature(configuredTemperature); + localStorage.setItem('omlx_chat_temperature', String(this.temperature)); + return true; + }, + + selectModel(modelId, { persistChat = true } = {}) { this.currentModel = modelId; + this.applyModelTemperature(modelId); + if (persistChat) { + this.saveCurrentChat(); + } + }, + + normalizeTemperature(value) { + const parsed = Number.parseFloat(value); + if (Number.isNaN(parsed)) { + return 1.0; + } + return Math.min(2, Math.max(0, Math.round(parsed * 10) / 10)); + }, + + getStoredTemperature() { + return this.normalizeTemperature(localStorage.getItem('omlx_chat_temperature')); + }, + + formatTemperature(value) { + return this.normalizeTemperature(value).toFixed(1); + }, + + setTemperature(value) { + this.temperature = this.normalizeTemperature(value); + localStorage.setItem('omlx_chat_temperature', String(this.temperature)); + this.saveCurrentChat(); }, loadChatHistory() { @@ -1710,6 +1809,10 @@

this.messages = []; this.uploadImages = []; this.systemPrompt = localStorage.getItem('omlx_chat_system_prompt') || ''; + this.temperature = this.getStoredTemperature(); + if (this.currentModel) { + this.applyModelTemperature(this.currentModel); + } this.isMobile = window.innerWidth < 768; this.sidebarOpen = window.innerWidth >= 768; this.resetStreamRenderState(); @@ -1724,6 +1827,14 @@

if (chat.model) { this.currentModel = chat.model; } + const configuredTemperature = chat.model + ? this.getModelConfiguredTemperature(chat.model) + : null; + this.temperature = this.normalizeTemperature( + chat.temperature + ?? configuredTemperature + ?? localStorage.getItem('omlx_chat_temperature') + ); this.scheduleEnhanceMessages(); } this.isMobile = window.innerWidth < 768; @@ -1762,6 +1873,7 @@

messages: messagesForStorage, model: this.currentModel, systemPrompt: this.systemPrompt, + temperature: this.temperature, updatedAt: new Date().toISOString() }; @@ -1836,12 +1948,13 @@

await this.streamResponse(); }, - async streamResponse(depth = 0) { + async streamResponse(depth = 0, requestTemperature = null) { if (depth > this.MAX_TOOL_DEPTH) { this.messages.push({ role: 'assistant', content: `Error: Maximum tool call depth (${this.MAX_TOOL_DEPTH}) exceeded. The model may be stuck in a loop.`, model: this.currentModel, + temperature: requestTemperature ?? this.temperature, _perfVisible: false, }); this.saveCurrentChat(); @@ -1866,6 +1979,7 @@

this.isStreaming = true; this.streamingContent = ''; this.abortController = new AbortController(); + requestTemperature = requestTemperature ?? this.temperature; this.autoScrollEnabled = true; this.setEngineStatus({ text: 'Starting' }); this.startPrefillPolling(); @@ -1897,6 +2011,7 @@

body: JSON.stringify({ model: this.currentModel, messages: messagesForApi, + temperature: requestTemperature, stream: true, stream_options: { include_usage: true }, ...(this.mcpTools.length > 0 && { tools: this.mcpTools }) @@ -2044,6 +2159,7 @@

role: 'assistant', content: this.streamingContent || null, model: this.currentModel, + temperature: requestTemperature, tool_calls: toolCalls, _ui: false, }); @@ -2107,7 +2223,7 @@

this.resetStreamRenderState(); // Recurse — model synthesises final answer using tool results - await this.streamResponse(depth + 1); + await this.streamResponse(depth + 1, requestTemperature); return; } // --- end MCP tool call loop --- @@ -2119,6 +2235,7 @@

role: 'assistant', content: this.streamingContent || '', model: this.currentModel, + temperature: requestTemperature, _perfVisible: false, _thinking: this.streamingThinking || null, _thinkingOpen: false, @@ -2141,6 +2258,7 @@

role: 'assistant', content: this.streamingContent, model: this.currentModel, + temperature: requestTemperature, _perfVisible: false, _thinking: this.streamingThinking || null, _thinkingOpen: false, @@ -2154,6 +2272,7 @@

role: 'assistant', content: `Error: ${error.message}`, model: this.currentModel, + temperature: requestTemperature, _perfVisible: false, }); this.scheduleEnhanceMessages();