show reasoning in frontend and some other stuff

This commit is contained in:
2026-09-21 20:24:40 +02:00
parent ceb3c752ff
commit fd76a79aaf
12 changed files with 220 additions and 89 deletions
+28 -11
View File
@@ -1,6 +1,6 @@
import {del, get, post, put, stream} from "./app/api.js?v=20260909-account-controls";
import {appendSources, createWebSearchToggle} from "./app/webSearch.js";
import {updateReasoning} from "./app/reasoning.js";
import {createReasoningToggle, finishReasoning, updateReasoning} from "./app/reasoning.js";
import {closeChatDrawer, openChatDrawer} from "./app/chatDrawer.js?v=20260909-account-controls";
import {onboardingPrompts, requestedChatId} from "./app/constants.js?v=20260909-account-controls";
import {getModelProfile} from "./app/modelProfiles.js?v=20260909-account-controls";
@@ -106,6 +106,7 @@ import {
const headerModelButton = document.getElementById("headerModelButton");
const webSearchButton = document.getElementById("webSearch");
const webSearchToggle = createWebSearchToggle(webSearchButton);
const reasoningToggle = createReasoningToggle(document.getElementById("reasoning"));
let isProcessing = false, activeChatId = null, editingPersonaId = null, editingPersonaType = "assistant", currentUsername = "";
let chatSessions = [], currentMessages = [], assistantPersonas = [], userPersonas = [];
@@ -911,6 +912,7 @@ function renderDrawerSummary() {
}
function renderModelSection() {
reasoningToggle.setModel(modelSelect.value);
updateModelHelp();
return modelSelect?.selectedOptions[0]?.textContent || "";
}
@@ -1361,6 +1363,7 @@ function setLoadingState(loading, overlayOptions = null) {
isProcessing = loading;
msgInput.disabled = loading;
webSearchButton.disabled = loading;
reasoningToggle.setBusy(loading);
sendBtn.classList.toggle("loading", loading);
messagesDiv.querySelectorAll('[data-message-action="regenerate"]').forEach((button) => {
button.disabled = loading || button.dataset.retryLimitReached === "true";
@@ -1811,6 +1814,7 @@ async function sendMessage() {
const message = msgInput.value.trim();
if (!message) return;
const webSearch = webSearchToggle.enabled;
const reasoning = reasoningToggle.enabled;
if (!activeChatId) await createNewChat();
const currentChat = getChatById(activeChatId);
if (currentChat && !currentChat.assistant_persona_id && currentChat.title === "New chat") {
@@ -1825,33 +1829,44 @@ async function sendMessage() {
const loadingBody = loadingMsg.querySelector(".msg-content");
const loadingShell = loadingMsg.querySelector(".loading-shell");
const loadingLabel = loadingMsg.querySelector(".loading-copy strong");
if (webSearch) loadingLabel.textContent = "Searching the web...";
loadingLabel.textContent = webSearch ? "Searching the web..." : "Generating response...";
try {
let streamError = null;
await stream("/chat/stream", {message, model: modelSelect.value, chatId: activeChatId, webSearch}, {
await stream("/chat/stream", {message, model: modelSelect.value, chatId: activeChatId, webSearch, reasoning}, {
onStatus: ({status, sourceCount}) => {
webSearchToggle.reset();
if (status === "accepted") {
webSearchToggle.reset();
reasoningToggle.reset();
return;
}
loadingLabel.textContent = status === "searching" ? "Searching the web..."
: sourceCount === 0 ? "No web results found. Generating response..." : "Generating response...";
},
onThinking: ({fullThinking}) => {
if (!reasoning) return;
const follow = messagesDiv.scrollHeight - messagesDiv.scrollTop - messagesDiv.clientHeight < 100;
updateReasoning(loadingMsg, fullThinking, true);
loadingLabel.textContent = "Thinking…";
loadingShell?.classList.toggle("hidden", Boolean(fullThinking));
if (follow) scrollMessagesToBottom();
},
onChunk: ({fullReply}) => {
const reasoningSummary = loadingMsg.querySelector(".msg-reasoning summary");
if (reasoningSummary) reasoningSummary.textContent = "Reasoning";
const follow = messagesDiv.scrollHeight - messagesDiv.scrollTop - messagesDiv.clientHeight < 100;
if (fullReply) finishReasoning(loadingMsg);
if (loadingBody) {
loadingShell?.classList.add("hidden");
loadingShell?.classList.toggle("hidden", Boolean(fullReply));
loadingLabel.textContent = "Generating response...";
loadingBody.classList.add("has-content");
setMessageContent(loadingBody, fullReply);
}
if (follow) scrollMessagesToBottom();
},
onDone: () => { webSearchToggle.reset(); },
onDone: () => { webSearchToggle.reset(); reasoningToggle.reset(); finishReasoning(loadingMsg); },
onError: ({error}) => {
streamError = error || "Failed to generate reply.";
}
});
const followOutput = messagesDiv.scrollHeight - messagesDiv.scrollTop - messagesDiv.clientHeight < 100;
const previousScrollTop = messagesDiv.scrollTop;
if (messagesDiv.contains(loadingMsg)) messagesDiv.removeChild(loadingMsg);
if (streamError) {
if (webSearch) {
@@ -1865,15 +1880,17 @@ async function sendMessage() {
await setActiveChat(activeChatId);
await loadSummary();
markOnboardingCompleted();
if (!followOutput) requestAnimationFrame(() => { messagesDiv.scrollTop = previousScrollTop; });
maybeShowPersonaNudge();
setNotice("Reply received.", "success");
} catch {
} catch (error) {
if (messagesDiv.contains(loadingMsg)) messagesDiv.removeChild(loadingMsg);
if (webSearch) {
msgInput.value = message;
resizeComposerInput();
}
addMessage("[Error: Network error]"); setNotice("Network error while sending message.", "error");
const errorMessage = error?.message || "Network error while sending message.";
addMessage(`[Error: ${errorMessage}]`); setNotice(errorMessage, "error");
} finally {
setLoadingState(false); msgInput.focus(); updateChatActionState();
}