16 KiB
Research Execution Flow - Code Walkthrough
Overview
This document traces the complete flow from when a user clicks "Start Research" to when they see the results on the UI.
1. User Clicks "Start Research" Button
Location: ActionButtons.tsx (Line 104-119)
<Button
variant="contained"
color="primary"
startIcon={isExecuting ? <CircularProgress size={16} color="inherit" /> : <PlayIcon />}
onClick={onExecute} // ← This is the entry point
disabled={isExecuting || !canExecute}
>
{isExecuting ? 'Researching...' : 'Start Research'}
</Button>
What happens:
- Button shows loading spinner when
isExecutingis true - Calls
onExecutecallback - Button is disabled if no queries are selected (
canExecutemust be true)
2. IntentConfirmationPanel Handles Execution
Location: IntentConfirmationPanel.tsx (Line 106-122)
const handleExecute = () => {
const updatedIntent = { ...intent };
// Pass wizard state to onConfirm for draft saving
onConfirm(updatedIntent, wizardState);
// Get selected queries (sorted by priority)
const queriesToUse = Array.from(selectedQueries)
.sort((a, b) => a - b)
.map(idx => editedQueries[idx])
.filter(q => q && q.query.trim().length > 0);
// Store updated trends config
if (editedTrendsConfig && intentAnalysis) {
intentAnalysis.trends_config = editedTrendsConfig;
}
onExecute(queriesToUse); // ← Passes queries to ResearchInput
};
What happens:
- Confirms the intent (saves draft)
- Extracts selected queries from the UI
- Updates trends configuration if modified
- Calls
onExecutewith the selected queries
3. ResearchInput Passes to Execution Hook
Location: ResearchInput.tsx (Line 580-586)
onExecute={async (selectedQueries) => {
const result = await execution.executeIntentResearch(state, selectedQueries);
if (result?.success) {
// Skip to results step
onUpdate({ currentStep: 3 });
}
}}
What happens:
- Calls
execution.executeIntentResearch()with wizard state and selected queries - If successful, automatically navigates to Step 3 (Results)
4. Execution Hook Processes Research
Location: useResearchExecution.ts (Line 284-378)
const executeIntentResearch = useCallback(async (
state: WizardState,
selectedQueries?: ResearchQuery[]
): Promise<IntentDrivenResearchResponse | null> => {
// 1. Ensure intent is available
let intent = confirmedIntent;
if (!intent) {
const analysis = await analyzeIntent(state);
if (!analysis?.success) {
return null;
}
intent = analysis.intent;
}
// 2. Set loading state
setIsExecuting(true);
setError(null);
try {
// 3. Prepare queries (use provided or fall back to suggested)
const queriesToUse = selectedQueries ||
intentAnalysis?.suggested_queries?.slice(0, 5) || [];
// 4. Make API call
const response = await intentResearchApi.executeIntentResearch({
user_input: state.keywords.join(' '),
confirmed_intent: intent,
selected_queries: queriesToUse.map(q => ({
query: q.query,
purpose: q.purpose,
provider: q.provider,
priority: q.priority,
expected_results: q.expected_results,
})),
max_sources: state.config.max_sources || 10,
include_domains: state.config.exa_include_domains ||
state.config.tavily_include_domains || [],
exclude_domains: state.config.exa_exclude_domains ||
state.config.tavily_exclude_domains || [],
trends_config: intentAnalysis?.trends_config,
skip_inference: true,
});
// 5. Handle response
if (!response.success) {
setError(response.error_message || 'Research failed');
setIsExecuting(false);
return null;
}
// 6. Store results
setIntentResult(response);
// 7. Save draft to database
autoSaveDraft(state, {
intentAnalysis: intentAnalysis || undefined,
confirmedIntent: intent,
intentResult: response,
}).catch(error => {
console.warn('[useResearchExecution] Failed to save draft:', error);
});
// 8. Transform to legacy format for backward compatibility
const legacyResult = {
success: true,
sources: response.sources.map(s => ({
title: s.title,
url: s.url,
excerpt: s.excerpt ?? undefined,
credibility_score: s.credibility_score,
})),
keyword_analysis: {
primary_keywords: state.keywords,
secondary: response.suggested_outline,
},
competitor_analysis: {},
suggested_angles: response.key_takeaways,
search_queries: [],
intent_result: response,
};
setResult(legacyResult);
setIsExecuting(false);
// 9. Cache result
researchCache.cacheResult(
state.keywords,
state.industry,
state.targetAudience,
legacyResult
);
return response;
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Research failed';
setError(errorMessage);
setIsExecuting(false);
return null;
}
}, [confirmedIntent, intentAnalysis, analyzeIntent]);
What happens:
- ✅ Validates intent is available
- ✅ Sets
isExecuting = true(shows loading state) - ✅ Prepares queries from selection or defaults
- ✅ Makes API call to
/api/research/intent/research - ✅ Handles success/error responses
- ✅ Saves draft to database
- ✅ Transforms result to legacy format
- ✅ Caches result for future use
- ✅ Sets
isExecuting = false(hides loading state)
5. API Call to Backend
Location: intentResearchApi.ts (Line 50-114)
export const executeIntentResearch = async (
request: IntentDrivenResearchRequest
): Promise<IntentDrivenResearchResponse> => {
try {
const response = await axios.post(
'/api/research/intent/research',
request,
{
headers: {
'Content-Type': 'application/json',
},
timeout: 300000, // 5 minutes
}
);
return response.data;
} catch (error: any) {
// Error handling...
}
};
Backend Endpoint: POST /api/research/intent/research
Backend Handler: backend/api/research/handlers/intent.py (Line 619-809)
What backend does:
- Validates authentication
- Gets research persona
- Determines intent (from confirmed or infers)
- Generates queries if not provided
- Executes research using Research Engine
- Runs Google Trends analysis in parallel (if enabled)
- Analyzes results using IntentAwareAnalyzer
- Merges trends data
- Returns structured response
6. UI Updates During Execution
Loading State Changes:
-
Button State (
ActionButtons.tsx):- Text changes: "Start Research" → "Researching..."
- Shows spinner icon
- Button is disabled
-
Execution Hook State (
useResearchExecution.ts):isExecuting = true→ triggers re-renderserror = null→ clears any previous errors
-
ResearchInput Component (
ResearchInput.tsx):execution.isExecutingprop updates- IntentConfirmationPanel shows loading state
7. Navigation to Results Step
Location: ResearchInput.tsx (Line 580-586)
onExecute={async (selectedQueries) => {
const result = await execution.executeIntentResearch(state, selectedQueries);
if (result?.success) {
// Skip to results step
onUpdate({ currentStep: 3 }); // ← Navigates to Step 3
}
}}
What happens:
- After successful research, automatically updates wizard state
currentStepchanges from1to3- ResearchWizard re-renders and shows
StepResultscomponent
8. Results Display - StepResults Component
Location: StepResults.tsx (Line 15-405)
Initial Check (Line 19-35):
// Check if we have intent-driven results
const intentResult: IntentDrivenResearchResponse | null =
execution?.intentResult ||
(state.results as any)?.intent_result ||
null;
// Determine if we have both types of results
const hasIntentResults = !!intentResult;
const hasTraditionalResults = !!state.results && !intentResult;
const hasAnyResults = hasIntentResults || hasTraditionalResults;
if (!hasAnyResults) {
return (
<div style={{ padding: '24px', textAlign: 'center' }}>
<p style={{ color: '#666' }}>No results available</p>
</div>
);
}
Header Section (Line 73-134):
What user sees:
- Title: "Research Results"
- Action Buttons:
- ← Back (returns to previous step)
- 📥 Export JSON (downloads results)
- 🔄 Start New Research (resets to step 1)
Tab Navigation (Line 144-210):
Tabs available:
- 📋 Summary - Executive summary and key takeaways
- 📊 Deliverables - Statistics, quotes, case studies, trends
- 🔗 Sources - All research sources with links
- 📈 Analysis - Detailed analysis and insights
Tab badges show counts:
- Deliverables tab: Total count of all deliverables
- Sources tab: Number of sources found
Summary Tab Content (Line 217-232):
What user sees:
-
Executive Summary (if available):
{intentResult.executive_summary && ( <div style={{ backgroundColor: '#f0f9ff', border: '1px solid #bae6fd', borderRadius: '8px', padding: '16px', }}> <h4>Executive Summary</h4> <p>{intentResult.executive_summary}</p> </div> )} -
Direct Answer (if available):
{intentResult.primary_answer && ( <div style={{ backgroundColor: '#f0fdf4', border: '1px solid #86efac', }}> <h4>Direct Answer</h4> <p>{intentResult.primary_answer}</p> </div> )} -
Key Takeaways (if available):
- List of bullet points
- Styled as cards or list items
Deliverables Tab Content (Line 250-280):
What user sees:
-
Statistics (
intentResult.statistics):- Data points with labels
- Formatted as cards or tables
- May include charts/graphs
-
Expert Quotes (
intentResult.expert_quotes):- Quote text
- Source attribution
- Credibility score
-
Case Studies (
intentResult.case_studies):- Case study title
- Description
- Key findings
- Source link
-
Trends (
intentResult.trends):- Trend description
- Google Trends data (if available)
- Charts showing interest over time
- Regional interest data
-
Best Practices (
intentResult.best_practices):- List of actionable recommendations
-
Comparisons (
intentResult.comparisons):- Side-by-side comparisons
- Pros/cons tables
Sources Tab Content (Line 290-320):
What user sees:
{intentResult.sources.map((source, idx) => (
<div key={idx} style={{
border: '1px solid #e0e0e0',
borderRadius: '8px',
padding: '12px',
marginBottom: '12px',
}}>
<h4>
<a href={source.url} target="_blank" rel="noopener noreferrer">
{source.title}
</a>
</h4>
{source.excerpt && <p>{source.excerpt}</p>}
<div>
<span>Credibility: {source.credibility_score}</span>
<span>Domain: {new URL(source.url).hostname}</span>
</div>
</div>
))}
Each source shows:
- Title (clickable link)
- Excerpt/preview
- Credibility score
- Domain name
- Published date (if available)
Analysis Tab Content (Line 330-360):
What user sees:
-
Confidence Score:
- Visual indicator (progress bar or badge)
- Percentage or rating
-
Gaps Identified:
- List of areas needing more research
- Suggestions for follow-up
-
Follow-up Queries:
- Suggested next research questions
- Clickable to start new research
-
Suggested Outline:
- Content structure based on research
- Organized by sections
9. IntentResultsDisplay Component
Location: IntentResultsDisplay.tsx
Used when: Intent-driven results are available
Features:
- Tabbed interface for different deliverable types
- Interactive charts for trends
- Expandable sections for detailed views
- Export functionality for trends data
Tabs:
- Summary - Overview and primary answer
- Statistics - Data points and metrics
- Expert Quotes - Quotations with sources
- Case Studies - Real-world examples
- Trends - Trend analysis with charts
- Sources - All research sources
10. State Management After Completion
Draft Saving (Line 330-337):
// Save draft with research results
autoSaveDraft(state, {
intentAnalysis: intentAnalysis || undefined,
confirmedIntent: intent,
intentResult: response,
}).catch(error => {
console.warn('[useResearchExecution] Failed to save draft:', error);
});
What happens:
- Saves complete research state to:
- localStorage (for browser persistence)
- Database (via
/api/research/projects/save)
Saved data includes:
- Keywords
- Intent analysis
- Confirmed intent
- Research results
- Configuration
- Current step (3 = completed)
Result Caching (Line 363-369):
researchCache.cacheResult(
state.keywords,
state.industry,
state.targetAudience,
legacyResult
);
Purpose: Allows quick retrieval of results for similar queries
11. User Actions After Results
Available Actions:
-
← Back Button:
- Returns to Step 1 (Research Input)
- Preserves all data
-
📥 Export JSON:
- Downloads complete results as JSON file
- Includes all deliverables, sources, and metadata
-
🔄 Start New Research:
- Resets wizard to Step 1
- Clears all results
- Starts fresh research
-
Tab Navigation:
- Switch between Summary, Deliverables, Sources, Analysis
- Each tab shows different aspect of results
12. Error Handling
If Research Fails:
-
API Error:
setError(errorMessage)in execution hook- Error displayed in UI
- Button re-enabled for retry
-
Network Error:
- Timeout after 5 minutes
- User sees "Network error" message
- Can retry the request
-
Validation Error:
- If no queries selected: Warning alert shown
- Button remains disabled until valid
Summary Flow Diagram
User clicks "Start Research"
↓
ActionButtons.onExecute()
↓
IntentConfirmationPanel.handleExecute()
↓
ResearchInput.onExecute(selectedQueries)
↓
execution.executeIntentResearch(state, queries)
↓
[Loading State: isExecuting = true]
↓
intentResearchApi.executeIntentResearch(request)
↓
POST /api/research/intent/research
↓
Backend: Research Engine + Intent Analyzer
↓
Response: IntentDrivenResearchResponse
↓
[Save draft to database]
↓
[Cache result]
↓
[Loading State: isExecuting = false]
↓
onUpdate({ currentStep: 3 })
↓
StepResults Component Renders
↓
User sees:
- Executive Summary
- Direct Answer
- Key Takeaways
- Deliverables (Statistics, Quotes, Case Studies, Trends)
- Sources (with links)
- Analysis (Confidence, Gaps, Follow-ups)
Key Files Reference
-
Frontend Components:
ActionButtons.tsx- Start Research buttonIntentConfirmationPanel.tsx- Intent confirmation UIResearchInput.tsx- Step 1 componentStepResults.tsx- Step 3 results displayIntentResultsDisplay.tsx- Intent-driven results renderer
-
Hooks:
useResearchExecution.ts- Research execution logicuseResearchWizard.ts- Wizard state management
-
API:
intentResearchApi.ts- API client for research endpoints
-
Backend:
handlers/intent.py- Intent research endpoint handlerservices/research/intent/- Intent analysis servicesservices/research/core/- Research engine
UI States Summary
| State | Button Text | Button State | UI Feedback |
|---|---|---|---|
| Ready | "Start Research" | Enabled | Info alert: "Ready to start research!" |
| No Queries | "Start Research" | Disabled | Warning: "Please select at least one query" |
| Executing | "Researching..." | Disabled + Spinner | Loading indicator |
| Success | N/A (on Results page) | N/A | Results displayed in tabs |
| Error | "Start Research" | Enabled | Error message displayed |
This completes the code walkthrough from button click to results display! 🎉