Messaggi e FAQ
Invio Messaggi con useCallback
Il nuovo widget utilizza useCallback per ottimizzare la funzione di invio messaggi:
resources/js/shadowDomWidgetPreact.jsx
// Helper function to send a message
const sendMessage = useCallback(
async (messageText) => {
if (!messageText.trim() || isRunning) return;
const text = messageText.trim();
// Hide FAQ
setShowFAQ(false);
// Hide all previous FAQ and add user message
setMessages((prev) => {
// First hide all FAQ from previous messages
const messagesWithHiddenFAQ = prev.map((msg) => ({
...msg,
showFAQ: false,
}));
// Add the new user message
return [
...messagesWithHiddenFAQ,
{
type: "user",
content: text,
textClass: userTextClass,
},
];
});
// Add loading indicator
setMessages((prev) => [
...prev,
{
type: "loading",
textClass: assistantTextClass,
},
]);
setIsRunning(true);
// Ensure thread exists and get the actual threadId to use
let actualThreadId = currentThreadId;
let agentId = widgetAssistantId;
if (!agentId.split("_")[1]) {
agentId =
type === "assistant"
? `assistant_${agentId}`
: `agent_${agentId}`;
}
if (!actualThreadId) {
socketRef.current.emit("create_thread", {
agentId,
company_name: companyName,
company_description: companyDescription,
language: language,
laravelAppUrl: appUrl,
type: type,
});
// Wait for thread creation and get the threadId
actualThreadId = await new Promise((resolve) => {
const handleThreadCreated = ({ threadId }) => {
setCurrentThreadId(threadId);
socketRef.current.off(
"thread_created",
handleThreadCreated
);
resolve(threadId); // Return the threadId directly
};
socketRef.current.on("thread_created", handleThreadCreated);
});
}
// `create_thread` usa `agentId` normalizzato (prefisso assistant_/agent_).
// I messaggi successivi inviano `widgetAssistantId` così com'è passato al widget.
// Send message with the actual threadId
const msgObj = {
message: text,
threadId: actualThreadId, // Use the actual threadId instead of state
agentId: widgetAssistantId,
collection_name: collectionName,
companyName: companyName,
companyDescription: companyDescription,
baseFeedUrl: baseFeedUrl,
hasDocuments: hasDocuments,
language: language,
laravelAppUrl: appUrl,
websiteUrl: websiteUrl,
type: type,
assistantInstructions: assistantInstructions,
};
if (payload) msgObj.payload = payload;
lastFailedMessageRef.current = msgObj;
socketRef.current.emit("chat_message", msgObj);
// Loading indicator will be removed when chat_response_end is received
},
[
isRunning,
currentThreadId,
userTextClass,
assistantTextClass,
widgetAssistantId,
collectionName,
companyName,
companyDescription,
baseFeedUrl,
hasDocuments,
language,
appUrl,
websiteUrl,
type,
assistantInstructions,
payload,
setShowFAQ,
setMessages,
setIsRunning,
setInputDisabled,
setCurrentThreadId,
ensureOnlyLastFAQVisible,
]
);
Rendering Messaggi
resources/js/shadowDomWidget.js
addMessageToChat(type, content, isHistory = false) {
const frag = document.createDocumentFragment();
const msgDiv = this.createMessageElement(type, content);
frag.appendChild(msgDiv);
if (type === "user" && !isHistory) {
frag.appendChild(this.createLoadingIndicator());
}
this.dom.chatHistory.appendChild(frag);
this.scrollChatToBottomIfNeeded();
return msgDiv;
}
createMessageElement(role, content) {
const div = document.createElement("div");
div.className = `message ${role}-message`;
// Aggiunta classi colore testo
if (role === "user" && this.userTextClass) {
div.classList.add(this.userTextClass);
} else if (role === "system" && this.assistantTextClass) {
div.classList.add(this.assistantTextClass);
}
if (role === "system") {
div.innerHTML = marked.parse(this.cleanTmpMsg(content));
} else {
div.textContent = content;
}
return div;
}
🎯 Sistema FAQ
Gestione Domande Frequenti
resources/js/shadowDomWidget.js
_initFAQ() {
if (!this.options.faqMessages ||
!this.options.faqMessages.length ||
this.currentThreadId) {
return;
}
const wrapper = document.createElement("div");
wrapper.classList.add("faq-wrapper", "initial");
this.displayQuestions(this.options.faqMessages, wrapper);
this.dom.chatHistory.appendChild(wrapper);
}
displayQuestions(questions, wrapper) {
const title = document.createElement("p");
title.classList.add("faq-title");
title.textContent = "Prova a chiedere";
wrapper.appendChild(title);
const container = document.createElement("div");
container.classList.add("faq-container");
for (const q of questions) {
const btn = document.createElement("button");
btn.type = "button";
btn.classList.add("faq-single");
btn.textContent = q;
btn.addEventListener("click", () => {
if (this.isRunning) return;
this.dom.messageInput.value = q;
this.sendMessage();
wrapper.remove();
});
container.appendChild(btn);
}
wrapper.appendChild(container);
}