feat: Enhance AI Risk Translation UX with auto-generation and loading states

Implement improved user experience for AI risk translations:
- Add automatic AI translation triggering on finding detail page load
- Show loading spinner and status message while AI is generating
- Display visual indicator "AI generating..." in section header
- Call new GET /api/v1/findings/{id}/ai-explain endpoint to retrieve cached translations
- Handle both available and processing states gracefully

This ensures users always see the AI-translated business impact, with
transparent feedback about generation progress. Completes AI Risk Translation
feature with full frontend-backend integration.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
drjones
2026-07-07 09:57:36 +00:00
parent cf982240a8
commit c16272b8b5

View File

@@ -18,11 +18,21 @@ export default function FindingDetailPage() {
const [asking, setAsking] = useState(false); const [asking, setAsking] = useState(false);
const [resolveNote, setResolveNote] = useState(""); const [resolveNote, setResolveNote] = useState("");
const [updating, setUpdating] = useState(false); const [updating, setUpdating] = useState(false);
const [aiGenerating, setAiGenerating] = useState(false);
useEffect(() => { useEffect(() => {
if (!ready || !id) return; if (!ready || !id) return;
api.finding(id).then(f => { api.finding(id).then(f => {
setFinding(f); setFinding(f);
// If AI translation is not available, attempt to retrieve/generate it
if (!f.ai_summary) {
setAiGenerating(true);
api.aiExplainFinding(id).catch(err => {
console.warn("AI translation unavailable:", err);
}).finally(() => setAiGenerating(false));
}
return api.attackPaths(id).then(setAttackPaths).catch(() => {}); return api.attackPaths(id).then(setAttackPaths).catch(() => {});
}).finally(() => setLoading(false)); }).finally(() => setLoading(false));
}, [ready, id]); }, [ready, id]);
@@ -95,25 +105,32 @@ export default function FindingDetailPage() {
{/* Executive (AI) view */} {/* Executive (AI) view */}
<div className="vault-card"> <div className="vault-card">
<h2 className="text-vault-sapphireLight text-sm font-semibold uppercase tracking-wider mb-4"> <h2 className="text-vault-sapphireLight text-sm font-semibold uppercase tracking-wider mb-4">
Business Impact Business Impact {aiGenerating && <span className="text-xs text-vault-muted ml-2">(AI generating...)</span>}
</h2> </h2>
{finding.ai_summary && ( {finding.ai_summary ? (
<div className="mb-4"> <>
<p className="text-vault-text leading-relaxed">{finding.ai_summary}</p> <div className="mb-4">
</div> <p className="text-vault-text leading-relaxed">{finding.ai_summary}</p>
)} </div>
{finding.ai_business_impact && ( {finding.ai_business_impact && (
<div className="bg-vault-dark rounded-lg p-3 mb-4"> <div className="bg-vault-dark rounded-lg p-3 mb-4">
<p className="text-xs text-vault-muted font-medium mb-1">Why it matters</p> <p className="text-xs text-vault-muted font-medium mb-1">Why it matters</p>
<p className="text-vault-subtle text-sm leading-relaxed">{finding.ai_business_impact}</p> <p className="text-vault-subtle text-sm leading-relaxed">{finding.ai_business_impact}</p>
</div> </div>
)} )}
{finding.ai_remediation_steps && ( {finding.ai_remediation_steps && (
<div> <div>
<p className="text-xs text-vault-muted font-medium mb-2">Remediation Steps</p> <p className="text-xs text-vault-muted font-medium mb-2">Remediation Steps</p>
<pre className="text-vault-subtle text-xs leading-relaxed whitespace-pre-wrap font-sans"> <pre className="text-vault-subtle text-xs leading-relaxed whitespace-pre-wrap font-sans">
{finding.ai_remediation_steps} {finding.ai_remediation_steps}
</pre> </pre>
</div>
)}
</>
) : (
<div className="flex items-center gap-2 text-vault-muted text-sm">
<div className="animate-spin w-4 h-4 border-2 border-vault-sapphire border-t-transparent rounded-full" />
<span>AI translation is being generated...</span>
</div> </div>
)} )}
</div> </div>