/** * AI Web Search Assistant * Uses DuckDuckGo Instant Answer API for real-time responses */ // Configuration const CONFIG = { DDG_API_BASE: 'https://api.duckduckgo.com/', MAX_TOPICS: 8, // Use a CORS proxy for better compatibility CORS_PROXY: 'https://corsproxy.io/?' }; // DOM Elements const elements = { chatMessages: document.getElementById('chatMessages'), chatForm: document.getElementById('chatForm'), userInput: document.getElementById('userInput'), sendButton: document.getElementById('sendButton'), statusBar: document.getElementById('statusBar'), statusText: document.getElementById('statusText'), loadingTemplate: document.getElementById('loadingTemplate') }; // State let isLoading = false; /** * Update status bar */ function updateStatus(text, state = 'ready') { elements.statusText.textContent = text; elements.statusBar.className = 'status-bar'; if (state === 'searching') { elements.statusBar.classList.add('searching'); } else if (state === 'error') { elements.statusBar.classList.add('error'); } } /** * Create a loading message element */ function createLoadingMessage() { const template = elements.loadingTemplate.content.cloneNode(true); return template.querySelector('.loading-message'); } /** * Add a message to the chat */ function addMessage(content, isUser = false) { const messageDiv = document.createElement('div'); messageDiv.className = `message ${isUser ? 'user-message' : 'ai-message'}`; const avatar = document.createElement('div'); avatar.className = 'message-avatar'; avatar.innerHTML = isUser ? '' : ''; const contentDiv = document.createElement('div'); contentDiv.className = 'message-content'; if (typeof content === 'string') { contentDiv.innerHTML = `

${content}

`; } else { contentDiv.appendChild(content); } messageDiv.appendChild(avatar); messageDiv.appendChild(contentDiv); elements.chatMessages.appendChild(messageDiv); // Scroll to bottom elements.chatMessages.parentElement.scrollTop = elements.chatMessages.parentElement.scrollHeight; return messageDiv; } /** * Create an answer section */ function createAnswerSection(title, content, className = '', source = null) { const section = document.createElement('div'); section.className = `answer-section ${className}`; let html = `

${title}

${content}

`; if (source) { html += ``; } section.innerHTML = html; return section; } /** * Format DuckDuckGo response for display */ function formatDuckDuckGoResponse(data) { const container = document.createElement('div'); let hasContent = false; // Debug: Log the raw response console.log('DuckDuckGo Response:', data); // Check for Instant Answer (most prominent) if (data.Answer && data.Answer.trim()) { const answerSection = createAnswerSection('💬 Answer', data.Answer, 'answer'); if (data.AnswerType) { answerSection.querySelector('h4').textContent += ` (${data.AnswerType})`; } container.appendChild(answerSection); hasContent = true; } // Check for Definition if (data.Definition && data.Definition.trim()) { const defSection = createAnswerSection('📖 Definition', data.Definition, 'definition', data.DefinitionSource); container.appendChild(defSection); hasContent = true; } // Check for Abstract (main topic info) if (data.Abstract && data.Abstract.trim()) { const abstractSection = createAnswerSection( `📝 ${data.AbstractTitle || 'Topic Overview'}`, data.Abstract, 'abstract', data.AbstractSource ); container.appendChild(abstractSection); hasContent = true; } // Check for Related Topics (from main entity) if (data.RelatedTopics && data.RelatedTopics.length > 0) { const topicsSection = document.createElement('div'); topicsSection.className = 'related-topics'; const title = document.createElement('h4'); title.textContent = '🔗 Related Topics'; topicsSection.appendChild(title); const tagsContainer = document.createElement('div'); tagsContainer.className = 'topic-tags'; data.RelatedTopics.slice(0, CONFIG.MAX_TOPICS).forEach(topic => { if (topic.Text && topic.FirstURL) { const tag = document.createElement('button'); tag.className = 'topic-tag'; tag.textContent = topic.Text.replace(/<[^>]*>/g, ''); tag.addEventListener('click', () => { elements.userInput.value = topic.Text.replace(/<[^>]*>/g, ''); elements.chatForm.dispatchEvent(new Event('submit')); }); tagsContainer.appendChild(tag); } else if (topic.Name && topic.Topics) { // Grouped topics (e.g., for famous people) topic.Topics.slice(0, 3).forEach(subTopic => { if (subTopic.Text && subTopic.FirstURL) { const tag = document.createElement('button'); tag.className = 'topic-tag'; tag.textContent = subTopic.Text.replace(/<[^>]*>/g, ''); tag.addEventListener('click', () => { elements.userInput.value = subTopic.Text.replace(/<[^>]*>/g, ''); elements.chatForm.dispatchEvent(new Event('submit')); }); tagsContainer.appendChild(tag); } }); } }); if (tagsContainer.children.length > 0) { topicsSection.appendChild(tagsContainer); container.appendChild(topicsSection); hasContent = true; } } // Check for Results (additional web results) if (data.Results && data.Results.length > 0) { const resultsSection = document.createElement('div'); resultsSection.className = 'related-topics'; const title = document.createElement('h4'); title.textContent = '🔍 More Results'; resultsSection.appendChild(title); const tagsContainer = document.createElement('div'); tagsContainer.className = 'topic-tags'; data.Results.slice(0, CONFIG.MAX_TOPICS).forEach(result => { if (result.Text && result.FirstURL) { const tag = document.createElement('button'); tag.className = 'topic-tag'; tag.textContent = result.Text.replace(/<[^>]*>/g, '').substring(0, 50); tag.title = result.FirstURL; tag.addEventListener('click', () => { window.open(result.FirstURL, '_blank'); }); tagsContainer.appendChild(tag); } }); if (tagsContainer.children.length > 0) { resultsSection.appendChild(tagsContainer); container.appendChild(resultsSection); hasContent = true; } } // Check for Redirect (when query needs different search) if (data.Redirect && data.Redirect.trim()) { const redirectMsg = document.createElement('p'); redirectMsg.innerHTML = `🔀 For better results, try searching for: ${data.Redirect}`; container.appendChild(redirectMsg); hasContent = true; } // If no data found - provide helpful message if (!hasContent) { const noResult = document.createElement('div'); noResult.innerHTML = `

