import { useEffect, useMemo, useRef, useState } from 'react'; const QUICK_REPLIES = [ { icon: '🔍', text: 'Find me a product' }, { icon: '❤️', text: 'Track my order' }, { icon: '🛒', text: 'View my cart' }, { icon: '🚚', text: "What's your shipping policy?" }, { icon: '✅', text: 'How do I return an item?' }, ]; function ChatIconSvg({ className }) { return ( ); } function ThemeIcon({ mode, className }) { if (mode === 'light') { return ( ); } return ( ); } function ClearIcon({ className }) { return ( ); } function CloseIcon({ className }) { return ( ); } function chatStorageKey(isLoggedIn) { return isLoggedIn ? 'acosmaiaHistoryUser' : 'acosmaiaHistorySession'; } function decodeHtmlEntities(value) { if (typeof value !== 'string') return value ?? ''; const textarea = document.createElement('textarea'); textarea.innerHTML = value; return textarea.value; } function renderInlineBold(text) { const parts = String(text).split(/(\*\*[^*]+\*\*)/g); return parts.map((part, index) => { if (part.startsWith('**') && part.endsWith('**') && part.length > 4) { return {part.slice(2, -2)}; } return {part}; }); } function renderMessageContent(value) { const decoded = decodeHtmlEntities(value || ''); const lines = String(decoded).split('\n'); return lines.map((line, index) => (
{renderInlineBold(line)}
)); } function TypingIndicator() { return (
); } function ProductCard({ product }) { const hasSale = Boolean(product.sale_price); const name = decodeHtmlEntities(product.name || ''); const description = decodeHtmlEntities(product.description || 'View product details.'); const price = decodeHtmlEntities(product.price || ''); const salePrice = decodeHtmlEntities(product.sale_price || ''); const [isAdding, setIsAdding] = useState(false); const [added, setAdded] = useState(false); const averageRating = Number(product.average_rating || 0); const reviewCount = Number(product.review_count || 0); const showRating = averageRating > 0 || reviewCount > 0; const getAjaxAddToCartUrl = () => { const wcAjaxTemplate = window.wc_add_to_cart_params?.wc_ajax_url; if (typeof wcAjaxTemplate === 'string' && wcAjaxTemplate.includes('%%endpoint%%')) { return wcAjaxTemplate.replace('%%endpoint%%', 'add_to_cart'); } const fallback = new URL(window.location.href); fallback.search = ''; fallback.searchParams.set('wc-ajax', 'add_to_cart'); return fallback.toString(); }; const getCartUrl = () => { const localized = typeof window.acosmaiaChat?.cart_url === 'string' ? window.acosmaiaChat.cart_url : ''; if (localized) return localized; const wcCart = window.wc_add_to_cart_params?.cart_url; if (typeof wcCart === 'string' && wcCart) return wcCart; const fallback = new URL(window.location.href); fallback.search = ''; fallback.hash = ''; fallback.pathname = '/cart/'; return fallback.toString(); }; const handleAddToCart = async (e) => { e.preventDefault(); e.stopPropagation(); if (added) { window.location.href = getCartUrl(); return; } if (isAdding) return; const productId = Number(product.id || 0); if (!productId) return; setIsAdding(true); try { const body = new URLSearchParams(); body.set('product_id', String(productId)); body.set('quantity', '1'); const response = await fetch(getAjaxAddToCartUrl(), { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', }, credentials: 'same-origin', body: body.toString(), }); const data = await response.json().catch(() => null); if (!response.ok || !data || data.error) { throw new Error('Add to cart failed'); } // Let WooCommerce themes refresh cart fragments/minicart if they listen. if (window.jQuery && typeof window.jQuery === 'function') { window.jQuery(document.body).trigger('added_to_cart', [data.fragments, data.cart_hash]); } setAdded(true); } catch { // Fallback to classic add-to-cart URL if AJAX fails. const fallbackUrl = typeof product.add_to_cart_url === 'string' ? product.add_to_cart_url.trim() : ''; if (fallbackUrl) { window.location.href = fallbackUrl; } } finally { setIsAdding(false); } }; return ( {product.image ? {name} :
No image
}

{name}

{description}

{showRating ? (
Rating: {averageRating > 0 ? averageRating.toFixed(1) : 'N/A'} ({reviewCount} reviews)
) : null}
{product.stock_status === 'instock' ? 'In stock' : 'Out of stock'} {hasSale ? ( <> {salePrice} {price} ) : ( {price} )}
{product.add_to_cart_url ? ( ) : null}
); } export default function App() { const { assistant_name, welcome_message, api_url, nonce, position = 'bottom-right', primary_color = '#0073aa', default_theme = 'light', is_logged_in: isLoggedIn = false, api_configured = false, } = window.acosmaiaChat || {}; const [isOpen, setIsOpen] = useState(false); const [messages, setMessages] = useState([]); const [input, setInput] = useState(''); const [isTyping, setIsTyping] = useState(false); const [theme, setTheme] = useState(default_theme); const [showSuggestions, setShowSuggestions] = useState(true); const messagesEnd = useRef(null); const storageKey = useMemo(() => chatStorageKey(isLoggedIn), [isLoggedIn]); useEffect(() => { document.documentElement.style.setProperty('--woo-chat-primary', primary_color); }, [primary_color]); useEffect(() => { const raw = localStorage.getItem(storageKey); if (!raw) return; try { const parsed = JSON.parse(raw); if (Array.isArray(parsed.messages)) { setMessages(parsed.messages); } const parsedDefaultTheme = parsed?.default_theme === 'dark' ? 'dark' : 'light'; if ((parsed.theme === 'light' || parsed.theme === 'dark') && parsedDefaultTheme === default_theme) { setTheme(parsed.theme); } } catch { localStorage.removeItem(storageKey); } }, [storageKey]); useEffect(() => { localStorage.setItem(storageKey, JSON.stringify({ messages, theme, default_theme })); messagesEnd.current?.scrollIntoView({ behavior: 'smooth' }); }, [messages, theme, default_theme, storageKey]); useEffect(() => { if (!isOpen) return; // Wait for chat window mount and then jump to latest message. requestAnimationFrame(() => { messagesEnd.current?.scrollIntoView({ behavior: 'auto', block: 'end' }); }); }, [isOpen]); useEffect(() => { if (isOpen && messages.length === 0) { const initialMessage = api_configured ? (welcome_message || 'Hi! How can I help you today?') : 'Please configure the AI API key in the admin settings to use this chat feature.'; setMessages([{ role: 'assistant', content: initialMessage, products: [] }]); } }, [isOpen, messages.length, welcome_message, api_configured]); const send = async (forcedMessage) => { const text = (forcedMessage ?? input).trim(); if (!text || isTyping) return; const userMsg = { role: 'user', content: text, products: [] }; setMessages((prev) => [...prev, userMsg]); setInput(''); setIsTyping(true); try { const res = await fetch(api_url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-AI-Nonce': nonce, }, body: JSON.stringify({ message: text }), }); const data = await res.json(); setMessages((prev) => [ ...prev, { role: 'assistant', content: data.message || data.error || 'No response.', show_more_url: typeof data.show_more_url === 'string' ? data.show_more_url : '', show_more_label: typeof data.show_more_label === 'string' && data.show_more_label ? data.show_more_label : 'Show more products', products: Array.isArray(data.products) ? data.products : [], }, ]); } catch { setMessages((prev) => [...prev, { role: 'assistant', content: 'Connection error. Please try again.', show_more_url: '', show_more_label: '', products: [], }]); } finally { setIsTyping(false); } }; const clear = () => { setMessages([]); localStorage.removeItem(storageKey); }; const toggleOpen = () => { setIsOpen((prev) => !prev); }; const bubbleClass = `woo-chat-bubble ${position}`; const windowClass = `woo-chat-window ${position} ${theme}`; return ( <> {isOpen ? (
{assistant_name || 'AI Assistant'}
{messages.map((m, i) => (
{typeof m.content === 'string' && m.content.trim() ? (
{renderMessageContent(m.content)}
) : null} {m.role === 'assistant' && Array.isArray(m.products) && m.products.length > 0 ? (
{m.products.slice(0, 6).map((product) => ( ))}
) : null} {m.role === 'assistant' && m.show_more_url ? (
{m.show_more_label || 'Show more products'}
) : null}
))} {isTyping ? : null}
{showSuggestions && (
{api_configured && QUICK_REPLIES.map((reply) => ( ))}
)}
setInput(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && send()} placeholder={api_configured ? "Ask about products, price, stock, or policies..." : "API key not configured"} disabled={!api_configured} />
) : null} ); }