Spaces:
Running
Running
Upload index.js with huggingface_hub
Browse files
index.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
| 1 |
/**
|
| 2 |
-
*
|
| 3 |
* Uses DuckDuckGo Instant Answer API for real-time responses
|
| 4 |
*/
|
| 5 |
|
| 6 |
// Configuration
|
| 7 |
const CONFIG = {
|
| 8 |
DDG_API_BASE: 'https://api.duckduckgo.com/',
|
| 9 |
-
MAX_TOPICS:
|
| 10 |
-
|
|
|
|
| 11 |
};
|
| 12 |
|
| 13 |
// DOM Elements
|
|
@@ -77,64 +78,63 @@ function addMessage(content, isUser = false) {
|
|
| 77 |
return messageDiv;
|
| 78 |
}
|
| 79 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
/**
|
| 81 |
* Format DuckDuckGo response for display
|
| 82 |
*/
|
| 83 |
function formatDuckDuckGoResponse(data) {
|
| 84 |
const container = document.createElement('div');
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
|
|
|
|
|
|
| 94 |
container.appendChild(answerSection);
|
|
|
|
| 95 |
}
|
| 96 |
|
| 97 |
// Check for Definition
|
| 98 |
-
if (data.Definition) {
|
| 99 |
-
const defSection =
|
| 100 |
-
defSection.className = 'answer-section definition';
|
| 101 |
-
defSection.innerHTML = `
|
| 102 |
-
<h4>π Definition</h4>
|
| 103 |
-
<p>${data.Definition}</p>
|
| 104 |
-
`;
|
| 105 |
container.appendChild(defSection);
|
| 106 |
-
|
| 107 |
-
if (data.DefinitionSource) {
|
| 108 |
-
const source = document.createElement('p');
|
| 109 |
-
source.style.fontSize = '0.75rem';
|
| 110 |
-
source.style.color = 'var(--text-muted)';
|
| 111 |
-
source.style.marginTop = '0.5rem';
|
| 112 |
-
source.textContent = `Source: ${data.DefinitionSource}`;
|
| 113 |
-
defSection.querySelector('p').appendChild(source);
|
| 114 |
-
}
|
| 115 |
}
|
| 116 |
|
| 117 |
// Check for Abstract (main topic info)
|
| 118 |
-
if (data.Abstract) {
|
| 119 |
-
const abstractSection =
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
container.appendChild(abstractSection);
|
| 126 |
-
|
| 127 |
-
if (data.AbstractSource) {
|
| 128 |
-
const source = document.createElement('p');
|
| 129 |
-
source.style.fontSize = '0.75rem';
|
| 130 |
-
source.style.color = 'var(--text-muted)';
|
| 131 |
-
source.style.marginTop = '0.5rem';
|
| 132 |
-
source.textContent = `Source: ${data.AbstractSource}`;
|
| 133 |
-
abstractSection.querySelector('p').appendChild(source);
|
| 134 |
-
}
|
| 135 |
}
|
| 136 |
|
| 137 |
-
// Check for Related Topics
|
| 138 |
if (data.RelatedTopics && data.RelatedTopics.length > 0) {
|
| 139 |
const topicsSection = document.createElement('div');
|
| 140 |
topicsSection.className = 'related-topics';
|
|
@@ -148,24 +148,39 @@ function formatDuckDuckGoResponse(data) {
|
|
| 148 |
|
| 149 |
data.RelatedTopics.slice(0, CONFIG.MAX_TOPICS).forEach(topic => {
|
| 150 |
if (topic.Text && topic.FirstURL) {
|
| 151 |
-
const tag = document.createElement('
|
| 152 |
tag.className = 'topic-tag';
|
| 153 |
-
tag.textContent = topic.Text;
|
| 154 |
tag.addEventListener('click', () => {
|
| 155 |
-
elements.userInput.value = topic.Text;
|
| 156 |
elements.chatForm.dispatchEvent(new Event('submit'));
|
| 157 |
});
|
| 158 |
tagsContainer.appendChild(tag);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 159 |
}
|
| 160 |
});
|
| 161 |
|
| 162 |
if (tagsContainer.children.length > 0) {
|
| 163 |
topicsSection.appendChild(tagsContainer);
|
| 164 |
container.appendChild(topicsSection);
|
|
|
|
| 165 |
}
|
| 166 |
}
|
| 167 |
|
| 168 |
-
// Check for Results
|
| 169 |
if (data.Results && data.Results.length > 0) {
|
| 170 |
const resultsSection = document.createElement('div');
|
| 171 |
resultsSection.className = 'related-topics';
|
|
@@ -179,9 +194,10 @@ function formatDuckDuckGoResponse(data) {
|
|
| 179 |
|
| 180 |
data.Results.slice(0, CONFIG.MAX_TOPICS).forEach(result => {
|
| 181 |
if (result.Text && result.FirstURL) {
|
| 182 |
-
const tag = document.createElement('
|
| 183 |
tag.className = 'topic-tag';
|
| 184 |
-
tag.textContent = result.Text.replace(/<[^>]*>/g, '');
|
|
|
|
| 185 |
tag.addEventListener('click', () => {
|
| 186 |
window.open(result.FirstURL, '_blank');
|
| 187 |
});
|
|
@@ -192,13 +208,31 @@ function formatDuckDuckGoResponse(data) {
|
|
| 192 |
if (tagsContainer.children.length > 0) {
|
| 193 |
resultsSection.appendChild(tagsContainer);
|
| 194 |
container.appendChild(resultsSection);
|
|
|
|
| 195 |
}
|
| 196 |
}
|
| 197 |
|
| 198 |
-
//
|
| 199 |
-
if (
|
| 200 |
-
const
|
| 201 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
container.appendChild(noResult);
|
| 203 |
}
|
| 204 |
|
|
@@ -213,24 +247,57 @@ async function fetchDuckDuckGoAnswer(query) {
|
|
| 213 |
q: query,
|
| 214 |
format: 'json',
|
| 215 |
no_html: '1',
|
| 216 |
-
skip_disambig: '1'
|
|
|
|
| 217 |
});
|
| 218 |
|
| 219 |
const url = `${CONFIG.DDG_API_BASE}?${params}`;
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 226 |
}
|
| 227 |
-
|
| 228 |
-
const data = await response.json();
|
| 229 |
-
return data;
|
| 230 |
-
} catch (error) {
|
| 231 |
-
console.error('DuckDuckGo API Error:', error);
|
| 232 |
-
throw error;
|
| 233 |
}
|
|
|
|
|
|
|
| 234 |
}
|
| 235 |
|
| 236 |
/**
|
|
@@ -255,7 +322,7 @@ async function handleSubmit(event) {
|
|
| 255 |
elements.chatMessages.appendChild(loadingMessage);
|
| 256 |
elements.chatMessages.parentElement.scrollTop = elements.chatMessages.parentElement.scrollHeight;
|
| 257 |
|
| 258 |
-
updateStatus('Searching
|
| 259 |
|
| 260 |
try {
|
| 261 |
// Fetch from DuckDuckGo
|
|
@@ -268,16 +335,23 @@ async function handleSubmit(event) {
|
|
| 268 |
const formattedResponse = formatDuckDuckGoResponse(data);
|
| 269 |
addMessage(formattedResponse, false);
|
| 270 |
|
| 271 |
-
updateStatus('Ready to
|
| 272 |
} catch (error) {
|
| 273 |
// Remove loading message
|
| 274 |
loadingMessage.remove();
|
| 275 |
|
| 276 |
-
// Show error message
|
| 277 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 278 |
|
| 279 |
updateStatus('Error occurred', 'error');
|
| 280 |
-
console.error('Error:', error);
|
| 281 |
} finally {
|
| 282 |
// Reset loading state
|
| 283 |
isLoading = false;
|
|
@@ -321,10 +395,10 @@ function init() {
|
|
| 321 |
// Focus input
|
| 322 |
elements.userInput.focus();
|
| 323 |
|
| 324 |
-
updateStatus('Ready to
|
| 325 |
|
| 326 |
-
console.log('
|
| 327 |
-
console.log('Powered by
|
| 328 |
}
|
| 329 |
|
| 330 |
// Start the app
|
|
|
|
| 1 |
/**
|
| 2 |
+
* AI Web Search Assistant
|
| 3 |
* Uses DuckDuckGo Instant Answer API for real-time responses
|
| 4 |
*/
|
| 5 |
|
| 6 |
// Configuration
|
| 7 |
const CONFIG = {
|
| 8 |
DDG_API_BASE: 'https://api.duckduckgo.com/',
|
| 9 |
+
MAX_TOPICS: 8,
|
| 10 |
+
// Use a CORS proxy for better compatibility
|
| 11 |
+
CORS_PROXY: 'https://corsproxy.io/?'
|
| 12 |
};
|
| 13 |
|
| 14 |
// DOM Elements
|
|
|
|
| 78 |
return messageDiv;
|
| 79 |
}
|
| 80 |
|
| 81 |
+
/**
|
| 82 |
+
* Create an answer section
|
| 83 |
+
*/
|
| 84 |
+
function createAnswerSection(title, content, className = '', source = null) {
|
| 85 |
+
const section = document.createElement('div');
|
| 86 |
+
section.className = `answer-section ${className}`;
|
| 87 |
+
|
| 88 |
+
let html = `<h4>${title}</h4><p>${content}</p>`;
|
| 89 |
+
|
| 90 |
+
if (source) {
|
| 91 |
+
html += `<p class="source-link">Source: ${source}</p>`;
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
section.innerHTML = html;
|
| 95 |
+
return section;
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
/**
|
| 99 |
* Format DuckDuckGo response for display
|
| 100 |
*/
|
| 101 |
function formatDuckDuckGoResponse(data) {
|
| 102 |
const container = document.createElement('div');
|
| 103 |
+
let hasContent = false;
|
| 104 |
+
|
| 105 |
+
// Debug: Log the raw response
|
| 106 |
+
console.log('DuckDuckGo Response:', data);
|
| 107 |
+
|
| 108 |
+
// Check for Instant Answer (most prominent)
|
| 109 |
+
if (data.Answer && data.Answer.trim()) {
|
| 110 |
+
const answerSection = createAnswerSection('π¬ Answer', data.Answer, 'answer');
|
| 111 |
+
if (data.AnswerType) {
|
| 112 |
+
answerSection.querySelector('h4').textContent += ` (${data.AnswerType})`;
|
| 113 |
+
}
|
| 114 |
container.appendChild(answerSection);
|
| 115 |
+
hasContent = true;
|
| 116 |
}
|
| 117 |
|
| 118 |
// Check for Definition
|
| 119 |
+
if (data.Definition && data.Definition.trim()) {
|
| 120 |
+
const defSection = createAnswerSection('π Definition', data.Definition, 'definition', data.DefinitionSource);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
container.appendChild(defSection);
|
| 122 |
+
hasContent = true;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
}
|
| 124 |
|
| 125 |
// Check for Abstract (main topic info)
|
| 126 |
+
if (data.Abstract && data.Abstract.trim()) {
|
| 127 |
+
const abstractSection = createAnswerSection(
|
| 128 |
+
`π ${data.AbstractTitle || 'Topic Overview'}`,
|
| 129 |
+
data.Abstract,
|
| 130 |
+
'abstract',
|
| 131 |
+
data.AbstractSource
|
| 132 |
+
);
|
| 133 |
container.appendChild(abstractSection);
|
| 134 |
+
hasContent = true;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
}
|
| 136 |
|
| 137 |
+
// Check for Related Topics (from main entity)
|
| 138 |
if (data.RelatedTopics && data.RelatedTopics.length > 0) {
|
| 139 |
const topicsSection = document.createElement('div');
|
| 140 |
topicsSection.className = 'related-topics';
|
|
|
|
| 148 |
|
| 149 |
data.RelatedTopics.slice(0, CONFIG.MAX_TOPICS).forEach(topic => {
|
| 150 |
if (topic.Text && topic.FirstURL) {
|
| 151 |
+
const tag = document.createElement('button');
|
| 152 |
tag.className = 'topic-tag';
|
| 153 |
+
tag.textContent = topic.Text.replace(/<[^>]*>/g, '');
|
| 154 |
tag.addEventListener('click', () => {
|
| 155 |
+
elements.userInput.value = topic.Text.replace(/<[^>]*>/g, '');
|
| 156 |
elements.chatForm.dispatchEvent(new Event('submit'));
|
| 157 |
});
|
| 158 |
tagsContainer.appendChild(tag);
|
| 159 |
+
} else if (topic.Name && topic.Topics) {
|
| 160 |
+
// Grouped topics (e.g., for famous people)
|
| 161 |
+
topic.Topics.slice(0, 3).forEach(subTopic => {
|
| 162 |
+
if (subTopic.Text && subTopic.FirstURL) {
|
| 163 |
+
const tag = document.createElement('button');
|
| 164 |
+
tag.className = 'topic-tag';
|
| 165 |
+
tag.textContent = subTopic.Text.replace(/<[^>]*>/g, '');
|
| 166 |
+
tag.addEventListener('click', () => {
|
| 167 |
+
elements.userInput.value = subTopic.Text.replace(/<[^>]*>/g, '');
|
| 168 |
+
elements.chatForm.dispatchEvent(new Event('submit'));
|
| 169 |
+
});
|
| 170 |
+
tagsContainer.appendChild(tag);
|
| 171 |
+
}
|
| 172 |
+
});
|
| 173 |
}
|
| 174 |
});
|
| 175 |
|
| 176 |
if (tagsContainer.children.length > 0) {
|
| 177 |
topicsSection.appendChild(tagsContainer);
|
| 178 |
container.appendChild(topicsSection);
|
| 179 |
+
hasContent = true;
|
| 180 |
}
|
| 181 |
}
|
| 182 |
|
| 183 |
+
// Check for Results (additional web results)
|
| 184 |
if (data.Results && data.Results.length > 0) {
|
| 185 |
const resultsSection = document.createElement('div');
|
| 186 |
resultsSection.className = 'related-topics';
|
|
|
|
| 194 |
|
| 195 |
data.Results.slice(0, CONFIG.MAX_TOPICS).forEach(result => {
|
| 196 |
if (result.Text && result.FirstURL) {
|
| 197 |
+
const tag = document.createElement('button');
|
| 198 |
tag.className = 'topic-tag';
|
| 199 |
+
tag.textContent = result.Text.replace(/<[^>]*>/g, '').substring(0, 50);
|
| 200 |
+
tag.title = result.FirstURL;
|
| 201 |
tag.addEventListener('click', () => {
|
| 202 |
window.open(result.FirstURL, '_blank');
|
| 203 |
});
|
|
|
|
| 208 |
if (tagsContainer.children.length > 0) {
|
| 209 |
resultsSection.appendChild(tagsContainer);
|
| 210 |
container.appendChild(resultsSection);
|
| 211 |
+
hasContent = true;
|
| 212 |
}
|
| 213 |
}
|
| 214 |
|
| 215 |
+
// Check for Redirect (when query needs different search)
|
| 216 |
+
if (data.Redirect && data.Redirect.trim()) {
|
| 217 |
+
const redirectMsg = document.createElement('p');
|
| 218 |
+
redirectMsg.innerHTML = `π For better results, try searching for: <strong>${data.Redirect}</strong>`;
|
| 219 |
+
container.appendChild(redirectMsg);
|
| 220 |
+
hasContent = true;
|
| 221 |
+
}
|
| 222 |
+
|
| 223 |
+
// If no data found - provide helpful message
|
| 224 |
+
if (!hasContent) {
|
| 225 |
+
const noResult = document.createElement('div');
|
| 226 |
+
noResult.innerHTML = `
|
| 227 |
+
<p>π I couldn't find specific information for that query.</p>
|
| 228 |
+
<p style="margin-top: 0.5rem; font-size: 0.875rem; color: var(--text-secondary);">
|
| 229 |
+
Try:</p>
|
| 230 |
+
<ul style="margin-left: 1.25rem; margin-top: 0.25rem; color: var(--text-secondary); font-size: 0.875rem;">
|
| 231 |
+
<li>Using different keywords</li>
|
| 232 |
+
<li>Asking a factual question</li>
|
| 233 |
+
<li>Searching for a specific person, place, or concept</li>
|
| 234 |
+
</ul>
|
| 235 |
+
`;
|
| 236 |
container.appendChild(noResult);
|
| 237 |
}
|
| 238 |
|
|
|
|
| 247 |
q: query,
|
| 248 |
format: 'json',
|
| 249 |
no_html: '1',
|
| 250 |
+
skip_disambig: '1',
|
| 251 |
+
pretty: '1'
|
| 252 |
});
|
| 253 |
|
| 254 |
const url = `${CONFIG.DDG_API_BASE}?${params}`;
|
| 255 |
+
|
| 256 |
+
// Try direct first, then with CORS proxy
|
| 257 |
+
const urlsToTry = [url, CONFIG.CORS_PROXY + encodeURIComponent(url)];
|
| 258 |
+
|
| 259 |
+
let lastError = null;
|
| 260 |
+
|
| 261 |
+
for (const attemptUrl of urlsToTry) {
|
| 262 |
+
try {
|
| 263 |
+
console.log('Trying URL:', attemptUrl);
|
| 264 |
+
|
| 265 |
+
const controller = new AbortController();
|
| 266 |
+
const timeoutId = setTimeout(() => controller.abort(), 10000);
|
| 267 |
+
|
| 268 |
+
const response = await fetch(attemptUrl, {
|
| 269 |
+
method: 'GET',
|
| 270 |
+
headers: {
|
| 271 |
+
'Accept': 'application/json',
|
| 272 |
+
},
|
| 273 |
+
signal: controller.signal
|
| 274 |
+
});
|
| 275 |
+
|
| 276 |
+
clearTimeout(timeoutId);
|
| 277 |
+
|
| 278 |
+
if (!response.ok) {
|
| 279 |
+
throw new Error(`HTTP error! status: ${response.status}`);
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
const data = await response.json();
|
| 283 |
+
|
| 284 |
+
// Check if we got any meaningful response
|
| 285 |
+
if (data && (data.Abstract || data.Answer || data.Definition || data.RelatedTopics || data.Results)) {
|
| 286 |
+
return data;
|
| 287 |
+
}
|
| 288 |
+
|
| 289 |
+
// If response exists but is empty, try next URL
|
| 290 |
+
console.log('Empty response, trying next URL');
|
| 291 |
+
lastError = new Error('Empty response');
|
| 292 |
+
|
| 293 |
+
} catch (error) {
|
| 294 |
+
console.log('Error with URL:', attemptUrl, error.message);
|
| 295 |
+
lastError = error;
|
| 296 |
+
continue;
|
| 297 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 298 |
}
|
| 299 |
+
|
| 300 |
+
throw lastError || new Error('Failed to fetch results');
|
| 301 |
}
|
| 302 |
|
| 303 |
/**
|
|
|
|
| 322 |
elements.chatMessages.appendChild(loadingMessage);
|
| 323 |
elements.chatMessages.parentElement.scrollTop = elements.chatMessages.parentElement.scrollHeight;
|
| 324 |
|
| 325 |
+
updateStatus('Searching the web...', 'searching');
|
| 326 |
|
| 327 |
try {
|
| 328 |
// Fetch from DuckDuckGo
|
|
|
|
| 335 |
const formattedResponse = formatDuckDuckGoResponse(data);
|
| 336 |
addMessage(formattedResponse, false);
|
| 337 |
|
| 338 |
+
updateStatus('Ready to search', 'ready');
|
| 339 |
} catch (error) {
|
| 340 |
// Remove loading message
|
| 341 |
loadingMessage.remove();
|
| 342 |
|
| 343 |
+
// Show error message with retry suggestion
|
| 344 |
+
const errorContent = document.createElement('div');
|
| 345 |
+
errorContent.innerHTML = `
|
| 346 |
+
<p>π Sorry, I couldn't find results for that query.</p>
|
| 347 |
+
<p style="margin-top: 0.5rem; font-size: 0.875rem; color: var(--text-secondary);">
|
| 348 |
+
${error.message.includes('abort') ? 'Request timed out. Please try again.' : 'Try using different keywords or check your internet connection.'}
|
| 349 |
+
</p>
|
| 350 |
+
`;
|
| 351 |
+
addMessage(errorContent, false);
|
| 352 |
|
| 353 |
updateStatus('Error occurred', 'error');
|
| 354 |
+
console.error('Search Error:', error);
|
| 355 |
} finally {
|
| 356 |
// Reset loading state
|
| 357 |
isLoading = false;
|
|
|
|
| 395 |
// Focus input
|
| 396 |
elements.userInput.focus();
|
| 397 |
|
| 398 |
+
updateStatus('Ready to search', 'ready');
|
| 399 |
|
| 400 |
+
console.log('π AI Web Search Assistant initialized!');
|
| 401 |
+
console.log('Powered by DuckDuckGo Instant Answer API');
|
| 402 |
}
|
| 403 |
|
| 404 |
// Start the app
|