| 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; |
|
|