Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 133 additions & 14 deletions omlx/admin/templates/chat.html
Original file line number Diff line number Diff line change
Expand Up @@ -843,13 +843,43 @@ <h2 class="text-xl font-bold mb-2">{{ t('login.login.label_api_key') }}</h2>
</div>
</div>

<button @click="systemPromptDraft = systemPrompt; showSystemPromptModal = true"
class="p-2 hover:bg-surface-muted rounded-lg relative" style="color: var(--text-primary);"
:title="window.t('chat.system_prompt.title')">
<i data-lucide="settings" class="w-5 h-5"></i>
<span x-show="systemPrompt.trim()" x-cloak
class="absolute top-1 right-1 w-2 h-2 bg-accent rounded-full"></span>
</button>
<div class="flex items-center gap-2">
<div class="relative" x-data="{ open: false }" @keydown.escape.window="open = false">
<button @click="open = !open"
class="flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium hover:bg-surface-muted rounded-lg"
style="color: var(--text-primary);" :title="window.t('settings.generation.temperature')">
<i data-lucide="thermometer" class="w-4 h-4"></i>
<span x-text="formatTemperature(temperature)"></span>
</button>
<div x-show="open" x-cloak @click.away="open = false"
class="absolute top-full right-0 mt-2 w-64 p-3 border border-line rounded-xl shadow-lg z-50"
style="background: var(--bg-primary); border-color: var(--border-faint);">
<div class="mb-3">
<span class="text-xs font-medium" style="color: var(--text-secondary);"
x-text="window.t('settings.generation.temperature')"></span>
</div>
<div class="flex items-center gap-3">
<input type="range" min="0" max="2" step="0.1" :value="temperature"
@input="setTemperature($event.target.value)"
class="flex-1 h-1.5 rounded-full appearance-none cursor-pointer"
style="background: var(--bg-tertiary); accent-color: var(--text-primary);">
<input type="number" min="0" max="2" step="0.1" :value="formatTemperature(temperature)"
@input="setTemperature($event.target.value)"
@blur="$event.target.value = formatTemperature(temperature)"
class="w-20 shrink-0 box-border px-2 py-1.5 text-sm text-right rounded-lg border"
style="background: var(--bg-primary); border-color: var(--border-faint); color: var(--text-primary);">
</div>
</div>
</div>

<button @click="systemPromptDraft = systemPrompt; showSystemPromptModal = true"
class="p-2 hover:bg-surface-muted rounded-lg relative" style="color: var(--text-primary);"
:title="window.t('chat.system_prompt.title')">
<i data-lucide="settings" class="w-5 h-5"></i>
<span x-show="systemPrompt.trim()" x-cloak
class="absolute top-1 right-1 w-2 h-2 bg-accent rounded-full"></span>
</button>
</div>
</header>

<!-- Messages Area -->
Expand Down Expand Up @@ -953,10 +983,16 @@ <h1 class="text-2xl font-bold">{{ t('chat.welcome_heading') }}</h1>
<!-- Assistant Message -->
<div x-show="msg.role === 'assistant' && msg._ui !== false" class="assistant-message">
<div class="message-header">
<div class="flex items-center gap-2 text-sm font-medium"
<div class="flex items-center gap-2 text-sm font-medium flex-wrap"
style="color: var(--text-primary);">
<i data-lucide="cpu" class="w-4 h-4" style="color: var(--text-tertiary);"></i>
<span x-text="msg.model || currentModel || window.t('chat.assistant_label')"></span>
<span x-show="msg.temperature != null"
class="inline-flex items-center gap-1.5 text-xs font-medium px-2 py-0.5 rounded-full"
style="color: var(--text-secondary); background: var(--bg-secondary); border: 1px solid var(--border-faint);">
<i data-lucide="thermometer" class="w-3.5 h-3.5"></i>
<span x-text="formatTemperature(msg.temperature)"></span>
</span>
</div>
<div class="flex gap-1">
<!-- Timeline toggle: only visible when timeline data is attached -->
Expand Down Expand Up @@ -1279,6 +1315,7 @@ <h3 class="text-lg font-bold" style="color: var(--text-primary);">
// Model State
availableModels: [],
currentModel: null,
modelTemperatureMap: {},

