UNPKG

langflow-chatbot

Version:

Add a Langflow-powered chatbot to your website.

1,323 lines (1,284 loc) 147 kB
"use strict"; var LangflowChatbotPlugin = (() => { var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/plugins/LangflowChatbotPlugin.ts var LangflowChatbotPlugin_exports = {}; __export(LangflowChatbotPlugin_exports, { LangflowChatbotInstance: () => LangflowChatbotInstance, init: () => init }); // src/utils/logger.ts var LOG_LEVELS = { error: 0, warn: 1, info: 2, debug: 3 }; var Logger = class { constructor(level = "warn", prefix = "LangflowChatbot") { this.level = level; this.prefix = prefix; } setLevel(level) { this.level = level; } shouldLog(level) { return LOG_LEVELS[level] <= LOG_LEVELS[this.level]; } format(level, ...args) { const tag = `[${this.prefix}] [${level.toUpperCase()}]`; return [tag, ...args]; } error(...args) { if (this.shouldLog("error")) { console.error(...this.format("error", ...args)); } } warn(...args) { if (this.shouldLog("warn")) { console.warn(...this.format("warn", ...args)); } } info(...args) { if (this.shouldLog("info")) { console.info(...this.format("info", ...args)); } } debug(...args) { if (this.shouldLog("debug")) { console.debug(...this.format("debug", ...args)); } } }; // src/config/apiPaths.ts var PROFILE_CONFIG_ENDPOINT_PREFIX = "/config"; var PROFILE_CHAT_ENDPOINT_PREFIX = "/chat"; // src/clients/LangflowChatClient.ts var LangflowChatClient = class { /** * Creates an instance of LangflowChatClient. * @param {string} profileId - The unique identifier for the chatbot profile. * @param {string} baseApiUrl - The base API URL for the Langflow proxy. * @param {Logger} [logger] - Optional logger instance. */ constructor(profileId, baseApiUrl, logger) { if (!profileId || profileId.trim() === "") { throw new Error("profileId is required and cannot be empty."); } if (!baseApiUrl || baseApiUrl.trim() === "") { throw new Error("baseApiUrl is required and cannot be empty."); } this.profileId = profileId; this.baseApiUrl = baseApiUrl.endsWith("/") ? baseApiUrl.slice(0, -1) : baseApiUrl; this.logger = logger || new Logger("info", "LangflowChatClient"); this.chatEndpoint = `${this.baseApiUrl}${PROFILE_CHAT_ENDPOINT_PREFIX}/${this.profileId}`; this.historyEndpoint = `${this.baseApiUrl}${PROFILE_CHAT_ENDPOINT_PREFIX}/${this.profileId}/history`; } generateSessionId() { return crypto.randomUUID(); } async sendMessage(message2, sessionId) { const effectiveSessionId = sessionId || this.generateSessionId(); try { const requestBody = { message: message2, sessionId: effectiveSessionId, stream: false }; const response = await fetch(this.chatEndpoint, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(requestBody) }); if (!response.ok) { let errorData = { error: `API request failed with status ${response.status}` }; try { errorData = await response.json(); } catch (e) { } this.logger.error("API Error:", response.status, errorData); return { error: errorData.error || `API request failed: ${response.statusText}`, detail: errorData.detail, sessionId: errorData.sessionId || effectiveSessionId }; } const responseData = await response.json(); responseData.sessionId = responseData.sessionId || effectiveSessionId; return responseData; } catch (error) { this.logger.error("Failed to send message or parse response:", error); return { error: "Network error or invalid response from server.", detail: error.message || "Unknown fetch error", sessionId: effectiveSessionId }; } } async *streamMessage(message2, sessionId) { const effectiveSessionId = sessionId || this.generateSessionId(); yield { event: "stream_started", data: { sessionId: effectiveSessionId } }; const requestBody = { message: message2, sessionId: effectiveSessionId, stream: true }; try { const response = await fetch(this.chatEndpoint, { method: "POST", headers: { "Content-Type": "application/json", "Accept": "application/x-ndjson" }, body: JSON.stringify(requestBody) }); if (!response.ok) { let errorData = { error: `API request failed with status ${response.status}` }; try { const errJson = await response.json(); errorData = { error: errJson.error || `API request failed: ${response.statusText}`, detail: errJson.detail, sessionId: errJson.sessionId || effectiveSessionId }; } catch (e) { errorData.detail = response.statusText; errorData.sessionId = effectiveSessionId; } this.logger.error("API Stream Error:", response.status, errorData); yield { event: "error", data: { message: errorData.error, detail: errorData.detail, code: response.status, // @ts-ignore sessionId: effectiveSessionId } }; return; } if (!response.body) { this.logger.error("Response body is null"); yield { event: "error", data: { message: "Response body is null", // @ts-ignore sessionId: effectiveSessionId } }; return; } const reader = response.body.getReader(); const decoder = new TextDecoder("utf-8"); let buffer = ""; while (true) { const { done, value } = await reader.read(); if (value) { const decodedChunk = decoder.decode(value, { stream: true }); buffer += decodedChunk; } let newlineIndex; while ((newlineIndex = buffer.indexOf("\n")) >= 0) { const line = buffer.substring(0, newlineIndex); buffer = buffer.substring(newlineIndex + 1); const trimmedLine = line.trim(); if (trimmedLine.length > 0) { try { let parsedEvent = JSON.parse(trimmedLine); if (parsedEvent.event === "end") { if (parsedEvent.data && parsedEvent.data.flowResponse) { parsedEvent.data.flowResponse.sessionId = effectiveSessionId; } else if (parsedEvent.data) { parsedEvent.data.sessionId = effectiveSessionId; } } yield parsedEvent; } catch (e) { this.logger.error(`[Stream] Error parsing line:`, JSON.stringify(trimmedLine), e); yield { event: "error", data: { message: `Failed to parse JSON line`, detail: e.message, // @ts-ignore sessionId: effectiveSessionId } }; } } } if (done) { const finalChunk = decoder.decode(); if (finalChunk && finalChunk.length > 0) { buffer += finalChunk; } let lastNewlineIndex; while ((lastNewlineIndex = buffer.indexOf("\n")) >= 0) { const finalLineSegment = buffer.substring(0, lastNewlineIndex); buffer = buffer.substring(lastNewlineIndex + 1); const trimmedFinalLine = finalLineSegment.trim(); if (trimmedFinalLine.length > 0) { try { let parsedEvent = JSON.parse(trimmedFinalLine); if (parsedEvent.event === "end") { if (parsedEvent.data && parsedEvent.data.flowResponse) { parsedEvent.data.flowResponse.sessionId = effectiveSessionId; } else if (parsedEvent.data) { parsedEvent.data.sessionId = effectiveSessionId; } } yield parsedEvent; } catch (e) { this.logger.error(`[Stream] Error parsing final line segment:`, JSON.stringify(trimmedFinalLine), e); yield { event: "error", data: { message: `Failed to parse final JSON line segment`, detail: e.message, // @ts-ignore sessionId: effectiveSessionId } }; } } } if (buffer.trim().length > 0) { try { let parsedEvent = JSON.parse(buffer.trim()); if (parsedEvent.event === "end") { if (parsedEvent.data && parsedEvent.data.flowResponse) { parsedEvent.data.flowResponse.sessionId = effectiveSessionId; } else if (parsedEvent.data) { parsedEvent.data.sessionId = effectiveSessionId; } } yield parsedEvent; } catch (e) { this.logger.error(`[Stream] Error parsing remaining buffer:`, JSON.stringify(buffer.trim()), e); yield { event: "error", data: { message: `Failed to parse final buffer content`, detail: e.message, // @ts-ignore sessionId: effectiveSessionId } }; } } break; } } } catch (error) { this.logger.error("General stream error:", error); yield { event: "error", data: { message: "General stream error", detail: error.message, // @ts-ignore sessionId: effectiveSessionId } }; } } async getMessageHistory(sessionId) { if (!sessionId) { this.logger.error("Session ID is required to fetch message history."); return null; } try { const historyUrl = new URL(this.historyEndpoint, window.location.origin); historyUrl.searchParams.append("session_id", sessionId); const response = await fetch(historyUrl.toString(), { method: "GET", headers: { "Accept": "application/json" } }); if (!response.ok) { this.logger.error(`API request for history failed with status ${response.status}`); let errorDetail = `Status: ${response.status}`; try { const errorJson = await response.json(); errorDetail = errorJson.detail || errorJson.error || JSON.stringify(errorJson); } catch (e) { } this.logger.error(`Full error detail for history fetch: ${errorDetail}`); return null; } return await response.json(); } catch (error) { this.logger.error("Failed to fetch message history:", error); return null; } } }; // src/config/uiConstants.ts var SVG_CHAT_ICON = '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"></path></svg>'; var SVG_MINIMIZE_ICON = '<svg viewBox="0 0 24 24" stroke-width="2"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M18 12H6"></path></svg>'; var SVG_RESET_ICON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"></polyline><polyline points="1 20 1 14 7 14"></polyline><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path></svg>'; var DEFAULT_WIDGET_HEADER_TEMPLATE = ` <div class="chat-widget-header"> <span class="chat-widget-title-text">{{widgetTitle}}</span> <button class="chat-widget-reset-button">{{resetButton}}</button> </div> `; var DEFAULT_FLOATING_WIDGET_HEADER_TEMPLATE = ` <div class="chat-widget-header"> <span class="chat-widget-title-text">{{widgetTitle}}</span> <button class="chat-widget-reset-button">{{resetButton}}</button> <button class="chat-widget-minimize-button">${SVG_MINIMIZE_ICON}</button> </div> `; var DEFAULT_MAIN_CONTAINER_TEMPLATE = ` <div class="chat-widget" style="display: flex; flex-direction: column; height: 100%;"> <div id="chat-widget-header-container" style="flex-shrink: 0;"> <!-- Widget header will be injected here --> </div> <div class="chat-messages" style="flex-grow: 1; overflow-y: auto;"> <!-- Messages will appear here --> </div> <div id="chat-input-area-container" style="flex-shrink: 0;"></div> </div> `; var DEFAULT_INPUT_AREA_TEMPLATE = ` <div class="chat-input-area"> <input type="text" class="chat-input" placeholder="Type your message..." /> <button class="send-button">Send</button> </div> `; var DEFAULT_MESSAGE_TEMPLATE = ` <div class="{{messageClasses}} message-block"> <div class="sender-name-display" style="font-size: 0.8em; color: #888; margin-bottom: 2px;">{{sender}}</div> <div class="message-bubble"> <span class="message-text-content" style="white-space: pre-wrap;">{{message}}</span> </div> <div class="message-datetime">{{datetime}}</div> </div>`; var THINKING_BUBBLE_HTML = '<div class="thinking-bubble"><span class="dot"></span><span class="dot"></span><span class="dot"></span></div>'; var ERROR_MESSAGE_TEMPLATE = (errorMessage) => `<div style="color: red; padding: 10px;">Error initializing chatbot: ${errorMessage}</div>`; // src/components/ChatMessageProcessor.ts var ChatMessageProcessor = class { /** * Constructs a ChatMessageProcessor. * @param chatClient The client for interacting with the Langflow API. * @param config Configuration defining the sender names/roles (e.g., user, bot, error, system). * @param logger Logger instance for logging messages. * @param ui Callbacks for UI interactions related to message processing. * @param messageParser The message parser for parsing messages. * @param getEnableStream Function to dynamically get the current stream enabled status. * @param getCurrentSessionId Function to dynamically get the current session ID. */ constructor(chatClient, config, logger, ui, messageParser, getEnableStream, getCurrentSessionId) { this.chatClient = chatClient; this.config = config; this.logger = logger; this.ui = ui; this.messageParser = messageParser; this.getEnableStream = getEnableStream; this.getCurrentSessionId = getCurrentSessionId; } /** * Displays the initial "thinking" indicator in the UI. */ displayInitialThinkingIndicator() { const thinkingMsgElement = this.ui.addMessage(this.config.botSender, THINKING_BUBBLE_HTML, true, (/* @__PURE__ */ new Date()).toISOString()); this.ui.setBotMessageElement(thinkingMsgElement); } /** * Main public method to process a user's message. * It disables input, determines streaming vs. non-streaming, calls the appropriate handler, * and re-enables input. * @param messageText The text of the message from the user. */ async process(messageText) { this.logger.info(`ChatMessageProcessor starting to process: "${messageText}"`); const useStream = this.getEnableStream(); let sessionIdToSend = this.getCurrentSessionId() || void 0; this.ui.setInputDisabled(true); this.ui.setBotMessageElement(null); if (useStream) { await this.handleStreamingResponse(messageText, sessionIdToSend); } else { await this.handleNonStreamingResponse(messageText, sessionIdToSend); } this.ui.setInputDisabled(false); this.logger.info(`ChatMessageProcessor finished processing: "${messageText}"`); } /** * Attempts to update an existing "thinking" message bubble to display an error. * @param baseErrorMessage The main error message text. * @param detailErrorMessage Optional additional details for the error. * @returns True if a thinking bubble was successfully updated to an error, false otherwise. */ tryUpdateThinkingToError(baseErrorMessage, detailErrorMessage) { const botElement = this.ui.getBotMessageElement(); if (botElement && botElement.classList.contains("thinking")) { const fullErrorMessage = detailErrorMessage ? `${baseErrorMessage}: ${detailErrorMessage}` : baseErrorMessage; const parsedErrorMessage = this.messageParser.parseComplete(fullErrorMessage); this.ui.updateMessageContent(botElement, parsedErrorMessage); botElement.classList.remove("thinking", "bot-message"); botElement.classList.add("error-message"); return true; } return false; } /** * Handles the message processing logic when streaming is enabled. * Iterates through stream events, updates UI progressively, and handles errors/completion. * @param messageText The user's message text. * @param sessionIdToSend The session ID to use for the request. */ async handleStreamingResponse(messageText, sessionIdToSend) { this.displayInitialThinkingIndicator(); let accumulatedResponse = ""; try { for await (const event of this.chatClient.streamMessage(messageText, sessionIdToSend)) { const currentBotElement = this.ui.getBotMessageElement(); if (event.event === "stream_started") { const startData = event.data; if (startData.sessionId) { this.ui.updateSessionId(startData.sessionId); } continue; } this.clearThinkingIndicatorIfNeeded(event, currentBotElement, accumulatedResponse); accumulatedResponse = this.processStreamEvent(event, accumulatedResponse); } } catch (error) { this.logger.error("handleStreamingResponse: Failed to process stream message:", error); const displayMessage = error.message || "Error processing stream."; if (!this.tryUpdateThinkingToError("Stream Error", displayMessage)) { const parsedDisplayMessage = this.messageParser.parseComplete(`Stream Error: ${displayMessage}`); this.ui.addMessage(this.config.errorSender, parsedDisplayMessage, false, (/* @__PURE__ */ new Date()).toISOString()); } } finally { const botElementForFinally = this.ui.getBotMessageElement(); const messageSpan = botElementForFinally?.querySelector(".message-text-content"); if (botElementForFinally && botElementForFinally.classList.contains("thinking")) { this.logger.warn("handleStreamingResponse: Bot element still marked as 'thinking' in finally block. Stream may have ended without content or error."); botElementForFinally.classList.remove("thinking"); const messageSpanAfterRemove = botElementForFinally.querySelector(".message-text-content"); if (messageSpanAfterRemove && messageSpanAfterRemove.innerHTML.trim() === "" && !botElementForFinally.classList.contains("error-message")) { this.logger.warn("handleStreamingResponse: Message span is empty and not an error after 'thinking' removed. Setting to '(No content streamed)'."); const parsedNoContent = this.messageParser.parseComplete("(No content streamed)"); this.ui.updateMessageContent(botElementForFinally, parsedNoContent); } else if (messageSpanAfterRemove && messageSpanAfterRemove.innerHTML.includes("thinking-bubble") && !botElementForFinally.classList.contains("error-message")) { this.logger.warn("handleStreamingResponse: Message span still contains 'thinking-bubble' and not an error. Setting to '(No content streamed)'."); const parsedNoContent = this.messageParser.parseComplete("(No content streamed)"); this.ui.updateMessageContent(botElementForFinally, parsedNoContent); } } this.ui.setBotMessageElement(null); } } /** * Clears the "thinking" indicator from a message element if conditions are met * (e.g., content starts streaming, or an error/end event occurs). * @param event The current stream event. * @param currentBotElement The current bot message HTML element. * @param accumulatedResponse The accumulated response text so far. */ clearThinkingIndicatorIfNeeded(event, currentBotElement, accumulatedResponse) { if (currentBotElement && currentBotElement.classList.contains("thinking")) { let shouldClearThinking = false; if (event.event === "token" && event.data.chunk.length > 0) { shouldClearThinking = true; } if (event.event === "end") { const endData = event.data; if (endData.flowResponse?.reply || accumulatedResponse.length > 0) { shouldClearThinking = true; } } if (event.event === "error") { shouldClearThinking = true; } if (shouldClearThinking) { this.ui.updateMessageContent(currentBotElement, ""); currentBotElement.classList.remove("thinking"); } } } /** * Processes an individual stream event by dispatching to event-specific handlers. * @param event The stream event to process. * @param accumulatedResponse The accumulated response string before this event. * @returns The updated accumulated response string. */ processStreamEvent(event, accumulatedResponse) { switch (event.event) { case "token": accumulatedResponse = this.handleStreamTokenEvent(event.data, accumulatedResponse); break; case "error": this.handleStreamErrorEvent(event.data); break; case "add_message": this.handleStreamAddMessageEvent(event.data); break; case "end": this.handleStreamEndEvent(event.data, accumulatedResponse); break; default: this.logger.warn(`Received unknown stream event type: ${event.event}`); } return accumulatedResponse; } /** * Handles a 'token' event from the stream. * Appends the token to the accumulated response and updates the UI. * @param data The data associated with the token event. * @param accumulatedResponse The response accumulated so far. * @returns The new accumulated response. */ handleStreamTokenEvent(data, accumulatedResponse) { const botMessageElement = this.ui.getBotMessageElement(); if (botMessageElement) { const textSpan = botMessageElement.querySelector(".message-text-content"); if (textSpan) { const parsedChunk = this.messageParser.parseChunk(data.chunk, accumulatedResponse); textSpan.innerHTML += parsedChunk; this.ui.scrollChatToBottom(); } else { this.logger.warn("Stream token: message-text-content span not found in bot message element. Cannot append token."); } } return accumulatedResponse + data.chunk; } /** * Handles an 'error' event from the stream. * Updates the UI to display the error. * @param data The data associated with the error event. */ handleStreamErrorEvent(data) { const currentBotElement = this.ui.getBotMessageElement(); if (currentBotElement) { const displayMessage = data.detail ? `${data.message}: ${data.detail}` : data.message; const parsedDisplayMessage = this.messageParser.parseComplete(displayMessage); this.ui.updateMessageContent(currentBotElement, parsedDisplayMessage); currentBotElement.classList.remove("thinking", "bot-message"); currentBotElement.classList.add("error-message"); } else { const parsedErrorMessage = this.messageParser.parseComplete(`Stream Error: ${data.message}${data.detail ? " Details: " + JSON.stringify(data.detail) : ""}`); this.ui.addMessage(this.config.errorSender, parsedErrorMessage, false, (/* @__PURE__ */ new Date()).toISOString()); } } /** * Handles an 'add_message' event from the stream (e.g. for auxiliary messages). * Currently, it only logs the event. * @param data The data associated with the add_message event. */ handleStreamAddMessageEvent(data) { this.logger.debug("handleStreamAddMessageEvent: Received 'add_message' event. Full data:", { data }); const eventData = data; const isBotMessage = eventData.sender === "Machine"; if (!isBotMessage) { return; } let messageContent = void 0; if (typeof eventData.text === "string") { messageContent = eventData.text; } else if (typeof eventData.message === "string") { messageContent = eventData.message; } else if (typeof eventData.html === "string") { messageContent = eventData.html; } else if (typeof eventData.content === "string") { messageContent = eventData.content; } if (messageContent === void 0 || messageContent.trim() === "") { return; } const currentBotElement = this.ui.getBotMessageElement(); if (currentBotElement) { if (currentBotElement.classList.contains("thinking")) { this.ui.updateMessageContent(currentBotElement, ""); currentBotElement.classList.remove("thinking"); } const parsedMessage = this.messageParser.parseComplete(messageContent); const textSpan = currentBotElement.querySelector(".message-text-content"); if (textSpan) { textSpan.innerHTML = parsedMessage; } else { this.logger.warn("handleStreamAddMessageEvent: .message-text-content span not found. Updating currentBotElement directly."); this.ui.updateMessageContent(currentBotElement, parsedMessage); } this.ui.scrollChatToBottom(); } else { this.logger.warn("handleStreamAddMessageEvent: Bot message content found, but no currentBotElement to update. This is unusual if a thinking indicator was expected."); } } /** * Handles an 'end' event from the stream. * Finalizes the message content in the UI and updates the session ID. * @param data The data associated with the end event. * @param accumulatedResponse The total response accumulated from tokens. */ handleStreamEndEvent(data, accumulatedResponse) { const botElement = this.ui.getBotMessageElement(); if (botElement) { if (botElement.classList.contains("thinking") && data.flowResponse?.reply && accumulatedResponse === "") { const parsedReply = this.messageParser.parseComplete(data.flowResponse.reply); this.ui.updateMessageContent(botElement, parsedReply); botElement.classList.remove("thinking"); } else if (accumulatedResponse.length === 0 && !data.flowResponse?.reply) { } else if (data.flowResponse?.reply && accumulatedResponse !== data.flowResponse.reply) { if (accumulatedResponse.length === 0) { const parsedReply = this.messageParser.parseComplete(data.flowResponse.reply); this.ui.updateMessageContent(botElement, parsedReply); if (botElement.classList.contains("thinking")) { botElement.classList.remove("thinking"); } } } if (data.sessionId) { this.ui.updateSessionId(data.sessionId); } } else { this.logger.warn("handleStreamEndEvent: No bot message element found at stream end. This is unusual."); } } /** * Handles the message processing logic when streaming is disabled. * Sends the message, waits for a full response, and updates the UI. * @param messageText The user's message text. * @param sessionIdToSend The session ID to use for the request. */ async handleNonStreamingResponse(messageText, sessionIdToSend) { this.displayInitialThinkingIndicator(); try { const result = await this.chatClient.sendMessage(messageText, sessionIdToSend); const botElement = this.ui.getBotMessageElement(); if (botElement && botElement.classList.contains("thinking")) { if (result.reply) { const parsedReply = this.messageParser.parseComplete(result.reply); this.ui.updateMessageContent(botElement, parsedReply); } else if (result.error) { const errorMessage = result.detail ? `${result.error}: ${result.detail}` : result.error; const parsedErrorMessage = this.messageParser.parseComplete(errorMessage); this.ui.updateMessageContent(botElement, parsedErrorMessage); botElement.classList.remove("bot-message"); botElement.classList.add("error-message"); } else { this.logger.warn("handleNonStreamingResponse: No reply content or error from bot."); const parsedNoResponse = this.messageParser.parseComplete("Sorry, I couldn't get a valid response."); this.ui.updateMessageContent(botElement, parsedNoResponse); } botElement.classList.remove("thinking"); } else { this.logger.warn("handleNonStreamingResponse: Thinking message element was not found or not in expected state. Adding new message."); const parsedFallbackReply = this.messageParser.parseComplete(result.reply || "Sorry, I couldn't get a valid response."); this.ui.addMessage(this.config.botSender, parsedFallbackReply, false, (/* @__PURE__ */ new Date()).toISOString()); } if (result.sessionId) { this.ui.updateSessionId(result.sessionId); } } catch (error) { this.logger.error("Failed to send message via ChatClient:", error); const exceptionMessage = error.message || "Failed to send message."; if (!this.tryUpdateThinkingToError("Error sending message", exceptionMessage)) { const parsedErrorMessage = this.messageParser.parseComplete(`Error: ${exceptionMessage}`); this.ui.addMessage(this.config.errorSender, parsedErrorMessage, false, (/* @__PURE__ */ new Date()).toISOString()); } } finally { this.ui.setBotMessageElement(null); } } }; // node_modules/date-fns/constants.js var daysInYear = 365.2425; var maxTime = Math.pow(10, 8) * 24 * 60 * 60 * 1e3; var minTime = -maxTime; var millisecondsInWeek = 6048e5; var millisecondsInDay = 864e5; var minutesInMonth = 43200; var minutesInDay = 1440; var secondsInHour = 3600; var secondsInDay = secondsInHour * 24; var secondsInWeek = secondsInDay * 7; var secondsInYear = secondsInDay * daysInYear; var secondsInMonth = secondsInYear / 12; var secondsInQuarter = secondsInMonth * 3; var constructFromSymbol = Symbol.for("constructDateFrom"); // node_modules/date-fns/constructFrom.js function constructFrom(date, value) { if (typeof date === "function") return date(value); if (date && typeof date === "object" && constructFromSymbol in date) return date[constructFromSymbol](value); if (date instanceof Date) return new date.constructor(value); return new Date(value); } // node_modules/date-fns/toDate.js function toDate(argument, context) { return constructFrom(context || argument, argument); } // node_modules/date-fns/_lib/defaultOptions.js var defaultOptions = {}; function getDefaultOptions() { return defaultOptions; } // node_modules/date-fns/startOfWeek.js function startOfWeek(date, options) { const defaultOptions2 = getDefaultOptions(); const weekStartsOn = options?.weekStartsOn ?? options?.locale?.options?.weekStartsOn ?? defaultOptions2.weekStartsOn ?? defaultOptions2.locale?.options?.weekStartsOn ?? 0; const _date = toDate(date, options?.in); const day = _date.getDay(); const diff = (day < weekStartsOn ? 7 : 0) + day - weekStartsOn; _date.setDate(_date.getDate() - diff); _date.setHours(0, 0, 0, 0); return _date; } // node_modules/date-fns/startOfISOWeek.js function startOfISOWeek(date, options) { return startOfWeek(date, { ...options, weekStartsOn: 1 }); } // node_modules/date-fns/getISOWeekYear.js function getISOWeekYear(date, options) { const _date = toDate(date, options?.in); const year = _date.getFullYear(); const fourthOfJanuaryOfNextYear = constructFrom(_date, 0); fourthOfJanuaryOfNextYear.setFullYear(year + 1, 0, 4); fourthOfJanuaryOfNextYear.setHours(0, 0, 0, 0); const startOfNextYear = startOfISOWeek(fourthOfJanuaryOfNextYear); const fourthOfJanuaryOfThisYear = constructFrom(_date, 0); fourthOfJanuaryOfThisYear.setFullYear(year, 0, 4); fourthOfJanuaryOfThisYear.setHours(0, 0, 0, 0); const startOfThisYear = startOfISOWeek(fourthOfJanuaryOfThisYear); if (_date.getTime() >= startOfNextYear.getTime()) { return year + 1; } else if (_date.getTime() >= startOfThisYear.getTime()) { return year; } else { return year - 1; } } // node_modules/date-fns/_lib/getTimezoneOffsetInMilliseconds.js function getTimezoneOffsetInMilliseconds(date) { const _date = toDate(date); const utcDate = new Date( Date.UTC( _date.getFullYear(), _date.getMonth(), _date.getDate(), _date.getHours(), _date.getMinutes(), _date.getSeconds(), _date.getMilliseconds() ) ); utcDate.setUTCFullYear(_date.getFullYear()); return +date - +utcDate; } // node_modules/date-fns/_lib/normalizeDates.js function normalizeDates(context, ...dates) { const normalize = constructFrom.bind( null, context || dates.find((date) => typeof date === "object") ); return dates.map(normalize); } // node_modules/date-fns/startOfDay.js function startOfDay(date, options) { const _date = toDate(date, options?.in); _date.setHours(0, 0, 0, 0); return _date; } // node_modules/date-fns/differenceInCalendarDays.js function differenceInCalendarDays(laterDate, earlierDate, options) { const [laterDate_, earlierDate_] = normalizeDates( options?.in, laterDate, earlierDate ); const laterStartOfDay = startOfDay(laterDate_); const earlierStartOfDay = startOfDay(earlierDate_); const laterTimestamp = +laterStartOfDay - getTimezoneOffsetInMilliseconds(laterStartOfDay); const earlierTimestamp = +earlierStartOfDay - getTimezoneOffsetInMilliseconds(earlierStartOfDay); return Math.round((laterTimestamp - earlierTimestamp) / millisecondsInDay); } // node_modules/date-fns/startOfISOWeekYear.js function startOfISOWeekYear(date, options) { const year = getISOWeekYear(date, options); const fourthOfJanuary = constructFrom(options?.in || date, 0); fourthOfJanuary.setFullYear(year, 0, 4); fourthOfJanuary.setHours(0, 0, 0, 0); return startOfISOWeek(fourthOfJanuary); } // node_modules/date-fns/compareAsc.js function compareAsc(dateLeft, dateRight) { const diff = +toDate(dateLeft) - +toDate(dateRight); if (diff < 0) return -1; else if (diff > 0) return 1; return diff; } // node_modules/date-fns/constructNow.js function constructNow(date) { return constructFrom(date, Date.now()); } // node_modules/date-fns/isDate.js function isDate(value) { return value instanceof Date || typeof value === "object" && Object.prototype.toString.call(value) === "[object Date]"; } // node_modules/date-fns/isValid.js function isValid(date) { return !(!isDate(date) && typeof date !== "number" || isNaN(+toDate(date))); } // node_modules/date-fns/differenceInCalendarMonths.js function differenceInCalendarMonths(laterDate, earlierDate, options) { const [laterDate_, earlierDate_] = normalizeDates( options?.in, laterDate, earlierDate ); const yearsDiff = laterDate_.getFullYear() - earlierDate_.getFullYear(); const monthsDiff = laterDate_.getMonth() - earlierDate_.getMonth(); return yearsDiff * 12 + monthsDiff; } // node_modules/date-fns/_lib/getRoundingMethod.js function getRoundingMethod(method) { return (number) => { const round = method ? Math[method] : Math.trunc; const result = round(number); return result === 0 ? 0 : result; }; } // node_modules/date-fns/differenceInMilliseconds.js function differenceInMilliseconds(laterDate, earlierDate) { return +toDate(laterDate) - +toDate(earlierDate); } // node_modules/date-fns/endOfDay.js function endOfDay(date, options) { const _date = toDate(date, options?.in); _date.setHours(23, 59, 59, 999); return _date; } // node_modules/date-fns/endOfMonth.js function endOfMonth(date, options) { const _date = toDate(date, options?.in); const month = _date.getMonth(); _date.setFullYear(_date.getFullYear(), month + 1, 0); _date.setHours(23, 59, 59, 999); return _date; } // node_modules/date-fns/isLastDayOfMonth.js function isLastDayOfMonth(date, options) { const _date = toDate(date, options?.in); return +endOfDay(_date, options) === +endOfMonth(_date, options); } // node_modules/date-fns/differenceInMonths.js function differenceInMonths(laterDate, earlierDate, options) { const [laterDate_, workingLaterDate, earlierDate_] = normalizeDates( options?.in, laterDate, laterDate, earlierDate ); const sign = compareAsc(workingLaterDate, earlierDate_); const difference = Math.abs( differenceInCalendarMonths(workingLaterDate, earlierDate_) ); if (difference < 1) return 0; if (workingLaterDate.getMonth() === 1 && workingLaterDate.getDate() > 27) workingLaterDate.setDate(30); workingLaterDate.setMonth(workingLaterDate.getMonth() - sign * difference); let isLastMonthNotFull = compareAsc(workingLaterDate, earlierDate_) === -sign; if (isLastDayOfMonth(laterDate_) && difference === 1 && compareAsc(laterDate_, earlierDate_) === 1) { isLastMonthNotFull = false; } const result = sign * (difference - +isLastMonthNotFull); return result === 0 ? 0 : result; } // node_modules/date-fns/differenceInSeconds.js function differenceInSeconds(laterDate, earlierDate, options) { const diff = differenceInMilliseconds(laterDate, earlierDate) / 1e3; return getRoundingMethod(options?.roundingMethod)(diff); } // node_modules/date-fns/startOfYear.js function startOfYear(date, options) { const date_ = toDate(date, options?.in); date_.setFullYear(date_.getFullYear(), 0, 1); date_.setHours(0, 0, 0, 0); return date_; } // node_modules/date-fns/locale/en-US/_lib/formatDistance.js var formatDistanceLocale = { lessThanXSeconds: { one: "less than a second", other: "less than {{count}} seconds" }, xSeconds: { one: "1 second", other: "{{count}} seconds" }, halfAMinute: "half a minute", lessThanXMinutes: { one: "less than a minute", other: "less than {{count}} minutes" }, xMinutes: { one: "1 minute", other: "{{count}} minutes" }, aboutXHours: { one: "about 1 hour", other: "about {{count}} hours" }, xHours: { one: "1 hour", other: "{{count}} hours" }, xDays: { one: "1 day", other: "{{count}} days" }, aboutXWeeks: { one: "about 1 week", other: "about {{count}} weeks" }, xWeeks: { one: "1 week", other: "{{count}} weeks" }, aboutXMonths: { one: "about 1 month", other: "about {{count}} months" }, xMonths: { one: "1 month", other: "{{count}} months" }, aboutXYears: { one: "about 1 year", other: "about {{count}} years" }, xYears: { one: "1 year", other: "{{count}} years" }, overXYears: { one: "over 1 year", other: "over {{count}} years" }, almostXYears: { one: "almost 1 year", other: "almost {{count}} years" } }; var formatDistance = (token, count, options) => { let result; const tokenValue = formatDistanceLocale[token]; if (typeof tokenValue === "string") { result = tokenValue; } else if (count === 1) { result = tokenValue.one; } else { result = tokenValue.other.replace("{{count}}", count.toString()); } if (options?.addSuffix) { if (options.comparison && options.comparison > 0) { return "in " + result; } else { return result + " ago"; } } return result; }; // node_modules/date-fns/locale/_lib/buildFormatLongFn.js function buildFormatLongFn(args) { return (options = {}) => { const width = options.width ? String(options.width) : args.defaultWidth; const format2 = args.formats[width] || args.formats[args.defaultWidth]; return format2; }; } // node_modules/date-fns/locale/en-US/_lib/formatLong.js var dateFormats = { full: "EEEE, MMMM do, y", long: "MMMM do, y", medium: "MMM d, y", short: "MM/dd/yyyy" }; var timeFormats = { full: "h:mm:ss a zzzz", long: "h:mm:ss a z", medium: "h:mm:ss a", short: "h:mm a" }; var dateTimeFormats = { full: "{{date}} 'at' {{time}}", long: "{{date}} 'at' {{time}}", medium: "{{date}}, {{time}}", short: "{{date}}, {{time}}" }; var formatLong = { date: buildFormatLongFn({ formats: dateFormats, defaultWidth: "full" }), time: buildFormatLongFn({ formats: timeFormats, defaultWidth: "full" }), dateTime: buildFormatLongFn({ formats: dateTimeFormats, defaultWidth: "full" }) }; // node_modules/date-fns/locale/en-US/_lib/formatRelative.js var formatRelativeLocale = { lastWeek: "'last' eeee 'at' p", yesterday: "'yesterday at' p", today: "'today at' p", tomorrow: "'tomorrow at' p", nextWeek: "eeee 'at' p", other: "P" }; var formatRelative = (token, _date, _baseDate, _options) => formatRelativeLocale[token]; // node_modules/date-fns/locale/_lib/buildLocalizeFn.js function buildLocalizeFn(args) { return (value, options) => { const context = options?.context ? String(options.context) : "standalone"; let valuesArray; if (context === "formatting" && args.formattingValues) { const defaultWidth = args.defaultFormattingWidth || args.defaultWidth; const width = options?.width ? String(options.width) : defaultWidth; valuesArray = args.formattingValues[width] || args.formattingValues[defaultWidth]; } else { const defaultWidth = args.defaultWidth; const width = options?.width ? String(options.width) : args.defaultWidth; valuesArray = args.values[width] || args.values[defaultWidth]; } const index = args.argumentCallback ? args.argumentCallback(value) : value; return valuesArray[index]; }; } // node_modules/date-fns/locale/en-US/_lib/localize.js var eraValues = { narrow: ["B", "A"], abbreviated: ["BC", "AD"], wide: ["Before Christ", "Anno Domini"] }; var quarterValues = { narrow: ["1", "2", "3", "4"], abbreviated: ["Q1", "Q2", "Q3", "Q4"], wide: ["1st quarter", "2nd quarter", "3rd quarter", "4th quarter"] }; var monthValues = { narrow: ["J", "F", "M", "A", "M", "J", "J", "A", "S", "O", "N", "D"], abbreviated: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ], wide: [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ] }; var dayValues = { narrow: ["S", "M", "T", "W", "T", "F", "S"], short: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"], abbreviated: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], wide: [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ] }; var dayPeriodValues = { narrow: { am: "a", pm: "p", midnight: "mi", noon: "n", morning: "morning", afternoon: "afternoon", evening: "evening", night: "night" }, abbreviated: { am: "AM", pm: "PM", midnight: "midnight", noon: "noon", morning: "morning", afternoon: "afternoon", evening: "evening", night: "night" }, wide: { am: "a.m.", pm: "p.m.", midnight: "midnight", noon: "noon", morning: "morning", afternoon: "afternoon", evening: "evening", night: "night" } }; var formattingDayPeriodValues = { narrow: { am: "a", pm: "p", midnight: "mi", noon: "n", morning: "in the morning", afternoon: "in the afternoon", evening: "in the evening", night: "at night" }, abbreviated: { am: "AM", pm: "PM", midnight: "midnight", noon: "noon", morning: "in the morning", afternoon: "in the afternoon", evening: "in the evening", night: "at night" }, wide: { am: "a.m.", pm: "p.m.", midnight: "midnight", noon: "noon", morning: "in the morning", afternoon: "in the afternoon", evening: "in the evening", night: "at night" } }; var ordinalNumber = (dirtyNumber, _options) => { const number = Number(dirtyNumber); const rem100 = number % 100; if (rem100 > 20 || rem100 < 10) { switch (rem100 % 10) { case 1: return number + "st"; case 2: return number + "nd"; case 3: return number + "rd"; } } return number + "th"; }; var localize = { ordinalNumber, era: buildLocalizeFn({ values: eraValues, defaultWidth: "wide" }), quarter: buildLocalizeFn({ values: quarterValues, defaultWidth: "wide", argumentCallback: (quarter) => quarter - 1 }), month: buildLocalizeFn({ values: monthValues, defaultWidth: "wide" }), day: buildLocalizeFn({ values: dayValues, defaultWidth: "wide" }), dayPeriod: buildLocalizeFn({ values: dayPeriodValues, defaultWidth: "wide", formattingValues: formattingDayPeriodValues, defaultFormattingWidth: "wide" }) }; // node_modules/date-fns/locale/_lib/buildMatchFn.js function buildMatchFn(args) { return (string, options = {}) => { const width = options.width; const matchPattern = width && args.matchPatterns[width] || args.matchPatterns[args.defaultMatchWidth]; const matchResult = string.match(matchPattern); if (!matchResult) { return null; } const matchedString = matchResult[0]; const parsePatterns = width && args.parsePatterns[width] || args.parsePatterns[args.defaultParseWidth]; const key = Array.isArray(parsePatterns) ? findIndex(parsePatterns, (pattern) => pattern.test(matchedString)) : ( // [TODO] -- I challenge you to fix the type findKey(parsePatterns, (pattern) => pattern.test(matchedString)) ); let value; value = args.valueCallback ? args.valueCallback(key) : key; value = options.valueCallback ? ( // [TODO] -- I challenge you to fix the type options.valueCallback(value) ) : value; const rest = string.slice(matchedString.length); return { value, rest }; }; } function findKey(object, predicate) { for (const key in object) { if (Object.prototype.hasOwnProperty.call(object, key) && predicate(object[key])) { return key; } } return void 0; } function findIndex(array, predicate) { for (let key = 0; key < array.length; key++) { if (predicate(array[key])) { return key; } } return void 0; } // node_modules/date-fns/locale/_lib/buildMatchPatternFn.js function buildMatchPatternFn(args) { return (string, options = {}) => { const matchResult = string.match(args.matchPattern); if (!matchResult) return null; const matchedString = matchResult[0]; const parseResult = string.match(arg