|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>AI Web Scraper with Comparison</title> |
|
|
<script src="https://cdn.tailwindcss.com"></script> |
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
|
<style> |
|
|
.gradient-bg { |
|
|
background: linear-gradient(135deg, #6e8efb, #a777e3); |
|
|
} |
|
|
.result-container { |
|
|
transition: all 0.3s ease; |
|
|
} |
|
|
.loader { |
|
|
border-top-color: #3498db; |
|
|
-webkit-animation: spinner 1.5s linear infinite; |
|
|
animation: spinner 1.5s linear infinite; |
|
|
} |
|
|
@-webkit-keyframes spinner { |
|
|
0% { -webkit-transform: rotate(0deg); } |
|
|
100% { -webkit-transform: rotate(360deg); } |
|
|
} |
|
|
@keyframes spinner { |
|
|
0% { transform: rotate(0deg); } |
|
|
100% { transform: rotate(360deg); } |
|
|
} |
|
|
.storage-item { |
|
|
transition: all 0.2s ease; |
|
|
} |
|
|
.storage-item:hover { |
|
|
transform: translateY(-2px); |
|
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); |
|
|
} |
|
|
.compare-highlight { |
|
|
background-color: rgba(255, 255, 0, 0.3); |
|
|
border-left: 3px solid #f59e0b; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body class="min-h-screen gradient-bg"> |
|
|
<div class="container mx-auto px-4 py-12"> |
|
|
<div class="max-w-6xl mx-auto"> |
|
|
|
|
|
<div class="text-center mb-12"> |
|
|
<h1 class="text-4xl font-bold text-white mb-2">AI Web Scraper Pro</h1> |
|
|
<p class="text-xl text-white opacity-80">Extract, analyze and compare website data with AI</p> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="flex mb-6 bg-white rounded-lg shadow-md overflow-hidden max-w-2xl mx-auto"> |
|
|
<button id="scrape-tab" class="flex-1 py-3 px-4 font-medium text-center border-b-2 border-purple-600 text-purple-600 focus:outline-none"> |
|
|
<i class="fas fa-globe mr-2"></i>Scrape |
|
|
</button> |
|
|
<button id="compare-tab" class="flex-1 py-3 px-4 font-medium text-center text-gray-500 hover:text-gray-700 focus:outline-none"> |
|
|
<i class="fas fa-exchange-alt mr-2"></i>Compare |
|
|
</button> |
|
|
<button id="storage-tab" class="flex-1 py-3 px-4 font-medium text-center text-gray-500 hover:text-gray-700 focus:outline-none"> |
|
|
<i class="fas fa-database mr-2"></i>Storage |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-white rounded-xl shadow-2xl overflow-hidden"> |
|
|
|
|
|
<div id="scrape-content" class="tab-content"> |
|
|
|
|
|
<div class="p-8"> |
|
|
<div class="mb-6"> |
|
|
<label for="url" class="block text-gray-700 font-medium mb-2"> |
|
|
<i class="fas fa-globe mr-2"></i>Website URL |
|
|
</label> |
|
|
<div class="flex"> |
|
|
<input type="url" id="url" placeholder="https://example.com" |
|
|
class="flex-grow px-4 py-3 border border-gray-300 rounded-l-lg focus:outline-none focus:ring-2 focus:ring-purple-500"> |
|
|
<button id="scrape-btn" class="bg-purple-600 hover:bg-purple-700 text-white px-6 py-3 rounded-r-lg transition duration-200"> |
|
|
<i class="fas fa-search mr-2"></i>Scrape |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div id="query-section" class="hidden"> |
|
|
<div class="mb-6"> |
|
|
<label for="query" class="block text-gray-700 font-medium mb-2"> |
|
|
<i class="fas fa-robot mr-2"></i>Ask AI About This Website |
|
|
</label> |
|
|
<div class="flex"> |
|
|
<input type="text" id="query" placeholder="What products do they sell? How much do they cost?" |
|
|
class="flex-grow px-4 py-3 border border-gray-300 rounded-l-lg focus:outline-none focus:ring-2 focus:ring-purple-500"> |
|
|
<button id="ask-btn" class="bg-purple-600 hover:bg-purple-700 text-white px-6 py-3 rounded-r-lg transition duration-200"> |
|
|
<i class="fas fa-paper-plane mr-2"></i>Ask |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
<div class="flex justify-end mb-4"> |
|
|
<button id="save-btn" class="text-purple-600 hover:text-purple-800 font-medium"> |
|
|
<i class="fas fa-save mr-1"></i> Save to Storage |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="results" class="border-t border-gray-200 p-8"> |
|
|
<div id="initial-state" class="text-center py-12"> |
|
|
<i class="fas fa-search text-gray-300 text-5xl mb-4"></i> |
|
|
<h3 class="text-xl text-gray-500 font-medium">Enter a website URL to begin scraping</h3> |
|
|
</div> |
|
|
|
|
|
<div id="loading-state" class="hidden text-center py-12"> |
|
|
<div class="loader ease-linear rounded-full border-4 border-t-4 border-gray-200 h-12 w-12 mx-auto mb-4"></div> |
|
|
<h3 class="text-xl text-gray-700 font-medium">Analyzing website...</h3> |
|
|
</div> |
|
|
|
|
|
<div id="scraped-content" class="hidden"> |
|
|
<div class="flex justify-between items-center mb-6"> |
|
|
<h3 class="text-xl font-semibold text-gray-800">Scraped Results</h3> |
|
|
<span id="website-url" class="text-sm text-gray-500 bg-gray-100 px-3 py-1 rounded-full"></span> |
|
|
</div> |
|
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8"> |
|
|
<div class="bg-gray-50 p-4 rounded-lg"> |
|
|
<div class="text-purple-600 mb-2"> |
|
|
<i class="fas fa-file-alt"></i> |
|
|
</div> |
|
|
<h4 class="font-medium text-gray-700">Pages Found</h4> |
|
|
<p id="page-count" class="text-2xl font-bold text-gray-900">0</p> |
|
|
</div> |
|
|
<div class="bg-gray-50 p-4 rounded-lg"> |
|
|
<div class="text-purple-600 mb-2"> |
|
|
<i class="fas fa-image"></i> |
|
|
</div> |
|
|
<h4 class="font-medium text-gray-700">Images Found</h4> |
|
|
<p id="image-count" class="text-2xl font-bold text-gray-900">0</p> |
|
|
</div> |
|
|
<div class="bg-gray-50 p-4 rounded-lg"> |
|
|
<div class="text-purple-600 mb-2"> |
|
|
<i class="fas fa-link"></i> |
|
|
</div> |
|
|
<h4 class="font-medium text-gray-700">Links Found</h4> |
|
|
<p id="link-count" class="text-2xl font-bold text-gray-900">0</p> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="mb-8"> |
|
|
<h4 class="font-medium text-gray-700 mb-2">Page Content Preview</h4> |
|
|
<div id="content-preview" class="bg-gray-50 p-4 rounded-lg max-h-60 overflow-y-auto text-gray-700"> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div id="ai-response" class="hidden mt-8"> |
|
|
<div class="flex justify-between items-center mb-4"> |
|
|
<h3 class="text-xl font-semibold text-gray-800">AI Analysis</h3> |
|
|
<span class="text-sm text-purple-600 bg-purple-100 px-3 py-1 rounded-full"> |
|
|
<i class="fas fa-robot mr-1"></i> AI Response |
|
|
</span> |
|
|
</div> |
|
|
<div class="bg-purple-50 border-l-4 border-purple-500 p-4 rounded-r-lg"> |
|
|
<div id="ai-response-content" class="text-gray-700"> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="compare-content" class="tab-content hidden"> |
|
|
<div class="p-8"> |
|
|
<div class="mb-8"> |
|
|
<h3 class="text-xl font-bold text-gray-800 mb-4"><i class="fas fa-exchange-alt mr-2"></i>Compare Websites</h3> |
|
|
<p class="text-gray-600 mb-4">Select two websites from your storage to compare their content.</p> |
|
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6"> |
|
|
<div> |
|
|
<label class="block text-gray-700 font-medium mb-2">First Website</label> |
|
|
<select id="compare-url-1" class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500"> |
|
|
<option value="">Select a website</option> |
|
|
</select> |
|
|
</div> |
|
|
<div> |
|
|
<label class="block text-gray-700 font-medium mb-2">Second Website</label> |
|
|
<select id="compare-url-2" class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500"> |
|
|
<option value="">Select a website</option> |
|
|
</select> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="flex justify-between"> |
|
|
<button id="compare-btn" class="bg-purple-600 hover:bg-purple-700 text-white px-6 py-3 rounded-lg transition duration-200"> |
|
|
<i class="fas fa-balance-scale-left mr-2"></i>Compare Websites |
|
|
</button> |
|
|
<button id="clear-comparison-btn" class="text-gray-500 hover:text-gray-700 font-medium"> |
|
|
<i class="fas fa-trash-alt mr-1"></i> Clear Comparison |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div id="comparison-results" class="hidden"> |
|
|
<h4 class="text-lg font-semibold text-gray-800 mb-3">Comparison Results</h4> |
|
|
<div class="overflow-x-auto"> |
|
|
<table class="min-w-full bg-white rounded-lg overflow-hidden"> |
|
|
<thead class="bg-gray-100"> |
|
|
<tr> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Metric</th> |
|
|
<th id="compare-site-1-header" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Site 1</th> |
|
|
<th id="compare-site-2-header" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Site 2</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Difference</th> |
|
|
</tr> |
|
|
</thead> |
|
|
<tbody class="divide-y divide-gray-200"> |
|
|
<tr> |
|
|
<td class="px-6 py-4 whitespace-nowrap font-medium text-gray-900">Page Count</td> |
|
|
<td id="compare-page-count-1" class="px-6 py-4 whitespace-nowrap">0</td> |
|
|
<td id="compare-page-count-2" class="px-6 py-4 whitespace-nowrap">0</td> |
|
|
<td id="compare-page-diff" class="px-6 py-4 whitespace-nowrap">0</td> |
|
|
</tr> |
|
|
<tr> |
|
|
<td class="px-6 py-4 whitespace-nowrap font-medium text-gray-900">Image Count</td> |
|
|
<td id="compare-image-count-1" class="px-6 py-4 whitespace-nowrap">0</td> |
|
|
<td id="compare-image-count-2" class="px-6 py-4 whitespace-nowrap">0</td> |
|
|
<td id="compare-image-diff" class="px-6 py-4 whitespace-nowrap">0</td> |
|
|
</tr> |
|
|
<tr> |
|
|
<td class="px-6 py-4 whitespace-nowrap font-medium text-gray-900">Link Count</td> |
|
|
<td id="compare-link-count-1" class="px-6 py-4 whitespace-nowrap">0</td> |
|
|
<td id="compare-link-count-2" class="px-6 py-4 whitespace-nowrap">0</td> |
|
|
<td id="compare-link-diff" class="px-6 py-4 whitespace-nowrap">0</td> |
|
|
</tr> |
|
|
</tbody> |
|
|
</table> |
|
|
</div> |
|
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-8 mt-8"> |
|
|
<div> |
|
|
<h5 id="compare-site-1-title" class="text-md font-semibold mb-2">Site 1 Content</h5> |
|
|
<div id="compare-content-1" class="bg-gray-50 p-4 rounded-lg h-64 overflow-y-auto text-gray-700 border-l-2 border-blue-500"> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
<div> |
|
|
<h5 id="compare-site-2-title" class="text-md font-semibold mb-2">Site 2 Content</h5> |
|
|
<div id="compare-content-2" class="bg-gray-50 p-4 rounded-lg h-64 overflow-y-auto text-gray-700 border-l-2 border-green-500"> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="mt-8"> |
|
|
<h5 class="text-md font-semibold mb-2">AI Comparison Analysis</h5> |
|
|
<button id="ai-compare-btn" class="bg-purple-600 hover:bg-purple-700 text-white px-6 py-2 rounded-lg mb-3 transition duration-200"> |
|
|
<i class="fas fa-robot mr-2"></i>Get AI Comparison Summary |
|
|
</button> |
|
|
<div id="ai-comparison-response" class="bg-purple-50 border-l-4 border-purple-500 p-4 rounded-r-lg hidden"> |
|
|
<div id="ai-comparison-content" class="text-gray-700"> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="storage-content" class="tab-content hidden"> |
|
|
<div class="p-8"> |
|
|
<div class="mb-8"> |
|
|
<h3 class="text-xl font-bold text-gray-800 mb-4"><i class="fas fa-database mr-2"></i>Saved Websites</h3> |
|
|
|
|
|
<div class="relative"> |
|
|
<input type="text" id="storage-search" placeholder="Search saved websites..." |
|
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500 pl-10"> |
|
|
<i class="fas fa-search absolute left-3 top-4 text-gray-400"></i> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div id="storage-list" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> |
|
|
|
|
|
</div> |
|
|
|
|
|
<div id="empty-storage" class="text-center py-12"> |
|
|
<i class="fas fa-database text-gray-300 text-5xl mb-4"></i> |
|
|
<h3 class="text-xl text-gray-500 font-medium">No websites saved yet</h3> |
|
|
<p class="text-gray-400 mt-2">Save websites from the Scrape tab to view them here</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="text-center mt-8 text-white opacity-70 text-sm"> |
|
|
<p>AI Web Scraper Pro - Extract, analyze and compare website data with artificial intelligence</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
|
|
|
const scrapeBtn = document.getElementById('scrape-btn'); |
|
|
const askBtn = document.getElementById('ask-btn'); |
|
|
const saveBtn = document.getElementById('save-btn'); |
|
|
const urlInput = document.getElementById('url'); |
|
|
const queryInput = document.getElementById('query'); |
|
|
const initialState = document.getElementById('initial-state'); |
|
|
const loadingState = document.getElementById('loading-state'); |
|
|
const scrapedContent = document.getElementById('scraped-content'); |
|
|
const querySection = document.getElementById('query-section'); |
|
|
const aiResponse = document.getElementById('ai-response'); |
|
|
const websiteUrl = document.getElementById('website-url'); |
|
|
const pageCount = document.getElementById('page-count'); |
|
|
const imageCount = document.getElementById('image-count'); |
|
|
const linkCount = document.getElementById('link-count'); |
|
|
const contentPreview = document.getElementById('content-preview'); |
|
|
const aiResponseContent = document.getElementById('ai-response-content'); |
|
|
|
|
|
|
|
|
const scrapeTab = document.getElementById('scrape-tab'); |
|
|
const compareTab = document.getElementById('compare-tab'); |
|
|
const storageTab = document.getElementById('storage-tab'); |
|
|
const scrapeContent = document.getElementById('scrape-content'); |
|
|
const compareContent = document.getElementById('compare-content'); |
|
|
const storageContent = document.getElementById('storage-content'); |
|
|
|
|
|
|
|
|
const compareUrl1 = document.getElementById('compare-url-1'); |
|
|
const compareUrl2 = document.getElementById('compare-url-2'); |
|
|
const compareBtn = document.getElementById('compare-btn'); |
|
|
const clearComparisonBtn = document.getElementById('clear-comparison-btn'); |
|
|
const comparisonResults = document.getElementById('comparison-results'); |
|
|
const compareSite1Header = document.getElementById('compare-site-1-header'); |
|
|
const compareSite2Header = document.getElementById('compare-site-2-header'); |
|
|
const comparePageCount1 = document.getElementById('compare-page-count-1'); |
|
|
const comparePageCount2 = document.getElementById('compare-page-count-2'); |
|
|
const comparePageDiff = document.getElementById('compare-page-diff'); |
|
|
const compareImageCount1 = document.getElementById('compare-image-count-1'); |
|
|
const compareImageCount2 = document.getElementById('compare-image-count-2'); |
|
|
const compareImageDiff = document.getElementById('compare-image-diff'); |
|
|
const compareLinkCount1 = document.getElementById('compare-link-count-1'); |
|
|
const compareLinkCount2 = document.getElementById('compare-link-count-2'); |
|
|
const compareLinkDiff = document.getElementById('compare-link-diff'); |
|
|
const compareSite1Title = document.getElementById('compare-site-1-title'); |
|
|
const compareSite2Title = document.getElementById('compare-site-2-title'); |
|
|
const compareContent1 = document.getElementById('compare-content-1'); |
|
|
const compareContent2 = document.getElementById('compare-content-2'); |
|
|
const aiCompareBtn = document.getElementById('ai-compare-btn'); |
|
|
const aiComparisonResponse = document.getElementById('ai-comparison-response'); |
|
|
const aiComparisonContent = document.getElementById('ai-comparison-content'); |
|
|
|
|
|
|
|
|
const storageSearch = document.getElementById('storage-search'); |
|
|
const storageList = document.getElementById('storage-list'); |
|
|
const emptyStorage = document.getElementById('empty-storage'); |
|
|
|
|
|
|
|
|
let currentScrapedData = null; |
|
|
|
|
|
|
|
|
function initTabs() { |
|
|
scrapeTab.addEventListener('click', function() { |
|
|
showTab('scrape'); |
|
|
}); |
|
|
|
|
|
compareTab.addEventListener('click', function() { |
|
|
showTab('compare'); |
|
|
updateCompareDropdowns(); |
|
|
}); |
|
|
|
|
|
storageTab.addEventListener('click', function() { |
|
|
showTab('storage'); |
|
|
loadStorage(); |
|
|
}); |
|
|
|
|
|
showTab('scrape'); |
|
|
} |
|
|
|
|
|
function showTab(tabName) { |
|
|
|
|
|
scrapeTab.classList.remove('border-purple-600', 'text-purple-600'); |
|
|
scrapeTab.classList.add('text-gray-500', 'hover:text-gray-700'); |
|
|
compareTab.classList.remove('border-purple-600', 'text-purple-600'); |
|
|
compareTab.classList.add('text-gray-500', 'hover:text-gray-700'); |
|
|
storageTab.classList.remove('border-purple-600', 'text-purple-600'); |
|
|
storageTab.classList.add('text-gray-500', 'hover:text-gray-700'); |
|
|
|
|
|
|
|
|
scrapeContent.classList.add('hidden'); |
|
|
compareContent.classList.add('hidden'); |
|
|
storageContent.classList.add('hidden'); |
|
|
|
|
|
|
|
|
switch(tabName) { |
|
|
case 'scrape': |
|
|
scrapeTab.classList.add('border-purple-600', 'text-purple-600'); |
|
|
scrapeTab.classList.remove('text-gray-500', 'hover:text-gray-700'); |
|
|
scrapeContent.classList.remove('hidden'); |
|
|
break; |
|
|
case 'compare': |
|
|
compareTab.classList.add('border-purple-600', 'text-purple-600'); |
|
|
compareTab.classList.remove('text-gray-500', 'hover:text-gray-700'); |
|
|
compareContent.classList.remove('hidden'); |
|
|
break; |
|
|
case 'storage': |
|
|
storageTab.classList.add('border-purple-600', 'text-purple-600'); |
|
|
storageTab.classList.remove('text-gray-500', 'hover:text-gray-700'); |
|
|
storageContent.classList.remove('hidden'); |
|
|
break; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function scrapeWebsite(url) { |
|
|
return new Promise((resolve) => { |
|
|
|
|
|
setTimeout(() => { |
|
|
|
|
|
const mockData = { |
|
|
url: url, |
|
|
pageCount: Math.floor(Math.random() * 50) + 5, |
|
|
imageCount: Math.floor(Math.random() * 100) + 10, |
|
|
linkCount: Math.floor(Math.random() * 200) + 20, |
|
|
content: `This is a simulated preview of content from ${url}. In a real implementation, this would show actual content scraped from the website. The AI web scraper would analyze the structure and content of the page to extract meaningful information based on your queries. |
|
|
|
|
|
Example content that might be found: |
|
|
- Product listings with prices |
|
|
- Article text and headings |
|
|
- Contact information |
|
|
- Navigation structure |
|
|
- Metadata and SEO information |
|
|
|
|
|
The AI can then answer questions about this content, summarize it, or extract specific information you're interested in.`, |
|
|
timestamp: new Date().toISOString(), |
|
|
id: generateId() |
|
|
}; |
|
|
resolve(mockData); |
|
|
}, 2000); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function queryAI(url, question) { |
|
|
return new Promise((resolve) => { |
|
|
|
|
|
setTimeout(() => { |
|
|
|
|
|
let response = ""; |
|
|
if (question.toLowerCase().includes("product") || question.toLowerCase().includes("sell")) { |
|
|
response = `Based on my analysis of ${url}, the website appears to sell various products. While I can't see actual products in this demo, a real implementation would extract product names, descriptions, prices, and other relevant details.`; |
|
|
} else if (question.toLowerCase().includes("contact") || question.toLowerCase().includes("address")) { |
|
|
response = `The contact information for ${url} would typically be found in the footer or a dedicated 'Contact Us' page. A real implementation would extract phone numbers, email addresses, physical addresses, and contact forms.`; |
|
|
} else if (question.toLowerCase().includes("price") || question.toLowerCase().includes("cost")) { |
|
|
response = `Price information would be extracted from product pages on ${url}. In this demo, I can't see actual prices, but a real implementation would identify and list all pricing information with currency symbols.`; |
|
|
} else if (question.toLowerCase().includes("seo") || question.toLowerCase().includes("optimization")) { |
|
|
response = `SEO analysis would examine meta tags, headings, alt attributes, and content structure. For ${url}, this would help determine how well optimized the site is for search engines.`; |
|
|
} else { |
|
|
response = `I've analyzed ${url} and found it contains various types of content. For more specific information, please ask about particular elements like products, services, contact information, or other details you're interested in.`; |
|
|
} |
|
|
resolve(response); |
|
|
}, 1500); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function compareWebsitesAI(url1, url2) { |
|
|
return new Promise((resolve) => { |
|
|
|
|
|
setTimeout(() => { |
|
|
const response = `AI comparison analysis between ${url1} and ${url2}: |
|
|
|
|
|
1. Content Volume: ${url1} appears to have more extensive content based on word count and section diversity. |
|
|
|
|
|
2. SEO Optimization: Both sites use basic SEO practices, but ${url2} has more comprehensive meta descriptions and alt tags. |
|
|
|
|
|
3. Navigation Structure: ${url1} has a simpler navigation with clear categories, while ${url2} offers more depth but can be confusing. |
|
|
|
|
|
4. Visual Elements: ${url2} uses more images and visual content compared to ${url1}. |
|
|
|
|
|
5. Call-to-Action: ${url1} has more prominent and strategically placed call-to-action buttons. |
|
|
|
|
|
In summary, while both sites serve their purpose, ${url1} might provide a better user experience for quick information access, while ${url2} offers more detailed content for engaged visitors.`; |
|
|
resolve(response); |
|
|
}, 2000); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function generateId() { |
|
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { |
|
|
const r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8); |
|
|
return v.toString(16); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function saveToStorage(data) { |
|
|
let scrapedData = JSON.parse(localStorage.getItem('scrapedData')) || []; |
|
|
const existingIndex = scrapedData.findIndex(item => item.url === data.url); |
|
|
|
|
|
if (existingIndex !== -1) { |
|
|
|
|
|
scrapedData[existingIndex] = data; |
|
|
} else { |
|
|
|
|
|
scrapedData.push(data); |
|
|
} |
|
|
|
|
|
localStorage.setItem('scrapedData', JSON.stringify(scrapedData)); |
|
|
return true; |
|
|
} |
|
|
|
|
|
function loadStorage() { |
|
|
const scrapedData = JSON.parse(localStorage.getItem('scrapedData')) || []; |
|
|
|
|
|
if (scrapedData.length === 0) { |
|
|
emptyStorage.classList.remove('hidden'); |
|
|
storageList.classList.add('hidden'); |
|
|
return; |
|
|
} |
|
|
|
|
|
emptyStorage.classList.add('hidden'); |
|
|
storageList.classList.remove('hidden'); |
|
|
storageList.innerHTML = ''; |
|
|
|
|
|
|
|
|
const searchTerm = storageSearch.value.toLowerCase(); |
|
|
const filteredData = searchTerm ? |
|
|
scrapedData.filter(item => |
|
|
item.url.toLowerCase().includes(searchTerm) || |
|
|
(item.content && item.content.toLowerCase().includes(searchTerm)) |
|
|
) : scrapedData; |
|
|
|
|
|
if (filteredData.length === 0) { |
|
|
storageList.innerHTML = '<div class="col-span-3 text-center py-8 text-gray-500">No results found matching your search</div>'; |
|
|
return; |
|
|
} |
|
|
|
|
|
filteredData.forEach(item => { |
|
|
const storageItem = document.createElement('div'); |
|
|
storageItem.className = 'storage-item bg-gray-50 rounded-lg p-4 shadow-sm hover:shadow-md cursor-pointer'; |
|
|
storageItem.innerHTML = ` |
|
|
<div class="flex justify-between items-start mb-2"> |
|
|
<h4 class="font-medium text-gray-800 truncate">${item.url}</h4> |
|
|
<span class="text-xs text-gray-500">${new Date(item.timestamp).toLocaleDateString()}</span> |
|
|
</div> |
|
|
<p class="text-sm text-gray-600 mb-3 line-clamp-2">${item.content.substring(0, 150)}...</p> |
|
|
<div class="flex justify-between text-xs text-gray-500"> |
|
|
<span><i class="fas fa-file-alt mr-1"></i> ${item.pageCount} pages</span> |
|
|
<span><i class="fas fa-image mr-1"></i> ${item.imageCount} images</span> |
|
|
<span><i class="fas fa-link mr-1"></i> ${item.linkCount} links</span> |
|
|
</div> |
|
|
<div class="flex justify-end mt-3 space-x-2"> |
|
|
<button class="load-btn text-purple-600 hover:text-purple-800 text-sm" data-id="${item.id}"> |
|
|
<i class="fas fa-eye mr-1"></i> View |
|
|
</button> |
|
|
<button class="delete-btn text-red-600 hover:text-red-800 text-sm" data-id="${item.id}"> |
|
|
<i class="fas fa-trash-alt mr-1"></i> Delete |
|
|
</button> |
|
|
</div> |
|
|
`; |
|
|
storageList.appendChild(storageItem); |
|
|
}); |
|
|
|
|
|
|
|
|
document.querySelectorAll('.load-btn').forEach(btn => { |
|
|
btn.addEventListener('click', function(e) { |
|
|
e.stopPropagation(); |
|
|
const id = this.getAttribute('data-id'); |
|
|
loadFromStorage(id); |
|
|
showTab('scrape'); |
|
|
}); |
|
|
}); |
|
|
|
|
|
document.querySelectorAll('.delete-btn').forEach(btn => { |
|
|
btn.addEventListener('click', function(e) { |
|
|
e.stopPropagation(); |
|
|
const id = this.getAttribute('data-id'); |
|
|
deleteFromStorage(id); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
document.querySelectorAll('.storage-item').forEach(item => { |
|
|
item.addEventListener('click', function() { |
|
|
const id = this.querySelector('.load-btn').getAttribute('data-id'); |
|
|
loadFromStorage(id); |
|
|
showTab('scrape'); |
|
|
}); |
|
|
}); |
|
|
} |
|
|
|
|
|
function loadFromStorage(id) { |
|
|
const scrapedData = JSON.parse(localStorage.getItem('scrapedData')) || []; |
|
|
const item = scrapedData.find(item => item.id === id); |
|
|
|
|
|
if (item) { |
|
|
currentScrapedData = item; |
|
|
|
|
|
|
|
|
urlInput.value = item.url; |
|
|
websiteUrl.textContent = item.url; |
|
|
pageCount.textContent = item.pageCount; |
|
|
imageCount.textContent = item.imageCount; |
|
|
linkCount.textContent = item.linkCount; |
|
|
contentPreview.textContent = item.content; |
|
|
|
|
|
|
|
|
initialState.classList.add('hidden'); |
|
|
loadingState.classList.add('hidden'); |
|
|
scrapedContent.classList.remove('hidden'); |
|
|
querySection.classList.remove('hidden'); |
|
|
aiResponse.classList.add('hidden'); |
|
|
} |
|
|
} |
|
|
|
|
|
function deleteFromStorage(id) { |
|
|
if (confirm('Are you sure you want to delete this saved website?')) { |
|
|
let scrapedData = JSON.parse(localStorage.getItem('scrapedData')) || []; |
|
|
scrapedData = scrapedData.filter(item => item.id !== id); |
|
|
localStorage.setItem('scrapedData', JSON.stringify(scrapedData)); |
|
|
loadStorage(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function updateCompareDropdowns() { |
|
|
const scrapedData = JSON.parse(localStorage.getItem('scrapedData')) || []; |
|
|
|
|
|
compareUrl1.innerHTML = '<option value="">Select a website</option>'; |
|
|
compareUrl2.innerHTML = '<option value="">Select a website</option>'; |
|
|
|
|
|
scrapedData.forEach(item => { |
|
|
const option1 = document.createElement('option'); |
|
|
option1.value = item.id; |
|
|
option1.textContent = item.url; |
|
|
compareUrl1.appendChild(option1); |
|
|
|
|
|
const option2 = document.createElement('option'); |
|
|
option2.value = item.id; |
|
|
option2.textContent = item.url; |
|
|
compareUrl2.appendChild(option2); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function compareWebsites() { |
|
|
const id1 = compareUrl1.value; |
|
|
const id2 = compareUrl2.value; |
|
|
|
|
|
if (!id1 || !id2) { |
|
|
alert('Please select two websites to compare'); |
|
|
return; |
|
|
} |
|
|
|
|
|
if (id1 === id2) { |
|
|
alert('Please select two different websites to compare'); |
|
|
return; |
|
|
} |
|
|
|
|
|
const scrapedData = JSON.parse(localStorage.getItem('scrapedData')) || []; |
|
|
const item1 = scrapedData.find(item => item.id === id1); |
|
|
const item2 = scrapedData.find(item => item.id === id2); |
|
|
|
|
|
if (!item1 || !item2) { |
|
|
alert('Error loading website data. Please try again.'); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
compareSite1Header.textContent = item1.url; |
|
|
compareSite2Header.textContent = item2.url; |
|
|
compareSite1Title.textContent = item1.url; |
|
|
compareSite2Title.textContent = item2.url; |
|
|
|
|
|
comparePageCount1.textContent = item1.pageCount; |
|
|
comparePageCount2.textContent = item2.pageCount; |
|
|
comparePageDiff.textContent = Math.abs(item1.pageCount - item2.pageCount); |
|
|
|
|
|
compareImageCount1.textContent = item1.imageCount; |
|
|
compareImageCount2.textContent = item2.imageCount; |
|
|
compareImageDiff.textContent = Math.abs(item1.imageCount - item2.imageCount); |
|
|
|
|
|
compareLinkCount1.textContent = item1.linkCount; |
|
|
compareLinkCount2.textContent = item2.linkCount; |
|
|
compareLinkDiff.textContent = Math.abs(item1.linkCount - item2.linkCount); |
|
|
|
|
|
compareContent1.textContent = item1.content.substring(0, 500) + (item1.content.length > 500 ? '...' : ''); |
|
|
compareContent2.textContent = item2.content.substring(0, 500) + (item2.content.length > 500 ? '...' : ''); |
|
|
|
|
|
|
|
|
highlightDifferences(comparePageCount1, comparePageCount2, comparePageDiff); |
|
|
highlightDifferences(compareImageCount1, compareImageCount2, compareImageDiff); |
|
|
highlightDifferences(compareLinkCount1, compareLinkCount2, compareLinkDiff); |
|
|
|
|
|
|
|
|
comparisonResults.classList.remove('hidden'); |
|
|
aiComparisonResponse.classList.add('hidden'); |
|
|
} |
|
|
|
|
|
function highlightDifferences(el1, el2, diffEl) { |
|
|
const val1 = parseInt(el1.textContent); |
|
|
const val2 = parseInt(el2.textContent); |
|
|
|
|
|
if (val1 > val2) { |
|
|
el1.classList.add('font-bold', 'text-green-600'); |
|
|
el2.classList.remove('font-bold', 'text-green-600'); |
|
|
diffEl.classList.add('font-bold', 'text-green-600'); |
|
|
} else if (val2 > val1) { |
|
|
el2.classList.add('font-bold', 'text-green-600'); |
|
|
el1.classList.remove('font-bold', 'text-green-600'); |
|
|
diffEl.classList.add('font-bold', 'text-green-600'); |
|
|
} else { |
|
|
el1.classList.remove('font-bold', 'text-green-600'); |
|
|
el2.classList.remove('font-bold', 'text-green-600'); |
|
|
diffEl.classList.remove('font-bold', 'text-green-600'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function clearComparison() { |
|
|
compareUrl1.value = ''; |
|
|
compareUrl2.value = ''; |
|
|
comparisonResults.classList.add('hidden'); |
|
|
aiComparisonResponse.classList.add('hidden'); |
|
|
} |
|
|
|
|
|
|
|
|
function initEventListeners() { |
|
|
|
|
|
scrapeBtn.addEventListener('click', async function() { |
|
|
const url = urlInput.value.trim(); |
|
|
|
|
|
if (!url) { |
|
|
alert('Please enter a valid URL'); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
initialState.classList.add('hidden'); |
|
|
loadingState.classList.remove('hidden'); |
|
|
scrapedContent.classList.add('hidden'); |
|
|
aiResponse.classList.add('hidden'); |
|
|
|
|
|
try { |
|
|
|
|
|
const data = await scrapeWebsite(url); |
|
|
currentScrapedData = data; |
|
|
|
|
|
|
|
|
websiteUrl.textContent = data.url; |
|
|
pageCount.textContent = data.pageCount; |
|
|
imageCount.textContent = data.imageCount; |
|
|
linkCount.textContent = data.linkCount; |
|
|
contentPreview.textContent = data.content; |
|
|
|
|
|
|
|
|
loadingState.classList.add('hidden'); |
|
|
scrapedContent.classList.remove('hidden'); |
|
|
querySection.classList.remove('hidden'); |
|
|
} catch (error) { |
|
|
console.error('Scraping error:', error); |
|
|
loadingState.classList.add('hidden'); |
|
|
initialState.classList.remove('hidden'); |
|
|
alert('Error scraping website. Please try again.'); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
askBtn.addEventListener('click', async function() { |
|
|
const question = queryInput.value.trim(); |
|
|
const url = urlInput.value.trim(); |
|
|
|
|
|
if (!question) { |
|
|
alert('Please enter a question'); |
|
|
return; |
|
|
} |
|
|
|
|
|
if (!url) { |
|
|
alert('Please scrape a website first'); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
aiResponseContent.innerHTML = '<div class="flex items-center"><div class="loader ease-linear rounded-full border-2 border-t-2 border-purple-500 h-4 w-4 mr-2"></div> <span>AI is thinking...</span></div>'; |
|
|
aiResponse.classList.remove('hidden'); |
|
|
|
|
|
try { |
|
|
|
|
|
const response = await queryAI(url, question); |
|
|
|
|
|
|
|
|
aiResponseContent.textContent = response; |
|
|
} catch (error) { |
|
|
console.error('AI query error:', error); |
|
|
aiResponseContent.textContent = 'Error getting AI response. Please try again.'; |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
saveBtn.addEventListener('click', function() { |
|
|
if (!currentScrapedData) { |
|
|
alert('No website data to save. Please scrape a website first.'); |
|
|
return; |
|
|
} |
|
|
|
|
|
const success = saveToStorage(currentScrapedData); |
|
|
if (success) { |
|
|
alert('Website data saved successfully!'); |
|
|
} else { |
|
|
alert('Error saving website data. Please try again.'); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
compareBtn.addEventListener('click', compareWebsites); |
|
|
|
|
|
|
|
|
clearComparisonBtn.addEventListener('click', clearComparison); |
|
|
|
|
|
|
|
|
aiCompareBtn.addEventListener('click', async function() { |
|
|
const id1 = compareUrl1.value; |
|
|
const id2 = compareUrl2.value; |
|
|
|
|
|
if (!id1 || !id2) { |
|
|
alert('Please select two websites to compare first'); |
|
|
return; |
|
|
} |
|
|
|
|
|
const scrapedData = JSON.parse(localStorage.getItem('scrapedData')) || []; |
|
|
const item1 = scrapedData.find(item => item.id === id1); |
|
|
const item2 = scrapedData.find(item => item.id === id2); |
|
|
|
|
|
if (!item1 || !item2) { |
|
|
alert('Error loading website data. Please try again.'); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
aiComparisonContent.innerHTML = '<div class="flex items-center"><div class="loader ease-linear rounded-full border-2 border-t-2 border-purple-500 h-4 w-4 mr-2"></div> <span>AI is analyzing the comparison...</span></div>'; |
|
|
aiComparisonResponse.classList.remove('hidden'); |
|
|
|
|
|
try { |
|
|
|
|
|
const response = await compareWebsitesAI(item1.url, item2.url); |
|
|
|
|
|
|
|
|
aiComparisonContent.textContent = response; |
|
|
} catch (error) { |
|
|
console.error('AI comparison error:', error); |
|
|
aiComparisonContent.textContent = 'Error getting AI comparison. Please try again.'; |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
storageSearch.addEventListener('input', function() { |
|
|
loadStorage(); |
|
|
}); |
|
|
|
|
|
|
|
|
urlInput.addEventListener('keypress', function(e) { |
|
|
if (e.key === 'Enter') { |
|
|
scrapeBtn.click(); |
|
|
} |
|
|
}); |
|
|
|
|
|
queryInput.addEventListener('keypress', function(e) { |
|
|
if (e.key === 'Enter') { |
|
|
askBtn.click(); |
|
|
} |
|
|
}); |
|
|
|
|
|
storageSearch.addEventListener('keypress', function(e) { |
|
|
if (e.key === 'Enter') { |
|
|
loadStorage(); |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function init() { |
|
|
initTabs(); |
|
|
initEventListeners(); |
|
|
} |
|
|
|
|
|
init(); |
|
|
}); |
|
|
</script> |
|
|
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - <a href="https://enzostvs-deepsite.hf.space?remix=bulet123/scraper" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body> |
|
|
</html> |