Added video studio router and endpoints. Added research router and endpoints. Added youtube router and endpoints. Added onboarding utils router and endpoints. Added onboarding utils service. Added onboarding utils models. Added onboarding utils routes. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils. Added onboarding utils utils.
This commit is contained in:
@@ -7,7 +7,8 @@ import {
|
||||
ResearchHistoryEntry
|
||||
} from '../../../utils/researchHistory';
|
||||
import {
|
||||
expandKeywords
|
||||
expandKeywords,
|
||||
expandKeywordsWithPersona
|
||||
} from '../../../utils/keywordExpansion';
|
||||
import {
|
||||
generateResearchAngles
|
||||
@@ -28,13 +29,17 @@ import { CurrentKeywords } from './components/CurrentKeywords';
|
||||
import { ResearchAngles } from './components/ResearchAngles';
|
||||
import { TavilyOptions } from './components/TavilyOptions';
|
||||
import { ExaOptions } from './components/ExaOptions';
|
||||
import { PersonalizationIndicator, PersonalizationBadge } from './components/PersonalizationIndicator';
|
||||
import { IntentConfirmationPanel } from './components/IntentConfirmationPanel';
|
||||
import { ResearchExecution } from '../types/research.types';
|
||||
|
||||
interface ResearchInputProps extends WizardStepProps {
|
||||
advanced?: boolean;
|
||||
onAdvancedChange?: (advanced: boolean) => void;
|
||||
execution?: ResearchExecution;
|
||||
}
|
||||
|
||||
export const ResearchInput: React.FC<ResearchInputProps> = ({ state, onUpdate, advanced: advancedProp, onAdvancedChange }) => {
|
||||
export const ResearchInput: React.FC<ResearchInputProps> = ({ state, onUpdate, onNext, advanced: advancedProp, onAdvancedChange, execution }) => {
|
||||
const [currentPlaceholder, setCurrentPlaceholder] = useState(0);
|
||||
const [providerAvailability, setProviderAvailability] = useState<ProviderAvailability | null>(null);
|
||||
const [loadingConfig, setLoadingConfig] = useState(true);
|
||||
@@ -46,6 +51,18 @@ export const ResearchInput: React.FC<ResearchInputProps> = ({ state, onUpdate, a
|
||||
suggestions: string[];
|
||||
} | null>(null);
|
||||
const [researchAngles, setResearchAngles] = useState<string[]>([]);
|
||||
const [researchPersona, setResearchPersona] = useState<{
|
||||
research_angles?: string[];
|
||||
recommended_presets?: Array<{
|
||||
name: string;
|
||||
keywords: string | string[];
|
||||
description?: string;
|
||||
}>;
|
||||
suggested_keywords?: string[];
|
||||
keyword_expansion_patterns?: Record<string, string[]>;
|
||||
industry?: string;
|
||||
target_audience?: string;
|
||||
} | null>(null);
|
||||
const fileInputRef = React.useRef<HTMLInputElement>(null);
|
||||
|
||||
// Use prop if provided, otherwise use local state
|
||||
@@ -81,36 +98,165 @@ export const ResearchInput: React.FC<ResearchInputProps> = ({ state, onUpdate, a
|
||||
exa_key_status: 'missing'
|
||||
});
|
||||
|
||||
// Apply persona defaults if not already set (with null checks)
|
||||
// Phase 2: Apply persona defaults from API
|
||||
// Backend now returns hyper-personalized values (never "General")
|
||||
// Always apply if we have values and user hasn't customized
|
||||
if (config?.persona_defaults) {
|
||||
if (config.persona_defaults.industry && state.industry === 'General') {
|
||||
onUpdate({ industry: config.persona_defaults.industry });
|
||||
const defaults = config.persona_defaults;
|
||||
|
||||
// Log whether research persona exists
|
||||
console.log('[ResearchInput] Persona defaults loaded:', {
|
||||
hasResearchPersona: defaults.has_research_persona,
|
||||
industry: defaults.industry,
|
||||
targetAudience: defaults.target_audience,
|
||||
hasDomains: defaults.suggested_domains?.length > 0
|
||||
});
|
||||
|
||||
// Apply industry if provided and user hasn't customized
|
||||
// Phase 2: Backend never returns "General", so we apply unless user has real value
|
||||
if (defaults.industry && (!state.industry || state.industry === 'General')) {
|
||||
onUpdate({ industry: defaults.industry });
|
||||
}
|
||||
if (config.persona_defaults.target_audience && state.targetAudience === 'General') {
|
||||
onUpdate({ targetAudience: config.persona_defaults.target_audience });
|
||||
|
||||
// Apply target audience if provided
|
||||
if (defaults.target_audience && (!state.targetAudience || state.targetAudience === 'General')) {
|
||||
onUpdate({ targetAudience: defaults.target_audience });
|
||||
}
|
||||
|
||||
// Apply suggested Exa domains if Exa is available and not already set
|
||||
if (config.provider_availability?.exa_available && config.persona_defaults.suggested_domains?.length > 0) {
|
||||
if (config.provider_availability?.exa_available && defaults.suggested_domains?.length > 0) {
|
||||
if (!state.config.exa_include_domains || state.config.exa_include_domains.length === 0) {
|
||||
onUpdate({
|
||||
config: {
|
||||
...state.config,
|
||||
exa_include_domains: config.persona_defaults.suggested_domains
|
||||
exa_include_domains: defaults.suggested_domains
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Apply suggested Exa category if available
|
||||
if (config.persona_defaults.suggested_exa_category && !state.config.exa_category) {
|
||||
if (defaults.suggested_exa_category && !state.config.exa_category) {
|
||||
onUpdate({
|
||||
config: {
|
||||
...state.config,
|
||||
exa_category: config.persona_defaults.suggested_exa_category
|
||||
exa_category: defaults.suggested_exa_category
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Phase 2+: Apply enhanced Exa defaults from research persona
|
||||
if (defaults.suggested_exa_search_type && !state.config.exa_search_type) {
|
||||
onUpdate({
|
||||
config: {
|
||||
...state.config,
|
||||
exa_search_type: defaults.suggested_exa_search_type as 'auto' | 'keyword' | 'neural'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Phase 2+: Apply Tavily defaults from research persona
|
||||
if (defaults.suggested_tavily_topic && !state.config.tavily_topic) {
|
||||
onUpdate({
|
||||
config: {
|
||||
...state.config,
|
||||
tavily_topic: defaults.suggested_tavily_topic as 'general' | 'news' | 'finance'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (defaults.suggested_tavily_search_depth && !state.config.tavily_search_depth) {
|
||||
onUpdate({
|
||||
config: {
|
||||
...state.config,
|
||||
tavily_search_depth: defaults.suggested_tavily_search_depth as 'basic' | 'advanced'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (defaults.suggested_tavily_include_answer && !state.config.tavily_include_answer) {
|
||||
const answerValue = defaults.suggested_tavily_include_answer === 'true' ? true :
|
||||
defaults.suggested_tavily_include_answer === 'false' ? false :
|
||||
defaults.suggested_tavily_include_answer as 'basic' | 'advanced';
|
||||
onUpdate({
|
||||
config: {
|
||||
...state.config,
|
||||
tavily_include_answer: answerValue
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (defaults.suggested_tavily_time_range && !state.config.tavily_time_range) {
|
||||
onUpdate({
|
||||
config: {
|
||||
...state.config,
|
||||
tavily_time_range: defaults.suggested_tavily_time_range as 'day' | 'week' | 'month' | 'year'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (defaults.suggested_tavily_raw_content_format && !state.config.tavily_include_raw_content) {
|
||||
const rawContentValue = defaults.suggested_tavily_raw_content_format === 'true' ? true :
|
||||
defaults.suggested_tavily_raw_content_format === 'false' ? false :
|
||||
defaults.suggested_tavily_raw_content_format as 'markdown' | 'text';
|
||||
onUpdate({
|
||||
config: {
|
||||
...state.config,
|
||||
tavily_include_raw_content: rawContentValue
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Phase 2: Apply additional hyper-personalization defaults from research persona
|
||||
if (defaults.has_research_persona && config.research_persona) {
|
||||
console.log('[ResearchInput] Applying research persona hyper-personalization:', {
|
||||
researchMode: defaults.default_research_mode,
|
||||
provider: defaults.default_provider,
|
||||
suggestedKeywords: defaults.suggested_keywords?.length || 0,
|
||||
researchAngles: defaults.research_angles?.length || 0,
|
||||
recommendedPresets: config.research_persona.recommended_presets?.length || 0
|
||||
});
|
||||
|
||||
// Store research persona data for personalized placeholders, keyword expansion, and research angles
|
||||
setResearchPersona({
|
||||
research_angles: config.research_persona.research_angles || defaults.research_angles,
|
||||
recommended_presets: config.research_persona.recommended_presets || [],
|
||||
suggested_keywords: config.research_persona.suggested_keywords || defaults.suggested_keywords,
|
||||
keyword_expansion_patterns: config.research_persona.keyword_expansion_patterns,
|
||||
industry: config.research_persona.default_industry || defaults.industry,
|
||||
target_audience: config.research_persona.default_target_audience || defaults.target_audience
|
||||
});
|
||||
|
||||
// Apply default research mode if not already customized
|
||||
if (defaults.default_research_mode && state.researchMode === 'comprehensive') {
|
||||
const validModes = ['basic', 'comprehensive', 'targeted'] as const;
|
||||
if (validModes.includes(defaults.default_research_mode as typeof validModes[number])) {
|
||||
onUpdate({ researchMode: defaults.default_research_mode as typeof validModes[number] });
|
||||
}
|
||||
}
|
||||
|
||||
// Apply default provider (only if it's available)
|
||||
if (defaults.default_provider) {
|
||||
const validProviders = ['exa', 'tavily', 'google'] as const;
|
||||
type ValidProvider = typeof validProviders[number];
|
||||
|
||||
if (validProviders.includes(defaults.default_provider as ValidProvider)) {
|
||||
const providerAvailable =
|
||||
(defaults.default_provider === 'exa' && config.provider_availability?.exa_available) ||
|
||||
(defaults.default_provider === 'tavily' && config.provider_availability?.tavily_available) ||
|
||||
(defaults.default_provider === 'google' && config.provider_availability?.google_available);
|
||||
|
||||
if (providerAvailable && !state.config.provider) {
|
||||
onUpdate({
|
||||
config: {
|
||||
...state.config,
|
||||
provider: defaults.default_provider as ValidProvider
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
@@ -135,8 +281,8 @@ export const ResearchInput: React.FC<ResearchInputProps> = ({ state, onUpdate, a
|
||||
loadConfig();
|
||||
}, []); // Only run once on mount
|
||||
|
||||
// Get industry-specific placeholders
|
||||
const placeholderExamples = getIndustryPlaceholders(state.industry);
|
||||
// Get industry-specific placeholders, enhanced with research persona data
|
||||
const placeholderExamples = getIndustryPlaceholders(state.industry, researchPersona || undefined);
|
||||
|
||||
// Rotate placeholder examples every 4 seconds
|
||||
useEffect(() => {
|
||||
@@ -151,41 +297,26 @@ export const ResearchInput: React.FC<ResearchInputProps> = ({ state, onUpdate, a
|
||||
setCurrentPlaceholder(0);
|
||||
}, [state.industry]);
|
||||
|
||||
// Auto-set provider based on research mode
|
||||
// Auto-set provider based on availability
|
||||
// Priority: Exa → Tavily → Google for ALL modes (including basic)
|
||||
// This provides better semantic search results for content creators
|
||||
useEffect(() => {
|
||||
if (!providerAvailability) return;
|
||||
|
||||
// Priority: Exa → Tavily → Google for all modes
|
||||
let newProvider: ResearchProvider = 'google';
|
||||
|
||||
switch (state.researchMode) {
|
||||
case 'basic':
|
||||
// Basic: Google only (fast, simple)
|
||||
newProvider = 'google';
|
||||
break;
|
||||
case 'comprehensive':
|
||||
// Comprehensive: Prefer Exa if available, then Tavily, fallback to Google
|
||||
if (providerAvailability.exa_available) {
|
||||
newProvider = 'exa';
|
||||
} else if (providerAvailability.tavily_available) {
|
||||
newProvider = 'tavily';
|
||||
} else {
|
||||
newProvider = 'google';
|
||||
}
|
||||
break;
|
||||
case 'targeted':
|
||||
// Targeted: Prefer Exa if available, then Tavily, fallback to Google
|
||||
if (providerAvailability.exa_available) {
|
||||
newProvider = 'exa';
|
||||
} else if (providerAvailability.tavily_available) {
|
||||
newProvider = 'tavily';
|
||||
} else {
|
||||
newProvider = 'google';
|
||||
}
|
||||
break;
|
||||
if (providerAvailability.exa_available) {
|
||||
newProvider = 'exa';
|
||||
} else if (providerAvailability.tavily_available) {
|
||||
newProvider = 'tavily';
|
||||
} else {
|
||||
newProvider = 'google';
|
||||
}
|
||||
|
||||
// Only update if provider changed
|
||||
if (state.config.provider !== newProvider) {
|
||||
console.log('[ResearchInput] Auto-selecting provider:', newProvider, 'for mode:', state.researchMode);
|
||||
onUpdate({ config: { ...state.config, provider: newProvider } });
|
||||
}
|
||||
}, [state.researchMode, providerAvailability]);
|
||||
@@ -225,26 +356,70 @@ export const ResearchInput: React.FC<ResearchInputProps> = ({ state, onUpdate, a
|
||||
}, [state.industry, providerAvailability]);
|
||||
|
||||
// Expand keywords when keywords or industry changes
|
||||
// Enhanced to use research persona data if available
|
||||
useEffect(() => {
|
||||
if (state.keywords.length > 0 && state.industry !== 'General') {
|
||||
const expansion = expandKeywords(state.keywords, state.industry);
|
||||
if (state.keywords.length > 0) {
|
||||
let expansion;
|
||||
|
||||
// If we have research persona with keyword expansion patterns, use them
|
||||
if (researchPersona?.keyword_expansion_patterns && Object.keys(researchPersona.keyword_expansion_patterns).length > 0) {
|
||||
expansion = expandKeywordsWithPersona(state.keywords, researchPersona.keyword_expansion_patterns, researchPersona.suggested_keywords);
|
||||
} else if (state.industry !== 'General') {
|
||||
// Fallback to industry-based expansion
|
||||
expansion = expandKeywords(state.keywords, state.industry);
|
||||
} else {
|
||||
expansion = { original: state.keywords, expanded: state.keywords, suggestions: [] };
|
||||
}
|
||||
|
||||
setKeywordExpansion(expansion);
|
||||
} else {
|
||||
setKeywordExpansion(null);
|
||||
}
|
||||
}, [state.keywords, state.industry]);
|
||||
}, [state.keywords, state.industry, researchPersona]);
|
||||
|
||||
// Generate research angles when keywords change
|
||||
// Enhanced to prioritize research persona angles if available
|
||||
useEffect(() => {
|
||||
if (state.keywords.length > 0) {
|
||||
// Use the first keyword (or joined keywords) as the query
|
||||
const query = state.keywords.join(' ');
|
||||
const angles = generateResearchAngles(query, state.industry);
|
||||
setResearchAngles(angles);
|
||||
let angles: string[] = [];
|
||||
|
||||
// Priority 1: Use research persona angles if available and relevant
|
||||
if (researchPersona?.research_angles && researchPersona.research_angles.length > 0) {
|
||||
// Filter persona angles that are relevant to the current query
|
||||
const relevantPersonaAngles = researchPersona.research_angles
|
||||
.filter(angle => {
|
||||
const angleLower = angle.toLowerCase();
|
||||
const queryLower = query.toLowerCase();
|
||||
// Check if angle contains any keyword from query or vice versa
|
||||
return state.keywords.some(kw => angleLower.includes(kw.toLowerCase()) || queryLower.includes(kw.toLowerCase())) ||
|
||||
angleLower.includes(queryLower) || queryLower.includes(angleLower);
|
||||
})
|
||||
.slice(0, 3); // Use top 3 relevant persona angles
|
||||
|
||||
angles.push(...relevantPersonaAngles);
|
||||
}
|
||||
|
||||
// Priority 2: Generate additional angles using pattern matching
|
||||
const generatedAngles = generateResearchAngles(query, state.industry);
|
||||
|
||||
// Merge and deduplicate, prioritizing persona angles
|
||||
const allAngles = [...angles, ...generatedAngles];
|
||||
const uniqueAngles = Array.from(new Set(allAngles.map(a => a.toLowerCase())))
|
||||
.slice(0, 5) // Limit to 5 total
|
||||
.map(a => {
|
||||
// Find original casing from persona angles first, then generated
|
||||
const personaMatch = angles.find(pa => pa.toLowerCase() === a);
|
||||
if (personaMatch) return personaMatch;
|
||||
const generatedMatch = generatedAngles.find(ga => ga.toLowerCase() === a);
|
||||
return generatedMatch || a.charAt(0).toUpperCase() + a.slice(1);
|
||||
});
|
||||
|
||||
setResearchAngles(uniqueAngles);
|
||||
} else {
|
||||
setResearchAngles([]);
|
||||
}
|
||||
}, [state.keywords, state.industry]);
|
||||
}, [state.keywords, state.industry, researchPersona]);
|
||||
|
||||
// Event handlers
|
||||
const handleKeywordsChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
@@ -356,6 +531,11 @@ export const ResearchInput: React.FC<ResearchInputProps> = ({ state, onUpdate, a
|
||||
fontSize: '20px',
|
||||
}}>🔍</span>
|
||||
Research Topic & Keywords
|
||||
<PersonalizationIndicator
|
||||
type="placeholder"
|
||||
hasPersona={!!researchPersona}
|
||||
source={researchPersona ? "from your research persona" : undefined}
|
||||
/>
|
||||
</label>
|
||||
|
||||
{/* Advanced Toggle and Upload Button */}
|
||||
@@ -482,6 +662,26 @@ export const ResearchInput: React.FC<ResearchInputProps> = ({ state, onUpdate, a
|
||||
{/* Smart Input Detection Indicator */}
|
||||
<SmartInputIndicator keywords={state.keywords} />
|
||||
|
||||
{/* Intent Analysis Panel - Show when intent analysis is available */}
|
||||
{execution && (execution.isAnalyzingIntent || execution.intentAnalysis) && (
|
||||
<IntentConfirmationPanel
|
||||
isAnalyzing={execution.isAnalyzingIntent}
|
||||
intentAnalysis={execution.intentAnalysis}
|
||||
confirmedIntent={execution.confirmedIntent}
|
||||
onConfirm={execution.confirmIntent}
|
||||
onUpdateField={execution.updateIntentField}
|
||||
onExecute={async () => {
|
||||
const result = await execution.executeIntentResearch(state);
|
||||
if (result?.success) {
|
||||
// Skip to results step
|
||||
onUpdate({ currentStep: 3 });
|
||||
}
|
||||
}}
|
||||
onDismiss={execution.clearIntent}
|
||||
isExecuting={execution.isExecuting}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Keyword Expansion Suggestions */}
|
||||
{keywordExpansion && keywordExpansion.suggestions.length > 0 && (
|
||||
<KeywordExpansion
|
||||
@@ -502,6 +702,7 @@ export const ResearchInput: React.FC<ResearchInputProps> = ({ state, onUpdate, a
|
||||
<ResearchAngles
|
||||
angles={researchAngles}
|
||||
onUseAngle={handleUseAngle}
|
||||
hasPersona={!!researchPersona}
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user