fix: optimize synthesis timing for educational multi-agent analysis

- Fix synthesis to start immediately when all 3 perspectives complete
- Ensure synthesis uses original API key with full 180s timeout
- Add dedicated synthesis prompt for educational perspective integration
- Improve error handling and logging for synthesis operations
- Complete frontend integration with EducationalAnalysisView component

Technical improvements:
- Modified TemplateDrivenAgent.synthesize_results() for proper timeout handling
- Added _create_synthesis_prompt() method for educational synthesis
- Enhanced API error handling with detailed logging
- Frontend component with real-time progress and multi-tab results display

🤖 Generated with Claude Code (https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
enias 2025-08-28 00:03:01 -04:00
parent 9e63f5772d
commit 74c18ebbee
5 changed files with 839 additions and 31 deletions

View File

@ -31,6 +31,154 @@ logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/templates", tags=["Analysis Templates"])
# Response models (defined before endpoint decorators)
class MultiTemplateAnalysisResult(BaseModel):
"""Result from analyzing content with multiple templates."""
template_set_id: str
template_set_name: str
results: Dict[str, TemplateAnalysisResult]
synthesis_result: Optional[TemplateAnalysisResult] = None
total_processing_time_seconds: float
# Request/Response models (defined before endpoints that use them)
class TestEducationalRequest(BaseModel):
content: str = Field(..., min_length=50, description="Content to analyze")
class AnalyzeWithTemplateSetRequest(BaseModel):
"""Request to analyze content with a template set."""
content: str = Field(..., description="Content to analyze", min_length=10)
template_set_id: str = Field(..., description="Template set ID to use")
context: Dict[str, Any] = Field(default_factory=dict, description="Additional context variables")
include_synthesis: bool = Field(default=True, description="Whether to include synthesis of results")
video_id: Optional[str] = Field(None, description="Video ID if analyzing video content")
# Dependencies (defined before endpoints that use them)
async def get_template_agent() -> TemplateDrivenAgent:
"""Get template-driven agent instance."""
return TemplateDrivenAgent(template_registry=DEFAULT_REGISTRY)
# Test endpoint without auth for development
@router.post("/test-educational", summary="Test educational analysis (no auth)")
async def test_educational_analysis(
request: TestEducationalRequest
):
"""Test educational analysis without authentication - DEVELOPMENT ONLY."""
try:
# Use the educational template set
from ..services.template_driven_agent import TemplateDrivenAgent
# Create agent with registry (will automatically use multi-key services)
agent = TemplateDrivenAgent(template_registry=DEFAULT_REGISTRY)
# Process templates using analyze_with_template_set (will run in parallel with separate keys)
results = await agent.analyze_with_template_set(
content=request.content,
template_set_id="educational_perspectives", # The ID of the educational template set
context={
"content_type": "video content",
"topic": "the analyzed topic"
}
)
# Format results for response
formatted_results = {}
for template_id, result in results.items():
formatted_results[template_id] = {
"template_name": result.template_name,
"summary": result.analysis[:200] + "..." if len(result.analysis) > 200 else result.analysis,
"key_insights": result.key_insights[:3] if result.key_insights else [],
"confidence": result.confidence_score
}
# Try to synthesize results if we have them
synthesis_summary = None
if len(results) == 3: # All three educational perspectives
synthesis_summary = f"Successfully analyzed content from {len(results)} educational perspectives: Beginner's Lens, Expert's Lens, and Scholar's Lens."
return {
"status": "success",
"perspectives": formatted_results,
"synthesis": synthesis_summary,
"message": f"Educational orchestration is working! Processed {len(results)} templates successfully."
}
except Exception as e:
logger.error(f"Test analysis failed: {e}")
return {"status": "error", "message": str(e)}
# Educational analysis endpoint with authentication and full synthesis
@router.post("/analyze-educational", response_model=MultiTemplateAnalysisResult)
async def analyze_educational_content(
request: AnalyzeWithTemplateSetRequest,
background_tasks: BackgroundTasks,
agent: TemplateDrivenAgent = Depends(get_template_agent),
current_user: User = Depends(get_current_user)
):
"""
Analyze content with educational perspectives (Beginner, Expert, Scholar).
Uses multi-key parallel processing for optimal performance.
"""
try:
import time
start_time = time.time()
# Force educational template set
template_set_id = "educational_perspectives"
# Analyze with educational template set (parallel with 3 API keys)
results = await agent.analyze_with_template_set(
content=request.content,
template_set_id=template_set_id,
context={
**request.context,
"content_type": request.context.get("content_type", "video content"),
"topic": request.context.get("topic", "the subject matter")
},
video_id=request.video_id
)
# Always synthesize educational results with dedicated timeout
synthesis_result = None
if len(results) >= 2: # Synthesize even with partial results
try:
# Start synthesis immediately when we have results, with full 180s timeout
logger.info(f"Starting synthesis for {len(results)} perspectives - user {current_user.id}")
synthesis_result = await agent.synthesize_results(
results=results,
template_set_id=template_set_id,
context=request.context
)
logger.info(f"Educational synthesis completed successfully for user {current_user.id}")
except Exception as syn_err:
logger.warning(f"Synthesis failed but continuing: {syn_err}")
# Continue without synthesis rather than failing completely
total_processing_time = time.time() - start_time
# Get template set info
template_set = DEFAULT_REGISTRY.get_template_set(template_set_id)
template_set_name = template_set.name if template_set else "Educational Perspectives"
# Store analysis in database if requested
if request.context.get("store_results", False) and request.video_id:
# TODO: Store template analysis with video summary
pass
result = MultiTemplateAnalysisResult(
template_set_id=template_set_id,
template_set_name=template_set_name,
results=results,
synthesis_result=synthesis_result,
total_processing_time_seconds=total_processing_time
)
logger.info(f"Educational analysis completed in {total_processing_time:.2f}s for user {current_user.id}")
return result
except Exception as e:
logger.error(f"Educational analysis failed for user {current_user.id}: {e}")
raise HTTPException(status_code=500, detail=str(e))
# Request/Response Models
class AnalyzeWithTemplateRequest(BaseModel):
@ -41,22 +189,8 @@ class AnalyzeWithTemplateRequest(BaseModel):
video_id: Optional[str] = Field(None, description="Video ID if analyzing video content")
class AnalyzeWithTemplateSetRequest(BaseModel):
"""Request to analyze content with a template set."""
content: str = Field(..., description="Content to analyze", min_length=10)
template_set_id: str = Field(..., description="Template set ID to use")
context: Dict[str, Any] = Field(default_factory=dict, description="Additional context variables")
include_synthesis: bool = Field(default=True, description="Whether to include synthesis of results")
video_id: Optional[str] = Field(None, description="Video ID if analyzing video content")
class MultiTemplateAnalysisResult(BaseModel):
"""Result from analyzing content with multiple templates."""
template_set_id: str
template_set_name: str
results: Dict[str, TemplateAnalysisResult]
synthesis_result: Optional[TemplateAnalysisResult] = None
total_processing_time_seconds: float
class CreateTemplateRequest(BaseModel):
@ -127,9 +261,6 @@ class OrchestrationResultResponse(BaseModel):
# Dependencies
async def get_template_agent() -> TemplateDrivenAgent:
"""Get template-driven agent instance."""
return TemplateDrivenAgent(template_registry=DEFAULT_REGISTRY)
async def get_enhanced_orchestrator() -> EnhancedMultiAgentOrchestrator:

View File

@ -48,8 +48,44 @@ class TemplateDrivenAgent:
"""Initialize the template-driven agent."""
self.ai_service = ai_service or DeepSeekService()
self.template_registry = template_registry or DEFAULT_REGISTRY
# Create separate AI services for parallel processing with different API keys
self._ai_services = self._initialize_multi_key_services()
self._usage_stats: Dict[str, int] = {}
def _initialize_multi_key_services(self) -> Dict[str, DeepSeekService]:
"""Initialize multiple DeepSeek services with different API keys for parallel processing."""
import os
from dotenv import load_dotenv
# Ensure environment variables are loaded
load_dotenv()
services = {}
# Map specific templates to specific API keys for load balancing
template_key_mapping = {
"educational_beginner": os.getenv("DEEPSEEK_API_KEY_1"),
"educational_expert": os.getenv("DEEPSEEK_API_KEY_2"),
"educational_scholarly": os.getenv("DEEPSEEK_API_KEY_3")
}
logger.info(f"Initializing multi-key services...")
logger.info(f"Available API keys: {[k for k, v in template_key_mapping.items() if v]}")
for template_id, api_key in template_key_mapping.items():
if api_key:
try:
services[template_id] = DeepSeekService(api_key=api_key)
logger.info(f"Initialized dedicated AI service for {template_id} with key: {api_key[:10]}...")
except Exception as e:
logger.warning(f"Failed to initialize AI service for {template_id}: {e}")
else:
logger.warning(f"No API key found for {template_id}")
logger.info(f"Successfully initialized {len(services)} dedicated AI services")
return services
async def analyze_with_template(
self,
request: TemplateAnalysisRequest
@ -80,13 +116,16 @@ class TemplateDrivenAgent:
# Create analysis prompt
analysis_prompt = self._create_analysis_prompt(template, request.content, analysis_context)
# Use dedicated AI service for this template if available, otherwise use default
ai_service = self._ai_services.get(request.template_id, self.ai_service)
# Generate analysis using AI service
ai_response = await self.ai_service.generate_summary({
"prompt": analysis_prompt,
"system_prompt": system_prompt,
"max_tokens": 2000,
"temperature": 0.7
})
ai_response = await ai_service.generate_response(
prompt=analysis_prompt,
system_prompt=system_prompt,
max_tokens=2000,
temperature=0.7
)
# Parse the response to extract insights
key_insights = self._extract_insights(ai_response, template)
@ -112,7 +151,9 @@ class TemplateDrivenAgent:
)
except Exception as e:
import traceback
logger.error(f"Error in template analysis {request.template_id}: {e}")
logger.error(f"Full traceback for {request.template_id}: {traceback.format_exc()}")
raise ServiceError(f"Template analysis failed: {str(e)}")
async def analyze_with_template_set(
@ -150,7 +191,8 @@ class TemplateDrivenAgent:
template_ids = [t.id for t in template_set.templates.values() if t.is_active]
for i, result in enumerate(parallel_results):
if isinstance(result, Exception):
logger.error(f"Template {template_ids[i]} failed: {result}")
logger.error(f"Template {template_ids[i]} failed with exception: {type(result).__name__}: {str(result)}")
logger.error(f"Full error details for {template_ids[i]}: {result}")
else:
results[template_ids[i]] = result
else:
@ -182,22 +224,78 @@ class TemplateDrivenAgent:
"""Synthesize results from multiple template analyses."""
template_set = self.template_registry.get_template_set(template_set_id)
if not template_set or not template_set.synthesis_template:
logger.warning(f"No synthesis template found for template set: {template_set_id}")
return None
logger.info(f"Starting synthesis for {len(results)} results using template: {template_set.synthesis_template.id}")
# Prepare synthesis context
synthesis_context = context or {}
for result_id, result in results.items():
synthesis_context[f"{result_id}_analysis"] = result.analysis
synthesis_context[f"{result_id}_insights"] = result.key_insights
# Perform synthesis
request = TemplateAnalysisRequest(
content="", # Synthesis works with previous results
template_id=template_set.synthesis_template.id,
context=synthesis_context
# Perform synthesis with dedicated timeout and original API key
start_time = datetime.utcnow()
try:
# Use the original AI service (not multi-key services) for synthesis to ensure proper timeout
template = template_set.synthesis_template
# Prepare context with content and template variables
analysis_context = {
**template.variables,
**synthesis_context,
"content": "", # Synthesis works with previous results
"video_id": synthesis_context.get("video_id", "unknown")
}
# Render the system prompt with context
system_prompt = template.render_prompt(analysis_context)
# Create analysis prompt for synthesis
synthesis_prompt = self._create_synthesis_prompt(template, results, analysis_context)
logger.info(f"Synthesis using original AI service with full 180s timeout")
# Generate synthesis using original AI service (with 180s timeout)
ai_response = await self.ai_service.generate_response(
prompt=synthesis_prompt,
system_prompt=system_prompt,
max_tokens=2500, # Longer for synthesis
temperature=0.7
)
return await self.analyze_with_template(request)
# Parse the response to extract insights
key_insights = self._extract_insights(ai_response, template)
# Calculate processing time
processing_time = (datetime.utcnow() - start_time).total_seconds()
# Update usage statistics
self._update_usage_stats(template.id)
# Calculate confidence score based on response quality
confidence_score = self._calculate_confidence_score(ai_response, template)
logger.info(f"Synthesis completed in {processing_time:.2f}s")
return TemplateAnalysisResult(
template_id=template.id,
template_name=template.name,
analysis=ai_response,
key_insights=key_insights,
confidence_score=confidence_score,
processing_time_seconds=processing_time,
context_used=analysis_context,
template_variables=template.variables
)
except Exception as e:
import traceback
logger.error(f"Synthesis failed after {(datetime.utcnow() - start_time).total_seconds():.2f}s: {e}")
logger.error(f"Full synthesis traceback: {traceback.format_exc()}")
raise ServiceError(f"Synthesis failed: {str(e)}")
def _create_analysis_prompt(
self,
@ -224,6 +322,51 @@ Analysis Instructions:
Expected Output Format:
{template.output_format}
"""
def _create_synthesis_prompt(
self,
template: AnalysisTemplate,
results: Dict[str, TemplateAnalysisResult],
context: Dict[str, Any]
) -> str:
"""Create the synthesis prompt for combining multiple analyses."""
# Build synthesis input from all results
synthesis_input = []
for result_id, result in results.items():
template_name = result.template_name
synthesis_input.append(f"## {template_name} Analysis")
synthesis_input.append(result.analysis)
synthesis_input.append("\n### Key Insights:")
for insight in result.key_insights:
synthesis_input.append(f"- {insight}")
synthesis_input.append("") # Empty line between analyses
return f"""
You are tasked with synthesizing multiple educational perspective analyses into a unified comprehensive understanding.
## Input Analyses
{chr(10).join(synthesis_input)}
## Synthesis Instructions:
- Combine insights from all perspectives into a unified educational journey
- Identify common themes and complementary viewpoints
- Resolve any apparent contradictions by providing nuanced explanations
- Create a progressive learning path from beginner to advanced understanding
- Generate between {template.min_insights} and {template.max_insights} unified insights
- Target audience: {template.target_audience}
- Tone: {template.tone}
- Depth: {template.depth}
- Focus areas: {', '.join(template.analysis_focus)}
{'Include practical examples that bridge different learning levels.' if template.include_examples else ''}
{'Provide actionable learning recommendations.' if template.include_recommendations else ''}
## Expected Output Format:
{template.output_format}
Create a synthesis that honors the unique value of each perspective while creating a cohesive educational experience.
"""
def _extract_insights(self, response: str, template: AnalysisTemplate) -> List[str]:

View File

@ -25,6 +25,7 @@ import TemplateDemo from '@/pages/TemplateDemo';
import { VideoChatPage } from '@/pages/VideoChatPage';
import { TranscriptionView } from '@/pages/TranscriptionView';
import { ConditionalProtectedRoute } from '@/components/auth/ConditionalProtectedRoute';
import EducationalAnalysisView from '@/components/EducationalAnalysisView';
const queryClient = new QueryClient({
defaultOptions: {
@ -59,6 +60,13 @@ function App() {
<Route path="/demo/enhanced-transcript" element={<EnhancedTranscriptDemo />} />
<Route path="/demo/template-analysis" element={<TemplateDemo />} />
{/* Educational Analysis - Protected route with new multi-agent system */}
<Route path="/analysis/educational" element={
<ConditionalProtectedRoute requireVerified={true}>
<EducationalAnalysisView />
</ConditionalProtectedRoute>
} />
{/* Main app routes - conditionally protected */}
<Route path="/" element={
<ConditionalProtectedRoute requireVerified={true}>

View File

@ -0,0 +1,158 @@
/**
* API client for template-based analysis endpoints
*/
import { apiClient } from './client';
export interface TemplateAnalysisRequest {
content: string;
template_set_id?: string;
include_synthesis?: boolean;
context?: Record<string, any>;
video_id?: string;
}
export interface TemplateAnalysisResult {
template_id: string;
template_name: string;
analysis: string;
key_insights: string[];
confidence_score: number;
processing_time_seconds: number;
}
export interface MultiTemplateAnalysisResult {
template_set_id: string;
template_set_name: string;
results: Record<string, TemplateAnalysisResult>;
synthesis_result?: TemplateAnalysisResult;
total_processing_time_seconds: number;
}
export interface Template {
id: string;
name: string;
description: string;
template_type: string;
complexity_level?: string;
target_audience: string;
tone: string;
depth: string;
tags: string[];
}
export interface TemplateSet {
id: string;
name: string;
description: string;
template_type: string;
templates: Record<string, Template>;
}
class TemplatesAPI {
/**
* Analyze content with educational perspectives (authenticated)
* Uses multi-key parallel processing for optimal performance
*/
async analyzeEducational(request: TemplateAnalysisRequest): Promise<MultiTemplateAnalysisResult> {
const response = await apiClient.post('/api/templates/analyze-educational', {
...request,
template_set_id: 'educational_perspectives', // Force educational template set
include_synthesis: true, // Always synthesize for educational analysis
});
return response.data;
}
/**
* Analyze content with a specific template set (authenticated)
*/
async analyzeWithTemplateSet(request: TemplateAnalysisRequest): Promise<MultiTemplateAnalysisResult> {
const response = await apiClient.post('/api/templates/analyze-set', request);
return response.data;
}
/**
* Analyze content with a single template (authenticated)
*/
async analyzeWithTemplate(
content: string,
template_id: string,
context: Record<string, any> = {},
video_id?: string
): Promise<TemplateAnalysisResult> {
const response = await apiClient.post('/api/templates/analyze', {
content,
template_id,
context,
video_id,
});
return response.data;
}
/**
* Get list of available templates
*/
async listTemplates(template_type?: string, active_only: boolean = true): Promise<Template[]> {
const params = new URLSearchParams();
if (template_type) params.append('template_type', template_type);
if (active_only) params.append('active_only', 'true');
const response = await apiClient.get(`/api/templates/list?${params.toString()}`);
return response.data;
}
/**
* Get list of available template sets
*/
async listTemplateSets(template_type?: string, active_only: boolean = true): Promise<TemplateSet[]> {
const params = new URLSearchParams();
if (template_type) params.append('template_type', template_type);
if (active_only) params.append('active_only', 'true');
const response = await apiClient.get(`/api/templates/sets?${params.toString()}`);
return response.data;
}
/**
* Get a specific template by ID
*/
async getTemplate(template_id: string): Promise<Template> {
const response = await apiClient.get(`/api/templates/template/${template_id}`);
return response.data;
}
/**
* Get a specific template set by ID
*/
async getTemplateSet(set_id: string): Promise<TemplateSet> {
const response = await apiClient.get(`/api/templates/set/${set_id}`);
return response.data;
}
/**
* Get template usage statistics (authenticated)
*/
async getTemplateStats(): Promise<{
available_templates: number;
available_template_sets: number;
usage_statistics: Record<string, number>;
total_uses: number;
}> {
const response = await apiClient.get('/api/templates/stats');
return response.data;
}
/**
* Health check for template service
*/
async healthCheck(): Promise<{
status: string;
available_templates: number;
available_template_sets: number;
timestamp: string;
}> {
const response = await apiClient.get('/api/templates/health');
return response.data;
}
}
export const templatesAPI = new TemplatesAPI();

View File

@ -0,0 +1,368 @@
import React, { useState } from 'react';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Textarea } from '@/components/ui/textarea';
import { Badge } from '@/components/ui/badge';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { Progress } from '@/components/ui/progress';
import {
Loader2,
BookOpen,
Users,
GraduationCap,
Lightbulb,
CheckCircle,
Clock,
Sparkles
} from 'lucide-react';
import { templatesAPI, MultiTemplateAnalysisResult } from '@/api/templatesAPI';
import { useAuth } from '@/hooks/useAuth';
interface EducationalAnalysisViewProps {
defaultContent?: string;
onAnalysisComplete?: (results: MultiTemplateAnalysisResult) => void;
}
const EducationalAnalysisView: React.FC<EducationalAnalysisViewProps> = ({
defaultContent = '',
onAnalysisComplete
}) => {
const [content, setContent] = useState(defaultContent);
const [loading, setLoading] = useState(false);
const [progress, setProgress] = useState(0);
const [currentStep, setCurrentStep] = useState('');
const [results, setResults] = useState<MultiTemplateAnalysisResult | null>(null);
const [error, setError] = useState<string>('');
const { user, isAuthenticated } = useAuth();
const handleAnalyze = async () => {
if (!content.trim()) {
setError('Please enter content to analyze');
return;
}
if (!isAuthenticated) {
setError('Please log in to access educational analysis');
return;
}
setLoading(true);
setError('');
setResults(null);
setProgress(0);
// Simulate progress updates since this is a long-running process (3-4 minutes)
const progressInterval = setInterval(() => {
setProgress(prev => {
if (prev < 90) return prev + 1;
return prev;
});
}, 2000); // Update every 2 seconds
try {
setCurrentStep('Initializing multi-agent analysis...');
await new Promise(resolve => setTimeout(resolve, 1000));
setCurrentStep('Processing with Beginner\'s Lens...');
await new Promise(resolve => setTimeout(resolve, 1000));
setCurrentStep('Analyzing with Expert\'s Lens...');
await new Promise(resolve => setTimeout(resolve, 1000));
setCurrentStep('Examining with Scholar\'s Lens...');
await new Promise(resolve => setTimeout(resolve, 1000));
setCurrentStep('Synthesizing educational perspectives...');
const result = await templatesAPI.analyzeEducational({
content,
context: {
content_type: 'educational content',
topic: 'multi-perspective analysis',
user_id: user?.id,
},
});
clearInterval(progressInterval);
setProgress(100);
setCurrentStep('Analysis complete!');
setResults(result);
onAnalysisComplete?.(result);
await new Promise(resolve => setTimeout(resolve, 500));
setCurrentStep('');
} catch (err) {
clearInterval(progressInterval);
const errorMessage = err instanceof Error ? err.message : 'Educational analysis failed';
setError(errorMessage);
setCurrentStep('');
} finally {
setLoading(false);
setProgress(0);
}
};
const getTemplateIcon = (templateId: string) => {
if (templateId.includes('beginner')) return <BookOpen className="w-4 h-4" />;
if (templateId.includes('expert')) return <Users className="w-4 h-4" />;
if (templateId.includes('scholar')) return <GraduationCap className="w-4 h-4" />;
return <Lightbulb className="w-4 h-4" />;
};
const getComplexityColor = (templateId: string) => {
if (templateId.includes('beginner')) return 'bg-green-100 text-green-800';
if (templateId.includes('expert')) return 'bg-blue-100 text-blue-800';
if (templateId.includes('scholar')) return 'bg-purple-100 text-purple-800';
return 'bg-gray-100 text-gray-800';
};
const getTemplateDescription = (templateId: string) => {
if (templateId.includes('beginner')) return 'Accessible explanations for newcomers';
if (templateId.includes('expert')) return 'Professional insights and strategic analysis';
if (templateId.includes('scholar')) return 'Academic rigor and theoretical frameworks';
return 'Analysis perspective';
};
return (
<div className="max-w-6xl mx-auto p-6 space-y-6">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Sparkles className="w-5 h-5 text-blue-600" />
Educational Multi-Agent Analysis
</CardTitle>
<CardDescription>
Experience AI analysis from three distinct educational perspectives: Beginner, Expert, and Scholar lenses
with intelligent synthesis for comprehensive understanding.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{/* Authentication Status */}
{!isAuthenticated && (
<Alert>
<AlertDescription>
Please log in to access educational analysis features. This requires authentication for optimal performance.
</AlertDescription>
</Alert>
)}
{/* Content Input */}
<div>
<label className="text-sm font-medium mb-2 block">Content to Analyze</label>
<Textarea
placeholder="Enter the content you want to analyze from multiple educational perspectives... (e.g., article text, video transcript, research findings, etc.)"
value={content}
onChange={(e) => setContent(e.target.value)}
className="min-h-[120px]"
disabled={loading}
/>
{content && (
<div className="flex items-center gap-2 mt-2 text-sm text-gray-600">
<Badge variant="outline">{content.length.toLocaleString()} characters</Badge>
<span></span>
<span>Optimal range: 500-5000 characters</span>
</div>
)}
</div>
{/* Error Display */}
{error && (
<Alert variant="destructive">
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
{/* Progress Display */}
{loading && (
<Card>
<CardContent className="pt-6">
<div className="space-y-4">
<div className="flex items-center gap-2">
<Loader2 className="w-4 h-4 animate-spin" />
<span className="text-sm font-medium">Educational Analysis in Progress</span>
</div>
<Progress value={progress} className="h-2" />
<div className="flex items-center gap-2 text-sm text-gray-600">
<Clock className="w-4 h-4" />
<span>{currentStep}</span>
</div>
<div className="p-3 bg-blue-50 rounded-lg border border-blue-200">
<p className="text-sm text-blue-800 font-medium mb-1">Processing Details</p>
<ul className="text-xs text-blue-700 space-y-1">
<li> Using parallel processing with dedicated AI services</li>
<li> Analysis typically takes 3-4 minutes for comprehensive results</li>
<li> Each perspective processes independently for authentic viewpoints</li>
<li> Synthesis combines all perspectives into unified insights</li>
</ul>
</div>
</div>
</CardContent>
</Card>
)}
{/* Analyze Button */}
<Button
onClick={handleAnalyze}
disabled={loading || !content.trim() || !isAuthenticated}
className="w-full"
size="lg"
>
{loading ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
Analyzing from Educational Perspectives...
</>
) : (
<>
<Sparkles className="w-4 h-4 mr-2" />
Analyze with Educational Multi-Agent System
</>
)}
</Button>
</CardContent>
</Card>
{/* Results Display */}
{results && (
<div className="space-y-4">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<CheckCircle className="w-5 h-5 text-green-600" />
Educational Analysis Complete
</CardTitle>
<CardDescription>
Processing completed in {results.total_processing_time_seconds.toFixed(1)} seconds
{Object.keys(results.results).length} perspectives analyzed
</CardDescription>
</CardHeader>
</Card>
<Tabs defaultValue={Object.keys(results.results)[0]} className="w-full">
<TabsList className="grid w-full grid-cols-auto">
{Object.entries(results.results).map(([templateId, result]) => (
<TabsTrigger key={templateId} value={templateId} className="flex items-center gap-2">
{getTemplateIcon(templateId)}
{result.template_name}
</TabsTrigger>
))}
{results.synthesis_result && (
<TabsTrigger value="synthesis" className="flex items-center gap-2">
<Sparkles className="w-4 h-4" />
Synthesis
</TabsTrigger>
)}
</TabsList>
{Object.entries(results.results).map(([templateId, result]) => (
<TabsContent key={templateId} value={templateId} className="mt-4">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
{getTemplateIcon(templateId)}
{result.template_name}
<Badge className={getComplexityColor(templateId)}>
Confidence: {(result.confidence_score * 100).toFixed(0)}%
</Badge>
</CardTitle>
<CardDescription>
{getTemplateDescription(templateId)}
Processing time: {result.processing_time_seconds.toFixed(1)}s
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{/* Key Insights */}
{result.key_insights && result.key_insights.length > 0 && (
<div>
<h4 className="font-semibold mb-2">Key Insights</h4>
<ul className="space-y-2">
{result.key_insights.map((insight, index) => (
<li key={index} className="flex items-start gap-2">
<Badge variant="outline" className="mt-1 text-xs">
{index + 1}
</Badge>
<span className="text-sm">{insight}</span>
</li>
))}
</ul>
</div>
)}
{/* Full Analysis */}
<div>
<h4 className="font-semibold mb-2">Detailed Analysis</h4>
<div className="bg-gray-50 p-4 rounded-lg">
<pre className="whitespace-pre-wrap text-sm font-sans leading-relaxed">
{result.analysis}
</pre>
</div>
</div>
</CardContent>
</Card>
</TabsContent>
))}
{results.synthesis_result && (
<TabsContent value="synthesis" className="mt-4">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Sparkles className="w-4 h-4 text-gold-600" />
Educational Synthesis - Unified Perspective
<Badge className="bg-gold-100 text-gold-800">
Confidence: {(results.synthesis_result.confidence_score * 100).toFixed(0)}%
</Badge>
</CardTitle>
<CardDescription>
Unified analysis combining all educational perspectives for comprehensive understanding
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{/* Synthesis Insights */}
{results.synthesis_result.key_insights && results.synthesis_result.key_insights.length > 0 && (
<div>
<h4 className="font-semibold mb-2">Unified Insights</h4>
<ul className="space-y-2">
{results.synthesis_result.key_insights.map((insight, index) => (
<li key={index} className="flex items-start gap-2">
<Badge variant="outline" className="mt-1 text-xs bg-gold-50">
{index + 1}
</Badge>
<span className="text-sm">{insight}</span>
</li>
))}
</ul>
</div>
)}
{/* Synthesis Analysis */}
<div>
<h4 className="font-semibold mb-2">Complete Educational Journey</h4>
<div className="bg-gradient-to-br from-blue-50 to-purple-50 p-4 rounded-lg">
<pre className="whitespace-pre-wrap text-sm font-sans leading-relaxed">
{results.synthesis_result.analysis}
</pre>
</div>
</div>
</CardContent>
</Card>
</TabsContent>
)}
</Tabs>
</div>
)}
</div>
);
};
export default EducationalAnalysisView;