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:
parent
1800e71229
commit
a15bce8796
2 changed files with 171 additions and 3 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue