Scheduled research persona generation

This commit is contained in:
ajaysi
2025-11-05 08:51:00 +05:30
parent 55087c4f37
commit d99c7c83a7
98 changed files with 14518 additions and 828 deletions

View File

@@ -0,0 +1,191 @@
/**
* Smart Keyword Expansion Utility
* Expands user keywords with industry-specific related terms using rule-based logic
*/
// Industry-specific keyword expansion maps
// Format: { industry: { keyword: [expansions] } }
const industryKeywordExpansions: Record<string, Record<string, string[]>> = {
Healthcare: {
'AI': ['medical AI', 'healthcare AI', 'clinical AI', 'diagnostic AI', 'healthcare automation'],
'tools': ['medical devices', 'clinical tools', 'diagnostic systems', 'healthcare software'],
'automation': ['healthcare automation', 'clinical automation', 'patient care automation', 'medical workflow automation'],
'technology': ['healthtech', 'medical technology', 'clinical technology', 'digital health'],
'data': ['health data', 'medical records', 'patient data', 'clinical data', 'healthcare analytics'],
'research': ['medical research', 'clinical research', 'biomedical research', 'healthcare studies'],
'management': ['patient management', 'care coordination', 'healthcare administration'],
},
Technology: {
'AI': ['machine learning', 'deep learning', 'neural networks', 'artificial intelligence applications'],
'cloud': ['AWS', 'Azure', 'GCP', 'cloud infrastructure', 'cloud computing'],
'security': ['cybersecurity', 'data protection', 'privacy compliance', 'information security'],
'automation': ['IT automation', 'devops automation', 'software automation', 'process automation'],
'development': ['software development', 'web development', 'mobile development', 'app development'],
'tools': ['development tools', 'software tools', 'developer tools', 'tech stack'],
'platform': ['SaaS platform', 'cloud platform', 'development platform', 'tech platform'],
},
Finance: {
'fintech': ['financial technology', 'digital banking', 'payment solutions', 'financial services tech'],
'investing': ['investment strategies', 'portfolio management', 'trading platforms', 'wealth management'],
'cryptocurrency': ['blockchain', 'digital assets', 'DeFi', 'crypto trading'],
'banking': ['digital banking', 'online banking', 'mobile banking', 'banking technology'],
'payment': ['payment processing', 'payment gateways', 'digital payments', 'payment solutions'],
'analysis': ['financial analysis', 'market analysis', 'risk analysis', 'investment analysis'],
'compliance': ['financial compliance', 'regulatory compliance', 'fintech regulations'],
},
Marketing: {
'SEO': ['search engine optimization', 'SEO strategy', 'SEO tools', 'keyword research'],
'content': ['content marketing', 'content strategy', 'content creation', 'content distribution'],
'social media': ['social media marketing', 'social media strategy', 'social media advertising'],
'advertising': ['digital advertising', 'online advertising', 'PPC', 'display advertising'],
'analytics': ['marketing analytics', 'web analytics', 'campaign analytics', 'performance metrics'],
'automation': ['marketing automation', 'email marketing', 'lead generation', 'CRM'],
'strategy': ['marketing strategy', 'brand strategy', 'digital strategy', 'growth strategy'],
},
Business: {
'management': ['business management', 'operations management', 'strategic management'],
'strategy': ['business strategy', 'growth strategy', 'competitive strategy', 'market strategy'],
'startup': ['startup funding', 'venture capital', 'startup ecosystem', 'entrepreneurship'],
'operations': ['business operations', 'process optimization', 'operational efficiency'],
'leadership': ['business leadership', 'executive leadership', 'management leadership'],
'innovation': ['business innovation', 'digital transformation', 'business disruption'],
'analytics': ['business analytics', 'data analytics', 'business intelligence', 'KPIs'],
},
Education: {
'e-learning': ['online learning', 'distance education', 'digital learning', 'virtual classrooms'],
'edtech': ['education technology', 'learning management systems', 'educational software'],
'teaching': ['teaching methods', 'pedagogy', 'instructional design', 'curriculum development'],
'student': ['student engagement', 'student success', 'student analytics', 'learning outcomes'],
'training': ['professional training', 'skills development', 'corporate training', 'certification'],
'assessment': ['educational assessment', 'learning assessment', 'student evaluation'],
},
Real_Estate: {
'property': ['real estate', 'real estate market', 'property investment', 'property management'],
'technology': ['proptech', 'real estate technology', 'property tech', 'real estate software'],
'investment': ['real estate investment', 'property investment', 'real estate portfolio'],
'market': ['housing market', 'real estate trends', 'market analysis', 'property values'],
'management': ['property management', 'facility management', 'real estate operations'],
},
Travel: {
'tourism': ['travel industry', 'hospitality', 'travel trends', 'tourism technology'],
'booking': ['travel booking', 'online booking', 'travel platforms', 'reservation systems'],
'technology': ['travel tech', 'travel technology', 'tourism tech', 'hospitality technology'],
'experience': ['travel experience', 'customer experience', 'tourism experiences'],
},
Science: {
'research': ['scientific research', 'academic research', 'research methods', 'research publications'],
'technology': ['scientific technology', 'laboratory technology', 'research tools'],
'data': ['scientific data', 'research data', 'experimental data', 'data analysis'],
'innovation': ['scientific innovation', 'research innovation', 'scientific breakthroughs'],
},
Legal: {
'technology': ['legal tech', 'legal technology', 'law tech', 'legal software'],
'compliance': ['legal compliance', 'regulatory compliance', 'legal requirements'],
'automation': ['legal automation', 'document automation', 'legal process automation'],
'research': ['legal research', 'case research', 'legal analysis'],
},
Manufacturing: {
'automation': ['manufacturing automation', 'industrial automation', 'factory automation', 'production automation'],
'technology': ['industrial technology', 'manufacturing tech', 'Industry 4.0', 'smart manufacturing'],
'quality': ['quality control', 'quality assurance', 'quality management', 'quality standards'],
'efficiency': ['manufacturing efficiency', 'production efficiency', 'operational efficiency'],
},
Retail: {
'e-commerce': ['online retail', 'digital commerce', 'ecommerce platform', 'online shopping'],
'technology': ['retail tech', 'retail technology', 'retail innovation', 'retail software'],
'customer': ['customer experience', 'customer engagement', 'customer service', 'customer analytics'],
'inventory': ['inventory management', 'stock management', 'supply chain', 'warehouse management'],
},
Energy: {
'renewable': ['solar energy', 'wind energy', 'renewable technology', 'clean energy'],
'technology': ['energy technology', 'energy innovation', 'energy management systems'],
'efficiency': ['energy efficiency', 'energy optimization', 'energy conservation'],
},
Agriculture: {
'technology': ['agtech', 'agricultural technology', 'farm technology', 'precision agriculture'],
'automation': ['farm automation', 'agricultural automation', 'precision farming'],
'sustainability': ['sustainable farming', 'organic farming', 'agricultural sustainability'],
},
};
/**
* Expands keywords based on industry context
* @param keywords - Array of user-entered keywords
* @param industry - Selected industry (or 'General')
* @returns Array of expanded keywords (originals + suggestions)
*/
export function expandKeywords(keywords: string[], industry: string): {
original: string[];
expanded: string[];
suggestions: string[];
} {
if (!keywords || keywords.length === 0) {
return { original: [], expanded: [], suggestions: [] };
}
// Normalize industry name (handle spaces and case)
const normalizedIndustry = industry.replace(/\s+/g, '_');
// Get expansion map for this industry, or empty object if not found
const expansionMap = industryKeywordExpansions[normalizedIndustry] || {};
const originalKeywords = [...keywords];
const suggestions: string[] = [];
const expandedSet = new Set<string>();
// Add original keywords to expanded set
originalKeywords.forEach(k => expandedSet.add(k.toLowerCase().trim()));
// For each keyword, find expansions
originalKeywords.forEach(keyword => {
const keywordLower = keyword.toLowerCase().trim();
// Direct match in expansion map
if (expansionMap[keywordLower]) {
expansionMap[keywordLower].forEach(expansion => {
if (!expandedSet.has(expansion.toLowerCase())) {
suggestions.push(expansion);
expandedSet.add(expansion.toLowerCase());
}
});
}
// Partial match: check if keyword contains any expansion key
Object.keys(expansionMap).forEach(expansionKey => {
if (keywordLower.includes(expansionKey) || expansionKey.includes(keywordLower)) {
expansionMap[expansionKey].forEach(expansion => {
if (!expandedSet.has(expansion.toLowerCase())) {
suggestions.push(expansion);
expandedSet.add(expansion.toLowerCase());
}
});
}
});
});
// Return structure
return {
original: originalKeywords,
expanded: Array.from(expandedSet).map(k => {
// Preserve original casing if it exists in originals
const originalMatch = originalKeywords.find(ok => ok.toLowerCase() === k);
return originalMatch || k;
}),
suggestions: suggestions.slice(0, 8), // Limit to 8 suggestions to avoid overwhelming UI
};
}
/**
* Formats keyword for display (capitalize first letter)
*/
export function formatKeyword(keyword: string): string {
if (!keyword) return keyword;
return keyword.charAt(0).toUpperCase() + keyword.slice(1);
}
/**
* Checks if a keyword is an original (user-entered) or a suggestion
*/
export function isOriginalKeyword(keyword: string, originalKeywords: string[]): boolean {
return originalKeywords.some(ok => ok.toLowerCase() === keyword.toLowerCase());
}

