UNPKG

llmasaservice-ui

Version:

Prebuilt UI components for LLMAsAService.io

1,335 lines (1,331 loc) 118 kB
var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; var __getOwnPropDescs = Object.getOwnPropertyDescriptors; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); var __objRest = (source, exclude) => { var target = {}; for (var prop in source) if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0) target[prop] = source[prop]; if (source != null && __getOwnPropSymbols) for (var prop of __getOwnPropSymbols(source)) { if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop)) target[prop] = source[prop]; } return target; }; var __async = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { step(generator.next(value)); } catch (e) { reject(e); } }; var rejected = (value) => { try { step(generator.throw(value)); } catch (e) { reject(e); } }; var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); step((generator = generator.apply(__this, __arguments)).next()); }); }; // src/ChatPanel.tsx import { useLLM } from "llmasaservice-client"; import React3, { useCallback, useEffect as useEffect2, useMemo, useRef, useState as useState2 } from "react"; import ReactMarkdown from "react-markdown"; import ReactDOMServer from "react-dom/server"; import remarkGfm from "remark-gfm"; import rehypeRaw from "rehype-raw"; import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; import materialDark from "react-syntax-highlighter/dist/esm/styles/prism/material-dark.js"; import materialLight from "react-syntax-highlighter/dist/esm/styles/prism/material-light.js"; // src/EmailModal.tsx import React, { useState } from "react"; var EmailModal = ({ isOpen, onClose, onSend, defaultEmail }) => { const [email, setEmail] = useState(""); const [emailFrom, setEmailFrom] = useState(defaultEmail || ""); const handleSend = () => { onSend(email, emailFrom); onClose(); }; if (!isOpen) return null; return /* @__PURE__ */ React.createElement("div", { className: "modal-overlay" }, /* @__PURE__ */ React.createElement("div", { className: "modal-content" }, /* @__PURE__ */ React.createElement("p", { className: "modal-text" }, "Email Addresses", /* @__PURE__ */ React.createElement("br", null), " (If multiple, comma separate them)"), /* @__PURE__ */ React.createElement("p", null, /* @__PURE__ */ React.createElement( "input", { type: "email", width: "100%", value: email, onChange: (e) => setEmail(e.target.value), placeholder: "To email address" } )), /* @__PURE__ */ React.createElement("p", null, /* @__PURE__ */ React.createElement( "input", { type: "email", width: "100%", value: emailFrom, onChange: (e) => setEmailFrom(e.target.value), placeholder: "From email address (optional)" } )), /* @__PURE__ */ React.createElement("div", { className: "modal-buttons" }, /* @__PURE__ */ React.createElement("button", { onClick: onClose }, "Cancel"), /* @__PURE__ */ React.createElement("button", { onClick: handleSend }, "Send")))); }; var EmailModal_default = EmailModal; // src/ToolInfoModal.tsx import React2 from "react"; var ToolInfoModal = ({ isOpen, onClose, data }) => { if (!isOpen || !data) return null; return /* @__PURE__ */ React2.createElement("div", { className: "modal-overlay", onClick: onClose }, /* @__PURE__ */ React2.createElement( "div", { className: "modal-content tool-info-modal-content", onClick: (e) => e.stopPropagation() }, /* @__PURE__ */ React2.createElement("div", { className: "tool-info-container" }, /* @__PURE__ */ React2.createElement("div", { className: "tool-info-section" }, /* @__PURE__ */ React2.createElement("b", null, "Tool Calls"), /* @__PURE__ */ React2.createElement( "textarea", { className: "tool-info-json", readOnly: true, value: JSON.stringify(data.calls, null, 2) } )), /* @__PURE__ */ React2.createElement("div", { className: "tool-info-section" }, /* @__PURE__ */ React2.createElement("b", null, "Tool Responses"), /* @__PURE__ */ React2.createElement( "textarea", { className: "tool-info-json", readOnly: true, value: JSON.stringify(data.responses, null, 2) } ))), /* @__PURE__ */ React2.createElement("div", { className: "modal-buttons" }, /* @__PURE__ */ React2.createElement("button", { onClick: onClose }, "Close")) )); }; var ToolInfoModal_default = ToolInfoModal; // src/ChatPanel.tsx var ChatPanel = ({ project_id, initialPrompt = "", title = "", placeholder = "Type a message", hideInitialPrompt = true, customer = {}, messages = [], data = [], thumbsUpClick, thumbsDownClick, theme = "light", cssUrl = "", markdownClass = null, width = "300px", height = "100vh", url = null, scrollToEnd = false, initialMessage = "", prismStyle = theme === "light" ? materialLight : materialDark, service = null, historyChangedCallback = null, responseCompleteCallback = null, promptTemplate = "", actions, showSaveButton = true, showEmailButton = true, showNewConversationButton = true, followOnQuestions = [], clearFollowOnQuestionsNextPrompt = false, followOnPrompt = "", showPoweredBy = true, agent = null, conversation = null, showCallToAction = false, callToActionButtonText = "Submit", callToActionEmailAddress = "", callToActionEmailSubject = "Agent CTA submitted", initialHistory = {}, hideRagContextInPrompt = true, createConversationOnFirstChat = true, customerEmailCaptureMode = "HIDE", customerEmailCapturePlaceholder = "Please enter your email...", mcpServers, progressiveActions = true }) => { var _a; const isEmailAddress = (email) => { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); }; const [nextPrompt, setNextPrompt] = useState2(""); const [lastController, setLastController] = useState2(new AbortController()); const [lastMessages, setLastMessages] = useState2([]); const [history, setHistory] = useState2( initialHistory ); const [isLoading, setIsLoading] = useState2(false); const [lastPrompt, setLastPrompt] = useState2(null); const [lastKey, setLastKey] = useState2(null); const [hasScroll, setHasScroll] = useState2(false); const bottomRef = useRef(null); const bottomPanelRef = useRef(null); const textareaRef = useRef(null); const [isAtBottom, setIsAtBottom] = useState2(true); const [isEmailModalOpen, setIsEmailModalOpen] = useState2(false); const [isToolInfoModalOpen, setIsToolInfoModalOpen] = useState2(false); const [toolInfoData, setToolInfoData] = useState2(null); const [currentConversation, setCurrentConversation] = useState2( conversation ); const [emailInput, setEmailInput] = useState2( (_a = customer == null ? void 0 : customer.customer_user_email) != null ? _a : "" ); const [emailInputSet, setEmailInputSet] = useState2( isEmailAddress(emailInput) ); const [emailValid, setEmailValid] = useState2(true); const [showEmailPanel, setShowEmailPanel] = useState2( customerEmailCaptureMode !== "HIDE" ); const [callToActionSent, setCallToActionSent] = useState2(false); const [CTAClickedButNoEmail, setCTAClickedButNoEmail] = useState2(false); const [emailSent, setEmailSent] = useState2(false); const [emailClickedButNoEmail, setEmailClickedButNoEmail] = useState2(false); const [currentCustomer, setCurrentCustomer] = useState2( customer ); const [justReset, setJustReset] = useState2(false); const [newConversationConfirm, setNewConversationConfirm] = useState2(false); const [allActions, setAllActions] = useState2([]); const [pendingToolRequests, setPendingToolRequests] = useState2([]); const [followOnQuestionsState, setFollowOnQuestionsState] = useState2(followOnQuestions.filter((q) => q.trim() !== "")); const [sessionApprovedTools, setSessionApprovedTools] = useState2( [] ); const [alwaysApprovedTools, setAlwaysApprovedTools] = useState2([]); const [thinkingBlocks, setThinkingBlocks] = useState2([]); const [currentThinkingIndex, setCurrentThinkingIndex] = useState2(0); const [thinkingExpanded, setThinkingExpanded] = useState2(false); const [userResizedHeight, setUserResizedHeight] = useState2(null); const [pendingButtonAttachments, setPendingButtonAttachments] = useState2([]); const actionMatchRegistry = useRef(/* @__PURE__ */ new Map()); const deferredActionsRef = useRef(/* @__PURE__ */ new Map()); const finalizedButtonsRef = useRef(/* @__PURE__ */ new Set()); const actionSequenceRef = useRef(0); const lastProcessedResponseRef = useRef(""); const finalizedForCallRef = useRef(null); const buttonActionRegistry = useRef(/* @__PURE__ */ new Map()); useEffect2(() => { const stored = localStorage.getItem("alwaysApprovedTools"); if (stored) setAlwaysApprovedTools(JSON.parse(stored)); }, []); useEffect2(() => { localStorage.setItem( "alwaysApprovedTools", JSON.stringify(alwaysApprovedTools) ); }, [alwaysApprovedTools]); useEffect2(() => { const filtered = followOnQuestions.filter((q) => q.trim() !== ""); if (filtered.length !== followOnQuestionsState.length || filtered.some((q, i) => q !== followOnQuestionsState[i])) { setFollowOnQuestionsState(filtered); } }, [followOnQuestions]); useEffect2(() => { return () => { buttonActionRegistry.current.clear(); }; }, []); useEffect2(() => { const cleanupInterval = setInterval(() => { const registryKeys = Array.from(buttonActionRegistry.current.keys()); const orphanedKeys = []; registryKeys.forEach((buttonId) => { const button = document.getElementById(buttonId); if (!button) { orphanedKeys.push(buttonId); buttonActionRegistry.current.delete(buttonId); } }); if (orphanedKeys.length > 0) { } }, 1e4); return () => clearInterval(cleanupInterval); }, []); const [iframeUrl, setIframeUrl] = useState2(null); const responseAreaRef = useRef(null); const THINKING_PATTERNS = useMemo( () => ({ reasoning: /<reasoning>([\s\S]*?)<\/reasoning>/gi, searching: /<searching>([\s\S]*?)<\/searching>/gi }), [] ); const reasoningRegex = useMemo( () => new RegExp(THINKING_PATTERNS.reasoning.source, "gi"), [THINKING_PATTERNS.reasoning.source] ); const searchingRegex = useMemo( () => new RegExp(THINKING_PATTERNS.searching.source, "gi"), [THINKING_PATTERNS.searching.source] ); const cleanContentForDisplay = useCallback((content) => { let cleaned = content.replace(/\*\*(.*?)\*\*/g, "$1").replace(/\*(.*?)\*/g, "$1").replace(/\n+/g, " ").replace(/\s+/g, " ").trim(); if (cleaned.length > 100) { cleaned = cleaned.substring(0, 100) + "..."; } return cleaned || "Thinking"; }, []); const processThinkingTags = useCallback( (text) => { var _a2, _b; if (!text) { return { cleanedText: "", thinkingBlocks: [], lastThinkingContent: "Thinking" }; } const processedText = text.replace(/\u200B/g, ""); const allMatches = []; reasoningRegex.lastIndex = 0; searchingRegex.lastIndex = 0; let reasoningMatch; while ((reasoningMatch = reasoningRegex.exec(processedText)) !== null) { const content = (_a2 = reasoningMatch[1]) == null ? void 0 : _a2.trim(); if (content) { allMatches.push({ content, index: reasoningMatch.index, type: "reasoning" }); } } let searchingMatch; while ((searchingMatch = searchingRegex.exec(processedText)) !== null) { const content = (_b = searchingMatch[1]) == null ? void 0 : _b.trim(); if (content) { allMatches.push({ content, index: searchingMatch.index, type: "searching" }); } } const thinkingBlocks2 = allMatches.sort((a, b) => a.index - b.index); let cleanedText = processedText.replace(THINKING_PATTERNS.reasoning, "").replace(THINKING_PATTERNS.searching, "").trim(); let lastThinkingContent = "Thinking"; if (thinkingBlocks2.length > 0) { const lastBlock = thinkingBlocks2[thinkingBlocks2.length - 1]; if (lastBlock == null ? void 0 : lastBlock.content) { lastThinkingContent = cleanContentForDisplay(lastBlock.content); } } return { cleanedText, thinkingBlocks: thinkingBlocks2, lastThinkingContent }; }, [ THINKING_PATTERNS.reasoning, THINKING_PATTERNS.searching, reasoningRegex, searchingRegex, cleanContentForDisplay ] ); const renderThinkingBlocks = useCallback(() => { if (thinkingBlocks.length === 0) return null; const currentBlock = thinkingBlocks[currentThinkingIndex]; if (!currentBlock) return null; const icon = currentBlock.type === "reasoning" ? "\u{1F914}" : "\u{1F50D}"; const baseTitle = currentBlock.type === "reasoning" ? "Reasoning" : "Searching"; const extractTitleAndContent = (text) => { const trimmedText = text.trim(); const titleMatch = trimmedText.match(/^\*\*\[(.*?)\]\*\*/); if (titleMatch) { const extractedTitle = titleMatch[1]; const remainingContent = trimmedText.replace(/^\*\*\[.*?\]\*\*\s*\n?/, "").replace(/\*\*(.*?)\*\*/g, "$1").trim(); return { displayTitle: `${baseTitle}: ${extractedTitle}`, content: remainingContent }; } return { displayTitle: baseTitle, content: trimmedText.replace(/\*\*(.*?)\*\*/g, "$1") }; }; const { displayTitle, content } = extractTitleAndContent( currentBlock.content ); return /* @__PURE__ */ React3.createElement("div", { className: "thinking-block-container" }, /* @__PURE__ */ React3.createElement("div", { className: `thinking-section ${currentBlock.type}-section` }, /* @__PURE__ */ React3.createElement("div", { className: "thinking-header" }, icon, " ", displayTitle, thinkingBlocks.length > 1 && /* @__PURE__ */ React3.createElement("div", { className: "thinking-navigation" }, /* @__PURE__ */ React3.createElement( "button", { onClick: () => setCurrentThinkingIndex( Math.max(0, currentThinkingIndex - 1) ), disabled: currentThinkingIndex === 0, className: "thinking-nav-btn" }, "\u2190" ), /* @__PURE__ */ React3.createElement("span", { className: "thinking-counter" }, currentThinkingIndex + 1, " / ", thinkingBlocks.length), /* @__PURE__ */ React3.createElement( "button", { onClick: () => setCurrentThinkingIndex( Math.min( thinkingBlocks.length - 1, currentThinkingIndex + 1 ) ), disabled: currentThinkingIndex === thinkingBlocks.length - 1, className: "thinking-nav-btn" }, "\u2192" ))), /* @__PURE__ */ React3.createElement("div", { className: "thinking-content-row" }, /* @__PURE__ */ React3.createElement("div", { className: `thinking-content ${thinkingExpanded ? "thinking-content-expanded" : "thinking-content-collapsed"}` }, content), /* @__PURE__ */ React3.createElement( "button", { className: "thinking-expand-btn", onClick: () => setThinkingExpanded((prev) => !prev), title: thinkingExpanded ? "Collapse" : "Expand" }, thinkingExpanded ? "\u25B2" : "\u25BC" )))); }, [thinkingBlocks, currentThinkingIndex, thinkingExpanded]); const getBrowserInfo = () => { try { return { currentTimeUTC: (/* @__PURE__ */ new Date()).toISOString(), userTimezone: typeof Intl !== "undefined" && Intl.DateTimeFormat().resolvedOptions().timeZone || "unknown", userLanguage: typeof navigator !== "undefined" && (navigator.language || navigator.language) || "unknown" }; } catch (e) { console.warn("Error getting browser info:", e); return { currentTimeUTC: (/* @__PURE__ */ new Date()).toISOString(), userTimezone: "unknown", userLanguage: "unknown" }; } }; const browserInfo = useMemo(() => getBrowserInfo(), []); const dataWithExtras = () => { var _a2, _b, _c, _d, _e; return [ ...data, { key: "--customer_id", data: (_a2 = currentCustomer == null ? void 0 : currentCustomer.customer_id) != null ? _a2 : "" }, { key: "--customer_name", data: (_b = currentCustomer == null ? void 0 : currentCustomer.customer_name) != null ? _b : "" }, { key: "--customer_user_id", data: (_c = currentCustomer == null ? void 0 : currentCustomer.customer_user_id) != null ? _c : "" }, { key: "--customer_user_name", data: (_d = currentCustomer == null ? void 0 : currentCustomer.customer_user_name) != null ? _d : "" }, { key: "--customer_user_email", data: (_e = currentCustomer == null ? void 0 : currentCustomer.customer_user_email) != null ? _e : "" }, { key: "--email", data: emailInput != null ? emailInput : "" }, { key: "--emailValid", data: emailValid ? "true" : "false" }, { key: "--emailInputSet", data: emailInputSet ? "true" : "false" }, { key: "--emailPanelShowing", data: showEmailPanel ? "true" : "false" }, { key: "--callToActionSent", data: callToActionSent ? "true" : "false" }, { key: "--CTAClickedButNoEmail", data: CTAClickedButNoEmail ? "true" : "false" }, { key: "--emailSent", data: emailSent ? "true" : "false" }, { key: "--emailClickedButNoEmail", data: emailClickedButNoEmail ? "true" : "false" }, { key: "--currentTimeUTC", data: browserInfo == null ? void 0 : browserInfo.currentTimeUTC }, { key: "--userTimezone", data: browserInfo == null ? void 0 : browserInfo.userTimezone }, { key: "--userLanguage", data: browserInfo == null ? void 0 : browserInfo.userLanguage } ]; }; let publicAPIUrl = "https://api.llmasaservice.io"; if (window.location.hostname === "localhost" || window.location.hostname === "dev.llmasaservice.io") { publicAPIUrl = "https://8ftw8droff.execute-api.us-east-1.amazonaws.com/dev"; } const [toolList, setToolList] = useState2([]); const [toolsLoading, setToolsLoading] = useState2(false); const [toolsFetchError, setToolsFetchError] = useState2(false); useEffect2(() => { const fetchAndSetTools = () => __async(null, null, function* () { if (!mcpServers || mcpServers.length === 0) { setToolList([]); setToolsLoading(false); setToolsFetchError(false); return; } setToolsLoading(true); setToolsFetchError(false); try { const fetchPromises = (mcpServers != null ? mcpServers : []).map((m) => __async(null, null, function* () { const urlToFetch = `${publicAPIUrl}/tools/${encodeURIComponent( m.url )}`; try { const response2 = yield fetch(urlToFetch); if (!response2.ok) { console.error( `Error fetching tools from ${m.url}: ${response2.status} ${response2.statusText}` ); const errorBody = yield response2.text(); console.error(`Error body: ${errorBody}`); throw new Error( `HTTP ${response2.status}: ${response2.statusText}` ); } const toolsFromServer = yield response2.json(); if (Array.isArray(toolsFromServer)) { return toolsFromServer.map((tool) => __spreadProps(__spreadValues({}, tool), { url: m.url, accessToken: m.accessToken || "", headers: {} })); } else { return []; } } catch (fetchError) { console.error( `Network or parsing error fetching tools from ${m.url}:`, fetchError ); throw fetchError; } })); const results = yield Promise.all(fetchPromises); const allTools = results.flat(); setToolList(allTools); setToolsFetchError(false); } catch (error) { console.error( "An error occurred while processing tool fetches:", error ); setToolList([]); setToolsFetchError(true); } finally { setToolsLoading(false); } }); fetchAndSetTools(); }, [mcpServers, publicAPIUrl]); const { send, response, idle, stop, lastCallId, setResponse } = useLLM({ project_id, customer: currentCustomer, url, agent, tools: toolList.map((item) => { return { name: item.name, description: item.description, parameters: item.parameters }; }) }); const processActionsOnContent = useCallback((content, context) => { let workingContent = content; const buttonAttachments = []; allActions.filter((a) => a.actionType === "tool").forEach((action) => { try { const regex = new RegExp(action.pattern, "gmi"); workingContent = workingContent.replace(regex, ""); } catch (e) { console.warn("Invalid tool action regex", action.pattern, e); } }); if (context.type === "history") { const { cleanedText } = processThinkingTags(workingContent); workingContent = cleanedText; } const filteredActions = allActions.filter((a) => a.type !== "response" && a.actionType !== "tool"); console.log(`DEBUG: ${context.type} processing - filtered actions:`, filteredActions.length, "of", allActions.length); filteredActions.forEach((action, actionIndex) => { try { const regex = new RegExp(action.pattern, "gmi"); const matches = workingContent.match(regex); if (matches) { console.log(`${context.type === "history" ? "History" : "Streaming"} processing: Found matches for pattern "${action.pattern}":`, matches, "in content:", workingContent.substring(0, 100)); } workingContent = workingContent.replace( regex, (match, ...groups) => { var _a2, _b, _c, _d, _e; const actualMatch = match; const restGroups = groups.slice(0, -2); let buttonId; if (context.type === "history") { const matchIndex = restGroups[restGroups.length - 2]; buttonId = `button-init-${context.historyIndex}-${actionIndex}-${matchIndex}`; } else { const offset = groups[groups.length - 2]; const matchOffset = typeof offset === "number" ? offset : 0; const key = `${actionIndex}:${matchOffset}`; let existingButtonId = actionMatchRegistry.current.get(key); if (!existingButtonId) { existingButtonId = `button-stable-${actionSequenceRef.current++}`; actionMatchRegistry.current.set(key, existingButtonId); } buttonId = existingButtonId; if (context.isProgressive && finalizedButtonsRef.current.has(buttonId)) { return match; } } const substituteTemplate = (template) => { let html2 = template.replace(/\$match/gim, actualMatch); restGroups.forEach((g, gi) => { html2 = html2.replace(new RegExp(`\\$${gi + 1}`, "gmi"), g || ""); }); return html2; }; let html = actualMatch; if (action.type === "button" || action.type === "callback") { if (context.type === "history") { html = `<br /><button id="${buttonId}" ${action.style ? 'class="' + action.style + '"' : ""}>${(_a2 = action.markdown) != null ? _a2 : actualMatch}</button>`; } else { if (context.isProgressive && !context.isIdle) { html = `<br /><button id="${buttonId}" data-pending="true" ${action.style ? 'class="' + action.style + '"' : ""}>${substituteTemplate((_b = action.markdown) != null ? _b : actualMatch)}</button>`; } else { html = `<br /><button id="${buttonId}" ${action.style ? 'class="' + action.style + '"' : ""}>${substituteTemplate((_c = action.markdown) != null ? _c : actualMatch)}</button>`; } } } else if (action.type === "markdown" || action.type === "html") { html = context.type === "history" ? (_d = action.markdown) != null ? _d : "" : substituteTemplate((_e = action.markdown) != null ? _e : ""); } if (context.type === "history") { html = html.replace(new RegExp("\\$match", "gmi"), actualMatch); restGroups.forEach((group, gi) => { html = html.replace( new RegExp(`\\$${gi + 1}`, "gmi"), group || "" ); }); } if (action.type === "button" || action.type === "callback") { if (context.type === "history") { buttonAttachments.push({ buttonId, action, match: actualMatch, groups: restGroups }); buttonActionRegistry.current.set(buttonId, { action, match: actualMatch, groups: restGroups }); } else { if (!finalizedButtonsRef.current.has(buttonId)) { deferredActionsRef.current.set(buttonId, { action, match: actualMatch, groups: restGroups }); if (!context.isProgressive) { buttonAttachments.push({ buttonId, action, match: actualMatch, groups: restGroups }); } } } } return html; } ); } catch (e) { console.warn("Invalid action regex", action.pattern, e); } }); return { processedContent: workingContent, buttonAttachments }; }, [allActions, processThinkingTags, progressiveActions, idle]); useEffect2(() => { setShowEmailPanel(customerEmailCaptureMode !== "HIDE"); if (customerEmailCaptureMode === "REQUIRED") { if (!isEmailAddress(emailInput)) { setEmailValid(false); } } }, [customerEmailCaptureMode]); useEffect2(() => { if (lastCallId && lastCallId !== "" && idle && response) { allActions.filter((a) => a.type === "response").forEach((action) => { if (action.type === "response" && action.pattern) { const regex = new RegExp(action.pattern, "gi"); const matches = regex.exec(response); if (matches && action.callback) { action.callback(matches[0], matches.slice(1)); } } }); } if (responseCompleteCallback) { if (lastCallId && lastCallId !== "" && idle) responseCompleteCallback(lastCallId, lastPrompt != null ? lastPrompt : "", response); } }, [idle]); useEffect2(() => { if (Object.keys(initialHistory).length === 0) return; setHistory(initialHistory); }, [initialHistory]); useEffect2(() => { if (!conversation || conversation === "") return; setCurrentConversation(conversation); setHistory(initialHistory); }, [conversation]); useEffect2(() => { const existingLinks = document.querySelectorAll( 'link[data-source="llmasaservice-ui"]' ); existingLinks.forEach((link) => { var _a2; return (_a2 = link.parentNode) == null ? void 0 : _a2.removeChild(link); }); const existingStyles = document.querySelectorAll( 'style[data-source="llmasaservice-ui"]' ); existingStyles.forEach((style) => { var _a2; return (_a2 = style.parentNode) == null ? void 0 : _a2.removeChild(style); }); if (cssUrl) { if (cssUrl.startsWith("http://") || cssUrl.startsWith("https://")) { const link = document.createElement("link"); link.href = cssUrl; link.rel = "stylesheet"; link.setAttribute("data-source", "llmasaservice-ui"); document.head.appendChild(link); } else { const style = document.createElement("style"); style.textContent = cssUrl; style.setAttribute("data-source", "llmasaservice-ui"); document.head.appendChild(style); } } return () => { const links = document.querySelectorAll( 'link[data-source="llmasaservice-ui"]' ); links.forEach((link) => { var _a2; return (_a2 = link.parentNode) == null ? void 0 : _a2.removeChild(link); }); const styles = document.querySelectorAll( 'style[data-source="llmasaservice-ui"]' ); styles.forEach((style) => { var _a2; return (_a2 = style.parentNode) == null ? void 0 : _a2.removeChild(style); }); }; }, [cssUrl]); const extractValue = (match, groups = [], extraArgs = []) => { var _a2; if ((!extraArgs || extraArgs.length === 0) && (!groups || groups.length === 0)) { return match; } if ((!extraArgs || extraArgs.length === 0) && groups && groups.length > 0) { return groups[0]; } if (extraArgs && extraArgs.length > 0) { const template = (_a2 = extraArgs[0]) != null ? _a2 : ""; return template.replace(/\$(\d+)/g, (_, index) => { const i = parseInt(index, 10); return groups[i] !== void 0 ? groups[i] : ""; }); } return ""; }; const openUrlActionCallback = useCallback( (match, groups, ...extraArgs) => { const url2 = extractValue(match, groups, extraArgs); if ((url2 == null ? void 0 : url2.startsWith("http")) || (url2 == null ? void 0 : url2.startsWith("mailto"))) { window.open(url2, "_blank"); } }, [] ); const copyToClipboardCallback = useCallback( (match, groups, ...extraArgs) => { const val = extractValue(match, groups, extraArgs); navigator.clipboard.writeText(val); }, [] ); const showAlertCallback = useCallback( (match, groups, ...extraArgs) => { alert(extractValue(match, groups, extraArgs)); }, [] ); const sendFollowOnPromptCallback = useCallback( (match, groups, ...extraArgs) => { const val = extractValue(match, groups, extraArgs); if (val && val !== followOnPrompt) { continueChat(val); } }, [followOnPrompt] ); const setFollowUpQuestionsCallback = useCallback( (match, groups, ...extraArgs) => { const val = extractValue(match, groups, extraArgs).split("|").filter((q) => q.trim() !== ""); setFollowOnQuestionsState(val); }, [followOnQuestions] ); const openIframeCallback = useCallback( (match, groups, ...extraArgs) => { const url2 = extractValue(match, groups, extraArgs); if (url2 == null ? void 0 : url2.startsWith("http")) { setIframeUrl(url2); } }, [] ); const anthropic_toolAction = { pattern: '\\{"type":"tool_use","id":"([^"]+)","name":"([^"]+)","input":(\\{[\\s\\S]+?\\}),"service":"([^"]+)"\\}', type: "markdown", markdown: "<br />*Tool use requested: $2*", actionType: "tool" }; const openAI_toolAction = { pattern: '\\{"id":"([^"]+)","type":"function","function":\\{"name":"([^"]+)","arguments":"((?:\\\\.|[^"\\\\])*)"\\},"service":"([^"]+)"\\}', type: "markdown", markdown: "<br />*Tool use requested: $2*", actionType: "tool" }; const google_toolAction = { pattern: '^\\{\\s*"(functionCall)"\\s*:\\s*\\{\\s*"name"\\s*:\\s*"([^"]+)"\\s*,\\s*"args"\\s*:\\s*(\\{[\\s\\S]+?\\})\\s*\\}(?:\\s*,\\s*"thoughtSignature"\\s*:\\s*"[^"]*")?\\s*,\\s*"service"\\s*:\\s*"([^"]+)"\\s*\\}$', type: "markdown", markdown: "<br />*Tool use requested: $2*", actionType: "tool" }; const callbackMapping = useMemo( () => ({ openUrlActionCallback, copyToClipboardCallback, showAlertCallback, sendFollowOnPromptCallback, setFollowUpQuestionsCallback, openIframeCallback }), [ openUrlActionCallback, copyToClipboardCallback, showAlertCallback, sendFollowOnPromptCallback, setFollowUpQuestionsCallback, openIframeCallback ] ); const parseCallbackString = (callbackStr) => { const regex = /^(\w+)(?:\((.+)\))?$/; const match = callbackStr.match(regex); if (match) { const name = match[1]; const args = match[2] ? match[2].split(",").map((arg) => arg.trim()) : []; return { name, args }; } return null; }; const getActionsArraySafely = (actionsString) => { let actions2 = []; if (actionsString && actionsString !== "") { try { actions2 = JSON.parse(actionsString); if (!Array.isArray(actions2)) { throw new Error("Parsed actions is not an array"); } actions2 = actions2.map((action) => { if (typeof action.callback === "string") { const parsed = parseCallbackString(action.callback); if (parsed && parsed.name && callbackMapping[parsed.name]) { const mappedCallback = callbackMapping[parsed.name]; if (mappedCallback) { return __spreadProps(__spreadValues({}, action), { callback: (match, groups) => mappedCallback(match, groups, ...parsed.args) }); } } else { return null; } } else { return action; } }).filter(Boolean); } catch (error) { console.error("Error parsing actions string:", error); actions2 = []; } } return actions2; }; useEffect2(() => { console.log("ChatPanel received actions prop change:", { actions, actionsType: typeof actions, actionsLength: Array.isArray(actions) ? actions.length : "not array" }); let processedActions = []; if (typeof actions === "string") { processedActions = getActionsArraySafely(actions); } else if (Array.isArray(actions)) { console.log("Processing direct action array:", actions); processedActions = actions.map((action) => { console.log("Processing action:", { type: action.type, hasCallback: !!action.callback, callbackType: typeof action.callback, pattern: action.pattern }); if (typeof action.callback === "string") { const parsed = parseCallbackString(action.callback); if (parsed && parsed.name && callbackMapping[parsed.name]) { const mappedCallback = callbackMapping[parsed.name]; if (mappedCallback) { return __spreadProps(__spreadValues({}, action), { callback: (match, groups) => mappedCallback(match, groups, ...parsed.args) }); } } const _a2 = action, { callback } = _a2, rest = __objRest(_a2, ["callback"]); return rest; } return action; }).filter(Boolean); } else if (actions) { const actionsString = JSON.stringify(actions); processedActions = getActionsArraySafely(actionsString); } console.log("DEBUG: Setting allActions:", { processedActions, totalActions: [...processedActions, anthropic_toolAction, openAI_toolAction, google_toolAction].length }); setAllActions([ ...processedActions, anthropic_toolAction, openAI_toolAction, google_toolAction ]); }, [actions]); useEffect2(() => { if (!allActions || allActions.length === 0) return; setHistory((prevHistory) => { if (!prevHistory || Object.keys(prevHistory).length === 0) return prevHistory; let changed = false; const updated = __spreadValues({}, prevHistory); const newButtonAttachments = []; Object.entries(prevHistory).forEach(([prompt, entry], historyIndex) => { if (!entry || !entry.content) { return; } const { processedContent, buttonAttachments } = processActionsOnContent( entry.content, { type: "history", historyIndex } ); if (processedContent !== entry.content) { updated[prompt] = __spreadProps(__spreadValues({}, entry), { content: processedContent }); changed = true; } newButtonAttachments.push(...buttonAttachments); }); if (newButtonAttachments.length > 0) { setPendingButtonAttachments((prev) => [ ...prev, ...newButtonAttachments ]); } return changed ? updated : prevHistory; }); }, [allActions, processThinkingTags, initialHistory]); const pendingToolRequestsRef = useRef(pendingToolRequests); useEffect2(() => { pendingToolRequestsRef.current = pendingToolRequests; }, [pendingToolRequests]); const processGivenToolRequests = (requests) => __async(null, null, function* () { if (!requests || requests.length === 0) requests = pendingToolRequestsRef.current; if (requests.length === 0) return; setIsLoading(true); const toolsToProcess = [...requests]; setPendingToolRequests([]); try { const newMessages = [ { role: "user", content: [ { type: "text", text: lastKey } ] } ]; const toolCallsMessage = { role: "assistant", content: [], tool_calls: [] }; const toolCallsPromises = toolsToProcess.map((req) => __async(null, null, function* () { if (!req) return null; try { return { req, parsedToolCall: JSON.parse(req.match) }; } catch (e) { console.error("Failed to parse tool call:", e); return null; } })); const parsedToolCalls = yield Promise.all(toolCallsPromises); parsedToolCalls.forEach((item) => { if (item && item.parsedToolCall) { toolCallsMessage.tool_calls.push(item.parsedToolCall); } }); newMessages.push(toolCallsMessage); const finalToolCalls = toolCallsMessage.tool_calls; const toolResponsePromises = parsedToolCalls.map((item) => __async(null, null, function* () { var _a2; if (!item || !item.req) return null; const req = item.req; const mcpTool = toolList.find((tool) => tool.name === req.toolName); if (!mcpTool) { console.error(`Tool ${req.toolName} not found in tool list`); return null; } try { let args; try { args = JSON.parse(req.groups[2]); } catch (e) { try { args = JSON.parse(req.groups[2].replace(/\\"/g, '"')); } catch (err) { console.error("Failed to parse tool arguments:", err); return null; } } const body = { tool: req.groups[1], args }; const result = yield fetch( `${publicAPIUrl}/tools/${encodeURIComponent(mcpTool.url)}`, { method: "POST", headers: { "Content-Type": "application/json", "x-mcp-access-token": mcpTool.accessToken && mcpTool.accessToken !== "" ? mcpTool.accessToken : "", "x-project-id": project_id }, body: JSON.stringify(body) } ); if (!result.ok) { console.error( `Error calling tool ${req.toolName}: ${result.status} ${result.statusText}` ); const errorBody = yield result.text(); console.error(`Error body: ${errorBody}`); return null; } let resultData; try { resultData = yield result.json(); } catch (jsonError) { console.error( `Error parsing JSON response for tool ${req.toolName}:`, jsonError ); try { const textBody = yield result.text(); console.error("Response body (text):", textBody); } catch (textError) { console.error( "Failed to read response body as text either:", textError ); } return null; } if (resultData && resultData.content && resultData.content.length > 0) { const textResult = (_a2 = resultData.content[0]) == null ? void 0 : _a2.text; return { role: "tool", content: [ { type: "text", text: textResult } ], tool_call_id: req.groups[0] }; } else { console.error(`No content returned from tool ${req.toolName}`); return null; } } catch (error) { console.error(`Error processing tool ${req.toolName}:`, error); return null; } })); const toolResponses = yield Promise.all(toolResponsePromises); const finalToolResponses = toolResponses.filter(Boolean); if (lastKey) { setHistory((prev) => { const existingEntry = prev[lastKey] || {}; return __spreadProps(__spreadValues({}, prev), { [lastKey]: __spreadProps(__spreadValues({}, existingEntry), { toolCalls: [ ...existingEntry.toolCalls || [], ...finalToolCalls ], toolResponses: [ ...existingEntry.toolResponses || [], ...finalToolResponses ] }) }); }); } finalToolResponses.forEach((response2) => { if (response2) { newMessages.push(response2); } }); send( "", newMessages, [ ...dataWithExtras(), { key: "--messages", data: newMessages.length.toString() } ], true, true, service, currentConversation, lastController ); } catch (error) { console.error("Error in processing all tools:", error); setIsLoading(false); } }); useEffect2(() => { if (response && response.length > 0) { setIsLoading(false); const toolRequests = []; if (allActions && allActions.length > 0) { allActions.filter((a) => a.actionType === "tool").forEach((action) => { var _a2; const regex = new RegExp(action.pattern, "gmi"); let match; while ((match = regex.exec(response)) !== null) { toolRequests.push({ match: match[0], groups: Array.from(match).slice(1), toolName: (_a2 = match[2]) != null ? _a2 : "tool" // Tool name should always in the 2nd capture group }); } }); } if (toolRequests.length > 0) { setPendingToolRequests(toolRequests); } else { setPendingToolRequests([]); } let responseWithoutTools = response; if (allActions && allActions.length > 0) { allActions.filter((a) => a.actionType === "tool").forEach((action) => { const regex = new RegExp(action.pattern, "gmi"); responseWithoutTools = responseWithoutTools.replace(regex, ""); }); } const { cleanedText, thinkingBlocks: newThinkingBlocks } = processThinkingTags(responseWithoutTools); setThinkingBlocks(newThinkingBlocks); setCurrentThinkingIndex(Math.max(0, newThinkingBlocks.length - 1)); const { processedContent: newResponse, buttonAttachments } = processActionsOnContent( cleanedText, { type: "streaming", isProgressive: progressiveActions, isIdle: idle } ); if (!progressiveActions && buttonAttachments.length > 0) { setPendingButtonAttachments((prev) => [...prev, ...buttonAttachments]); } setHistory((prevHistory) => { const existingEntry = prevHistory[lastKey != null ? lastKey : ""] || { content: "", callId: "" }; const updatedHistory = __spreadProps(__spreadValues({}, prevHistory), { [lastKey != null ? lastKey : ""]: __spreadProps(__spreadValues({}, existingEntry), { // This preserves toolCalls and toolResponses content: newResponse, // Store cleaned response without thinking tags or tool JSON callId: lastCallId }) }); return updatedHistory; }); } }, [ response, allActions, lastKey, lastCallId, messages.length, lastPrompt, lastMessages, initialPrompt, processThinkingTags, progressiveActions, idle ]); useEffect2(() => { if (!progressiveActions) return; if (!idle) return; if (!lastKey) return; if (finalizedForCallRef.current === lastCallId) return; setHistory((prev) => { const entry = prev[lastKey]; if (!entry) return prev; let content = entry.content; content = content.replace(/data-pending="true"/g, ""); const updated = __spreadProps(__spreadValues({}, prev), { [lastKey]: __spreadProps(__spreadValues({}, entry), { content }) }); return updated; }); const attachments = []; deferredActionsRef.current.forEach((meta, buttonId) => { if (!finalizedButtonsRef.current.has(buttonId)) { attachments.push(__spreadValues({ buttonId }, meta)); finalizedButtonsRef.current.add(buttonId); } }); if (attachments.length > 0) { setPendingButtonAttachments((prev) => [...prev, ...attachments]); } attachments.forEach(({ buttonId, action, match, groups }) => { buttonActionRegistry.current.set(buttonId, { action, match, groups }); }); finalizedForCallRef.current = lastCallId; }, [idle, progressiveActions, lastCallId, lastKey]); const attachButtonHandlers = useCallback( (attachments, retryCount = 0) => { if (attachments.length === 0) return; console.log(`Attempting to attach ${attachments.length} button handlers (retry ${retryCount})`); let attachedCount = 0; let notFoundCount = 0; let alreadyAttachedCount = 0; const stillPending = []; const delay = retryCount === 0 ? 500 : 200; setTimeout(() => { attachments.forEach(({ buttonId, action, match, groups }) => { const button = document.getElementById(buttonId); if (button) { if (!button.onclick) { console.log("Attaching handler to button:", buttonId); button.onclick = () => { console.log("Button clicked:", buttonId, { match, groups }); console.log("Action callback info:", { hasCallback: !!action.callback, callbackType: typeof action.callback, actionType: action.type }); if (action.callback) { try { console.log("\u{1F3AF} Calling action callback with:", { match, groups }); action.callback(match, groups); console.log("\u2705 Action callback completed successfully"); } catch (error) { console.error("\u274C Error in action callback:", error); } } if (action.clickCode) { try { const func = new Function("match", action.clickCode); func(match); if (typeof interactionClicked === "function") { interactionClicked(lastCallId, "action"); } } catch (error) { console.error("Error executing clickCode:", error); } } }; attachedCount++; } else { alreadyAttachedCount++; } } else { notFoundCount++; stillPending.push({ buttonId, action, match, groups }); buttonActionRegistry.current.set(buttonId, { action, match, groups }); } }); cons