Skip to content
Merged
Show file tree
Hide file tree
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
48 changes: 39 additions & 9 deletions core/http/static/chat.js
Original file line number Diff line number Diff line change
Expand Up @@ -2473,23 +2473,53 @@ document.addEventListener('DOMContentLoaded', function() {
localStorage.removeItem(SYSTEM_PROMPT_STORAGE_KEY);
}
} else {
// Existing chats loaded - check URL parameter for MCP mode
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get('mcp') === 'true') {
const activeChat = chatStore.activeChat();
if (activeChat) {
activeChat.mcpMode = true;
saveChatsToStorage();
// Existing chats loaded - check if we need to create a new chat for the model in URL
const urlModel = document.getElementById("chat-model")?.value || "";
const activeChat = chatStore.activeChat();
const shouldCreateNewChat = sessionStorage.getItem('localai_create_new_chat') === 'true';

// Clear the flag after reading it
if (shouldCreateNewChat) {
sessionStorage.removeItem('localai_create_new_chat');
}

// If we should create a new chat (from manage.html) or URL model doesn't match active chat, create new chat
// This handles navigation from manage.html or direct links to /chat/MODEL_NAME
if (urlModel && urlModel.trim() && (shouldCreateNewChat || (activeChat && activeChat.model !== urlModel) || !activeChat)) {
// Create a new chat with the model from URL
const urlParams = new URLSearchParams(window.location.search);
const mcpFromUrl = urlParams.get('mcp') === 'true';
const newChat = chatStore.createChat(urlModel, "", mcpFromUrl);

// Update context size from template if available
const contextSizeInput = document.getElementById("chat-model");
if (contextSizeInput && contextSizeInput.dataset.contextSize) {
const contextSize = parseInt(contextSizeInput.dataset.contextSize);
if (!isNaN(contextSize)) {
newChat.contextSize = contextSize;
}
}

saveChatsToStorage();
updateUIForActiveChat();
} else {
// Check URL parameter for MCP mode (update existing active chat)
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get('mcp') === 'true') {
if (activeChat) {
activeChat.mcpMode = true;
saveChatsToStorage();
}
}
}
}

// Update context size from template if available
// Update context size from template if available (for existing active chat)
const contextSizeInput = document.getElementById("chat-model");
if (contextSizeInput && contextSizeInput.dataset.contextSize) {
const contextSize = parseInt(contextSizeInput.dataset.contextSize);
const activeChat = chatStore.activeChat();
if (activeChat) {
if (activeChat && !activeChat.contextSize) {
activeChat.contextSize = contextSize;
}
}
Expand Down
139 changes: 101 additions & 38 deletions core/http/views/chat.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@
__chatContextSize = {{ .ContextSize }};
{{ end }}

// Store gallery configs for header icon display
window.__galleryConfigs = {};
{{ $allGalleryConfigs:=.GalleryConfig }}
{{ range $modelName, $galleryConfig := $allGalleryConfigs }}
window.__galleryConfigs["{{$modelName}}"] = {};
{{ if $galleryConfig.Icon }}
window.__galleryConfigs["{{$modelName}}"].Icon = "{{$galleryConfig.Icon}}";
{{ end }}
{{ end }}

// Function to initialize store
function __initChatStore() {
if (!window.Alpine) return;
Expand Down Expand Up @@ -501,6 +511,21 @@
}
}

// Update model selector to reflect the change (ensure it stays in sync)
const modelSelector = document.getElementById('modelSelector');
if (modelSelector) {
// Find and select the option matching the model
const optionValue = 'chat/' + modelName;
for (let i = 0; i < modelSelector.options.length; i++) {
if (modelSelector.options[i].value === optionValue) {
modelSelector.selectedIndex = i;
break;
}
}
// Trigger Alpine reactivity by dispatching change event
modelSelector.dispatchEvent(new Event('change', { bubbles: true }));
}

// Trigger MCP availability check in Alpine component
// The MCP toggle component will reactively check the data-has-mcp attribute

Expand All @@ -513,14 +538,6 @@
if (typeof updateUIForActiveChat === 'function') {
updateUIForActiveChat();
}

