llmasaservice-ui
Version:
Prebuilt UI components for LLMAsAService.io
1,335 lines (1,331 loc) • 118 kB
JavaScript
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