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:
@@ -18,11 +18,21 @@ export default function FindingDetailPage() {
|
||||
const [asking, setAsking] = useState(false);
|
||||
const [resolveNote, setResolveNote] = useState("");
|
||||
const [updating, setUpdating] = useState(false);
|
||||
const [aiGenerating, setAiGenerating] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!ready || !id) return;
|
||||
api.finding(id).then(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(() => {});
|
||||
}).finally(() => setLoading(false));
|
||||
}, [ready, id]);
|
||||
@@ -95,25 +105,32 @@ export default function FindingDetailPage() {
|
||||
{/* Executive (AI) view */}
|
||||
<div className="vault-card">
|
||||
<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>
|
||||
{finding.ai_summary && (
|
||||
<div className="mb-4">
|
||||
<p className="text-vault-text leading-relaxed">{finding.ai_summary}</p>
|
||||
</div>
|
||||
)}
|
||||
{finding.ai_business_impact && (
|
||||
<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-vault-subtle text-sm leading-relaxed">{finding.ai_business_impact}</p>
|
||||
</div>
|
||||
)}
|
||||
{finding.ai_remediation_steps && (
|
||||
<div>
|
||||
<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">
|
||||
{finding.ai_remediation_steps}
|
||||
</pre>
|
||||
{finding.ai_summary ? (
|
||||
<>
|
||||
<div className="mb-4">
|
||||
<p className="text-vault-text leading-relaxed">{finding.ai_summary}</p>
|
||||
</div>
|
||||
{finding.ai_business_impact && (
|
||||
<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-vault-subtle text-sm leading-relaxed">{finding.ai_business_impact}</p>
|
||||
</div>
|
||||
)}
|
||||
{finding.ai_remediation_steps && (
|
||||
<div>
|
||||
<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">
|
||||
{finding.ai_remediation_steps}
|
||||
</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>
|
||||
|
||||
Reference in New Issue
Block a user