// Trigger MCP availability check in Alpine component
// Dispatch a custom event that the MCP toggle component can listen to
const modelSelector = document.getElementById('modelSelector');
if (modelSelector) {
// Trigger Alpine reactivity by dispatching change event
modelSelector.dispatchEvent(new Event('change', { bubbles: true }));
}
}
</script>
<script defer src="static/chat.js"></script>
Expand Down Expand Up @@ -559,29 +576,31 @@ <h2 class="text-sm font-semibold text-[var(--color-text-primary)]">Settings</h2>
<div class="p-3 space-y-3">
<!-- Model selection - Compact -->
<div class="space-y-1.5">
<div class="flex items-center justify-between">
<label class="text-xs font-medium text-[var(--color-text-secondary)] uppercase tracking-wide">Model</label>
{{ if $model }}
{{ $galleryConfig:= index $allGalleryConfigs $model}}
{{ if $galleryConfig }}
<button
data-twe-ripple-init
data-twe-ripple-color="light"
class="text-[var(--color-text-secondary)] hover:text-[var(--color-primary)] transition-colors text-xs p-1"
data-modal-target="model-info-modal"
data-modal-toggle="model-info-modal"
title="Model Information">
<i class="fas fa-info-circle"></i>
</button>
{{ end }}
{{ end }}
{{ if $model }}
<a href="/models/edit/{{$model}}"
class="text-[var(--color-text-secondary)] hover:text-[var(--color-warning)] transition-colors text-xs p-1"
title="Edit Model Configuration">
<i class="fas fa-edit"></i>
</a>
{{ end }}
<div class="flex items-center justify-between gap-2">
<label class="text-xs font-medium text-[var(--color-text-secondary)] uppercase tracking-wide flex-shrink-0">Model</label>
<div class="flex items-center gap-1 flex-shrink-0">
{{ if $model }}
{{ $galleryConfig:= index $allGalleryConfigs $model}}
{{ if $galleryConfig }}
<button
data-twe-ripple-init
data-twe-ripple-color="light"
class="text-[var(--color-text-secondary)] hover:text-[var(--color-primary)] transition-colors text-xs p-1 rounded hover:bg-[var(--color-bg-primary)]"
data-modal-target="model-info-modal"
data-modal-toggle="model-info-modal"
title="Model Information">
<i class="fas fa-info-circle"></i>
</button>
{{ end }}
{{ end }}
{{ if $model }}
<a href="/models/edit/{{$model}}"
class="text-[var(--color-text-secondary)] hover:text-[var(--color-warning)] transition-colors text-xs p-1 rounded hover:bg-[var(--color-bg-primary)]"
title="Edit Model Configuration">
<i class="fas fa-edit"></i>
</a>
{{ end }}
</div>
</div>
<select
id="modelSelector"
Expand Down Expand Up @@ -1030,14 +1049,22 @@ <h2 class="text-xs font-semibold text-[var(--color-text-secondary)] uppercase tr

<div class="flex items-center">
<i class="fa-solid fa-comments mr-2 text-[var(--color-primary)]"></i>
{{ if $model }}
{{ $galleryConfig:= index $allGalleryConfigs $model}}
{{ if $galleryConfig }}
{{ if $galleryConfig.Icon }}<img src="{{$galleryConfig.Icon}}" class="rounded-lg w-8 h-8 mr-2">{{end}}
{{ end }}
{{ end }}
<!-- Model icon - reactive to active chat -->
<template x-if="$store.chat.activeChat() && $store.chat.activeChat().model && window.__galleryConfigs && window.__galleryConfigs[$store.chat.activeChat().model] && window.__galleryConfigs[$store.chat.activeChat().model].Icon">
<img :src="window.__galleryConfigs[$store.chat.activeChat().model].Icon" class="rounded-lg w-8 h-8 mr-2">
</template>
<!-- Fallback icon for initial model from server (when no active chat yet) -->
<template x-if="(!$store.chat.activeChat() || !$store.chat.activeChat().model) && window.__galleryConfigs && window.__galleryConfigs['{{$model}}'] && window.__galleryConfigs['{{$model}}'].Icon">
<img :src="window.__galleryConfigs['{{$model}}'].Icon" class="rounded-lg w-8 h-8 mr-2">
</template>
<h1 class="text-lg font-semibold text-[var(--color-text-primary)]">
Chat {{ if .Model }} with {{.Model}} {{ end }}
Chat
<template x-if="$store.chat.activeChat() && $store.chat.activeChat().model">
<span x-text="' with ' + $store.chat.activeChat().model"></span>
</template>
<template x-if="!$store.chat.activeChat() || !$store.chat.activeChat().model">
{{ if .Model }}<span> with {{.Model}}</span>{{ end }}
</template>
</h1>
<!-- Loading indicator next to model name -->
<div id="header-loading-indicator" class="ml-3 text-[var(--color-primary)]" style="display: none;">
Expand Down Expand Up @@ -1698,6 +1725,42 @@ <h3 class="text-xl font-semibold text-gray-900 dark:text-white">{{ $model }}</h3
} else {
initMarkdownProcessing();
}

// Sync model selector with initial model from server on page load
// This ensures the selector is correct when navigating from manage.html or index.html
function syncModelSelectorOnLoad() {
const modelInput = document.getElementById("chat-model");
const modelSelector = document.getElementById("modelSelector");

if (modelInput && modelSelector && modelInput.value) {
const modelName = modelInput.value;
const optionValue = 'chat/' + modelName;

// Find and select the option matching the model
for (let i = 0; i < modelSelector.options.length; i++) {
if (modelSelector.options[i].value === optionValue) {
modelSelector.selectedIndex = i;
break;
}
}
}
}

// Run sync after DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', syncModelSelectorOnLoad);
} else {
syncModelSelectorOnLoad();
}

// Also sync after Alpine initializes (in case it runs after DOMContentLoaded)
if (window.Alpine) {
Alpine.nextTick(syncModelSelectorOnLoad);
} else {
document.addEventListener('alpine:init', () => {
Alpine.nextTick(syncModelSelectorOnLoad);
});
}
</script>

<style>
Expand Down
2 changes: 1 addition & 1 deletion core/http/views/manage.html
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ <h2 class="h3 mb-1 flex items-center">
<div class="flex flex-wrap gap-1">
{{ range .KnownUsecaseStrings }}
{{ if eq . "FLAG_CHAT" }}
<a href="chat/{{$backendCfg.Name}}" class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-[var(--color-primary)]/10 text-[var(--color-primary)] hover:bg-[var(--color-primary)]/20 transition-colors" title="Chat">
<a href="chat/{{$backendCfg.Name}}" onclick="sessionStorage.setItem('localai_create_new_chat', 'true');" class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-[var(--color-primary)]/10 text-[var(--color-primary)] hover:bg-[var(--color-primary)]/20 transition-colors" title="Chat">
<i class="fas fa-comment-alt text-[8px] mr-1"></i>Chat
</a>
{{ end }}
Expand Down
Loading