Add clickable error modal for failed proof analyses

When a proof analysis fails, "Analysis failed." is now a clickable
underlined link that opens a modal showing:
- The lead agent summary explaining why the analysis failed
- Details for each agent that returned an Error status

Applied to both Campaigns and Projects views.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
michael 2026-02-12 18:09:15 -06:00
parent 1800e71229
commit a15bce8796
2 changed files with 171 additions and 3 deletions

View file

@ -893,6 +893,77 @@ const DeleteConfirmationModal: React.FC<{
);
};
const AnalysisErrorModal: React.FC<{
isOpen: boolean;
onClose: () => void;
proofName: string;
feedback: AgentReview | null;
}> = ({ isOpen, onClose, proofName, feedback }) => {
if (!isOpen || !feedback) return null;
const agentEntries: { label: string; review: { ragStatus: string; feedback: string } }[] = [
{ label: 'Legal Agent', review: feedback.legalAgentReview },
{ label: 'Brand Agent', review: feedback.brandAgentReview },
{ label: 'Channel Best Practices Agent', review: feedback.channelBestPracticesAgentReview },
{ label: 'Channel Tech Specs Agent', review: feedback.channelTechSpecsAgentReview },
];
const failedAgents = agentEntries.filter(a => a.review.ragStatus === 'Error');
return (
<div
className="fixed inset-0 bg-black bg-opacity-60 flex items-center justify-center z-50 transition-opacity duration-300"
onClick={onClose}
aria-modal="true"
role="dialog"
>
<div
className="bg-white rounded-[10px] shadow-xl p-6 sm:p-8 w-full max-w-lg max-h-[80vh] overflow-y-auto transform transition-all"
onClick={e => e.stopPropagation()}
>
<div className="flex items-start gap-3 mb-4">
<div className="flex-shrink-0 p-2 bg-red-100 rounded-full">
<ExclamationTriangleIcon className="h-6 w-6 text-red-600" />
</div>
<div>
<h3 className="text-xl font-bold text-primary-blue">Analysis Error</h3>
<p className="text-sm text-grey-700 mt-0.5">{proofName}</p>
</div>
</div>
{feedback.leadAgentSummary && (
<div className="bg-red-50 border border-red-200 rounded-lg p-4 mb-4">
<h4 className="text-sm font-semibold text-red-800 mb-1">Summary</h4>
<p className="text-sm text-red-700">{feedback.leadAgentSummary}</p>
</div>
)}
{failedAgents.length > 0 && (
<div className="space-y-3">
<h4 className="text-sm font-semibold text-grey-700">Agent Details</h4>
{failedAgents.map(agent => (
<div key={agent.label} className="bg-grey-100 rounded-lg p-3">
<p className="text-sm font-medium text-primary-blue">{agent.label}</p>
<p className="text-sm text-grey-700 mt-1">{agent.review.feedback}</p>
</div>
))}
</div>
)}
<div className="mt-6 flex justify-end">
<button
type="button"
onClick={onClose}
className="border-2 border-active-blue text-active-blue font-semibold py-2 px-6 rounded-full hover:bg-active-blue hover:text-white transition-colors"
>
Close
</button>
</div>
</div>
</div>
);
};
const CampaignDeleteConfirmationModal: React.FC<{
isOpen: boolean;
onClose: () => void;
@ -958,6 +1029,7 @@ const CampaignDetail: React.FC<{
const [proofToDelete, setProofToDelete] = useState<any | null>(null);
const [proofForUpload, setProofForUpload] = useState<any | null>(null);
const [isExporting, setIsExporting] = useState(false);
const [errorProof, setErrorProof] = useState<any | null>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
const proofs = campaignProofs[campaignName] || [];
@ -1107,6 +1179,13 @@ const CampaignDetail: React.FC<{
existingProofNames={existingProofNames}
/>
<AnalysisErrorModal
isOpen={!!errorProof}
onClose={() => setErrorProof(null)}
proofName={errorProof?.proofName || ''}
feedback={errorProof?.versions?.[0]?.feedback || null}
/>
<section>
<div className="mb-6 flex justify-end gap-3">
<button
@ -1173,7 +1252,12 @@ const CampaignDetail: React.FC<{
<td className="px-6 py-4 whitespace-nowrap text-sm text-primary-blue">{proof.proofType || 'N/A'}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-error font-semibold" colSpan={2}>
<div className="flex items-center justify-between">
<span>Analysis failed.</span>
<button
onClick={() => setErrorProof(proof)}
className="text-error hover:text-red-800 underline underline-offset-2 cursor-pointer transition-colors"
>
Analysis failed.
</button>
<button
onClick={() => onRetryAnalysis(campaignName, proof.tempId)}
className="flex items-center gap-1.5 text-xs font-semibold text-active-blue hover:text-primary-blue whitespace-nowrap px-3 py-1.5 rounded-full bg-active-blue/10 hover:bg-active-blue/20 transition-colors"

View file

@ -5,7 +5,7 @@ import { ArrowLeftIcon } from './icons/ArrowLeftIcon';
import type { AgentReview, FlaggedItem, ResolvedItem } from '../types';
import { FeedbackReport } from './FeedbackReport';
import { CreateProjectModal } from './CreateProjectModal';
import { CheckCircleIcon, ArrowPathIcon } from './icons/StatusIcons';
import { CheckCircleIcon, ArrowPathIcon, ExclamationTriangleIcon } from './icons/StatusIcons';
import { AssetPreview } from './AssetPreview';
import { HistoryIcon } from './icons/HistoryIcon';
import { DropdownOptions } from '../App';
@ -481,6 +481,77 @@ const LoadingCell: React.FC<{ progress: { completed: number; total: number } }>
);
};
const AnalysisErrorModal: React.FC<{
isOpen: boolean;
onClose: () => void;
assetName: string;
feedback: AgentReview | null;
}> = ({ isOpen, onClose, assetName, feedback }) => {
if (!isOpen || !feedback) return null;
const agentEntries: { label: string; review: { ragStatus: string; feedback: string } }[] = [
{ label: 'Legal Agent', review: feedback.legalAgentReview },
{ label: 'Brand Agent', review: feedback.brandAgentReview },
{ label: 'Channel Best Practices Agent', review: feedback.channelBestPracticesAgentReview },
{ label: 'Channel Tech Specs Agent', review: feedback.channelTechSpecsAgentReview },
];
const failedAgents = agentEntries.filter(a => a.review.ragStatus === 'Error');
return (
<div
className="fixed inset-0 bg-black bg-opacity-60 flex items-center justify-center z-50 transition-opacity duration-300"
onClick={onClose}
aria-modal="true"
role="dialog"
>
<div
className="bg-white rounded-[10px] shadow-xl p-6 sm:p-8 w-full max-w-lg max-h-[80vh] overflow-y-auto transform transition-all"
onClick={e => e.stopPropagation()}
>
<div className="flex items-start gap-3 mb-4">
<div className="flex-shrink-0 p-2 bg-red-100 rounded-full">
<ExclamationTriangleIcon className="h-6 w-6 text-red-600" />
</div>
<div>
<h3 className="text-xl font-bold text-primary-blue">Analysis Error</h3>
<p className="text-sm text-grey-700 mt-0.5">{assetName}</p>
</div>
</div>
{feedback.leadAgentSummary && (
<div className="bg-red-50 border border-red-200 rounded-lg p-4 mb-4">
<h4 className="text-sm font-semibold text-red-800 mb-1">Summary</h4>
<p className="text-sm text-red-700">{feedback.leadAgentSummary}</p>
</div>
)}
{failedAgents.length > 0 && (
<div className="space-y-3">
<h4 className="text-sm font-semibold text-grey-700">Agent Details</h4>
{failedAgents.map(agent => (
<div key={agent.label} className="bg-grey-100 rounded-lg p-3">
<p className="text-sm font-medium text-primary-blue">{agent.label}</p>
<p className="text-sm text-grey-700 mt-1">{agent.review.feedback}</p>
</div>
))}
</div>
)}
<div className="mt-6 flex justify-end">
<button
type="button"
onClick={onClose}
className="border-2 border-active-blue text-active-blue font-semibold py-2 px-6 rounded-full hover:bg-active-blue hover:text-white transition-colors"
>
Close
</button>
</div>
</div>
</div>
);
};
const DeleteConfirmationModal: React.FC<{
isOpen: boolean;
onClose: () => void;
@ -538,6 +609,7 @@ const ProjectDetail: React.FC<{
const [isUploadFormVisible, setIsUploadFormVisible] = useState(false);
const [assetToDelete, setAssetToDelete] = useState<any | null>(null);
const [assetForUpload, setAssetForUpload] = useState<any | null>(null);
const [errorAsset, setErrorAsset] = useState<any | null>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
const assets = projectAssets[projectName] || [];
@ -585,6 +657,13 @@ const ProjectDetail: React.FC<{
assetName={assetToDelete?.assetName || ''}
/>
<AnalysisErrorModal
isOpen={!!errorAsset}
onClose={() => setErrorAsset(null)}
assetName={errorAsset?.assetName || ''}
feedback={errorAsset?.versions?.[0]?.feedback || null}
/>
<input
type="file"
ref={fileInputRef}
@ -687,7 +766,12 @@ const ProjectDetail: React.FC<{
<td className="px-6 py-4 whitespace-nowrap text-sm text-black-title">{asset.subChannel}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-error font-semibold" colSpan={2}>
<div className="flex items-center justify-between">
<span>Analysis failed.</span>
<button
onClick={() => setErrorAsset(asset)}
className="text-error hover:text-red-800 underline underline-offset-2 cursor-pointer transition-colors"
>
Analysis failed.
</button>
<button
onClick={() => onRetryAnalysis(projectName, asset.tempId)}
className="flex items-center gap-1.5 text-xs font-semibold text-active-blue hover:text-primary-blue whitespace-nowrap px-3 py-1.5 rounded-full bg-active-blue/10 hover:bg-active-blue/20 transition-colors"