import React, { useEffect, useState } from 'react'; import { createPortal } from 'react-dom'; import { HiSparkles as Sparkles } from 'react-icons/hi'; import { FaCheck as Check, FaFileAlt as FileText, FaPencilAlt as PenLine, FaRegCopy as Copy, } from 'react-icons/fa'; import { prepareProductSuggestionForGap } from '../../service/agent-analytics/agent-analytics.service'; import type { IAgentGap, IAgentGapProductSuggestionResponse, } from '../../service/agent-analytics/agent-analytics.interface'; /** Server-enforced max length of the "suggest more" hint - matches the * ``extra_prompt`` cap on ``AgentGapProductSuggestionRequest`` in * recomaze-agent. */ const SUGGEST_MORE_MAX_LENGTH: number = 600; interface GapProductSuggestionModalProps { /** Gap row the merchant clicked "Suggest snippet" on. */ gap: IAgentGap; /** Initial draft returned by the parent's first call to the endpoint. */ initial: IAgentGapProductSuggestionResponse; /** Called when the merchant cancels or closes the modal. */ onClose: () => void; } /** * Modal that renders the LLM-drafted product-page snippet (FAQ Q&A or * description rewrite) for one product-related gap. The merchant copies * ``suggested_text`` into the storefront per ``placement_hint``; the * "Suggest more" zone re-fires the same endpoint with the merchant's * hint so the draft can be regenerated in place. * * @param {GapProductSuggestionModalProps} props - Component props. * @return {JSX.Element} Modal overlay. */ function GapProductSuggestionModal({ gap, initial, onClose, }: GapProductSuggestionModalProps): JSX.Element { const [language, setLanguage] = useState(initial.language || 'en'); const [productTitle, setProductTitle] = useState( initial.product?.title || gap.target ); const [productSku, setProductSku] = useState( initial.product?.sku || '' ); const [currentDescription, setCurrentDescription] = useState( initial.product?.current_description || '' ); const [customerQuery, setCustomerQuery] = useState( initial.customer_query || '' ); const [problem, setProblem] = useState(initial.problem || ''); const [suggestionType, setSuggestionType] = useState<'faq' | 'description'>( initial.suggestion_type || 'description' ); const [suggestedText, setSuggestedText] = useState( initial.suggested_text || '' ); const [placementHint, setPlacementHint] = useState( initial.placement_hint || '' ); const [extraPrompt, setExtraPrompt] = useState(''); const [regenerating, setRegenerating] = useState(false); const [error, setError] = useState(null); const [copied, setCopied] = useState(false); useEffect(() => { setLanguage(initial.language || 'en'); setProductTitle(initial.product?.title || gap.target); setProductSku(initial.product?.sku || ''); setCurrentDescription(initial.product?.current_description || ''); setCustomerQuery(initial.customer_query || ''); setProblem(initial.problem || ''); setSuggestionType(initial.suggestion_type || 'description'); setSuggestedText(initial.suggested_text || ''); setPlacementHint(initial.placement_hint || ''); setExtraPrompt(''); setError(null); setCopied(false); }, [initial, gap.target]); useEffect(() => { const handleEscape = (event: KeyboardEvent) => { if (event.key === 'Escape' && !regenerating) onClose(); }; document.addEventListener('keydown', handleEscape); return () => { document.removeEventListener('keydown', handleEscape); }; }, [onClose, regenerating]); const handleRegenerate = async (): Promise => { if (regenerating) return; setError(null); setRegenerating(true); try { const trimmedHint: string = extraPrompt.trim(); const firstSample = gap.samples[0]; const result: IAgentGapProductSuggestionResponse | null = await prepareProductSuggestionForGap({ target: gap.target, category: gap.category, sample_fingerprint: firstSample?.fingerprint ?? null, sample_message_index: firstSample?.message_index ?? null, extra_prompt: trimmedHint || null, }); if (!result) { setError('Failed to regenerate the snippet.'); return; } setLanguage(result.language || 'en'); setProductTitle(result.product?.title || gap.target); setProductSku(result.product?.sku || ''); setCurrentDescription(result.product?.current_description || ''); setCustomerQuery(result.customer_query || ''); setProblem(result.problem || ''); setSuggestionType(result.suggestion_type || 'description'); setSuggestedText(result.suggested_text || ''); setPlacementHint(result.placement_hint || ''); setExtraPrompt(''); setCopied(false); } catch (regenerateError) { setError( regenerateError instanceof Error ? regenerateError.message : 'Failed to regenerate the snippet.' ); } finally { setRegenerating(false); } }; const handleCopy = async (): Promise => { if (!suggestedText) return; try { await navigator.clipboard.writeText(suggestedText); setCopied(true); window.setTimeout(() => setCopied(false), 2000); } catch { setCopied(false); } }; if (typeof document === 'undefined') return <>; return createPortal(
!regenerating && onClose()} />

Product page suggestion

Paste this snippet into the product page so the agent has the answer next time a customer asks about{' '} {gap.target}. Detected language: {language}.

Product

{productTitle || '—'}

{productSku && (

SKU: {productSku}

)}

Customer asked

{customerQuery || '—'}

{problem && (

What was missing

{problem}

)} {currentDescription && (

Current product description

                {currentDescription}
              
)}

Suggested snippet

{suggestionType === 'faq' ? ( <> Add as FAQ entry ) : ( <> Add to product description )} {placementHint && ( · {placementHint} )}