add optional web search functionality with UI updates and backend integration
This commit is contained in:
+43
-25
@@ -14,10 +14,13 @@ import {
|
||||
clampRetryActiveIndex,
|
||||
formatChatMessage,
|
||||
formatMessagesForSummary,
|
||||
parseSources,
|
||||
retryStylePrompts
|
||||
} from "./chatMessageHelpers.js";
|
||||
|
||||
export function createChatService(repositories, modelService, config) {
|
||||
import {SearXNGSearchProvider, WebSearchError, buildWebSearchContext} from "./webSearchService.js";
|
||||
|
||||
export function createChatService(repositories, modelService, config, webSearchProvider = new SearXNGSearchProvider(config)) {
|
||||
const chatGenerationQueues = new Map();
|
||||
const summaryQueues = new Map();
|
||||
const activeGenerations = new Map();
|
||||
@@ -219,7 +222,7 @@ export function createChatService(repositories, modelService, config) {
|
||||
return repositories.getActiveUserPersona(user);
|
||||
};
|
||||
|
||||
const buildConversationContext = ({user, chatId, session = null, message, upToMessageId = null}) => {
|
||||
const buildConversationContext = ({user, chatId, session = null, message, upToMessageId = null, sources = null}) => {
|
||||
const activeSession = session || repositories.getChat(chatId, user);
|
||||
if (!activeSession) return null;
|
||||
const activePersona = repositories.getAssistantPersonaForChat(chatId, user);
|
||||
@@ -251,6 +254,7 @@ export function createChatService(repositories, modelService, config) {
|
||||
if (memoryPrompt) messagesPayload.push({role: "system", content: memoryPrompt});
|
||||
const personaStatePrompt = buildPersonaStatePrompt(repositories.getPersonaChatState(chatId, user));
|
||||
if (personaStatePrompt) messagesPayload.push({role: "system", content: personaStatePrompt});
|
||||
if (sources) messagesPayload.push(...buildWebSearchContext(sources));
|
||||
messagesPayload.push(...conversation.map((entry) => ({
|
||||
role: entry.role === "bot" ? "assistant" : "user",
|
||||
content: entry.content
|
||||
@@ -291,7 +295,8 @@ export function createChatService(repositories, modelService, config) {
|
||||
chatId,
|
||||
session,
|
||||
message: null,
|
||||
upToMessageId: promptMessage.id
|
||||
upToMessageId: promptMessage.id,
|
||||
sources: targetMessage.sources ? parseSources(targetMessage.sources) : null
|
||||
});
|
||||
|
||||
const styledPayload = appendRetryStyleInstruction(messagesPayload, retryStyle);
|
||||
@@ -303,7 +308,7 @@ export function createChatService(repositories, modelService, config) {
|
||||
return {fullReply, promptMessageId: promptMessage.id};
|
||||
};
|
||||
|
||||
const generateChatTurn = ({user, chatId, message, model, onChunk = null, signal = null, parentMessageId = undefined}) =>
|
||||
const generateChatTurn = ({user, chatId, message, model, webSearch = false, onStatus = null, onChunk = null, signal = null, parentMessageId = undefined}) =>
|
||||
withChatGenerationLock(chatId, async () => {
|
||||
await (summaryQueues.get(chatId) || Promise.resolve());
|
||||
const session = repositories.getChat(chatId, user);
|
||||
@@ -323,21 +328,30 @@ export function createChatService(repositories, modelService, config) {
|
||||
const inserted = repositories.insertBranchChatMessage(chatId, "user", message, null, null, "pending", null, effectiveParentId);
|
||||
const userMessageId = Number(inserted?.lastInsertRowid);
|
||||
try {
|
||||
const payload = buildConversationContext({user, chatId, session, message: null});
|
||||
const fullReply = await withActiveGeneration(chatId, signal, (activeSignal) => payload.activePersona
|
||||
? generateValidatedRoleplayReply({
|
||||
selectedModel: effectiveModel,
|
||||
messagesPayload: payload.messagesPayload,
|
||||
userPersona: payload.activeUserPersona,
|
||||
signal: activeSignal,
|
||||
generation
|
||||
})
|
||||
: onChunk
|
||||
? modelService.streamReply(effectiveModel, payload.messagesPayload, onChunk, {signal: activeSignal, generation})
|
||||
: modelService.generateReply(effectiveModel, payload.messagesPayload, {signal: activeSignal, generation}));
|
||||
let sources = null;
|
||||
let payload;
|
||||
const fullReply = await withActiveGeneration(chatId, signal, async (activeSignal) => {
|
||||
if (webSearch) {
|
||||
onStatus?.({status: "searching"});
|
||||
sources = await webSearchProvider.search(message, {signal: activeSignal, limit: 5});
|
||||
onStatus?.({status: "generating", sourceCount: sources.length});
|
||||
}
|
||||
payload = buildConversationContext({user, chatId, session, message: null, sources});
|
||||
return payload.activePersona
|
||||
? generateValidatedRoleplayReply({
|
||||
selectedModel: effectiveModel,
|
||||
messagesPayload: payload.messagesPayload,
|
||||
userPersona: payload.activeUserPersona,
|
||||
signal: activeSignal,
|
||||
generation
|
||||
})
|
||||
: onChunk
|
||||
? modelService.streamReply(effectiveModel, payload.messagesPayload, onChunk, {signal: activeSignal, generation})
|
||||
: modelService.generateReply(effectiveModel, payload.messagesPayload, {signal: activeSignal, generation});
|
||||
});
|
||||
|
||||
if (!fullReply) {
|
||||
repositories.failChatTurn(chatId, userMessageId, "Failed to generate a valid character reply", effectiveParentId);
|
||||
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);
|
||||
@@ -348,13 +362,14 @@ export function createChatService(repositories, modelService, config) {
|
||||
retryRetriesUsed: 0,
|
||||
retryPromptMessageId: null
|
||||
};
|
||||
repositories.completeChatTurn(chatId, userMessageId, fullReply, retryState, effectiveModel);
|
||||
repositories.completeChatTurn(chatId, userMessageId, fullReply, retryState, effectiveModel, sources);
|
||||
repositories.touchChat(chatId, user);
|
||||
scheduleContextSummaryUpdate({user, chatId, selectedModel: effectiveModel});
|
||||
return {reply: fullReply, userMessageId};
|
||||
return {reply: fullReply, userMessageId, ...(sources ? {sources} : {})};
|
||||
} catch (error) {
|
||||
repositories.failChatTurn(chatId, userMessageId, String(error?.message || "Model request failed").slice(0, 500), effectiveParentId);
|
||||
repositories.failChatTurn(chatId, userMessageId, String(error?.message || "Model request failed").slice(0, 500), webSearch ? userMessageId : effectiveParentId);
|
||||
repositories.touchChat(chatId, user);
|
||||
if (error instanceof WebSearchError) return {error: error.message, status: error.status};
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
@@ -423,6 +438,7 @@ export function createChatService(repositories, modelService, config) {
|
||||
parentMessageId
|
||||
);
|
||||
parentMessageId = Number(inserted.lastInsertRowid);
|
||||
if (sourceMessage.sources) repositories.updateChatMessageSources(clonedChat.id, parentMessageId, parseSources(sourceMessage.sources));
|
||||
}
|
||||
} catch (error) {
|
||||
repositories.deleteChat(clonedChat.id, user);
|
||||
@@ -447,11 +463,11 @@ export function createChatService(repositories, modelService, config) {
|
||||
containsUserVoiceInRoleplayOpener,
|
||||
persistChatMessageRetryState,
|
||||
buildMessagesPayload: buildConversationContext,
|
||||
async sendChatMessage({user, chatId, message, model}) {
|
||||
return generateChatTurn({user, chatId, message, model});
|
||||
async sendChatMessage({user, chatId, message, model, webSearch = false}) {
|
||||
return generateChatTurn({user, chatId, message, model, webSearch});
|
||||
},
|
||||
async streamChatMessage({user, chatId, message, model, onChunk, signal = null}) {
|
||||
return generateChatTurn({user, chatId, message, model, onChunk, signal});
|
||||
async streamChatMessage({user, chatId, message, model, webSearch = false, onStatus, onChunk, signal = null}) {
|
||||
return generateChatTurn({user, chatId, message, model, webSearch, onStatus, onChunk, signal});
|
||||
},
|
||||
async retryMessage({user, chatId, selectedModel, targetMessage, retryStyle = null}) {
|
||||
return withChatGenerationLock(chatId, async () => {
|
||||
@@ -483,6 +499,7 @@ export function createChatService(repositories, modelService, config) {
|
||||
const inserted = repositories.insertBranchChatMessage(
|
||||
chatId, "bot", retryResult.fullReply, retryState, model, "complete", null, freshTarget.parent_message_id
|
||||
);
|
||||
if (freshTarget.sources) repositories.updateChatMessageSources(chatId, Number(inserted.lastInsertRowid), parseSources(freshTarget.sources));
|
||||
repositories.updateChatContextSummary(chatId, user, null, 0);
|
||||
repositories.touchChat(chatId, user);
|
||||
const message = formatChatMessage(repositories.getChatMessage(chatId, Number(inserted.lastInsertRowid)));
|
||||
@@ -490,7 +507,7 @@ export function createChatService(repositories, modelService, config) {
|
||||
});
|
||||
});
|
||||
},
|
||||
async editAndResendMessage({user, chatId, targetMessage, content, model, onChunk = null, signal = null}) {
|
||||
async editAndResendMessage({user, chatId, targetMessage, content, model, webSearch = false, onChunk = null, signal = null}) {
|
||||
if (!targetMessage || targetMessage.role !== "user") return {error: "Only user messages can be edited and resent", status: 400};
|
||||
const sourceChat = repositories.getChat(chatId, user);
|
||||
if (!sourceChat) return {error: "Chat not found", status: 404};
|
||||
@@ -506,6 +523,7 @@ export function createChatService(repositories, modelService, config) {
|
||||
chatId: cloned.chat.id,
|
||||
message: content,
|
||||
model,
|
||||
webSearch,
|
||||
onChunk,
|
||||
signal
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user