View File

@@ -0,0 +1,193 @@
/**
* Alternative Research Angles Utility
* Generates related research angles based on user query intent using rule-based patterns
*/
// Pattern-based angle generation templates
const anglePatterns: Record<string, string[]> = {
tools: [
'Compare {topic}',
'{topic} ROI analysis',
'Best {topic} for {industry}',
'{topic} implementation guide',
'Top {topic} features and pricing',
],
trends: [
'Latest {topic} trends',
'{topic} market analysis',
'{topic} future predictions',
'{topic} adoption rates',
'Emerging {topic} technologies',
],
strategies: [
'{topic} implementation guide',
'{topic} best practices',
'{topic} case studies',
'{topic} success strategies',
'{topic} optimization techniques',
],
analysis: [
'{topic} competitive analysis',
'{topic} market share',
'{topic} industry leaders',
'{topic} SWOT analysis',
'{topic} benchmarking',
],
guides: [
'{topic} getting started guide',
'{topic} for beginners',
'{topic} step-by-step tutorial',
'{topic} troubleshooting',
'{topic} expert tips',
],
comparison: [
'{topic} vs alternatives',
'Best {topic} comparison',
'{topic} feature comparison',
'{topic} pricing comparison',
'{topic} pros and cons',
],
general: [
'What is {topic}',
'How {topic} works',
'{topic} benefits and challenges',
'{topic} industry insights',
'{topic} expert opinions',
],
};
// Keywords that indicate query intent
const intentKeywords: Record<string, string[]> = {
tools: ['tools', 'software', 'platform', 'system', 'solution', 'app', 'application', 'toolkit', 'suite'],
trends: ['trends', 'future', 'emerging', 'latest', 'new', 'innovation', 'development', 'growth'],
strategies: ['strategy', 'plan', 'approach', 'method', 'best practices', 'how to', 'guide', 'implementation'],
analysis: ['analysis', 'compare', 'review', 'evaluate', 'assessment', 'study', 'research'],
guides: ['guide', 'tutorial', 'how to', 'getting started', 'learn', 'tips', 'advice'],
comparison: ['vs', 'versus', 'compare', 'comparison', 'alternative', 'difference'],
};
/**
* Detects the primary intent of a query
*/
function detectQueryIntent(query: string): string {
const queryLower = query.toLowerCase();
// Check each intent category
for (const [intent, keywords] of Object.entries(intentKeywords)) {
if (keywords.some(keyword => queryLower.includes(keyword))) {
return intent;
}
}
return 'general';
}
/**
* Extracts the main topic from a query
*/
function extractTopic(query: string, industry: string): string {
// Remove common intent words to get the core topic
const intentWords = Object.values(intentKeywords).flat();
let topic = query.toLowerCase();
// Remove intent keywords
for (const word of intentWords) {
const regex = new RegExp(`\\b${word}\\b`, 'gi');
topic = topic.replace(regex, '').trim();
}
// Clean up extra whitespace and common stop words
topic = topic
.replace(/\s+/g, ' ')
.replace(/\b(a|an|the|in|on|at|for|with|to|of|and|or|but)\b/g, '')
.trim();
// If topic is too short or empty, use original query
if (topic.length < 3 || topic.split(' ').length === 0) {
topic = query.toLowerCase();
}
// Capitalize first letter
return topic.charAt(0).toUpperCase() + topic.slice(1);
}
/**
* Generates alternative research angles based on user query
* @param query - User's research query/keywords
* @param industry - Selected industry (optional)
* @returns Array of alternative research angle suggestions
*/
export function generateResearchAngles(query: string, industry: string = 'General'): string[] {
if (!query || query.trim().length === 0) {
return [];
}
// Detect primary intent
const intent = detectQueryIntent(query);
// Extract main topic
const topic = extractTopic(query, industry);
// Get patterns for detected intent (fallback to general)
const patterns = anglePatterns[intent] || anglePatterns.general;
// Generate angles using patterns
const angles: string[] = [];
for (const pattern of patterns.slice(0, 5)) { // Limit to 5 angles
let angle = pattern.replace('{topic}', topic);
// Replace industry placeholder if present
if (industry && industry !== 'General') {
angle = angle.replace('{industry}', industry);
} else {
// Remove industry-specific placeholder if no industry
angle = angle.replace(' for {industry}', '');
}
// Capitalize first letter
angle = angle.charAt(0).toUpperCase() + angle.slice(1);
// Skip if angle is too similar to original query
const angleLower = angle.toLowerCase();
const queryLower = query.toLowerCase();
if (angleLower !== queryLower && !queryLower.includes(angleLower) && !angleLower.includes(queryLower)) {
angles.push(angle);
}
}
// Add industry-specific angle if industry is set
if (industry && industry !== 'General' && angles.length < 5) {
const industryAngle = `${topic} in ${industry} industry`;
if (!angles.some(a => a.toLowerCase() === industryAngle.toLowerCase())) {
angles.push(industryAngle);
}
}
// If we have fewer than 3 angles, add some general ones
if (angles.length < 3) {
const generalPatterns = anglePatterns.general.slice(0, 3 - angles.length);
for (const pattern of generalPatterns) {
const angle = pattern.replace('{topic}', topic);
if (!angles.some(a => a.toLowerCase() === angle.toLowerCase())) {
angles.push(angle);
}
}
}
// Remove duplicates and limit to 5
const uniqueAngles = Array.from(new Set(angles.map(a => a.toLowerCase())))
.slice(0, 5)
.map(a => a.charAt(0).toUpperCase() + a.slice(1));
return uniqueAngles;
}
/**
* Formats an angle for display
*/
export function formatAngle(angle: string): string {
if (!angle) return angle;
return angle.charAt(0).toUpperCase() + angle.slice(1);
}

