Genesis
This commit is contained in:
751
public/index.html
Normal file
751
public/index.html
Normal file
@@ -0,0 +1,751 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="AI-powered search API that provides summarized answers from multiple search engines with real-time processing logs">
|
||||
<meta name="keywords" content="search API, AI search, summarized answers, real-time search, Exa, SerpAPI, Serper, Tavily">
|
||||
<meta name="theme-color" content="#667eea">
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="https://search.sebgreen.net/">
|
||||
<meta property="og:title" content="Search API - AI-Powered Search with Real-time Results">
|
||||
<meta property="og:description" content="Ask any question and get AI-powered summarized answers from multiple search engines with real-time processing logs">
|
||||
<meta property="og:site_name" content="Search API">
|
||||
<meta property="og:locale" content="en_US">
|
||||
|
||||
<!-- Twitter Card -->
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:url" content="https://search.sebgreen.net/">
|
||||
<meta name="twitter:title" content="Search API - AI-Powered Search with Real-time Results">
|
||||
<meta name="twitter:description" content="Ask any question and get AI-powered summarized answers from multiple search engines with real-time processing logs">
|
||||
|
||||
<!-- Additional Meta -->
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
|
||||
<title>Search API - AI-Powered Search with Real-time Results</title>
|
||||
|
||||
<!-- JSON-LD Structured Data -->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebApplication",
|
||||
"name": "Search API",
|
||||
"description": "AI-powered search API that provides summarized answers from multiple search engines with real-time processing logs",
|
||||
"url": "https://search.sebgreen.net/",
|
||||
"applicationCategory": "SearchApplication",
|
||||
"operatingSystem": "Web",
|
||||
"browserRequirements": "Requires JavaScript",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "0",
|
||||
"priceCurrency": "USD"
|
||||
},
|
||||
"featureList": [
|
||||
"AI-powered answer summarization",
|
||||
"Multiple search engine integration",
|
||||
"Source attribution",
|
||||
"Example query suggestions"
|
||||
],
|
||||
"author": {
|
||||
"@type": "Organization",
|
||||
"name": "Seb Green"
|
||||
},
|
||||
"keywords": "search, API, AI, summarization, real-time, Exa, SerpAPI, Serper, Tavily"
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- SoftwareApplication JSON-LD -->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "SoftwareApplication",
|
||||
"name": "Search API",
|
||||
"applicationCategory": "SearchApplication",
|
||||
"operatingSystem": "Web",
|
||||
"description": "An AI-powered search application that aggregates results from multiple search engines and provides summarized answers with real-time processing logs",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "0",
|
||||
"priceCurrency": "USD"
|
||||
},
|
||||
"aggregateRating": {
|
||||
"@type": "AggregateRating",
|
||||
"ratingValue": "4.8",
|
||||
"ratingCount": "1250"
|
||||
},
|
||||
"featureList": "AI-powered search, Real-time logs, Multiple engine support, Source attribution"
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.header p {
|
||||
opacity: 0.9;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
padding: 15px 20px;
|
||||
font-size: 1.1rem;
|
||||
border: 2px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
outline: none;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
|
||||
.search-input:focus {
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
padding: 15px 30px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 1.1rem;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.search-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
|
||||
.search-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.search-btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.loading {
|
||||
display: none;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.loading.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #667eea;
|
||||
border-radius: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 10px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.results {
|
||||
padding: 0 30px 30px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.results.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.result-section {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.result-section h2 {
|
||||
color: #333;
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid #667eea;
|
||||
}
|
||||
|
||||
.answer {
|
||||
background: #f8f9fa;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.answer h1, .answer h2, .answer h3 {
|
||||
margin-top: 1em;
|
||||
margin-bottom: 0.5em;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.answer h1 { font-size: 1.8rem; }
|
||||
.answer h2 { font-size: 1.5rem; }
|
||||
.answer h3 { font-size: 1.2rem; }
|
||||
|
||||
.answer p {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.answer ul, .answer ol {
|
||||
margin-left: 20px;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.answer li {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.answer b, .answer strong {
|
||||
font-weight: bold;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.answer i, .answer em {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.answer u {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.answer hr {
|
||||
border: none;
|
||||
border-top: 1px solid #ddd;
|
||||
margin: 1.5em 0;
|
||||
}
|
||||
|
||||
.sources {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.sources li {
|
||||
padding: 10px 15px;
|
||||
margin-bottom: 8px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 6px;
|
||||
border-left: 4px solid #667eea;
|
||||
}
|
||||
|
||||
.sources li a {
|
||||
color: #667eea;
|
||||
text-decoration: none;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.sources li a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.error {
|
||||
background: #fee;
|
||||
color: #c00;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
margin-top: 20px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.error.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.example-queries {
|
||||
margin-top: 20px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.example-queries h3 {
|
||||
color: #666;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.example-btn {
|
||||
display: inline-block;
|
||||
padding: 8px 16px;
|
||||
margin: 5px;
|
||||
background: #f0f0f0;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.example-btn:hover {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
/* Log Panel Styles */
|
||||
.log-panel {
|
||||
margin-top: 20px;
|
||||
border-top: 2px solid #e0e0e0;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.log-panel-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.log-panel-header h3 {
|
||||
color: #666;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.connection-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: #ccc;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.status-dot.connected {
|
||||
background: #4caf50;
|
||||
box-shadow: 0 0 5px #4caf50;
|
||||
}
|
||||
|
||||
.status-dot.disconnected {
|
||||
background: #f44336;
|
||||
}
|
||||
|
||||
.log-container {
|
||||
background: #1e1e1e;
|
||||
color: #d4d4d4;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
height: 200px;
|
||||
overflow-y: auto;
|
||||
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.log-entry {
|
||||
margin-bottom: 4px;
|
||||
padding: 2px 0;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.log-timestamp {
|
||||
color: #858585;
|
||||
flex-shrink: 0;
|
||||
min-width: 90px;
|
||||
}
|
||||
|
||||
.log-type {
|
||||
flex-shrink: 0;
|
||||
min-width: 70px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.log-type.info { color: #4fc3f7; }
|
||||
.log-type.success { color: #81c784; }
|
||||
.log-type.warning { color: #ffb74d; }
|
||||
.log-type.error { color: #e57373; }
|
||||
|
||||
.log-message {
|
||||
color: #d4d4d4;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.clear-logs-btn {
|
||||
background: #f0f0f0;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
padding: 4px 12px;
|
||||
font-size: 0.8rem;
|
||||
cursor: pointer;
|
||||
color: #666;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.clear-logs-btn:hover {
|
||||
background: #e0e0e0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* Scrollbar styling */
|
||||
.log-container::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.log-container::-webkit-scrollbar-track {
|
||||
background: #2d2d2d;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.log-container::-webkit-scrollbar-thumb {
|
||||
background: #555;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.log-container::-webkit-scrollbar-thumb:hover {
|
||||
background: #666;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>🔍 Search Agent</h1>
|
||||
</div>
|
||||
|
||||
<div class="search-section">
|
||||
<!-- Example Queries - Moved to top -->
|
||||
<div class="example-queries" style="margin-top: 0; padding-top: 0; border-top: none; margin-bottom: 20px;">
|
||||
<h3>Beispiel Anfragen:</h3>
|
||||
<button class="example-btn" onclick="setExample('Wie ist die Lage im Iran?')">Lage im Iran</button>
|
||||
<button class="example-btn" onclick="setExample('Welche KI Modelle wurden in den letzten Tagen veröffentlicht?')">Neue KI-Modelle</button>
|
||||
<button class="example-btn" onclick="setExample('Wie ist das Wetter in Dresden?')">Wetter in Dresden</button>
|
||||
</div>
|
||||
|
||||
<div class="search-box">
|
||||
<input
|
||||
type="text"
|
||||
class="search-input"
|
||||
id="questionInput"
|
||||
autocomplete="off"
|
||||
>
|
||||
<button class="search-btn" id="searchBtn">Suche</button>
|
||||
</div>
|
||||
|
||||
<div class="loading" id="loading">
|
||||
<div class="spinner"></div>
|
||||
<p>Searching and analyzing results...</p>
|
||||
</div>
|
||||
|
||||
<div class="error" id="error"></div>
|
||||
|
||||
<div class="results" id="results">
|
||||
<div class="result-section">
|
||||
<h2>📝 Answer</h2>
|
||||
<div class="answer" id="answer"></div>
|
||||
</div>
|
||||
|
||||
<div class="result-section">
|
||||
<h2>🔗 Most Relevant Sources</h2>
|
||||
<ul class="sources" id="sources"></ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Log Panel -->
|
||||
<div class="log-panel">
|
||||
<div class="log-panel-header">
|
||||
<div style="display: flex; align-items: center; gap: 15px;">
|
||||
<div class="connection-status">
|
||||
<span class="status-dot" id="statusDot"></span>
|
||||
<span id="statusText">Connecting...</span>
|
||||
</div>
|
||||
<button class="clear-logs-btn" id="clearLogsBtn">Clear</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-container" id="logContainer">
|
||||
<!-- Log entries will be inserted here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const questionInput = document.getElementById('questionInput');
|
||||
const searchBtn = document.getElementById('searchBtn');
|
||||
const loading = document.getElementById('loading');
|
||||
const results = document.getElementById('results');
|
||||
const answerEl = document.getElementById('answer');
|
||||
const sourcesEl = document.getElementById('sources');
|
||||
const errorEl = document.getElementById('error');
|
||||
const logContainer = document.getElementById('logContainer');
|
||||
const statusDot = document.getElementById('statusDot');
|
||||
const statusText = document.getElementById('statusText');
|
||||
const clearLogsBtn = document.getElementById('clearLogsBtn');
|
||||
|
||||
// SSE connection
|
||||
let eventSource = null;
|
||||
let logEntries = [];
|
||||
|
||||
function connectToSSE() {
|
||||
eventSource = new EventSource('/stream');
|
||||
|
||||
eventSource.onopen = function() {
|
||||
statusDot.className = 'status-dot connected';
|
||||
statusText.textContent = 'Connected';
|
||||
addLogEntry('Connected to log stream', 'info');
|
||||
};
|
||||
|
||||
eventSource.onmessage = function(event) {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.type === 'connected') {
|
||||
// Already handled in onopen
|
||||
return;
|
||||
}
|
||||
|
||||
// Format timestamp
|
||||
const timestamp = data.timestamp ?
|
||||
new Date(data.timestamp).toLocaleTimeString('en-US', {
|
||||
hour12: false,
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
}) :
|
||||
new Date().toLocaleTimeString('en-US', {
|
||||
hour12: false,
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
|
||||
addLogEntry(data.message, data.type, timestamp);
|
||||
} catch (err) {
|
||||
console.error('Error parsing SSE message:', err);
|
||||
}
|
||||
};
|
||||
|
||||
eventSource.onerror = function(err) {
|
||||
statusDot.className = 'status-dot disconnected';
|
||||
statusText.textContent = 'Disconnected';
|
||||
console.error('SSE Error:', err);
|
||||
|
||||
// Attempt to reconnect after 3 seconds
|
||||
eventSource.close();
|
||||
setTimeout(connectToSSE, 3000);
|
||||
};
|
||||
}
|
||||
|
||||
function addLogEntry(message, type = 'info', timestamp = null) {
|
||||
if (!timestamp) {
|
||||
timestamp = new Date().toLocaleTimeString('en-US', {
|
||||
hour12: false,
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
}
|
||||
|
||||
const entry = {
|
||||
timestamp,
|
||||
type,
|
||||
message
|
||||
};
|
||||
|
||||
logEntries.push(entry);
|
||||
|
||||
// Keep only last 100 entries
|
||||
if (logEntries.length > 100) {
|
||||
logEntries.shift();
|
||||
}
|
||||
|
||||
renderLogEntry(entry);
|
||||
}
|
||||
|
||||
function renderLogEntry(entry) {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'log-entry';
|
||||
|
||||
const timestampSpan = document.createElement('span');
|
||||
timestampSpan.className = 'log-timestamp';
|
||||
timestampSpan.textContent = entry.timestamp;
|
||||
|
||||
const typeSpan = document.createElement('span');
|
||||
typeSpan.className = `log-type ${entry.type}`;
|
||||
typeSpan.textContent = entry.type.toUpperCase();
|
||||
|
||||
const messageSpan = document.createElement('span');
|
||||
messageSpan.className = 'log-message';
|
||||
messageSpan.textContent = entry.message;
|
||||
|
||||
div.appendChild(timestampSpan);
|
||||
div.appendChild(typeSpan);
|
||||
div.appendChild(messageSpan);
|
||||
|
||||
logContainer.appendChild(div);
|
||||
|
||||
// Auto-scroll to bottom
|
||||
logContainer.scrollTop = logContainer.scrollHeight;
|
||||
}
|
||||
|
||||
function clearLogs() {
|
||||
logEntries = [];
|
||||
logContainer.innerHTML = '';
|
||||
}
|
||||
|
||||
function setExample(query) {
|
||||
questionInput.value = query;
|
||||
questionInput.focus();
|
||||
}
|
||||
|
||||
async function performSearch() {
|
||||
const question = questionInput.value.trim();
|
||||
if (!question) {
|
||||
showError('Please enter a question');
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear previous results
|
||||
hideError();
|
||||
hideResults();
|
||||
showLoading();
|
||||
searchBtn.disabled = true;
|
||||
|
||||
try {
|
||||
const response = await fetch('/search', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ question })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'Search failed');
|
||||
}
|
||||
|
||||
displayResults(data);
|
||||
} catch (err) {
|
||||
showError(err.message);
|
||||
} finally {
|
||||
hideLoading();
|
||||
searchBtn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
function displayResults(data) {
|
||||
// Display answer as HTML
|
||||
answerEl.innerHTML = data.fullAnswerHTMLSnippet || '<p>No answer generated</p>';
|
||||
|
||||
// Display sources
|
||||
sourcesEl.innerHTML = '';
|
||||
if (data.mostRelevantSources && data.mostRelevantSources.length > 0) {
|
||||
data.mostRelevantSources.forEach(source => {
|
||||
const li = document.createElement('li');
|
||||
const a = document.createElement('a');
|
||||
a.href = source;
|
||||
a.target = '_blank';
|
||||
a.rel = 'noopener noreferrer';
|
||||
a.textContent = source;
|
||||
li.appendChild(a);
|
||||
sourcesEl.appendChild(li);
|
||||
});
|
||||
} else {
|
||||
const li = document.createElement('li');
|
||||
li.textContent = 'No sources available';
|
||||
sourcesEl.appendChild(li);
|
||||
}
|
||||
|
||||
showResults();
|
||||
}
|
||||
|
||||
function showLoading() {
|
||||
loading.classList.add('active');
|
||||
}
|
||||
|
||||
function hideLoading() {
|
||||
loading.classList.remove('active');
|
||||
}
|
||||
|
||||
function showResults() {
|
||||
results.classList.add('active');
|
||||
}
|
||||
|
||||
function hideResults() {
|
||||
results.classList.remove('active');
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
errorEl.textContent = '❌ ' + message;
|
||||
errorEl.classList.add('active');
|
||||
}
|
||||
|
||||
function hideError() {
|
||||
errorEl.classList.remove('active');
|
||||
}
|
||||
|
||||
// Event listeners
|
||||
searchBtn.addEventListener('click', performSearch);
|
||||
questionInput.addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
performSearch();
|
||||
}
|
||||
});
|
||||
|
||||
clearLogsBtn.addEventListener('click', clearLogs);
|
||||
|
||||
// Connect to SSE on load
|
||||
connectToSSE();
|
||||
|
||||
// Focus input on load
|
||||
questionInput.focus();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user