Eventi e Socket
Gestione Eventi con Preact Hooks
Il nuovo widget utilizza Preact hooks per una gestione degli eventi più moderna e reattiva:
resources/js/shadowDomWidgetPreact.jsx
// Event handlers con useCallback per ottimizzazione
const handleSubmit = useCallback(
async (e) => {
e.preventDefault();
if (!currentMessage.trim() || isRunning) return;
// Clear input field and send message
const text = currentMessage.trim();
setCurrentMessage("");
await sendMessage(text);
},
[currentMessage, isRunning, sendMessage]
);
const handleKeyDown = useCallback(
(e) => {
if (e.key === "Enter" && !e.shiftKey && !e.ctrlKey && !e.metaKey) {
e.preventDefault();
if (!inputDisabled && !isRunning && currentMessage.trim()) {
handleSubmit(e);
}
}
},
[handleSubmit, inputDisabled, isRunning, currentMessage]
);
const handleFAQQuestionClick = useCallback(
async (question) => {
if (isRunning || inputDisabled || !question.trim()) return;
// Hide FAQ immediately when question is clicked
setShowFAQ(false);
// Send the FAQ question directly without setting currentMessage
await sendMessage(question.trim());
},
[isRunning, inputDisabled, sendMessage]
);
const handleCopyChat = useCallback(() => {
const text = messages
.filter((msg) => msg.type !== "loading")
.map((msg) =>
msg.type === "user"
? "Tu: " + msg.content
: "Assistente: " + msg.content
)
.join("\n\n");
navigator.clipboard.writeText(text).then(() => {
setCopyMessage("Chat copiata");
setTimeout(() => setCopyMessage(""), 4000);
});
}, [messages]);
const handleScroll = useCallback((e) => {
const { scrollTop, clientHeight, scrollHeight } = e.target;
setAutoScrollEnabled(scrollTop + clientHeight >= scrollHeight - 10);
}, []);
WebSocket Communication con useEffect
Il nuovo widget utilizza useEffect per gestire la connessione WebSocket in modo reattivo:
resources/js/shadowDomWidgetPreact.jsx
// Socket setup effect
useEffect(() => {
const setupSocket = () => {
const token = window.localStorage.getItem("tidiko_jwt");
socketRef.current = io(socketUrl, {
auth: { token },
reconnection: true,
reconnectionDelay: 1000,
});
const socket = socketRef.current;
socket.on("disconnect", () => {
setIsSocketConnected(false);
setMessages((prev) => [
...prev,
{
type: "system",
content: "Connessione persa. Riconnessione in corso...",
textClass: assistantTextClass,
},
]);
});
socket.on("connect", () => {
setIsSocketConnected(true);
setMessages((prev) =>
prev.filter(
(msg) => !msg.content.includes("Connessione persa")
)
);
});
socket.on("chat_response_start", () => {
// Remove breathing from any tool_feedback messages and remove loading indicators
setMessages((prev) => {
return prev
.filter((msg) => msg.type !== "loading")
.map((msg) => ({
...msg,
isBreathing: false,
}));
});
setTmpMsg("");
setTmpMsgType("chat_response");
currentSystemMessageRef.current = null;
// Re-enable input as soon as response starts
setIsRunning(false);
});
socket.on("chat_response_chunk", ({ response }) => {
// Remove loading indicators when receiving response chunks
setMessages((prev) =>
prev.filter((msg) => msg.type !== "loading")
);
setTmpMsg((prev) => prev + response);
setTmpMsgType("chat_response");
setIsRunning(true);
});
socket.on("chat_response_end", () => {
// Process the last system message for questions
setMessages((prev) => {
const newMessages = [...prev];
const lastIndex = newMessages.length - 1;
if (
lastIndex >= 0 &&
newMessages[lastIndex].type === "system" &&
newMessages[lastIndex].isStreaming
) {
const lastMessage = newMessages[lastIndex];
const processed = processMessageContent(
lastMessage.content
);
newMessages[lastIndex] = {
...lastMessage,
content: processed.content,
questions: processed.questions,
showFAQ: processed.showFAQ,
showSkeletonFAQ: false,
isStreaming: false,
isBreathing: false,
};
}
// Ensure only the last message with questions shows FAQ
return ensureOnlyLastFAQVisible(newMessages);
});
setTmpMsg("");
setTmpMsgType("");
currentSystemMessageRef.current = null;
setIsRunning(false);
});
socket.on("tool_feedback", ({ feedback }) => {
// Remove loading indicators when receiving tool feedback
setMessages((prev) =>
prev.filter((msg) => msg.type !== "loading")
);
setTmpMsg(cleanTmpMsg(feedback));
setTmpMsgType("tool_feedback");
});
socket.on("thread_created", ({ threadId }) => {
setCurrentThreadId(threadId);
const key = `tidiko_thread_${widgetAssistantId}`;
window.localStorage.setItem(key, threadId);
});
socket.on("thread_messages", (data) => {
const msgs = Array.isArray(data.messages) ? data.messages : [];
if (options.defaultMessage) {
msgs.unshift({
type: "system",
content: options.defaultMessage,
});
}
const formattedMessages = msgs.map((m) => {
const baseMessage = {
type: m.type === "user" ? "user" : "system",
textClass:
m.type === "user"
? userTextClass
: assistantTextClass,
};
// Process system messages for questions
if (m.type !== "user") {
const processed = processMessageContent(m.content);
return {
...baseMessage,
content: processed.content,
questions: processed.questions,
showFAQ: processed.showFAQ,
showSkeletonFAQ: processed.showSkeletonFAQ,
};
}
return {
...baseMessage,
content: m.content,
};
});
// Ensure only the last message with questions shows FAQ
const messagesWithCorrectFAQVisibility =
ensureOnlyLastFAQVisible(formattedMessages);
// Store loaded messages but only show them if privacy is accepted
setLoadedMessages(messagesWithCorrectFAQVisibility);
setIsHistoryLoading(false);
// Check if privacy is accepted to show messages
const accepted = document.cookie.includes(
"tidiko_widget_privacy_accepted="
);
if (accepted) {
setMessages(messagesWithCorrectFAQVisibility);
setShowTools(true);
// Hide FAQ if there are existing messages (excluding default message)
if (data.messages && data.messages.length > 0) {
setShowFAQ(false);
} else {
setShowFAQ(true);
}
} else {
// Privacy not accepted - don't show messages yet
setShowFAQ(false);
}
});
socket.on("error", (error) => {
setMessages((prev) => [
...prev,
{
type: "system",
content: `Mi dispiace, si è verificato un errore: ${
error.error || "Unknown"
}`,
textClass: assistantTextClass,
},
]);
setIsRunning(false);
});
};
setupSocket();
return () => {
if (socketRef.current) {
socketRef.current.disconnect();
}
};
}, [
socketUrl,
assistantTextClass,
userTextClass,
options.defaultMessage,
widgetAssistantId,
cleanTmpMsg,
processMessageContent,
ensureOnlyLastFAQVisible,
]);