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
+25 -14
View File
@@ -19,6 +19,7 @@ import {
} from "./chatMessageHelpers.js";
import {SearXNGSearchProvider, WebSearchError, buildWebSearchContext, buildWebSearchQuery, decideWebSearch, selectSearchResults, MAX_CANDIDATES} from "./webSearchService.js";
import {supportsReasoning} from "../../public/app/modelCapabilities.js";
export function createChatService(repositories, modelService, config, webSearchProvider = new SearXNGSearchProvider(config)) {
const chatGenerationQueues = new Map();
@@ -192,21 +193,25 @@ export function createChatService(repositories, modelService, config, webSearchP
], {onThinking});
};
const generateValidatedRoleplayReply = async ({selectedModel, messagesPayload, userPersona, signal = null, generation = {}, onThinking = null}) => {
let reply = await modelService.generateReply(selectedModel, messagesPayload, {signal, generation, onThinking});
const generateValidatedRoleplayReply = async ({selectedModel, messagesPayload, userPersona, signal = null, generation = {}, onThinking = null, onChunk = null, reasoning = false}) => {
const generate = (messages) => onChunk
? modelService.streamReply(selectedModel, messages, onChunk, {signal, generation, onThinking, reasoning})
: modelService.generateReply(selectedModel, messages, {signal, generation, onThinking, reasoning});
let reply = await generate(messagesPayload);
if (reply && !containsUserVoiceInRoleplayOpener(reply, userPersona)) {
return reply;
}
onThinking?.("", "");
reply = await modelService.generateReply(selectedModel, [
onChunk?.("", "");
reply = await generate([
...messagesPayload,
{role: "assistant", content: reply || ""},
{
role: "user",
content: "Rewrite your last reply. Hard rule: only the assistant character may speak or act. Never write or imply user speech, thoughts, feelings, reactions, decisions, or actions. Stop before the user's reply."
}
], {signal, generation, onThinking});
]);
if (!reply || containsUserVoiceInRoleplayOpener(reply, userPersona)) {
return null;
@@ -320,7 +325,7 @@ export function createChatService(repositories, modelService, config, webSearchP
return {fullReply, thinking, promptMessageId: promptMessage.id};
};
const generateChatTurn = ({user, chatId, message, model, webSearch = false, onStatus = null, onChunk = null, onThinking = null, signal = null, parentMessageId = undefined}) =>
const generateChatTurn = ({user, chatId, message, model, webSearch = false, reasoning = false, onStatus = null, onChunk = null, onThinking = null, signal = null, parentMessageId = undefined}) =>
withChatGenerationLock(chatId, async () => {
await (summaryQueues.get(chatId) || Promise.resolve());
const session = repositories.getChat(chatId, user);
@@ -337,17 +342,22 @@ export function createChatService(repositories, modelService, config, webSearchP
return {error: "Branch point not found", status: 404};
}
const {model: effectiveModel, generation} = getGenerationSettings(session, model);
const useReasoning = reasoning === true && supportsReasoning(effectiveModel);
signal?.throwIfAborted();
const inserted = repositories.insertBranchChatMessage(chatId, "user", message, null, null, "pending", null, effectiveParentId);
const userMessageId = Number(inserted?.lastInsertRowid);
try {
let sources = null;
let thinking = "";
const captureThinking = (chunk, fullThinking) => {
if (!useReasoning) return;
thinking = fullThinking;
onThinking?.(chunk, fullThinking);
};
let payload;
const fullReply = await withActiveGeneration(chatId, signal, async (activeSignal) => {
activeSignal.throwIfAborted();
onStatus?.({status: "accepted"});
if (webSearch) {
const searchDecision = decideWebSearch(message);
let searchQuery = null;
@@ -364,7 +374,6 @@ export function createChatService(repositories, modelService, config, webSearchP
candidateCount = candidates.length;
sources = selectSearchResults(message, candidates, searchDecision);
selectedCount = sources.length;
onStatus?.({status: "generating", sourceCount: selectedCount});
}
if (config.structuredRequestLoggingEnabled) {
console.log(JSON.stringify({
@@ -379,6 +388,7 @@ export function createChatService(repositories, modelService, config, webSearchP
}));
}
}
onStatus?.({status: "generating", ...(sources !== null ? {sourceCount: sources.length} : {})});
payload = buildConversationContext({user, chatId, session, message: null, sources});
return payload.activePersona
? generateValidatedRoleplayReply({
@@ -387,18 +397,19 @@ export function createChatService(repositories, modelService, config, webSearchP
userPersona: payload.activeUserPersona,
signal: activeSignal,
generation,
onThinking: captureThinking
onThinking: captureThinking,
onChunk,
reasoning: useReasoning
})
: onChunk
? modelService.streamReply(effectiveModel, payload.messagesPayload, onChunk, {signal: activeSignal, generation, onThinking: captureThinking})
: modelService.generateReply(effectiveModel, payload.messagesPayload, {signal: activeSignal, generation, onThinking: captureThinking});
? modelService.streamReply(effectiveModel, payload.messagesPayload, onChunk, {signal: activeSignal, generation, onThinking: captureThinking, reasoning: useReasoning})
: modelService.generateReply(effectiveModel, payload.messagesPayload, {signal: activeSignal, generation, onThinking: captureThinking, reasoning: useReasoning});
});
if (!fullReply) {
repositories.failChatTurn(chatId, userMessageId, "Failed to generate a valid character reply", webSearch ? userMessageId : effectiveParentId);
return {error: "Failed to generate a valid character reply", status: 500};
}
if (payload.activePersona && onChunk) onChunk(fullReply, fullReply);
const retryState = {
retryVariants: [fullReply],
@@ -508,11 +519,11 @@ export function createChatService(repositories, modelService, config, webSearchP
containsUserVoiceInRoleplayOpener,
persistChatMessageRetryState,
buildMessagesPayload: buildConversationContext,
async sendChatMessage({user, chatId, message, model, webSearch = false}) {
return generateChatTurn({user, chatId, message, model, webSearch});
async sendChatMessage({user, chatId, message, model, webSearch = false, reasoning = false}) {
return generateChatTurn({user, chatId, message, model, webSearch, reasoning});
},
async streamChatMessage({user, chatId, message, model, webSearch = false, onStatus, onChunk, onThinking, signal = null}) {
return generateChatTurn({user, chatId, message, model, webSearch, onStatus, onChunk, onThinking, signal});
async streamChatMessage({user, chatId, message, model, webSearch = false, reasoning = false, onStatus, onChunk, onThinking, signal = null}) {
return generateChatTurn({user, chatId, message, model, webSearch, reasoning, onStatus, onChunk, onThinking, signal});
},
async retryMessage({user, chatId, selectedModel, targetMessage, retryStyle = null}) {
return withChatGenerationLock(chatId, async () => {