Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>AI Web Scraper with Deepseek AI</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; | |
| } | |
| .compare-content-box { | |
| position: relative; | |
| } | |
| .compare-content-box::after { | |
| content: ""; | |
| position: absolute; | |
| bottom: 0; | |
| left: 0; | |
| right: 0; | |
| height: 20px; | |
| background: linear-gradient(to bottom, rgba(249, 250, 251, 0), rgba(249, 250, 251, 1)); | |
| pointer-events: none; | |
| } | |
| .markdown-content pre { | |
| background-color: #f3f4f6; | |
| padding: 1rem; | |
| border-radius: 0.375rem; | |
| overflow-x: auto; | |
| margin: 1rem 0; | |
| } | |
| .markdown-content code { | |
| background-color: #f3f4f6; | |
| padding: 0.2rem 0.4rem; | |
| border-radius: 0.25rem; | |
| font-family: monospace; | |
| } | |
| .markdown-content ul, .markdown-content ol { | |
| padding-left: 1.5rem; | |
| margin: 1rem 0; | |
| } | |
| .markdown-content li { | |
| margin-bottom: 0.5rem; | |
| } | |
| .markdown-content h1, .markdown-content h2, .markdown-content h3 { | |
| font-weight: bold; | |
| margin-top: 1.5rem; | |
| margin-bottom: 1rem; | |
| } | |
| .markdown-content h1 { font-size: 1.5rem; } | |
| .markdown-content h2 { font-size: 1.25rem; } | |
| .markdown-content h3 { font-size: 1.125rem; } | |
| </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"> | |
| <!-- Header --> | |
| <div class="text-center mb-12"> | |
| <h1 class="text-4xl font-bold text-white mb-2">AI Web Scraper with Deepseek</h1> | |
| <p class="text-xl text-white opacity-80">Extract, analyze and compare website data with Deepseek AI</p> | |
| </div> | |
| <!-- Tabs --> | |
| <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> | |
| <button id="settings-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-cog mr-2"></i>Settings | |
| </button> | |
| </div> | |
| <!-- Main Card --> | |
| <div class="bg-white rounded-xl shadow-2xl overflow-hidden"> | |
| <!-- Scrape Tab Content --> | |
| <div id="scrape-content" class="tab-content"> | |
| <!-- Input Section --> | |
| <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 Deepseek 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> | |
| <!-- Results Section --> | |
| <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"> | |
| <!-- Content will be inserted here --> | |
| </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">Deepseek 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 markdown-content"> | |
| <!-- AI response will be inserted here --> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Compare Tab Content --> | |
| <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 using Deepseek AI.</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 class="compare-content-box"> | |
| <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"> | |
| <!-- Content for site 1 will be inserted here --> | |
| </div> | |
| </div> | |
| <div class="compare-content-box"> | |
| <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"> | |
| <!-- Content for site 2 will be inserted here --> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mt-8"> | |
| <h5 class="text-md font-semibold mb-3">Deepseek AI Comparison Analysis</h5> | |
| <div class="mb-6"> | |
| <label for="comparison-query" class="block text-gray-700 font-medium mb-2"> | |
| <i class="fas fa-question-circle mr-2"></i>Ask a Specific Comparison Question | |
| </label> | |
| <div class="flex"> | |
| <input type="text" id="comparison-query" placeholder="Which site has better prices? Which has more detailed product descriptions?" | |
| 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-comparison-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-robot mr-2"></i>Ask Deepseek | |
| </button> | |
| </div> | |
| <p class="text-xs text-gray-500 mt-1">Example: "Which site has cheaper prices for similar products?" or "Compare the product descriptions quality"</p> | |
| </div> | |
| <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 markdown-content"> | |
| <!-- AI comparison response will be inserted here --> | |
| </div> | |
| </div> | |
| <button id="ai-compare-btn" class="mt-4 bg-purple-600 hover:bg-purple-700 text-white px-6 py-2 rounded-lg transition duration-200"> | |
| <i class="fas fa-balance-scale mr-2"></i>Get Standard AI Comparison Summary | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Storage Tab Content --> | |
| <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"> | |
| <!-- Saved items will be inserted here --> | |
| </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> | |
| <!-- Settings Tab Content --> | |
| <div id="settings-content" class="tab-content hidden"> | |
| <div class="p-8"> | |
| <h3 class="text-xl font-bold text-gray-800 mb-6"><i class="fas fa-cog mr-2"></i>Settings</h3> | |
| <div class="bg-gray-50 p-6 rounded-lg"> | |
| <h4 class="text-lg font-semibold text-gray-800 mb-4">Deepseek AI Configuration</h4> | |
| <div class="mb-6"> | |
| <label for="api-key" class="block text-gray-700 font-medium mb-2"> | |
| <i class="fas fa-key mr-2"></i>Deepseek API Key | |
| </label> | |
| <input type="password" id="api-key" placeholder="Enter your Deepseek API key" | |
| class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500"> | |
| <p class="text-xs text-gray-500 mt-1">You can get a free API key from <a href="https://deepseek.com" target="_blank" class="text-purple-600 hover:underline">deepseek.com</a></p> | |
| </div> | |
| <div class="mb-6"> | |
| <label for="ai-model" class="block text-gray-700 font-medium mb-2"> | |
| <i class="fas fa-brain mr-2"></i>AI Model | |
| </label> | |
| <select id="ai-model" 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="deepseek-chat">Deepseek Chat (Default)</option> | |
| <option value="deepseek-coder">Deepseek Coder (For technical content)</option> | |
| </select> | |
| </div> | |
| <div class="flex justify-between items-center mb-6"> | |
| <div> | |
| <label class="block text-gray-700 font-medium mb-2"> | |
| <i class="fas fa-history mr-2"></i>Response Length | |
| </label> | |
| <p class="text-sm text-gray-600">Adjust how detailed AI responses should be</p> | |
| </div> | |
| <div class="flex items-center"> | |
| <button id="reduce-length" class="bg-gray-200 text-gray-700 px-3 py-1 rounded-l-lg hover:bg-gray-300"> | |
| <i class="fas fa-minus"></i> | |
| </button> | |
| <span id="length-value" class="bg-gray-100 px-4 py-1">Medium</span> | |
| <button id="increase-length" class="bg-gray-200 text-gray-700 px-3 py-1 rounded-r-lg hover:bg-gray-300"> | |
| <i class="fas fa-plus"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <button id="save-settings" class="bg-purple-600 hover:bg-purple-700 text-white px-6 py-3 rounded-lg transition duration-200 w-full"> | |
| <i class="fas fa-save mr-2"></i> Save Settings | |
| </button> | |
| </div> | |
| <div class="bg-gray-50 p-6 rounded-lg mt-6"> | |
| <h4 class="text-lg font-semibold text-gray-800 mb-4">Application Settings</h4> | |
| <div class="mb-4"> | |
| <label class="inline-flex items-center"> | |
| <input type="checkbox" id="dark-mode" class="rounded text-purple-600 focus:ring-purple-500"> | |
| <span class="ml-2 text-gray-700">Dark Mode</span> | |
| </label> | |
| </div> | |
| <div class="mb-4"> | |
| <label class="inline-flex items-center"> | |
| <input type="checkbox" id="auto-save" checked class="rounded text-purple-600 focus:ring-purple-500"> | |
| <span class="ml-2 text-gray-700">Auto-save scraped results</span> | |
| </label> | |
| </div> | |
| <div class="mb-4"> | |
| <label class="inline-flex items-center"> | |
| <input type="checkbox" id="notifications" checked class="rounded text-purple-600 focus:ring-purple-500"> | |
| <span class="ml-2 text-gray-700">Enable notifications</span> | |
| </label> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Footer --> | |
| <div class="text-center mt-8 text-white opacity-70 text-sm"> | |
| <p>AI Web Scraper with Deepseek AI - Powered by Deepseek's free model</p> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // Deepseek API configuration | |
| const DEEPSEEK_API_URL = 'https://api.deepseek.com/v1/chat/completions'; | |
| let apiKey = localStorage.getItem('deepseek_api_key') || ''; | |
| let aiModel = localStorage.getItem('ai_model') || 'deepseek-chat'; | |
| let responseLength = localStorage.getItem('response_length') || 'medium'; | |
| // DOM Elements | |
| 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'); | |
| // Tab elements | |
| const scrapeTab = document.getElementById('scrape-tab'); | |
| const compareTab = document.getElementById('compare-tab'); | |
| const storageTab = document.getElementById('storage-tab'); | |
| const settingsTab = document.getElementById('settings-tab'); | |
| const scrapeContent = document.getElementById('scrape-content'); | |
| const compareContent = document.getElementById('compare-content'); | |
| const storageContent = document.getElementById('storage-content'); | |
| const settingsContent = document.getElementById('settings-content'); | |
| // Compare elements | |
| 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 comparisonQuery = document.getElementById('comparison-query'); | |
| const askComparisonBtn = document.getElementById('ask-comparison-btn'); | |
| // Storage elements | |
| const storageSearch = document.getElementById('storage-search'); | |
| const storageList = document.getElementById('storage-list'); | |
| const emptyStorage = document.getElementById('empty-storage'); | |
| // Settings elements | |
| const apiKeyInput = document.getElementById('api-key'); | |
| const aiModelSelect = document.getElementById('ai-model'); | |
| const lengthValue = document.getElementById('length-value'); | |
| const reduceLengthBtn = document.getElementById('reduce-length'); | |
| const increaseLengthBtn = document.getElementById('increase-length'); | |
| const saveSettingsBtn = document.getElementById('save-settings'); | |
| const darkModeToggle = document.getElementById('dark-mode'); | |
| const autoSaveToggle = document.getElementById('auto-save'); | |
| const notificationsToggle = document.getElementById('notifications'); | |
| // Current scraped data | |
| let currentScrapedData = null; | |
| let comparedItem1 = null; | |
| let comparedItem2 = null; | |
| // Initialize tabs | |
| function initTabs() { | |
| scrapeTab.addEventListener('click', function() { | |
| showTab('scrape'); | |
| }); | |
| compareTab.addEventListener('click', function() { | |
| showTab('compare'); | |
| updateCompareDropdowns(); | |
| }); | |
| storageTab.addEventListener('click', function() { | |
| showTab('storage'); | |
| loadStorage(); | |
| }); | |
| settingsTab.addEventListener('click', function() { | |
| showTab('settings'); | |
| loadSettings(); | |
| }); | |
| showTab('scrape'); | |
| } | |
| function showTab(tabName) { | |
| // Reset tab styling | |
| 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'); | |
| settingsTab.classList.remove('border-purple-600', 'text-purple-600'); | |
| settingsTab.classList.add('text-gray-500', 'hover:text-gray-700'); | |
| // Hide all content | |
| scrapeContent.classList.add('hidden'); | |
| compareContent.classList.add('hidden'); | |
| storageContent.classList.add('hidden'); | |
| settingsContent.classList.add('hidden'); | |
| // Show selected tab | |
| 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; | |
| case 'settings': | |
| settingsTab.classList.add('border-purple-600', 'text-purple-600'); | |
| settingsTab.classList.remove('text-gray-500', 'hover:text-gray-700'); | |
| settingsContent.classList.remove('hidden'); | |
| break; | |
| } | |
| } | |
| // Load settings from localStorage | |
| function loadSettings() { | |
| apiKeyInput.value = apiKey; | |
| aiModelSelect.value = aiModel; | |
| lengthValue.textContent = responseLength.charAt(0).toUpperCase() + responseLength.slice(1); | |
| // Load other settings | |
| darkModeToggle.checked = localStorage.getItem('dark_mode') === 'true' || false; | |
| autoSaveToggle.checked = localStorage.getItem('auto_save') !== 'false'; | |
| notificationsToggle.checked = localStorage.getItem('notifications') !== 'false'; | |
| } | |
| // Save settings to localStorage | |
| function saveSettings() { | |
| apiKey = apiKeyInput.value.trim(); | |
| aiModel = aiModelSelect.value; | |
| responseLength = lengthValue.textContent.toLowerCase(); | |
| localStorage.setItem('deepseek_api_key', apiKey); | |
| localStorage.setItem('ai_model', aiModel); | |
| localStorage.setItem('response_length', responseLength); | |
| localStorage.setItem('dark_mode', darkModeToggle.checked); | |
| localStorage.setItem('auto_save', autoSaveToggle.checked); | |
| localStorage.setItem('notifications', notificationsToggle.checked); | |
| // Show confirmation | |
| showNotification('Settings saved successfully!', 'success'); | |
| // Apply dark mode if enabled | |
| applyDarkMode(); | |
| } | |
| // Apply dark mode | |
| function applyDarkMode() { | |
| if (darkModeToggle.checked) { | |
| document.documentElement.classList.add('dark'); | |
| } else { | |
| document.documentElement.classList.remove('dark'); | |
| } | |
| } | |
| // Show notification | |
| function showNotification(message, type = 'info') { | |
| if (!notificationsToggle.checked) return; | |
| const colors = { | |
| info: 'bg-blue-500', | |
| success: 'bg-green-500', | |
| warning: 'bg-yellow-500', | |
| error: 'bg-red-500' | |
| }; | |
| const notification = document.createElement('div'); | |
| notification.className = `fixed bottom-4 right-4 text-white px-4 py-2 rounded shadow-lg ${colors[type]} transition-opacity opacity-0`; | |
| notification.textContent = message; | |
| document.body.appendChild(notification); | |
| setTimeout(() => { | |
| notification.classList.add('opacity-100'); | |
| }, 10); | |
| setTimeout(() => { | |
| notification.classList.remove('opacity-100'); | |
| setTimeout(() => { | |
| notification.remove(); | |
| }, 300); | |
| }, 3000); | |
| } | |
| // Adjust response length | |
| function adjustLength(increase) { | |
| const lengths = ['short', 'medium', 'long', 'very long']; | |
| let currentIndex = lengths.indexOf(responseLength); | |
| if (increase) { | |
| if (currentIndex < lengths.length - 1) { | |
| responseLength = lengths[currentIndex + 1]; | |
| } | |
| } else { | |
| if (currentIndex > 0) { | |
| responseLength = lengths[currentIndex - 1]; | |
| } | |
| } | |
| lengthValue.textContent = responseLength.charAt(0).toUpperCase() + responseLength.slice(1); | |
| } | |
| // Mock scraping function (in a real app, this would call a backend service) | |
| function scrapeWebsite(url) { | |
| return new Promise((resolve) => { | |
| // Simulate API call delay | |
| setTimeout(() => { | |
| // Mock data | |
| 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 Deepseek AI 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 | |
| Deepseek 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); | |
| }); | |
| } | |
| // Call Deepseek API | |
| async function queryDeepseek(prompt, context = '') { | |
| if (!apiKey) { | |
| showNotification('Please set your Deepseek API key in Settings', 'error'); | |
| return "Error: API key not configured. Please set your Deepseek API key in the Settings tab."; | |
| } | |
| const fullPrompt = context ? `${context}\n\nQuestion: ${prompt}` : prompt; | |
| const maxTokens = responseLength === 'short' ? 300 : | |
| responseLength === 'medium' ? 600 : | |
| responseLength === 'long' ? 1000 : 1500; | |
| try { | |
| const response = await fetch(DEEPSEEK_API_URL, { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| 'Authorization': `Bearer ${apiKey}` | |
| }, | |
| body: JSON.stringify({ | |
| model: aiModel, | |
| messages: [ | |
| { | |
| role: "user", | |
| content: fullPrompt | |
| } | |
| ], | |
| max_tokens: maxTokens, | |
| temperature: 0.7 | |
| }) | |
| }); | |
| if (!response.ok) { | |
| throw new Error(`API error: ${response.status}`); | |
| } | |
| const data = await response.json(); | |
| return data.choices[0].message.content; | |
| } catch (error) { | |
| console.error('Deepseek API error:', error); | |
| return `Error: Could not get response from Deepseek AI. ${error.message}`; | |
| } | |
| } | |
| // Generate unique ID for saved items | |
| 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); | |
| }); | |
| } | |
| // Load and save scraped data to localStorage | |
| function saveToStorage(data) { | |
| let scrapedData = JSON.parse(localStorage.getItem('scrapedData')) || []; | |
| const existingIndex = scrapedData.findIndex(item => item.url === data.url); | |
| if (existingIndex !== -1) { | |
| // Update existing entry | |
| scrapedData[existingIndex] = data; | |
| } else { | |
| // Add new entry | |
| 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 = ''; | |
| // Filter by search term if any | |
| 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); | |
| }); | |
| // Add event listeners to buttons | |
| 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); | |
| }); | |
| }); | |
| // Add click event to load full item | |
| 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; | |
| // Update UI with stored data | |
| urlInput.value = item.url; | |
| websiteUrl.textContent = item.url; | |
| pageCount.textContent = item.pageCount; | |
| imageCount.textContent = item.imageCount; | |
| linkCount.textContent = item.linkCount; | |
| contentPreview.textContent = item.content; | |
| // Show results and query section | |
| 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(); | |
| showNotification('Website deleted from storage', 'success'); | |
| } | |
| } | |
| // Update compare dropdowns with saved websites | |
| 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); | |
| }); | |
| } | |
| // Compare two websites | |
| function compareWebsites() { | |
| const id1 = compareUrl1.value; | |
| const id2 = compareUrl2.value; | |
| if (!id1 || !id2) { | |
| showNotification('Please select two websites to compare', 'error'); | |
| return; | |
| } | |
| if (id1 === id2) { | |
| showNotification('Please select two different websites to compare', 'error'); | |
| return; | |
| } | |
| const scrapedData = JSON.parse(localStorage.getItem('scrapedData')) || []; | |
| comparedItem1 = scrapedData.find(item => item.id === id1); | |
| comparedItem2 = scrapedData.find(item => item.id === id2); | |
| if (!comparedItem1 || !comparedItem2) { | |
| showNotification('Error loading website data. Please try again.', 'error'); | |
| return; | |
| } | |
| // Update UI with comparison data | |
| compareSite1Header.textContent = comparedItem1.url; | |
| compareSite2Header.textContent = comparedItem2.url; | |
| compareSite1Title.textContent = comparedItem1.url; | |
| compareSite2Title.textContent = comparedItem2.url; | |
| comparePageCount1.textContent = comparedItem1.pageCount; | |
| comparePageCount2.textContent = comparedItem2.pageCount; | |
| comparePageDiff.textContent = Math.abs(comparedItem1.pageCount - comparedItem2.pageCount); | |
| compareImageCount1.textContent = comparedItem1.imageCount; | |
| compareImageCount2.textContent = comparedItem2.imageCount; | |
| compareImageDiff.textContent = Math.abs(comparedItem1.imageCount - comparedItem2.imageCount); | |
| compareLinkCount1.textContent = comparedItem1.linkCount; | |
| compareLinkCount2.textContent = comparedItem2.linkCount; | |
| compareLinkDiff.textContent = Math.abs(comparedItem1.linkCount - comparedItem2.linkCount); | |
| compareContent1.textContent = comparedItem1.content.substring(0, 500) + (comparedItem1.content.length > 500 ? '...' : ''); | |
| compareContent2.textContent = comparedItem2.content.substring(0, 500) + (comparedItem2.content.length > 500 ? '...' : ''); | |
| // Highlight differences | |
| highlightDifferences(comparePageCount1, comparePageCount2, comparePageDiff); | |
| highlightDifferences(compareImageCount1, compareImageCount2, compareImageDiff); | |
| highlightDifferences(compareLinkCount1, compareLinkCount2, compareLinkDiff); | |
| // Show results | |
| comparisonResults.classList.remove('hidden'); | |
| aiComparisonResponse.classList.add('hidden'); | |
| showNotification('Websites loaded for comparison', 'success'); | |
| } | |
| 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'); | |
| } | |
| } | |
| // Clear comparison | |
| function clearComparison() { | |
| compareUrl1.value = ''; | |
| compareUrl2.value = ''; | |
| comparisonResults.classList.add('hidden'); | |
| aiComparisonResponse.classList.add('hidden'); | |
| comparedItem1 = null; | |
| comparedItem2 = null; | |
| showNotification('Comparison cleared', 'info'); | |
| } | |
| // Get standard AI comparison | |
| async function getStandardComparison() { | |
| if (!comparedItem1 || !comparedItem2) { | |
| showNotification('Please select and compare two websites first', 'error'); | |
| return; | |
| } | |
| // Show loading in AI response area | |
| 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>Deepseek AI is analyzing the comparison...</span></div>'; | |
| aiComparisonResponse.classList.remove('hidden'); | |
| const prompt = `Compare these two websites based on their content, structure, and other relevant metrics: | |
| Website 1 (${comparedItem1.url}): | |
| ${comparedItem1.content.substring(0, 2000)}... | |
| Website 2 (${comparedItem2.url}): | |
| ${comparedItem2.content.substring(0, 2000)}... | |
| Provide a detailed comparison in the following format: | |
| 1. Content Analysis: Compare the depth, quality, and relevance of content | |
| 2. Structure: Compare the organization and navigation | |
| 3. SEO Metrics: Compare potential SEO performance | |
| 4. Recommendations: Which website is better in different aspects and why | |
| Format your response with clear headings and bullet points.`; | |
| try { | |
| const response = await queryDeepseek(prompt); | |
| aiComparisonContent.innerHTML = response; | |
| } catch (error) { | |
| console.error('Comparison error:', error); | |
| aiComparisonContent.innerHTML = 'Error processing your comparison request. Please try again.'; | |
| } | |
| } | |
| // Handle specific comparison questions | |
| async function askComparisonQuestion() { | |
| const question = comparisonQuery.value.trim(); | |
| if (!question) { | |
| showNotification('Please enter a comparison question', 'error'); | |
| return; | |
| } | |
| if (!comparedItem1 || !comparedItem2) { | |
| showNotification('Please select and compare two websites first', 'error'); | |
| return; | |
| } | |
| // Show loading in AI response area | |
| 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>Deepseek AI is analyzing your question...</span></div>'; | |
| aiComparisonResponse.classList.remove('hidden'); | |
| const context = `You are comparing two websites: | |
| Website 1 (${comparedItem1.url}): | |
| ${comparedItem1.content.substring(0, 2000)}... | |
| Website 2 (${comparedItem2.url}): | |
| ${comparedItem2.content.substring(0, 2000)}... | |
| Please answer the following question:`; | |
| try { | |
| const response = await queryDeepseek(question, context); | |
| aiComparisonContent.innerHTML = response; | |
| } catch (error) { | |
| console.error('Comparison error:', error); | |
| aiComparisonContent.innerHTML = 'Error processing your comparison question. Please try again.'; | |
| } | |
| } | |
| // Initialize event listeners | |
| function initEventListeners() { | |
| // Scrape button click handler | |
| scrapeBtn.addEventListener('click', async function() { | |
| const url = urlInput.value.trim(); | |
| if (!url) { | |
| showNotification('Please enter a valid URL', 'error'); | |
| return; | |
| } | |
| // Show loading state | |
| initialState.classList.add('hidden'); | |
| loadingState.classList.remove('hidden'); | |
| scrapedContent.classList.add('hidden'); | |
| aiResponse.classList.add('hidden'); | |
| try { | |
| // Simulate scraping | |
| const data = await scrapeWebsite(url); | |
| currentScrapedData = data; | |
| // Update UI with scraped data | |
| websiteUrl.textContent = data.url; | |
| pageCount.textContent = data.pageCount; | |
| imageCount.textContent = data.imageCount; | |
| linkCount.textContent = data.linkCount; | |
| contentPreview.textContent = data.content; | |
| // Show results | |
| loadingState.classList.add('hidden'); | |
| scrapedContent.classList.remove('hidden'); | |
| querySection.classList.remove('hidden'); | |
| // Auto-save if enabled | |
| if (autoSaveToggle.checked) { | |
| saveToStorage(data); | |
| showNotification('Website data saved automatically', 'success'); | |
| } | |
| } catch (error) { | |
| console.error('Scraping error:', error); | |
| loadingState.classList.add('hidden'); | |
| initialState.classList.remove('hidden'); | |
| showNotification('Error scraping website. Please try again.', 'error'); | |
| } | |
| }); | |
| // Ask button click handler | |
| askBtn.addEventListener('click', async function() { | |
| const question = queryInput.value.trim(); | |
| const url = urlInput.value.trim(); | |
| if (!question) { | |
| showNotification('Please enter a question', 'error'); | |
| return; | |
| } | |
| if (!url || !currentScrapedData) { | |
| showNotification('Please scrape a website first', 'error'); | |
| return; | |
| } | |
| // Show loading in AI response area | |
| 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>Deepseek AI is thinking...</span></div>'; | |
| aiResponse.classList.remove('hidden'); | |
| try { | |
| const context = `Website URL: ${url}\n\nContent:\n${currentScrapedData.content.substring(0, 3000)}`; | |
| const response = await queryDeepseek(question, context); | |
| // Update UI with AI response | |
| aiResponseContent.innerHTML = response; | |
| } catch (error) { | |
| console.error('AI query error:', error); | |
| aiResponseContent.innerHTML = 'Error getting response from Deepseek AI. Please try again.'; | |
| } | |
| }); | |
| // Save button click handler | |
| saveBtn.addEventListener('click', function() { | |
| if (!currentScrapedData) { | |
| showNotification('No website data to save. Please scrape a website first.', 'error'); | |
| return; | |
| } | |
| const success = saveToStorage(currentScrapedData); | |
| if (success) { | |
| showNotification('Website data saved successfully!', 'success'); | |
| } else { | |
| showNotification('Error saving website data. Please try again.', 'error'); | |
| } | |
| }); | |
| // Compare button click handler | |
| compareBtn.addEventListener('click', compareWebsites); | |
| // Clear comparison button click handler | |
| clearComparisonBtn.addEventListener('click', clearComparison); | |
| // AI comparison button click handler | |
| aiCompareBtn.addEventListener('click', getStandardComparison); | |
| // Ask specific comparison question | |
| askComparisonBtn.addEventListener('click', askComparisonQuestion); | |
| comparisonQuery.addEventListener('keypress', function(e) { | |
| if (e.key === 'Enter') { | |
| askComparisonQuestion(); | |
| } | |
| }); | |
| // Storage search handler | |
| storageSearch.addEventListener('input', function() { | |
| loadStorage(); | |
| }); | |
| // Settings handlers | |
| reduceLengthBtn.addEventListener('click', function() { | |
| adjustLength(false); | |
| }); | |
| increaseLengthBtn.addEventListener('click', function() { | |
| adjustLength(true); | |
| }); | |
| saveSettingsBtn.addEventListener('click', saveSettings); | |
| // Dark mode toggle | |
| darkModeToggle.addEventListener('change', function() { | |
| localStorage.setItem('dark_mode', this.checked); | |
| applyDarkMode(); | |
| }); | |
| // Apply dark mode on load | |
| applyDarkMode(); | |
| // Allow pressing Enter in input fields | |
| 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(); | |
| } | |
| }); | |
| } | |
| // Initialize the app | |
| function init() { | |
| initTabs(); | |
| initEventListeners(); | |
| } | |
| init(); | |
| }); | |
| </script> | |
| </body> | |
| </html> |