View File

@@ -0,0 +1,132 @@
import { ResearchMode } from '../services/blogWriterApi';
export interface ResearchHistoryEntry {
keywords: string[];
industry: string;
targetAudience: string;
researchMode: ResearchMode;
timestamp: number;
resultSummary?: string; // Optional: show snippet from results
}
const HISTORY_STORAGE_KEY = 'alwrity_research_history';
const MAX_HISTORY_ENTRIES = 5;
/**
* Get all research history entries, sorted by most recent first
*/
export function getResearchHistory(): ResearchHistoryEntry[] {
try {
const stored = localStorage.getItem(HISTORY_STORAGE_KEY);
if (!stored) return [];
const entries = JSON.parse(stored) as ResearchHistoryEntry[];
if (!Array.isArray(entries)) return [];
// Sort by timestamp (most recent first) and limit to MAX_HISTORY_ENTRIES
return entries
.sort((a, b) => b.timestamp - a.timestamp)
.slice(0, MAX_HISTORY_ENTRIES);
} catch (error) {
console.warn('Failed to load research history:', error);
return [];
}
}
/**
* Add a new research entry to history
*/
export function addResearchHistory(entry: Omit<ResearchHistoryEntry, 'timestamp'>): void {
try {
const currentHistory = getResearchHistory();
// Create new entry with timestamp
const newEntry: ResearchHistoryEntry = {
...entry,
timestamp: Date.now(),
};
// Check if similar entry already exists (same keywords, industry, audience)
const existingIndex = currentHistory.findIndex(
(e) =>
JSON.stringify(e.keywords.sort()) === JSON.stringify(entry.keywords.sort()) &&
e.industry === entry.industry &&
e.targetAudience === entry.targetAudience &&
e.researchMode === entry.researchMode
);
// If exists, remove it (we'll add it back at the top)
const updatedHistory =
existingIndex >= 0
? currentHistory.filter((_, i) => i !== existingIndex)
: currentHistory;
// Add new entry at the beginning and limit to MAX_HISTORY_ENTRIES
const finalHistory = [newEntry, ...updatedHistory].slice(0, MAX_HISTORY_ENTRIES);
localStorage.setItem(HISTORY_STORAGE_KEY, JSON.stringify(finalHistory));
} catch (error) {
console.warn('Failed to save research history:', error);
}
}
/**
* Clear all research history
*/
export function clearResearchHistory(): void {
try {
localStorage.removeItem(HISTORY_STORAGE_KEY);
} catch (error) {
console.warn('Failed to clear research history:', error);
}
}
/**
* Remove a specific entry from history by timestamp
*/
export function removeResearchHistoryEntry(timestamp: number): void {
try {
const currentHistory = getResearchHistory();
const updatedHistory = currentHistory.filter((e) => e.timestamp !== timestamp);
localStorage.setItem(HISTORY_STORAGE_KEY, JSON.stringify(updatedHistory));
} catch (error) {
console.warn('Failed to remove research history entry:', error);
}
}
/**
* Format timestamp for display (e.g., "2 hours ago", "Yesterday")
*/
export function formatHistoryTimestamp(timestamp: number): string {
const now = Date.now();
const diffMs = now - timestamp;
const diffMins = Math.floor(diffMs / 60000);
const diffHours = Math.floor(diffMs / 3600000);
const diffDays = Math.floor(diffMs / 86400000);
if (diffMins < 1) return 'Just now';
if (diffMins < 60) return `${diffMins} min${diffMins > 1 ? 's' : ''} ago`;
if (diffHours < 24) return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`;
if (diffDays === 1) return 'Yesterday';
if (diffDays < 7) return `${diffDays} days ago`;
// For older entries, show date
const date = new Date(timestamp);
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
}
/**
* Generate a short summary from keywords for display
*/
export function getHistorySummary(entry: ResearchHistoryEntry): string {
if (entry.resultSummary) {
return entry.resultSummary.length > 60
? entry.resultSummary.substring(0, 60) + '...'
: entry.resultSummary;
}
// Fallback to first keyword or keywords joined
if (entry.keywords.length === 0) return 'Research query';
if (entry.keywords.length === 1) return entry.keywords[0];
return entry.keywords.slice(0, 2).join(', ') + (entry.keywords.length > 2 ? '...' : '');
}