UNPKG

langflow-chatbot

Version:

Add a Langflow-powered chatbot to your website.

287 lines (286 loc) 13 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.LangflowChatClient = void 0; const logger_1 = require("../utils/logger"); const apiPaths_1 = require("../config/apiPaths"); class LangflowChatClient { /** * 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_1.Logger('info', 'LangflowChatClient'); // Construct endpoints using profileId this.chatEndpoint = `${this.baseApiUrl}${apiPaths_1.PROFILE_CHAT_ENDPOINT_PREFIX}/${this.profileId}`; this.historyEndpoint = `${this.baseApiUrl}${apiPaths_1.PROFILE_CHAT_ENDPOINT_PREFIX}/${this.profileId}/history`; } generateSessionId() { return crypto.randomUUID(); } async sendMessage(message, sessionId) { const effectiveSessionId = sessionId || this.generateSessionId(); try { const requestBody = { message, 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) { // Ignore if response is not JSON } 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(message, sessionId) { const effectiveSessionId = sessionId || this.generateSessionId(); yield { event: 'stream_started', data: { sessionId: effectiveSessionId } }; const requestBody = { message, 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 } }; } } } // Process any very last piece of data if buffer is not empty and has no newline 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) { /* ignore */ } // Optionally, rethrow or return a more specific error object // For now, just logging and returning null as per original design 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; } } } exports.LangflowChatClient = LangflowChatClient;