963 lines
30 KiB
HTML
963 lines
30 KiB
HTML
<!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;
|
|
}
|
|
|
|
/* Cost Breakdown Styles */
|
|
.cost-breakdown-details {
|
|
margin-top: 10px;
|
|
}
|
|
|
|
.cost-breakdown-summary {
|
|
cursor: pointer;
|
|
font-weight: 600;
|
|
color: #555;
|
|
padding: 12px 15px;
|
|
background: #f8f9fa;
|
|
border-radius: 6px;
|
|
border-left: 4px solid #667eea;
|
|
transition: background 0.2s;
|
|
list-style: none;
|
|
}
|
|
|
|
.cost-breakdown-summary:hover {
|
|
background: #e9ecef;
|
|
}
|
|
|
|
.cost-breakdown-summary::-webkit-details-marker {
|
|
display: none;
|
|
}
|
|
|
|
.cost-breakdown-content {
|
|
padding: 15px;
|
|
margin-top: 10px;
|
|
background: #fff;
|
|
border: 1px solid #e0e0e0;
|
|
border-radius: 6px;
|
|
}
|
|
|
|
.cost-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.cost-table th,
|
|
.cost-table td {
|
|
padding: 10px 12px;
|
|
text-align: left;
|
|
border-bottom: 1px solid #e0e0e0;
|
|
}
|
|
|
|
.cost-table th {
|
|
background: #f8f9fa;
|
|
font-weight: 600;
|
|
color: #555;
|
|
}
|
|
|
|
.cost-table tr:last-child td {
|
|
border-bottom: none;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.cost-table tbody tr:hover {
|
|
background: #f8f9fa;
|
|
}
|
|
|
|
.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>
|
|
<button class="example-btn" onclick="setExample('Was ist neu in React 19.2?')">React 19.2</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>
|
|
|
|
<!-- Cost Breakdown Section -->
|
|
<div class="result-section">
|
|
<details class="cost-breakdown-details" id="costBreakdownDetails">
|
|
<summary class="cost-breakdown-summary">💰 Cost Breakdown</summary>
|
|
<div class="cost-breakdown-content">
|
|
<table class="cost-table" id="costTable">
|
|
<thead>
|
|
<tr>
|
|
<th>#</th>
|
|
<th>Type</th>
|
|
<th>Input</th>
|
|
<th>Output</th>
|
|
<th>Cost (USD)</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="costTableBody">
|
|
<!-- Cost rows will be inserted here -->
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</details>
|
|
</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 = [];
|
|
|
|
// Clarification state
|
|
let isAwaitingClarification = false;
|
|
let clarificationData = null;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
async function performSearchWithClarification(clarificationText) {
|
|
if (!clarificationData || !clarificationData.originalQuestion) {
|
|
showError('Clarification data not available');
|
|
return;
|
|
}
|
|
|
|
// Show loading
|
|
hideError();
|
|
hideResults();
|
|
showLoading();
|
|
|
|
try {
|
|
const response = await fetch('/search', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
question: clarificationData.originalQuestion + ' ' + clarificationText,
|
|
previousClarification: clarificationText,
|
|
originalQuestion: clarificationData.originalQuestion
|
|
})
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (!response.ok) {
|
|
throw new Error(data.error || 'Search failed');
|
|
}
|
|
|
|
displayResults(data);
|
|
} catch (err) {
|
|
showError(err.message);
|
|
} finally {
|
|
hideLoading();
|
|
}
|
|
}
|
|
|
|
function displayResults(data) {
|
|
// Check if clarification is needed
|
|
if (data.clarificationNeeded) {
|
|
isAwaitingClarification = true;
|
|
clarificationData = {
|
|
originalQuestion: data.originalQuestion
|
|
};
|
|
|
|
// Show clarification prompt in the answer area
|
|
answerEl.innerHTML = `
|
|
<div style="background: #fff3cd; padding: 20px; border-radius: 8px; border-left: 4px solid #ffc107;">
|
|
<p style="margin-bottom: 15px; font-weight: 600;">${data.fullAnswerHTMLSnippet}</p>
|
|
<input
|
|
type="text"
|
|
id="clarificationInput"
|
|
class="search-input"
|
|
style="margin-bottom: 10px;"
|
|
>
|
|
<button
|
|
class="search-btn"
|
|
id="clarificationSubmitBtn"
|
|
style="padding: 10px 20px; font-size: 1rem;"
|
|
>
|
|
Submit Clarification
|
|
</button>
|
|
</div>
|
|
`;
|
|
|
|
// Clear sources since we're waiting for clarification
|
|
sourcesEl.innerHTML = '<li>Waiting for clarification...</li>';
|
|
|
|
// Add event listener for clarification submission
|
|
setTimeout(() => {
|
|
const clarificationInput = document.getElementById('clarificationInput');
|
|
const clarificationSubmitBtn = document.getElementById('clarificationSubmitBtn');
|
|
|
|
if (clarificationInput && clarificationSubmitBtn) {
|
|
clarificationInput.focus();
|
|
|
|
const handleClarification = () => {
|
|
const clarificationText = clarificationInput.value.trim();
|
|
if (clarificationText) {
|
|
// Search again with the clarification
|
|
performSearchWithClarification(clarificationText);
|
|
}
|
|
};
|
|
|
|
clarificationSubmitBtn.addEventListener('click', handleClarification);
|
|
clarificationInput.addEventListener('keypress', (e) => {
|
|
if (e.key === 'Enter') {
|
|
handleClarification();
|
|
}
|
|
});
|
|
}
|
|
}, 100);
|
|
|
|
showResults();
|
|
return;
|
|
}
|
|
|
|
// Reset clarification state for normal results
|
|
isAwaitingClarification = false;
|
|
clarificationData = null;
|
|
|
|
// 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);
|
|
}
|
|
|
|
// Display cost breakdown
|
|
const costTableBody = document.getElementById('costTableBody');
|
|
costTableBody.innerHTML = '';
|
|
if (data.costBreakdown && data.costBreakdown.length > 0) {
|
|
data.costBreakdown.forEach(item => {
|
|
const tr = document.createElement('tr');
|
|
tr.innerHTML = `
|
|
<td>${item.index}</td>
|
|
<td>${item.type}</td>
|
|
<td>${item.prompt_tokens || '-'}</td>
|
|
<td>${item.completion_tokens || '-'}</td>
|
|
<td>${item.cost}</td>
|
|
`;
|
|
costTableBody.appendChild(tr);
|
|
});
|
|
} else {
|
|
const tr = document.createElement('tr');
|
|
tr.innerHTML = '<td colspan="5">No cost data available</td>';
|
|
costTableBody.appendChild(tr);
|
|
}
|
|
|
|
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>
|