mirror of
https://github.com/mudler/LocalAI.git
synced 2026-07-18 20:24:11 -04:00
fix(chat/ui): record model name in history for consistency (#7845)
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
committed by
GitHub
parent
4cd95b8a9d
commit
33cc0b8e13
@@ -750,6 +750,7 @@ function stopRequest() {
|
||||
if (!activeChat) return;
|
||||
|
||||
const request = activeRequests.get(activeChat.id);
|
||||
const requestModel = request?.model || null; // Get model before deleting request
|
||||
if (request) {
|
||||
if (request.controller) {
|
||||
request.controller.abort();
|
||||
@@ -779,7 +780,8 @@ function stopRequest() {
|
||||
`<span class='error'>Request cancelled by user</span>`,
|
||||
null,
|
||||
null,
|
||||
activeChat.id
|
||||
activeChat.id,
|
||||
requestModel
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1231,7 +1233,8 @@ async function promptGPT(systemPrompt, input) {
|
||||
startTime: requestStartTime,
|
||||
tokensReceived: 0,
|
||||
interval: null,
|
||||
maxTokensPerSecond: 0
|
||||
maxTokensPerSecond: 0,
|
||||
model: model // Store the model used for this request
|
||||
});
|
||||
|
||||
// Update reactive tracking for UI indicators
|
||||
@@ -1271,21 +1274,27 @@ async function promptGPT(systemPrompt, input) {
|
||||
return;
|
||||
} else {
|
||||
// Timeout error (controller was aborted by timeout, not user)
|
||||
const request = activeRequests.get(chatId);
|
||||
const requestModel = request?.model || null;
|
||||
chatStore.add(
|
||||
"assistant",
|
||||
`<span class='error'>Request timeout: MCP processing is taking longer than expected. Please try again.</span>`,
|
||||
null,
|
||||
null,
|
||||
chatId
|
||||
chatId,
|
||||
requestModel
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const request = activeRequests.get(chatId);
|
||||
const requestModel = request?.model || null;
|
||||
chatStore.add(
|
||||
"assistant",
|
||||
`<span class='error'>Network Error: ${error.message}</span>`,
|
||||
null,
|
||||
null,
|
||||
chatId
|
||||
chatId,
|
||||
requestModel
|
||||
);
|
||||
}
|
||||
toggleLoader(false, chatId);
|
||||
@@ -1299,12 +1308,15 @@ async function promptGPT(systemPrompt, input) {
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
const request = activeRequests.get(chatId);
|
||||
const requestModel = request?.model || null;
|
||||
chatStore.add(
|
||||
"assistant",
|
||||
`<span class='error'>Error: POST ${endpoint} ${response.status}</span>`,
|
||||
null,
|
||||
null,
|
||||
chatId
|
||||
chatId,
|
||||
requestModel
|
||||
);
|
||||
toggleLoader(false, chatId);
|
||||
activeRequests.delete(chatId);
|
||||
@@ -1324,12 +1336,15 @@ async function promptGPT(systemPrompt, input) {
|
||||
.getReader();
|
||||
|
||||
if (!reader) {
|
||||
const request = activeRequests.get(chatId);
|
||||
const requestModel = request?.model || null;
|
||||
chatStore.add(
|
||||
"assistant",
|
||||
`<span class='error'>Error: Failed to decode MCP API response</span>`,
|
||||
null,
|
||||
null,
|
||||
chatId
|
||||
chatId,
|
||||
requestModel
|
||||
);
|
||||
toggleLoader(false, chatId);
|
||||
activeRequests.delete(chatId);
|
||||
@@ -1598,12 +1613,15 @@ async function promptGPT(systemPrompt, input) {
|
||||
break;
|
||||
|
||||
case "error":
|
||||
const request = activeRequests.get(chatId);
|
||||
const requestModel = request?.model || null;
|
||||
chatStore.add(
|
||||
"assistant",
|
||||
`<span class='error'>MCP Error: ${eventData.message}</span>`,
|
||||
null,
|
||||
null,
|
||||
chatId
|
||||
chatId,
|
||||
requestModel
|
||||
);
|
||||
break;
|
||||
}
|
||||
@@ -1624,9 +1642,11 @@ async function promptGPT(systemPrompt, input) {
|
||||
// Update or create assistant message with processed regular content
|
||||
const currentChat = chatStore.getChat(chatId);
|
||||
if (!currentChat) break; // Chat was deleted
|
||||
const request = activeRequests.get(chatId);
|
||||
const requestModel = request?.model || null;
|
||||
if (lastAssistantMessageIndex === -1) {
|
||||
if (processedRegular && processedRegular.trim()) {
|
||||
chatStore.add("assistant", processedRegular, null, null, chatId);
|
||||
chatStore.add("assistant", processedRegular, null, null, chatId, requestModel);
|
||||
lastAssistantMessageIndex = targetHistory.length - 1;
|
||||
}
|
||||
} else {
|
||||
@@ -1706,7 +1726,9 @@ async function promptGPT(systemPrompt, input) {
|
||||
lastMessage.html = DOMPurify.sanitize(marked.parse(lastMessage.content));
|
||||
}
|
||||
} else if (processedRegular && processedRegular.trim()) {
|
||||
chatStore.add("assistant", processedRegular, null, null, chatId);
|
||||
const request = activeRequests.get(chatId);
|
||||
const requestModel = request?.model || null;
|
||||
chatStore.add("assistant", processedRegular, null, null, chatId, requestModel);
|
||||
lastAssistantMessageIndex = targetHistory.length - 1;
|
||||
}
|
||||
}
|
||||
@@ -1754,7 +1776,9 @@ async function promptGPT(systemPrompt, input) {
|
||||
lastMessage.html = DOMPurify.sanitize(marked.parse(lastMessage.content));
|
||||
}
|
||||
} else {
|
||||
chatStore.add("assistant", finalRegular, null, null, chatId);
|
||||
const request = activeRequests.get(chatId);
|
||||
const requestModel = request?.model || null;
|
||||
chatStore.add("assistant", finalRegular, null, null, chatId, requestModel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1812,12 +1836,15 @@ async function promptGPT(systemPrompt, input) {
|
||||
.getReader();
|
||||
|
||||
if (!reader) {
|
||||
const request = activeRequests.get(chatId);
|
||||
const requestModel = request?.model || null;
|
||||
chatStore.add(
|
||||
"assistant",
|
||||
`<span class='error'>Error: Failed to decode API response</span>`,
|
||||
null,
|
||||
null,
|
||||
chatId
|
||||
chatId,
|
||||
requestModel
|
||||
);
|
||||
toggleLoader(false, chatId);
|
||||
activeRequests.delete(chatId);
|
||||
@@ -1848,9 +1875,11 @@ async function promptGPT(systemPrompt, input) {
|
||||
const addToChat = (token) => {
|
||||
const currentChat = chatStore.getChat(chatId);
|
||||
if (!currentChat) return; // Chat was deleted
|
||||
chatStore.add("assistant", token, null, null, chatId);
|
||||
// Count tokens for rate calculation (per chat)
|
||||
// Get model from request for this chat
|
||||
const request = activeRequests.get(chatId);
|
||||
const requestModel = request?.model || null;
|
||||
chatStore.add("assistant", token, null, null, chatId, requestModel);
|
||||
// Count tokens for rate calculation (per chat)
|
||||
if (request) {
|
||||
const tokenCount = Math.ceil(token.length / 4);
|
||||
request.tokensReceived += tokenCount;
|
||||
@@ -2008,12 +2037,15 @@ async function promptGPT(systemPrompt, input) {
|
||||
if (error.name !== 'AbortError' || !currentAbortController) {
|
||||
const currentChat = chatStore.getChat(chatId);
|
||||
if (currentChat) {
|
||||
const request = activeRequests.get(chatId);
|
||||
const requestModel = request?.model || null;
|
||||
chatStore.add(
|
||||
"assistant",
|
||||
`<span class='error'>Error: Failed to process stream</span>`,
|
||||
null,
|
||||
null,
|
||||
chatId
|
||||
chatId,
|
||||
requestModel
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,12 +276,31 @@ SOFTWARE.
|
||||
}
|
||||
},
|
||||
|
||||
add(role, content, image, audio, targetChatId = null) {
|
||||
add(role, content, image, audio, targetChatId = null, model = null) {
|
||||
// If targetChatId is provided, add to that chat, otherwise use active chat
|
||||
// This allows streaming to continue to the correct chat even if user switches
|
||||
const chat = targetChatId ? this.getChat(targetChatId) : this.activeChat();
|
||||
if (!chat) return;
|
||||
|
||||
// Determine model for this message:
|
||||
// - If model is explicitly provided, use it (for assistant messages with specific model)
|
||||
// - For user messages, use the current chat's model
|
||||
// - For other messages (thinking, tool_call, etc.), inherit from previous message or use chat model
|
||||
let messageModel = model;
|
||||
if (!messageModel) {
|
||||
if (role === "user") {
|
||||
// User messages always use the current chat's model
|
||||
messageModel = chat.model || "";
|
||||
} else if (role === "assistant") {
|
||||
// Assistant messages use the chat's model (should be set when request is made)
|
||||
messageModel = chat.model || "";
|
||||
} else {
|
||||
// For thinking, tool_call, etc., try to inherit from last assistant message, or use chat model
|
||||
const lastAssistant = chat.history.slice().reverse().find(m => m.role === "assistant");
|
||||
messageModel = lastAssistant?.model || chat.model || "";
|
||||
}
|
||||
}
|
||||
|
||||
const N = chat.history.length - 1;
|
||||
// For thinking, reasoning, tool_call, and tool_result messages, always create a new message
|
||||
if (role === "thinking" || role === "reasoning" || role === "tool_call" || role === "tool_result") {
|
||||
@@ -311,7 +330,7 @@ SOFTWARE.
|
||||
// Reasoning, tool_call, and tool_result are always collapsed by default
|
||||
const isMCPMode = chat.mcpMode || false;
|
||||
const shouldExpand = (role === "thinking" && !isMCPMode) || false;
|
||||
chat.history.push({ role, content, html: c, image, audio, expanded: shouldExpand });
|
||||
chat.history.push({ role, content, html: c, image, audio, expanded: shouldExpand, model: messageModel });
|
||||
|
||||
// Auto-name chat from first user message
|
||||
if (role === "user" && chat.name === "New Chat" && content.trim()) {
|
||||
@@ -332,6 +351,10 @@ SOFTWARE.
|
||||
if (audio && audio.length > 0) {
|
||||
chat.history[N].audio = [...(chat.history[N].audio || []), ...audio];
|
||||
}
|
||||
// Preserve model if merging (don't overwrite)
|
||||
if (!chat.history[N].model && messageModel) {
|
||||
chat.history[N].model = messageModel;
|
||||
}
|
||||
} else {
|
||||
let c = "";
|
||||
const lines = content.split("\n");
|
||||
@@ -343,7 +366,8 @@ SOFTWARE.
|
||||
content,
|
||||
html: c,
|
||||
image: image || [],
|
||||
audio: audio || []
|
||||
audio: audio || [],
|
||||
model: messageModel
|
||||
});
|
||||
|
||||
// Auto-name chat from first user message
|
||||
@@ -1248,11 +1272,20 @@ SOFTWARE.
|
||||
</template>
|
||||
<template x-if="message.role != 'user' && message.role != 'thinking' && message.role != 'reasoning' && message.role != 'tool_call' && message.role != 'tool_result'">
|
||||
<div class="flex items-center space-x-2">
|
||||
{{ if $galleryConfig }}
|
||||
{{ if $galleryConfig.Icon }}<img src="{{$galleryConfig.Icon}}" class="rounded-lg mt-2 max-w-8 max-h-8 border border-[var(--color-primary-border)]/20">{{end}}
|
||||
{{ end }}
|
||||
<!-- Model icon - from message history, fallback to active chat -->
|
||||
<template x-if="message.model && window.__galleryConfigs && window.__galleryConfigs[message.model] && window.__galleryConfigs[message.model].Icon">
|
||||
<img :src="window.__galleryConfigs[message.model].Icon" class="rounded-lg mt-2 max-w-8 max-h-8 border border-[var(--color-primary-border)]/20">
|
||||
</template>
|
||||
<!-- Fallback: use active chat model if message doesn't have one -->
|
||||
<template x-if="!message.model && $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 mt-2 max-w-8 max-h-8 border border-[var(--color-primary-border)]/20">
|
||||
</template>
|
||||
<!-- Final fallback: initial model from server -->
|
||||
<template x-if="!message.model && (!$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 mt-2 max-w-8 max-h-8 border border-[var(--color-primary-border)]/20">
|
||||
</template>
|
||||
<div class="flex flex-col flex-1">
|
||||
<span class="text-xs font-semibold text-[var(--color-text-secondary)] mb-1">{{if .Model}}{{.Model}}{{else}}Assistant{{end}}</span>
|
||||
<span class="text-xs font-semibold text-[var(--color-text-secondary)] mb-1" x-text="message.model || $store.chat.activeChat()?.model || '{{if .Model}}{{.Model}}{{else}}Assistant{{end}}'"></span>
|
||||
<div class="flex-1 text-[var(--color-text-primary)] flex items-center space-x-2 min-w-0">
|
||||
<div class="p-3 rounded-lg bg-[var(--color-bg-secondary)] border border-[var(--color-accent-border)]/20 shadow-lg max-w-full overflow-x-auto overflow-wrap-anywhere" x-html="message.html"></div>
|
||||
<button @click="copyToClipboard(message.html)" title="Copy to clipboard" class="text-[var(--color-text-secondary)] hover:text-[var(--color-primary)] transition-colors p-1 flex-shrink-0">
|
||||
|
||||
Reference in New Issue
Block a user