UNPKG

langflow-chatbot

Version:

Add a Langflow-powered chatbot to your website.

174 lines (173 loc) 9.28 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.LangflowProxyService = void 0; const langflow_client_1 = require("@datastax/langflow-client"); const config_loader_1 = require("./lib/startup/config-loader"); const flow_mapper_1 = require("./utils/flow-mapper"); const request_handler_1 = require("./lib/request-handler"); const request_utils_1 = require("./lib/request-utils"); class LangflowProxyService { constructor(config) { this.flowConfigs = new Map(); this.isInitialized = false; if (!config.proxyApiBasePath || typeof config.proxyApiBasePath !== 'string' || config.proxyApiBasePath.trim() === '') { throw new TypeError('LangflowProxyService: proxyApiBasePath is required in config and must be a non-empty string.'); } const { langflowConnection, serverDefaults, chatbotDefaults } = (0, config_loader_1.loadBaseConfig)(); this.langflowConnectionDetails = langflowConnection; this.proxyApiBasePath = config.proxyApiBasePath; console.log(`LangflowProxyService: API Base Path configured to: ${this.proxyApiBasePath}`); const clientConfig = { baseUrl: langflowConnection.endpoint_url, }; if (langflowConnection.api_key && langflowConnection.api_key.trim() !== '') { clientConfig.apiKey = langflowConnection.api_key; } this.langflowClient = new langflow_client_1.LangflowClient(clientConfig); console.log(`LangflowProxyService: LangflowClient initialized. Configured Endpoint: ${langflowConnection.endpoint_url}`); const rawInstanceProfiles = (0, config_loader_1.loadInstanceConfig)(config.instanceConfigPath); this.flowMapper = new flow_mapper_1.FlowMapper(langflowConnection.endpoint_url, langflowConnection.api_key); this.initializationPromise = this._internalAsyncInit(rawInstanceProfiles, serverDefaults, chatbotDefaults); } async _internalAsyncInit(rawInstanceProfiles, serverDefaultValues, chatbotDefaultValues) { try { console.log("LangflowProxyService: Starting internal asynchronous initialization..."); await this.flowMapper.initialize(); console.log("LangflowProxyService: FlowMapper initialized successfully internally."); rawInstanceProfiles.forEach(profile => { const completeProfile = { profileId: profile.profileId, server: { flowId: profile.server.flowId, enableStream: profile.server.enableStream ?? serverDefaultValues.enableStream, datetimeFormat: profile.server.datetimeFormat ?? serverDefaultValues.datetimeFormat, }, chatbot: { labels: { ...(chatbotDefaultValues.labels || {}), ...(profile.chatbot?.labels || {}) }, template: { ...(chatbotDefaultValues.template || {}), ...(profile.chatbot?.template || {}) }, floatingWidget: { ...(chatbotDefaultValues.floatingWidget || {}), ...(profile.chatbot?.floatingWidget || {}) } } }; const configuredFlowIdentifier = completeProfile.server.flowId; const resolvedFlowId = this.flowMapper.getTrueFlowId(configuredFlowIdentifier); if (resolvedFlowId) { if (resolvedFlowId !== configuredFlowIdentifier) { console.log(`LangflowProxyService: Resolved flow identifier '${configuredFlowIdentifier}' to UUID '${resolvedFlowId}' for profile '${completeProfile.profileId}'.`); } completeProfile.server.flowId = resolvedFlowId; } else { console.error(`LangflowProxyService: CRITICAL - Could not resolve flow identifier '${configuredFlowIdentifier}' for profile '${completeProfile.profileId}'. This profile will not function correctly as the identifier is not a valid UUID and was not found in the flow map.`); } this.flowConfigs.set(completeProfile.profileId, completeProfile); console.log(`LangflowProxyService: Loaded profile: '${completeProfile.profileId}' configured with resolved flowId '${completeProfile.server.flowId}'.`); }); if (this.flowConfigs.size === 0) { console.warn("LangflowProxyService: No chatbot profiles were loaded after async init. The service may not function as expected."); } else { const unresolvedCount = Array.from(this.flowConfigs.values()).filter(p => !(/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(p.server.flowId))).length; if (unresolvedCount > 0) { console.warn(`LangflowProxyService: Finished async profile loading. ${this.flowConfigs.size - unresolvedCount} profiles have a valid resolved flowId. ${unresolvedCount} profiles have unresolved flow identifiers and may not function.`); } else { console.log(`LangflowProxyService: Finished async profile loading. All ${this.flowConfigs.size} profiles have a valid resolved flowId.`); } } this.isInitialized = true; console.log("LangflowProxyService: Internal asynchronous initialization complete."); } catch (error) { console.error(`LangflowProxyService: CRITICAL - Error during internal asynchronous initialization: ${error.message}`); this.isInitialized = false; throw error; } } async getChatbotProfile(profileId) { await this.initializationPromise; return this.flowConfigs.get(profileId); } getLangflowConnectionDetails() { return this.langflowConnectionDetails; } async getAllFlowConfigs() { await this.initializationPromise; return this.flowConfigs; } async getAllChatbotProfiles() { await this.initializationPromise; return this.flowConfigs; } async _makeDirectLangflowApiRequest(res, path, method, queryParams) { if (!this.langflowConnectionDetails.endpoint_url) { console.warn(`LangflowProxyService: Attempted API call to "${path}" when Langflow endpoint URL is not configured.`); res.statusCode = 503; res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify({ error: "Langflow endpoint URL not configured in proxy." })); return null; } const targetUrl = new URL(path, this.langflowConnectionDetails.endpoint_url); if (queryParams) { queryParams.forEach((value, key) => { targetUrl.searchParams.append(key, value); }); } console.log(`LangflowProxyService: Forwarding ${method} request to Langflow: ${targetUrl.toString()}`); const headers = { 'Accept': 'application/json', }; if (this.langflowConnectionDetails.api_key) { headers['Authorization'] = `Bearer ${this.langflowConnectionDetails.api_key}`; } try { const response = await fetch(targetUrl.toString(), { method: method, headers: headers, }); if (!response.ok) { console.error(`LangflowProxyService: Langflow API request failed: ${response.status} ${response.statusText} for path ${path}`); } return response; } catch (error) { console.error(`LangflowProxyService: Error during Langflow API request to ${path}:`, error); return null; } } async handleRequest(req, res) { await this.initializationPromise; const entryReqUrl = req.url || ''; let effectiveFullPath; if (req.originalUrl) { effectiveFullPath = req.originalUrl; } else { effectiveFullPath = entryReqUrl; } let internalRoutePath; if (effectiveFullPath.startsWith(this.proxyApiBasePath)) { internalRoutePath = effectiveFullPath.substring(this.proxyApiBasePath.length); if (!internalRoutePath.startsWith('/')) { internalRoutePath = '/' + internalRoutePath; } } else { (0, request_utils_1.sendJsonError)(res, 404, "Endpoint not found. Path mismatch with proxy base."); return; } req.url = internalRoutePath; const preParsedBody = req.body; const isBodyPreParsed = preParsedBody !== undefined; if (isBodyPreParsed) { } else { } try { await (0, request_handler_1.handleRequest)(req, res, this.flowConfigs, this.langflowClient, this.langflowConnectionDetails.endpoint_url, this.langflowConnectionDetails.api_key, this._makeDirectLangflowApiRequest.bind(this), this.proxyApiBasePath, preParsedBody, isBodyPreParsed); } finally { req.url = entryReqUrl; } } } exports.LangflowProxyService = LangflowProxyService;