import React, { useEffect, useMemo, useState } from 'react'; import { CardSkeleton } from '../agent-analytics/CardSkeleton'; import EmptyState from '../agent-analytics/EmptyState'; import InfoTooltip from '../agent-analytics/InfoTooltip'; import type { PromptCategory, PromptResponse, } from '../../service/visibility/visibility.interface'; import { listPrompts } from '../../service/visibility/visibility.service'; import { toneToBadgeClass } from './helpers'; import EditPromptModal from './EditPromptModal'; import PromptResponseModal from './PromptResponseModal'; import RegeneratePromptsModal from './RegeneratePromptsModal'; interface TrackedPromptsListProps { clientId: string; token: string; brandId: string; brandDomain: string | null; } const CATEGORY_META: Record< PromptCategory, { label: string; color: string; description: string } > = { product: { label: 'Product', color: '#2c6ecb', description: "Direct product-comparison prompts - 'best …', 'where can I buy …', 'which …'.", }, scenario: { label: 'Scenario', color: '#8a3ffc', description: "Situational prompts - 'I need … for …', 'what do I buy for a wedding'.", }, best_of: { label: 'Best of', color: '#3f7b00', description: "Listicle-style prompts - 'top N brands for …', 'best companies that …'.", }, geo: { label: 'Geo', color: '#e57200', description: 'Location-grounded prompts - city/country/market hints in the query.', }, }; const CATEGORY_ORDER: PromptCategory[] = [ 'product', 'scenario', 'best_of', 'geo', ]; /** * Section showing the 16 monitored prompts grouped by category. Clicking a * prompt opens {@link PromptResponseModal} with per-engine evidence. */ const TrackedPromptsList = ({ clientId, token, brandId, brandDomain, }: TrackedPromptsListProps): JSX.Element => { const [prompts, setPrompts] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [selectedPromptId, setSelectedPromptId] = useState(null); const [editingPromptId, setEditingPromptId] = useState(null); const [regenerateOpen, setRegenerateOpen] = useState(false); const refreshPrompts = (): void => { listPrompts(clientId, token, brandId) .then(res => { setPrompts(res.prompts || []); }) .catch(() => { // Silent: optimistic merge below keeps the list usable. }); }; const handlePromptSaved = (updated: PromptResponse): void => { setPrompts(prev => { const matched = prev.some( existing => existing.prompt_id === updated.prompt_id ); if (!matched) { return [...prev, updated]; } return prev.map(existing => existing.prompt_id === updated.prompt_id ? updated : existing ); }); refreshPrompts(); }; useEffect(() => { let cancelled = false; setLoading(true); setError(null); listPrompts(clientId, token, brandId) .then(res => { if (cancelled) return; setPrompts(res.prompts || []); }) .catch(err => { if (cancelled) return; setError( err instanceof Error ? err.message : 'Failed to load prompts.' ); }) .finally(() => { if (!cancelled) setLoading(false); }); return () => { cancelled = true; }; }, [clientId, token, brandId]); const grouped = useMemo(() => { const buckets: Record = { product: [], scenario: [], best_of: [], geo: [], }; for (const p of prompts) { if (!p.enabled) continue; if (p.category in buckets) { buckets[p.category as PromptCategory].push(p); } } return buckets; }, [prompts]); const enabledCount = useMemo( () => prompts.filter(p => p.enabled).length, [prompts] ); const selectedPrompt = selectedPromptId != null ? (prompts.find(row => row.prompt_id === selectedPromptId) ?? null) : null; const editingPrompt = editingPromptId != null ? (prompts.find(row => row.prompt_id === editingPromptId) ?? null) : null; return ( <>

{`Tracked prompts${enabledCount ? ` (${enabledCount})` : ''}`}

Not matching what you sell? Rebuild them from your live catalog.

{error && (

{error}

)} {loading ? (
) : enabledCount === 0 ? ( ) : (
{CATEGORY_ORDER.map(cat => { const items = grouped[cat]; const meta = CATEGORY_META[cat]; if (items.length === 0) return null; return (
{items.map(prompt => (
))}
); })}
)}
setSelectedPromptId(null)} clientId={clientId} token={token} brandId={brandId} brandDomain={brandDomain} /> setEditingPromptId(null)} onSaved={handlePromptSaved} clientId={clientId} token={token} brandId={brandId} /> setRegenerateOpen(false)} onRegenerated={next => setPrompts(next)} clientId={clientId} token={token} brandId={brandId} /> ); }; export default TrackedPromptsList;