😕 I couldn't find specific information for that query.

Try:

`; container.appendChild(noResult); } return container; } /** * Fetch answer from DuckDuckGo API */ async function fetchDuckDuckGoAnswer(query) { const params = new URLSearchParams({ q: query, format: 'json', no_html: '1', skip_disambig: '1', pretty: '1' }); const url = `${CONFIG.DDG_API_BASE}?${params}`; // Try direct first, then with CORS proxy const urlsToTry = [url, CONFIG.CORS_PROXY + encodeURIComponent(url)]; let lastError = null; for (const attemptUrl of urlsToTry) { try { console.log('Trying URL:', attemptUrl); const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 10000); const response = await fetch(attemptUrl, { method: 'GET', headers: { 'Accept': 'application/json', }, signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); // Check if we got any meaningful response if (data && (data.Abstract || data.Answer || data.Definition || data.RelatedTopics || data.Results)) { return data; } // If response exists but is empty, try next URL console.log('Empty response, trying next URL'); lastError = new Error('Empty response'); } catch (error) { console.log('Error with URL:', attemptUrl, error.message); lastError = error; continue; } } throw lastError || new Error('Failed to fetch results'); } /** * Handle user submission */ async function handleSubmit(event) { event.preventDefault(); const query = elements.userInput.value.trim(); if (!query || isLoading) return; // Add user message addMessage(query, true); elements.userInput.value = ''; // Set loading state isLoading = true; elements.sendButton.disabled = true; elements.userInput.disabled = true; const loadingMessage = createLoadingMessage(); elements.chatMessages.appendChild(loadingMessage); elements.chatMessages.parentElement.scrollTop = elements.chatMessages.parentElement.scrollHeight; updateStatus('Searching the web...', 'searching'); try { // Fetch from DuckDuckGo const data = await fetchDuckDuckGoAnswer(query); // Remove loading message loadingMessage.remove(); // Format and display response const formattedResponse = formatDuckDuckGoResponse(data); addMessage(formattedResponse, false); updateStatus('Ready to search', 'ready'); } catch (error) { // Remove loading message loadingMessage.remove(); // Show error message with retry suggestion const errorContent = document.createElement('div'); errorContent.innerHTML = `

😕 Sorry, I couldn't find results for that query.

${error.message.includes('abort') ? 'Request timed out. Please try again.' : 'Try using different keywords or check your internet connection.'}

`; addMessage(errorContent, false); updateStatus('Error occurred', 'error'); console.error('Search Error:', error); } finally { // Reset loading state isLoading = false; elements.sendButton.disabled = false; elements.userInput.disabled = false; elements.userInput.focus(); } } /** * Handle example query clicks */ function setupExampleQueries() { elements.chatMessages.addEventListener('click', (event) => { if (event.target.tagName === 'LI') { const query = event.target.textContent; elements.userInput.value = query; elements.chatForm.dispatchEvent(new Event('submit')); } }); } /** * Initialize the application */ function init() { // Set up event listeners elements.chatForm.addEventListener('submit', handleSubmit); // Handle Enter key elements.userInput.addEventListener('keydown', (event) => { if (event.key === 'Enter' && !event.shiftKey) { event.preventDefault(); elements.chatForm.dispatchEvent(new Event('submit')); } }); // Set up example query clicks setupExampleQueries(); // Focus input elements.userInput.focus(); updateStatus('Ready to search', 'ready'); console.log('🔍 AI Web Search Assistant initialized!'); console.log('Powered by DuckDuckGo Instant Answer API'); } // Start the app document.addEventListener('DOMContentLoaded', init);