diff --git a/app/components/ChatWidget.tsx b/app/components/ChatWidget.tsx index e18efe0..2bb3407 100644 --- a/app/components/ChatWidget.tsx +++ b/app/components/ChatWidget.tsx @@ -51,6 +51,14 @@ export default function ChatWidget() { } }, [isOpen]); + // Helper function to decode HTML entities + const decodeHtmlEntities = (text: string): string => { + if (!text || typeof text !== 'string') return text; + const textarea = document.createElement('textarea'); + textarea.innerHTML = text; + return textarea.value; + }; + // Load messages from localStorage useEffect(() => { if (typeof window !== "undefined") { @@ -61,6 +69,7 @@ export default function ChatWidget() { setMessages( parsed.map((m: Message) => ({ ...m, + text: decodeHtmlEntities(m.text), // Decode HTML entities when loading timestamp: new Date(m.timestamp), })), ); @@ -72,7 +81,7 @@ export default function ChatWidget() { setMessages([ { id: "welcome", - text: "Hi! I'm Dennis's AI assistant. Ask me anything about his skills, projects, or experience! 🚀", + text: "Hi! I'm Dennis's AI assistant. Ask me anything about his skills, projects, or experience! 🚀", sender: "bot", timestamp: new Date(), }, @@ -128,12 +137,8 @@ export default function ChatWidget() { // Decode HTML entities in the reply let replyText = data.reply || "Sorry, I couldn't process that. Please try again."; - // Decode HTML entities client-side as well (double safety) - if (typeof window !== 'undefined') { - const textarea = document.createElement('textarea'); - textarea.innerHTML = replyText; - replyText = textarea.value; - } + // Decode HTML entities client-side (double safety) + replyText = decodeHtmlEntities(replyText); const botMessage: Message = { id: (Date.now() + 1).toString(), @@ -237,7 +242,7 @@ export default function ChatWidget() {

- Dennis's AI Assistant + Dennis{'\''}s AI Assistant

Always online

@@ -370,7 +375,7 @@ export default function ChatWidget() { {/* Quick Actions */}
{[ - "What are Dennis's skills?", + "What are Dennis's skills?", "Tell me about his projects", "How can I contact him?", ].map((suggestion, index) => ( diff --git a/lib/html-decode.ts b/lib/html-decode.ts index 13b2911..e46d1cd 100644 --- a/lib/html-decode.ts +++ b/lib/html-decode.ts @@ -21,7 +21,7 @@ export function decodeHtmlEntitiesServer(text: string): string { return text; } - // Map of common HTML entities + // Map of common HTML entities (including all variations of apostrophe) const entityMap: Record = { ''': "'", '"': '"', @@ -33,9 +33,26 @@ export function decodeHtmlEntitiesServer(text: string): string { '/': '/', '`': '`', '=': '=', + '’': "'", + '‘': "'", + '”': '"', + '“': '"', }; - return text.replace(/&[#\w]+;/g, (entity) => { - return entityMap[entity] || entity; + // First replace known entities + let decoded = text; + for (const [entity, replacement] of Object.entries(entityMap)) { + decoded = decoded.replace(new RegExp(entity, 'gi'), replacement); + } + + // Then handle numeric entities (' ' etc.) + decoded = decoded.replace(/&#(\d+);/g, (match, num) => { + return String.fromCharCode(parseInt(num, 10)); }); + + decoded = decoded.replace(/&#x([0-9a-f]+);/gi, (match, hex) => { + return String.fromCharCode(parseInt(hex, 16)); + }); + + return decoded; }