Files
repo1/app.js

323 lines
14 KiB
JavaScript

document.addEventListener('DOMContentLoaded', () => {
// DOM Elements
const keywordsContainer = document.getElementById('keywords-container');
const refreshBtn = document.getElementById('refresh-btn');
const loading = document.getElementById('loading');
const selectedKeywordText = document.getElementById('selected-keyword-text');
const generatePromptBtn = document.getElementById('generate-prompt-btn');
const promptResult = document.getElementById('prompt-result');
const promptTitle = document.getElementById('prompt-title');
const promptBody = document.getElementById('prompt-body');
const copyPromptBtn = document.getElementById('copy-prompt-btn');
const copySuccess = document.getElementById('copy-success');
// State
let selectedKeyword = null;
let currentPrompt = null;
// Fetch trending keywords with opportunity scoring
const fetchTrendingKeywords = async () => {
try {
loading.classList.remove('hidden');
keywordsContainer.innerHTML = '';
// Step 1: Get trending topics from multiple sources
const trendingTopics = await fetchTrendingTopics();
// Step 2: Enrich with search volume and result count data
const enrichedKeywords = await enrichKeywordsWithOpportunityData(trendingTopics);
// Step 3: Calculate opportunity score and sort
const keywordsWithOpportunity = calculateOpportunityScore(enrichedKeywords);
if (keywordsWithOpportunity.length > 0) {
renderKeywords(keywordsWithOpportunity);
} else {
useBackupKeywords();
}
} catch (error) {
console.error('Error fetching trending keywords:', error);
useBackupKeywords();
} finally {
loading.classList.add('hidden');
}
};
// Fetch trending topics from multiple sources for diversity
const fetchTrendingTopics = async () => {
try {
// Source 1: Wikipedia trending articles
const wikiResponse = await fetch('https://wikimedia.org/api/rest_v1/metrics/pageviews/top/en.wikipedia/all-access/2023/01/all-days');
const wikiData = await wikiResponse.json();
let combinedTopics = [];
// Process Wikipedia data
if (wikiData && wikiData.items && wikiData.items[0] && wikiData.items[0].articles) {
const wikiTopics = wikiData.items[0].articles
.filter(article => !article.article.startsWith('Special:') &&
!article.article.startsWith('Main_Page') &&
!article.article.startsWith('Wikipedia:'))
.slice(0, 15)
.map(article => ({
keyword: article.article.replace(/_/g, ' '),
searchVolume: article.views,
source: 'wikipedia'
}));
combinedTopics = [...combinedTopics, ...wikiTopics];
}
// Source 2: Use Google Trends API-like data (simulated)
// In a real implementation, you would use Google Trends API
const techTrendingTopics = [
{ keyword: 'quantum computing applications', searchVolume: 85000 },
{ keyword: 'edge computing use cases', searchVolume: 62000 },
{ keyword: 'zero-knowledge proofs', searchVolume: 45000 },
{ keyword: 'synthetic data generation', searchVolume: 73000 },
{ keyword: 'federated learning models', searchVolume: 58000 },
{ keyword: 'serverless architecture patterns', searchVolume: 67000 },
{ keyword: 'homomorphic encryption', searchVolume: 41000 },
{ keyword: 'computer vision in healthcare', searchVolume: 89000 },
{ keyword: 'explainable ai techniques', searchVolume: 76000 },
{ keyword: 'graph neural networks', searchVolume: 52000 }
].map(item => ({ ...item, source: 'tech_trends' }));
combinedTopics = [...combinedTopics, ...techTrendingTopics];
// Source 3: Emerging research topics (simulated)
const emergingTopics = [
{ keyword: 'biomimetic materials science', searchVolume: 32000 },
{ keyword: 'neuromorphic computing chips', searchVolume: 28000 },
{ keyword: 'digital twin technology applications', searchVolume: 47000 },
{ keyword: 'post-quantum cryptography standards', searchVolume: 39000 },
{ keyword: 'spatial computing interfaces', searchVolume: 43000 }
].map(item => ({ ...item, source: 'emerging_research' }));
combinedTopics = [...combinedTopics, ...emergingTopics];
// Deduplicate and return
return Array.from(new Map(combinedTopics.map(item =>
[item.keyword, item])).values());
} catch (error) {
console.error('Error fetching trending topics:', error);
return [];
}
};
// Enrich keywords with search result counts
const enrichKeywordsWithOpportunityData = async (keywords) => {
// In a production environment, you would use a real search API
// Here we'll simulate the data with a deterministic algorithm
return Promise.all(keywords.map(async (keyword) => {
// Simulate API call to get search result count
// In reality, you would use a search engine API
// Algorithm to generate realistic but varied result counts
// Technical/specific keywords tend to have fewer results
const wordCount = keyword.keyword.split(' ').length;
const containsTechnicalTerms = /\b(api|algorithm|framework|protocol|quantum|neural|encryption|serverless|computing)\b/i.test(keyword.keyword);
const isNiche = wordCount >= 3 || containsTechnicalTerms;
// Base result count - more specific terms have fewer results
let baseResultCount;
if (isNiche) {
baseResultCount = Math.floor(50000 + Math.random() * 500000);
} else {
baseResultCount = Math.floor(1000000 + Math.random() * 50000000);
}
// Add some randomness but keep it deterministic for the same keyword
const seed = keyword.keyword.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
const pseudoRandom = Math.sin(seed) * 10000;
const resultCount = Math.max(10000, Math.floor(baseResultCount + pseudoRandom));
return {
...keyword,
resultCount
};
}));
};
// Calculate opportunity score based on search volume vs. result count
const calculateOpportunityScore = (keywords) => {
// Calculate the opportunity score
// Higher score = high search volume + low result count (better opportunity)
const keywordsWithScore = keywords.map(keyword => {
// Normalize search volume (0-100)
const maxSearchVolume = Math.max(...keywords.map(k => k.searchVolume));
const normalizedSearchVolume = (keyword.searchVolume / maxSearchVolume) * 100;
// Normalize result count inversely (fewer results = higher score)
const maxResultCount = Math.max(...keywords.map(k => k.resultCount));
const normalizedResultCount = 100 - ((keyword.resultCount / maxResultCount) * 100);
// Calculate opportunity score (weighted average)
// 60% weight to search volume, 40% weight to inverse result count
const opportunityScore = (normalizedSearchVolume * 0.6) + (normalizedResultCount * 0.4);
// Calculate competition level (lower is better)
const competitionLevel = keyword.resultCount / keyword.searchVolume;
return {
...keyword,
opportunityScore: Math.round(opportunityScore),
competitionLevel: Math.round(competitionLevel)
};
});
// Sort by opportunity score (highest first)
return keywordsWithScore
.sort((a, b) => b.opportunityScore - a.opportunityScore)
.slice(0, 10);
};
// Use backup keywords when API fails
const useBackupKeywords = () => {
const backupKeywords = [
{ keyword: 'quantum machine learning algorithms', opportunityScore: 95, searchVolume: 42000, resultCount: 156000, competitionLevel: 4 },
{ keyword: 'zero-trust network architecture', opportunityScore: 92, searchVolume: 68000, resultCount: 310000, competitionLevel: 5 },
{ keyword: 'synthetic data generation techniques', opportunityScore: 89, searchVolume: 51000, resultCount: 245000, competitionLevel: 5 },
{ keyword: 'edge computing security frameworks', opportunityScore: 87, searchVolume: 73000, resultCount: 420000, competitionLevel: 6 },
{ keyword: 'federated learning privacy', opportunityScore: 85, searchVolume: 47000, resultCount: 280000, competitionLevel: 6 },
{ keyword: 'explainable ai for healthcare', opportunityScore: 82, searchVolume: 59000, resultCount: 390000, competitionLevel: 7 },
{ keyword: 'post-quantum cryptography implementation', opportunityScore: 80, searchVolume: 38000, resultCount: 265000, competitionLevel: 7 },
{ keyword: 'neuromorphic computing applications', opportunityScore: 78, searchVolume: 31000, resultCount: 225000, competitionLevel: 7 },
{ keyword: 'graph neural networks for recommendation', opportunityScore: 76, searchVolume: 44000, resultCount: 340000, competitionLevel: 8 },
{ keyword: 'digital twin technology standards', opportunityScore: 74, searchVolume: 53000, resultCount: 420000, competitionLevel: 8 }
];
renderKeywords(backupKeywords);
};
// Render keywords
const renderKeywords = (keywords) => {
keywordsContainer.innerHTML = '';
keywords.forEach(keyword => {
const keywordCard = document.createElement('div');
keywordCard.className = 'keyword-card';
keywordCard.dataset.keyword = keyword.keyword;
// Display opportunity score and metrics
const score = keyword.opportunityScore || keyword.score || 0;
const searchVolume = keyword.searchVolume ? `${(keyword.searchVolume/1000).toFixed(1)}K` : 'N/A';
const resultCount = keyword.resultCount ? `${(keyword.resultCount/1000).toFixed(1)}K` : 'N/A';
const competitionLevel = keyword.competitionLevel || 'N/A';
keywordCard.innerHTML = `
<div class="keyword-name">${keyword.keyword}</div>
<div class="keyword-metrics">
<div class="opportunity-score">Opportunity: <span class="highlight">${score}</span></div>
<div class="search-volume">Search: ${searchVolume}</div>
<div class="result-count">Results: ${resultCount}</div>
<div class="competition">Competition: ${competitionLevel}</div>
</div>
`;
keywordCard.addEventListener('click', () => selectKeyword(keyword.keyword, keywordCard));
keywordsContainer.appendChild(keywordCard);
});
};
// Select a keyword
const selectKeyword = (keyword, card) => {
// Remove selected class from all cards
document.querySelectorAll('.keyword-card').forEach(k => k.classList.remove('selected'));
// Add selected class to clicked card
card.classList.add('selected');
// Update selected keyword
selectedKeyword = keyword;
selectedKeywordText.textContent = keyword;
// Enable generate prompt button
generatePromptBtn.disabled = false;
// Hide prompt result if visible
promptResult.classList.add('hidden');
};
// Generate article prompt
const generatePrompt = () => {
if (!selectedKeyword) return;
try {
loading.classList.remove('hidden');
generatePromptBtn.disabled = true;
// Find the selected keyword data
const keywordData = Array.from(document.querySelectorAll('.keyword-card')).find(
card => card.dataset.keyword === selectedKeyword
);
// Get opportunity metrics if available
const opportunityScore = keywordData?.querySelector('.opportunity-score .highlight')?.textContent || 'high';
const searchVolume = keywordData?.querySelector('.search-volume')?.textContent.split(': ')[1] || 'significant';
const resultCount = keywordData?.querySelector('.result-count')?.textContent.split(': ')[1] || 'limited';
// Generate a ChatGPT-friendly prompt based on the keyword and opportunity metrics
const prompt = {
title: `Write a comprehensive article about "${selectedKeyword}" (High-Opportunity Keyword)`,
content: `Write a well-researched, engaging, and informative article about "${selectedKeyword}". This is a high-opportunity keyword with ${searchVolume} monthly searches but only ${resultCount} competing results.
1. Start with an attention-grabbing introduction that explains why "${selectedKeyword}" is important or relevant today
2. Include at least 5 main sections with appropriate headings
3. Incorporate current statistics, trends, and expert opinions
4. Address common questions or misconceptions about "${selectedKeyword}"
5. Provide practical tips, applications, or future predictions related to "${selectedKeyword}"
6. End with a compelling conclusion that summarizes key points and offers final thoughts
7. Optimize the content for SEO while maintaining high-quality, valuable information
The article should be approximately 1500-2000 words, written in a professional yet accessible tone, and formatted for easy online reading with appropriate headings, subheadings, and bullet points where relevant.`
};
currentPrompt = prompt;
displayPrompt(prompt);
} catch (error) {
console.error('Error generating prompt:', error);
alert('Failed to generate prompt. Please try again.');
} finally {
loading.classList.add('hidden');
generatePromptBtn.disabled = false;
}
};
// Display the generated prompt
const displayPrompt = (prompt) => {
promptTitle.textContent = prompt.title;
promptBody.textContent = prompt.content;
promptResult.classList.remove('hidden');
};
// Copy prompt to clipboard
const copyPrompt = () => {
if (!currentPrompt) return;
const fullPrompt = `${currentPrompt.title}\n\n${currentPrompt.content}`;
navigator.clipboard.writeText(fullPrompt)
.then(() => {
copySuccess.classList.remove('hidden');
setTimeout(() => {
copySuccess.classList.add('hidden');
}, 2000);
})
.catch(err => {
console.error('Failed to copy prompt:', err);
alert('Failed to copy prompt to clipboard');
});
};
// Event listeners
refreshBtn.addEventListener('click', fetchTrendingKeywords);
generatePromptBtn.addEventListener('click', generatePrompt);
copyPromptBtn.addEventListener('click', copyPrompt);
// Initial load
fetchTrendingKeywords();
});