| | const { sendEvent } = require('@librechat/api'); |
| | const { logger } = require('@librechat/data-schemas'); |
| | const { getResponseSender } = require('librechat-data-provider'); |
| | const { |
| | handleAbortError, |
| | createAbortController, |
| | cleanupAbortController, |
| | } = require('~/server/middleware'); |
| | const { |
| | disposeClient, |
| | processReqData, |
| | clientRegistry, |
| | requestDataMap, |
| | } = require('~/server/cleanup'); |
| | const { createOnProgress } = require('~/server/utils'); |
| | const { saveMessage } = require('~/models'); |
| |
|
| | const EditController = async (req, res, next, initializeClient) => { |
| | let { |
| | text, |
| | generation, |
| | endpointOption, |
| | conversationId, |
| | modelDisplayLabel, |
| | responseMessageId, |
| | isContinued = false, |
| | parentMessageId = null, |
| | overrideParentMessageId = null, |
| | } = req.body; |
| |
|
| | let client = null; |
| | let abortKey = null; |
| | let cleanupHandlers = []; |
| | let clientRef = null; |
| |
|
| | logger.debug('[EditController]', { |
| | text, |
| | generation, |
| | isContinued, |
| | conversationId, |
| | ...endpointOption, |
| | modelsConfig: endpointOption.modelsConfig ? 'exists' : '', |
| | }); |
| |
|
| | let userMessage = null; |
| | let userMessagePromise = null; |
| | let promptTokens = null; |
| | let getAbortData = null; |
| |
|
| | const sender = getResponseSender({ |
| | ...endpointOption, |
| | model: endpointOption.modelOptions.model, |
| | modelDisplayLabel, |
| | }); |
| | const userMessageId = parentMessageId; |
| | const userId = req.user.id; |
| |
|
| | let reqDataContext = { userMessage, userMessagePromise, responseMessageId, promptTokens }; |
| |
|
| | const updateReqData = (data = {}) => { |
| | reqDataContext = processReqData(data, reqDataContext); |
| | abortKey = reqDataContext.abortKey; |
| | userMessage = reqDataContext.userMessage; |
| | userMessagePromise = reqDataContext.userMessagePromise; |
| | responseMessageId = reqDataContext.responseMessageId; |
| | promptTokens = reqDataContext.promptTokens; |
| | }; |
| |
|
| | let { onProgress: progressCallback, getPartialText } = createOnProgress({ |
| | generation, |
| | }); |
| |
|
| | const performCleanup = () => { |
| | logger.debug('[EditController] Performing cleanup'); |
| | if (Array.isArray(cleanupHandlers)) { |
| | for (const handler of cleanupHandlers) { |
| | try { |
| | if (typeof handler === 'function') { |
| | handler(); |
| | } |
| | } catch (e) { |
| | |
| | } |
| | } |
| | } |
| |
|
| | if (abortKey) { |
| | logger.debug('[EditController] Cleaning up abort controller'); |
| | cleanupAbortController(abortKey); |
| | abortKey = null; |
| | } |
| |
|
| | if (client) { |
| | disposeClient(client); |
| | client = null; |
| | } |
| |
|
| | reqDataContext = null; |
| | userMessage = null; |
| | userMessagePromise = null; |
| | promptTokens = null; |
| | getAbortData = null; |
| | progressCallback = null; |
| | endpointOption = null; |
| | cleanupHandlers = null; |
| |
|
| | if (requestDataMap.has(req)) { |
| | requestDataMap.delete(req); |
| | } |
| | logger.debug('[EditController] Cleanup completed'); |
| | }; |
| |
|
| | try { |
| | ({ client } = await initializeClient({ req, res, endpointOption })); |
| |
|
| | if (clientRegistry && client) { |
| | clientRegistry.register(client, { userId }, client); |
| | } |
| |
|
| | if (client) { |
| | requestDataMap.set(req, { client }); |
| | } |
| |
|
| | clientRef = new WeakRef(client); |
| |
|
| | getAbortData = () => { |
| | const currentClient = clientRef?.deref(); |
| | const currentText = |
| | currentClient?.getStreamText != null ? currentClient.getStreamText() : getPartialText(); |
| |
|
| | return { |
| | sender, |
| | conversationId, |
| | messageId: reqDataContext.responseMessageId, |
| | parentMessageId: overrideParentMessageId ?? userMessageId, |
| | text: currentText, |
| | userMessage: userMessage, |
| | userMessagePromise: userMessagePromise, |
| | promptTokens: reqDataContext.promptTokens, |
| | }; |
| | }; |
| |
|
| | const { onStart, abortController } = createAbortController( |
| | req, |
| | res, |
| | getAbortData, |
| | updateReqData, |
| | ); |
| |
|
| | const closeHandler = () => { |
| | logger.debug('[EditController] Request closed'); |
| | if (!abortController || abortController.signal.aborted || abortController.requestCompleted) { |
| | return; |
| | } |
| | abortController.abort(); |
| | logger.debug('[EditController] Request aborted on close'); |
| | }; |
| |
|
| | res.on('close', closeHandler); |
| | cleanupHandlers.push(() => { |
| | try { |
| | res.removeListener('close', closeHandler); |
| | } catch (e) { |
| | |
| | } |
| | }); |
| |
|
| | let response = await client.sendMessage(text, { |
| | user: userId, |
| | generation, |
| | isContinued, |
| | isEdited: true, |
| | conversationId, |
| | parentMessageId, |
| | responseMessageId: reqDataContext.responseMessageId, |
| | overrideParentMessageId, |
| | getReqData: updateReqData, |
| | onStart, |
| | abortController, |
| | progressCallback, |
| | progressOptions: { |
| | res, |
| | }, |
| | }); |
| |
|
| | const databasePromise = response.databasePromise; |
| | delete response.databasePromise; |
| |
|
| | const { conversation: convoData = {} } = await databasePromise; |
| | const conversation = { ...convoData }; |
| | conversation.title = |
| | conversation && !conversation.title ? null : conversation?.title || 'New Chat'; |
| |
|
| | if (client?.options?.attachments && endpointOption?.modelOptions?.model) { |
| | conversation.model = endpointOption.modelOptions.model; |
| | } |
| |
|
| | if (!abortController.signal.aborted) { |
| | const finalUserMessage = reqDataContext.userMessage; |
| | const finalResponseMessage = { ...response }; |
| |
|
| | sendEvent(res, { |
| | final: true, |
| | conversation, |
| | title: conversation.title, |
| | requestMessage: finalUserMessage, |
| | responseMessage: finalResponseMessage, |
| | }); |
| | res.end(); |
| |
|
| | await saveMessage( |
| | req, |
| | { ...finalResponseMessage, user: userId }, |
| | { context: 'api/server/controllers/EditController.js - response end' }, |
| | ); |
| | } |
| |
|
| | performCleanup(); |
| | } catch (error) { |
| | logger.error('[EditController] Error handling request', error); |
| | let partialText = ''; |
| | try { |
| | const currentClient = clientRef?.deref(); |
| | partialText = |
| | currentClient?.getStreamText != null ? currentClient.getStreamText() : getPartialText(); |
| | } catch (getTextError) { |
| | logger.error('[EditController] Error calling getText() during error handling', getTextError); |
| | } |
| |
|
| | handleAbortError(res, req, error, { |
| | sender, |
| | partialText, |
| | conversationId, |
| | messageId: reqDataContext.responseMessageId, |
| | parentMessageId: overrideParentMessageId ?? userMessageId ?? parentMessageId, |
| | userMessageId, |
| | }) |
| | .catch((err) => { |
| | logger.error('[EditController] Error in `handleAbortError` during catch block', err); |
| | }) |
| | .finally(() => { |
| | performCleanup(); |
| | }); |
| | } |
| | }; |
| |
|
| | module.exports = EditController; |
| |
|