Files
n8n-enterprise-unlocked/packages/frontend/editor-ui/src/components/CanvasChat/utils.ts

123 lines
3.3 KiB
TypeScript

import { CHAT_TRIGGER_NODE_TYPE, MANUAL_CHAT_TRIGGER_NODE_TYPE } from '@/constants';
import { type IExecutionResponse, type INodeUi, type IWorkflowDb } from '@/Interface';
import { type ChatMessage } from '@n8n/chat/types';
import { get, isEmpty } from 'lodash-es';
import { NodeConnectionTypes, type IDataObject, type IRunExecutionData } from 'n8n-workflow';
import { v4 as uuid } from 'uuid';
export function isChatNode(node: INodeUi) {
return [CHAT_TRIGGER_NODE_TYPE, MANUAL_CHAT_TRIGGER_NODE_TYPE].includes(node.type);
}
export function getInputKey(node: INodeUi): string {
if (node.type === MANUAL_CHAT_TRIGGER_NODE_TYPE && node.typeVersion < 1.1) {
return 'input';
}
if (node.type === CHAT_TRIGGER_NODE_TYPE) {
return 'chatInput';
}
return 'chatInput';
}
function extractChatInput(
workflow: IWorkflowDb,
resultData: IRunExecutionData['resultData'],
): ChatMessage | undefined {
const chatTrigger = workflow.nodes.find(isChatNode);
if (chatTrigger === undefined) {
return undefined;
}
const inputKey = getInputKey(chatTrigger);
const runData = (resultData.runData[chatTrigger.name] ?? [])[0];
const message = runData?.data?.[NodeConnectionTypes.Main]?.[0]?.[0]?.json?.[inputKey];
if (runData === undefined || typeof message !== 'string') {
return undefined;
}
return {
text: message,
sender: 'user',
id: uuid(),
};
}
export function extractBotResponse(
resultData: IRunExecutionData['resultData'],
executionId: string,
emptyText?: string,
): ChatMessage | undefined {
const lastNodeExecuted = resultData.lastNodeExecuted;
if (!lastNodeExecuted) return undefined;
const nodeResponseDataArray = get(resultData.runData, lastNodeExecuted) ?? [];
const nodeResponseData = nodeResponseDataArray[nodeResponseDataArray.length - 1];
let responseMessage: string;
if (get(nodeResponseData, 'error')) {
responseMessage = '[ERROR: ' + get(nodeResponseData, 'error.message') + ']';
} else {
const responseData = get(nodeResponseData, 'data.main[0][0].json');
const text = extractResponseText(responseData) ?? emptyText;
if (!text) {
return undefined;
}
responseMessage = text;
}
return {
text: responseMessage,
sender: 'bot',
id: executionId ?? uuid(),
};
}
/** Extracts response message from workflow output */
function extractResponseText(responseData?: IDataObject): string | undefined {
if (!responseData || isEmpty(responseData)) {
return undefined;
}
// Paths where the response message might be located
const paths = ['output', 'text', 'response.text'];
const matchedPath = paths.find((path) => get(responseData, path));
if (!matchedPath) return JSON.stringify(responseData, null, 2);
const matchedOutput = get(responseData, matchedPath);
if (typeof matchedOutput === 'object') {
return '```json\n' + JSON.stringify(matchedOutput, null, 2) + '\n```';
}
return matchedOutput?.toString() ?? '';
}
export function restoreChatHistory(
workflowExecutionData: IExecutionResponse | null,
emptyText?: string,
): ChatMessage[] {
if (!workflowExecutionData?.data) {
return [];
}
const userMessage = extractChatInput(
workflowExecutionData.workflowData,
workflowExecutionData.data.resultData,
);
const botMessage = extractBotResponse(
workflowExecutionData.data.resultData,
workflowExecutionData.id,
emptyText,
);
return [...(userMessage ? [userMessage] : []), ...(botMessage ? [botMessage] : [])];
}