import React, { useCallback, useEffect, useMemo, useState } from 'react';
import type {
PromptResponse,
PromptResponseEntry,
PromptResponsesResponse,
} from '../../service/visibility/visibility.interface';
import { getPromptResponses } from '../../service/visibility/visibility.service';
import { isOwnDomain, sentimentToTone, toneToBadgeClass } from './helpers';
import Modal from './Modal';
interface PromptResponseModalProps {
open: boolean;
prompt: PromptResponse | null;
onClose: () => void;
clientId: string;
token: string;
brandId: string;
brandDomain: string | null;
}
const SNIPPET_PREVIEW_CHARS = 500;
const EntryCard = ({
entry,
brandDomain,
}: {
entry: PromptResponseEntry;
brandDomain: string | null;
}): JSX.Element => {
const [expanded, setExpanded] = useState(false);
const snippet = entry.response_snippet ?? '';
const shouldFold = snippet.length > SNIPPET_PREVIEW_CHARS;
const visibleSnippet =
shouldFold && !expanded
? `${snippet.slice(0, SNIPPET_PREVIEW_CHARS).trimEnd()}…`
: snippet;
const ownCitation = useMemo(
() => entry.sources.some(source => isOwnDomain(source, brandDomain)),
[entry.sources, brandDomain]
);
return (
{entry.engine.toUpperCase()}
{entry.scan_date && (
{`Scan date: ${entry.scan_date.slice(0, 10)}`}
)}
{entry.brand_mentioned ? (
{entry.brand_position
? `Mentioned · position #${entry.brand_position}`
: 'Mentioned'}
) : (
Not mentioned
)}
{entry.sentiment && (
{entry.sentiment}
)}
{ownCitation && (
Cited from your domain
)}
{snippet && (
{visibleSnippet}
{shouldFold && (
)}
)}
{entry.sources.length > 0 && (
Sources cited
{entry.sources.map((source, idx) => {
const own = isOwnDomain(source, brandDomain);
return (
-
{own && (
)}
{source}
);
})}
)}
);
};
/**
* Modal showing every historical LLM response recorded for a tracked
* prompt. Used as the "proof layer" behind visibility scores.
*/
const PromptResponseModal = ({
open,
prompt,
onClose,
clientId,
token,
brandId,
brandDomain,
}: PromptResponseModalProps): JSX.Element => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const load = useCallback(async () => {
if (!prompt) return;
setLoading(true);
setError(null);
try {
const response = await getPromptResponses(
clientId,
token,
brandId,
prompt.prompt_id
);
setData(response);
} catch (err) {
setError(
err instanceof Error ? err.message : 'Failed to load responses.'
);
} finally {
setLoading(false);
}
}, [clientId, token, brandId, prompt]);
useEffect(() => {
if (open && prompt) {
load();
}
if (!open) {
setData(null);
setError(null);
}
}, [open, prompt, load]);
return (
Close
}
>
{prompt && (
{prompt.category}
{prompt.language.toUpperCase()}
{prompt.prompt_text}
)}
{error && (
)}
{loading ? (
) : data && data.entries.length > 0 ? (
{data.entries.map((entry, idx) => (
))}
) : (
No scan responses recorded yet for this prompt. Run a weekly scan to
populate this history.
)}
);
};
export default PromptResponseModal;