langflow-chatbot
Version:
Add a Langflow-powered chatbot to your website.
1,323 lines (1,284 loc) • 147 kB
JavaScript
"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