/** * Intent-Driven Research API Client * * Client for the new intent-driven research endpoints: * - /api/research/intent/analyze - Analyze user intent * - /api/research/intent/research - Execute intent-driven research */ import { apiClient, aiApiClient } from './client'; import { AnalyzeIntentRequest, AnalyzeIntentResponse, IntentDrivenResearchRequest, IntentDrivenResearchResponse, ResearchIntent, } from '../components/Research/types/intent.types'; import { WizardState } from '../components/Research/types/research.types'; import { BlogResearchResponse } from '../services/blogWriterApi'; /** * Analyze user input to understand research intent. * * Uses AI to infer: * - What questions need answering * - What deliverables user expects (statistics, quotes, case studies) * - What depth and focus is appropriate */ export const analyzeIntent = async ( request: AnalyzeIntentRequest ): Promise => { try { const { data } = await apiClient.post( '/api/research/intent/analyze', request ); return data; } catch (error: any) { console.error('[intentResearchApi] analyzeIntent failed:', error); return { success: false, intent: { primary_question: request.user_input, secondary_questions: [], purpose: 'learn', content_output: 'general', expected_deliverables: ['key_statistics'], depth: 'detailed', focus_areas: [], also_answering: [], perspective: null, time_sensitivity: null, input_type: 'keywords', original_input: request.user_input, confidence: 0.5, needs_clarification: true, clarifying_questions: [], }, analysis_summary: 'Failed to analyze intent', suggested_queries: [], suggested_keywords: [], suggested_angles: [], quick_options: [], error_message: error.message || 'Failed to analyze intent', }; } }; /** * Execute research based on user intent. * * This is the main endpoint for intent-driven research. It: * 1. Uses the confirmed intent (or infers from user_input) * 2. Generates targeted queries for each expected deliverable * 3. Executes research using Exa/Tavily/Google * 4. Analyzes results through the lens of user intent * 5. Returns exactly what the user needs */ export const executeIntentResearch = async ( request: IntentDrivenResearchRequest ): Promise => { try { const { data } = await apiClient.post( '/api/research/intent/research', request ); return data; } catch (error: any) { console.error('[intentResearchApi] executeIntentResearch failed:', error); return { success: false, primary_answer: '', secondary_answers: {}, statistics: [], expert_quotes: [], case_studies: [], trends: [], comparisons: [], best_practices: [], step_by_step: [], pros_cons: null, definitions: {}, examples: [], predictions: [], executive_summary: '', key_takeaways: [], suggested_outline: [], sources: [], confidence: 0, gaps_identified: [], follow_up_queries: [], intent: null, error_message: error.message || 'Research failed', }; } }; /** * Combined function to analyze intent and execute research in one call. * * For simple use cases where user doesn't need to confirm intent. */ export const quickIntentResearch = async ( userInput: string, options?: { usePersona?: boolean; useCompetitorData?: boolean; maxSources?: number; includeDomains?: string[]; excludeDomains?: string[]; } ): Promise => { try { // First analyze intent const analyzeResponse = await analyzeIntent({ user_input: userInput, keywords: userInput.split(' ').filter(k => k.length > 2), use_persona: options?.usePersona ?? true, use_competitor_data: options?.useCompetitorData ?? true, }); if (!analyzeResponse.success) { return { success: false, primary_answer: '', secondary_answers: {}, statistics: [], expert_quotes: [], case_studies: [], trends: [], comparisons: [], best_practices: [], step_by_step: [], pros_cons: null, definitions: {}, examples: [], predictions: [], executive_summary: '', key_takeaways: [], suggested_outline: [], sources: [], confidence: 0, gaps_identified: [], follow_up_queries: [], intent: null, error_message: analyzeResponse.error_message || 'Failed to analyze intent', }; } // Execute research with inferred intent return await executeIntentResearch({ user_input: userInput, confirmed_intent: analyzeResponse.intent, selected_queries: analyzeResponse.suggested_queries.slice(0, 5), // Top 5 queries max_sources: options?.maxSources ?? 10, include_domains: options?.includeDomains ?? [], exclude_domains: options?.excludeDomains ?? [], skip_inference: true, // We already have intent }); } catch (error: any) { console.error('[intentResearchApi] quickIntentResearch failed:', error); return { success: false, primary_answer: '', secondary_answers: {}, statistics: [], expert_quotes: [], case_studies: [], trends: [], comparisons: [], best_practices: [], step_by_step: [], pros_cons: null, definitions: {}, examples: [], predictions: [], executive_summary: '', key_takeaways: [], suggested_outline: [], sources: [], confidence: 0, gaps_identified: [], follow_up_queries: [], intent: null, error_message: error.message || 'Research failed', }; } }; /** * Save research project to Asset Library. * * Saves the complete research project state so users can resume later. */ export const saveResearchProject = async ( state: WizardState, options?: { intentAnalysis?: AnalyzeIntentResponse | null; confirmedIntent?: ResearchIntent | null; intentResult?: IntentDrivenResearchResponse | null; legacyResult?: BlogResearchResponse | null; title?: string; description?: string; projectId?: string; // Project ID for updates (optional) } ): Promise<{ success: boolean; asset_id?: number; project_id?: string; message: string }> => { try { // Generate project title from keywords if not provided const projectTitle = options?.title || (state.keywords.length > 0 ? `Research: ${state.keywords.slice(0, 3).join(', ')}` : 'Research Project'); // Generate description if not provided const projectDescription = options?.description || `Research project on ${state.keywords.join(', ')}. ` + `Industry: ${state.industry}, Target Audience: ${state.targetAudience}`; const request = { project_id: options?.projectId || undefined, // Include project_id for updates title: projectTitle, keywords: state.keywords, industry: state.industry, target_audience: state.targetAudience, research_mode: state.researchMode, config: state.config, intent_analysis: options?.intentAnalysis ? { success: options.intentAnalysis.success, intent: options.intentAnalysis.intent, analysis_summary: options.intentAnalysis.analysis_summary, suggested_queries: options.intentAnalysis.suggested_queries, suggested_keywords: options.intentAnalysis.suggested_keywords, suggested_angles: options.intentAnalysis.suggested_angles, quick_options: options.intentAnalysis.quick_options, trends_config: options.intentAnalysis.trends_config, } : null, confirmed_intent: options?.confirmedIntent || null, intent_result: options?.intentResult ? { success: options.intentResult.success, primary_answer: options.intentResult.primary_answer, secondary_answers: options.intentResult.secondary_answers, statistics: options.intentResult.statistics, expert_quotes: options.intentResult.expert_quotes, case_studies: options.intentResult.case_studies, trends: options.intentResult.trends, comparisons: options.intentResult.comparisons, best_practices: options.intentResult.best_practices, step_by_step: options.intentResult.step_by_step, pros_cons: options.intentResult.pros_cons, definitions: options.intentResult.definitions, examples: options.intentResult.examples, predictions: options.intentResult.predictions, executive_summary: options.intentResult.executive_summary, key_takeaways: options.intentResult.key_takeaways, suggested_outline: options.intentResult.suggested_outline, sources: options.intentResult.sources, confidence: options.intentResult.confidence, gaps_identified: options.intentResult.gaps_identified, follow_up_queries: options.intentResult.follow_up_queries, intent: options.intentResult.intent, google_trends_data: options.intentResult.google_trends_data, } : null, legacy_result: options?.legacyResult || null, current_step: state.currentStep, description: projectDescription, }; const { data } = await apiClient.post<{ success: boolean; asset_id?: number; project_id?: string; message: string }>( '/api/research/projects/save', request ); // After saving project, also save to ContentAsset library (following podcast maker pattern) if (data.success && data.project_id) { try { await saveResearchProjectToAssetLibrary({ projectId: data.project_id, title: projectTitle, description: projectDescription, keywords: state.keywords, industry: state.industry, targetAudience: state.targetAudience, researchMode: state.researchMode, config: state.config, status: (options?.intentResult || options?.legacyResult) ? 'completed' : (options?.intentAnalysis ? 'draft' : 'draft'), currentStep: state.currentStep, }); console.log(`[intentResearchApi] ✅ Research project saved to asset library: project_id=${data.project_id}, status=${(options?.intentResult || options?.legacyResult) ? 'completed' : 'draft'}`); } catch (error) { console.warn('[intentResearchApi] Failed to save research project to asset library:', error); // Don't fail the whole operation if asset creation fails } } return data; } catch (error: any) { console.error('[intentResearchApi] saveResearchProject failed:', error); return { success: false, message: error.message || 'Failed to save research project', }; } }; /** * Save research project to Asset Library (ContentAsset). * Following podcast maker pattern: podcastApi.saveAudioToAssetLibrary() */ /** * Save research project to Asset Library (ContentAsset). * Following podcast maker pattern: podcastApi.saveAudioToAssetLibrary() * * Checks for existing asset with same project_id and updates it, otherwise creates new one. */ export const saveResearchProjectToAssetLibrary = async (params: { projectId: string; title: string; description?: string; keywords: string[]; industry?: string; targetAudience?: string; researchMode?: string; config?: any; status?: string; currentStep?: number; }): Promise<{ assetId: number }> => { try { const fileUrl = `/api/research/projects/${params.projectId}/export`; const assetMetadata = { project_type: 'research_project', project_id: params.projectId, status: params.status || 'draft', keywords: params.keywords, industry: params.industry, target_audience: params.targetAudience, research_mode: params.researchMode, current_step: params.currentStep || 1, }; // Check if asset already exists for this project_id try { const searchResponse = await aiApiClient.get('/api/content-assets/', { params: { asset_type: 'text', source_module: 'research_tools', search: params.projectId, limit: 100, }, }); // Find existing asset with matching project_id in metadata const existingAsset = searchResponse.data.assets?.find( (asset: any) => asset.asset_metadata?.project_type === 'research_project' && asset.asset_metadata?.project_id === params.projectId ); if (existingAsset) { // Update existing asset const updateResponse = await aiApiClient.put(`/api/content-assets/${existingAsset.id}`, { title: params.title, description: params.description || `Research project on ${params.keywords.slice(0, 3).join(', ')}`, tags: ['research', 'research_project', params.projectId, ...params.keywords.slice(0, 5)], asset_metadata: assetMetadata, }); console.log(`[intentResearchApi] Updated existing ContentAsset for project ${params.projectId}: asset_id=${existingAsset.id}`); return { assetId: updateResponse.data.id }; } } catch (searchError) { console.warn('[intentResearchApi] Failed to search for existing asset, creating new one:', searchError); // Continue to create new asset if search fails } // Create new asset if none exists const response = await aiApiClient.post('/api/content-assets/', { asset_type: 'text', source_module: 'research_tools', filename: `research_${params.projectId}.json`, file_url: fileUrl, title: params.title, description: params.description || `Research project on ${params.keywords.slice(0, 3).join(', ')}`, tags: ['research', 'research_project', params.projectId, ...params.keywords.slice(0, 5)], asset_metadata: assetMetadata, cost: 0, mime_type: 'application/json', }); console.log(`[intentResearchApi] ✅ Created new ContentAsset for project ${params.projectId}: asset_id=${response.data.id}, status=${params.status || 'draft'}, source_module=research_tools, asset_type=text`); return { assetId: response.data.id }; } catch (error: any) { console.error('[intentResearchApi] saveResearchProjectToAssetLibrary failed:', error); throw error; } }; /** * List research projects. */ export const listResearchProjects = async (params?: { status?: string; is_favorite?: boolean; limit?: number; offset?: number; }): Promise<{ projects: any[]; total: number; limit: number; offset: number }> => { try { const { data } = await apiClient.get<{ projects: any[]; total: number; limit: number; offset: number }>( '/api/research/projects', { params } ); return data; } catch (error: any) { console.error('[intentResearchApi] listResearchProjects failed:', error); return { projects: [], total: 0, limit: params?.limit || 50, offset: params?.offset || 0, }; } }; /** * Get a single research project by ID. */ export const getResearchProject = async (projectId: string): Promise => { try { const { data } = await apiClient.get(`/api/research/projects/${projectId}`); return data; } catch (error: any) { console.error('[intentResearchApi] getResearchProject failed:', error); throw error; } }; /** * Delete a research project. */ export const deleteResearchProject = async (projectId: string): Promise => { try { await apiClient.delete(`/api/research/projects/${projectId}`); } catch (error: any) { console.error('[intentResearchApi] deleteResearchProject failed:', error); throw error; } }; /** * Toggle favorite status of a research project. */ export const toggleResearchProjectFavorite = async (projectId: string): Promise => { try { // First get the project to check current favorite status const project = await getResearchProject(projectId); const newFavoriteStatus = !project.is_favorite; // Update the project with new favorite status const { data } = await apiClient.put(`/api/research/projects/${projectId}`, { is_favorite: newFavoriteStatus, }); return data; } catch (error: any) { console.error('[intentResearchApi] toggleResearchProjectFavorite failed:', error); throw error; } }; export const intentResearchApi = { analyzeIntent, executeIntentResearch, quickIntentResearch, saveResearchProject, saveResearchProjectToAssetLibrary, listResearchProjects, getResearchProject, deleteResearchProject, toggleResearchProjectFavorite, }; export default intentResearchApi;