feat(reviewiq): Add AI synthesis support to dashboard components
Frontend: - Add Synthesis type with action plan, insights, annotations - ExecutiveSummary: Accept synthesis prop for AI narrative - SentimentPie: Accept insight prop for contextual explanation - IntensityHeatmap: Accept insight + highlightDomain props - TimelineChart: Accept insight + annotations props - All components gracefully degrade when synthesis is null Backend: - Add Stage 4: Synthesize for generating AI narratives - Gathers context from classified spans - Generates executive narrative, section insights, action plan - Produces timeline annotations and marketing angles - Stores synthesis in pipeline.executions table Components show AI insights with purple gradient styling when available, fall back to existing behavior when synthesis is not yet generated. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -16,7 +16,7 @@ import {
|
||||
Award,
|
||||
} from 'lucide-react';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import type { Insights, WeaknessItem, OpportunitySpan, OpportunityMatrix, DomainScore, URTDomain } from '../types';
|
||||
import type { Insights, WeaknessItem, OpportunitySpan, OpportunityMatrix, DomainScore, URTDomain, Synthesis } from '../types';
|
||||
import { getSubcodeDefinition } from '@/lib/taxonomy/data';
|
||||
|
||||
interface ExecutiveSummaryProps {
|
||||
@@ -25,6 +25,8 @@ interface ExecutiveSummaryProps {
|
||||
domainScores?: DomainScore[];
|
||||
onDriverClick?: (subcode: string) => void;
|
||||
onDomainClick?: (domain: URTDomain) => void;
|
||||
// AI-generated narrative (optional - enhances when available)
|
||||
synthesis?: Synthesis | null;
|
||||
}
|
||||
|
||||
// User-friendly domain config
|
||||
@@ -199,10 +201,14 @@ export function ExecutiveSummary({
|
||||
domainScores,
|
||||
onDriverClick,
|
||||
onDomainClick,
|
||||
synthesis,
|
||||
}: ExecutiveSummaryProps) {
|
||||
const { strengths, weaknesses, executive_summary, opportunity_matrix, rating_simulator } = insights;
|
||||
const [showFullSummary, setShowFullSummary] = useState(false);
|
||||
|
||||
// Use AI narrative if available, otherwise fall back to generated summary
|
||||
const narrativeText = synthesis?.executive_narrative || executive_summary;
|
||||
|
||||
const topStrength = strengths[0];
|
||||
const topWeakness = weaknesses[0];
|
||||
const ratingDisplay = getRatingDisplay(avgRating);
|
||||
@@ -286,16 +292,23 @@ export function ExecutiveSummary({
|
||||
</div>
|
||||
|
||||
{/* AI Summary */}
|
||||
{executive_summary && (
|
||||
{narrativeText && (
|
||||
<div className="px-6 pb-4">
|
||||
<div className="p-4 bg-white/70 rounded-xl border border-blue-100">
|
||||
<div className={`p-4 rounded-xl border ${
|
||||
synthesis?.executive_narrative
|
||||
? 'bg-gradient-to-r from-purple-50 to-blue-50 border-purple-200'
|
||||
: 'bg-white/70 border-blue-100'
|
||||
}`}>
|
||||
<div className="flex items-start gap-2">
|
||||
<span className="text-lg">💡</span>
|
||||
<div>
|
||||
<p className={`text-gray-700 leading-relaxed ${!showFullSummary && 'line-clamp-2'}`}>
|
||||
{executive_summary}
|
||||
<span className="text-lg">{synthesis?.executive_narrative ? '✨' : '💡'}</span>
|
||||
<div className="flex-1">
|
||||
{synthesis?.executive_narrative && (
|
||||
<div className="text-xs font-medium text-purple-600 mb-1">AI-Generated Insight</div>
|
||||
)}
|
||||
<p className={`text-gray-700 leading-relaxed ${!showFullSummary && 'line-clamp-3'}`}>
|
||||
{narrativeText}
|
||||
</p>
|
||||
{executive_summary.length > 150 && (
|
||||
{narrativeText.length > 200 && (
|
||||
<button
|
||||
onClick={() => setShowFullSummary(!showFullSummary)}
|
||||
className="text-blue-600 text-sm font-medium mt-1 hover:underline"
|
||||
|
||||
Reference in New Issue
Block a user