// Streaming State
isStreaming: false,
Expand Down Expand Up @@ -1334,6 +1371,13 @@ <h3 class="text-lg font-bold" style="color: var(--text-primary);">
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)
Expand Down Expand Up @@ -1634,12 +1678,15 @@ <h3 class="text-lg font-bold" style="color: var(--text-primary);">
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')
]);

Expand All @@ -1658,6 +1705,18 @@ <h3 class="text-lg font-bold" style="color: var(--text-primary);">
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();

Expand All @@ -1676,18 +1735,58 @@ <h3 class="text-lg font-bold" style="color: var(--text-primary);">
// 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) {
console.error('Error loading models:', error);
}
},

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() {
Expand All @@ -1710,6 +1809,10 @@ <h3 class="text-lg font-bold" style="color: var(--text-primary);">
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();
Expand All @@ -1724,6 +1827,14 @@ <h3 class="text-lg font-bold" style="color: var(--text-primary);">
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;
Expand Down Expand Up @@ -1762,6 +1873,7 @@ <h3 class="text-lg font-bold" style="color: var(--text-primary);">
messages: messagesForStorage,
model: this.currentModel,
systemPrompt: this.systemPrompt,
temperature: this.temperature,
updatedAt: new Date().toISOString()
};

Expand Down Expand Up @@ -1836,12 +1948,13 @@ <h3 class="text-lg font-bold" style="color: var(--text-primary);">
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();
Expand All @@ -1866,6 +1979,7 @@ <h3 class="text-lg font-bold" style="color: var(--text-primary);">
this.isStreaming = true;
this.streamingContent = '';
this.abortController = new AbortController();
requestTemperature = requestTemperature ?? this.temperature;
this.autoScrollEnabled = true;
this.setEngineStatus({ text: 'Starting' });
this.startPrefillPolling();
Expand Down Expand Up @@ -1897,6 +2011,7 @@ <h3 class="text-lg font-bold" style="color: var(--text-primary);">
body: JSON.stringify({
model: this.currentModel,
messages: messagesForApi,
temperature: requestTemperature,
stream: true,
stream_options: { include_usage: true },
...(this.mcpTools.length > 0 && { tools: this.mcpTools })
Expand Down Expand Up @@ -2044,6 +2159,7 @@ <h3 class="text-lg font-bold" style="color: var(--text-primary);">
role: 'assistant',
content: this.streamingContent || null,
model: this.currentModel,
temperature: requestTemperature,
tool_calls: toolCalls,
_ui: false,
});
Expand Down Expand Up @@ -2107,7 +2223,7 @@ <h3 class="text-lg font-bold" style="color: var(--text-primary);">
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 ---
Expand All @@ -2119,6 +2235,7 @@ <h3 class="text-lg font-bold" style="color: var(--text-primary);">
role: 'assistant',
content: this.streamingContent || '',
model: this.currentModel,
temperature: requestTemperature,
_perfVisible: false,
_thinking: this.streamingThinking || null,
_thinkingOpen: false,
Expand All @@ -2141,6 +2258,7 @@ <h3 class="text-lg font-bold" style="color: var(--text-primary);">
role: 'assistant',
content: this.streamingContent,
model: this.currentModel,
temperature: requestTemperature,
_perfVisible: false,
_thinking: this.streamingThinking || null,
_thinkingOpen: false,
Expand All @@ -2154,6 +2272,7 @@ <h3 class="text-lg font-bold" style="color: var(--text-primary);">
role: 'assistant',
content: `Error: ${error.message}`,
model: this.currentModel,
temperature: requestTemperature,
_perfVisible: false,
});
this.scheduleEnhanceMessages();
Expand Down